From 9295d21bfd8bb3c1f9fcc70ca9f7764c61d70d79 Mon Sep 17 00:00:00 2001 From: dan Date: Sat, 6 Sep 2014 20:19:38 +0000 Subject: [PATCH] Add support for delete operations to the ota extension. FossilOrigin-Name: f988234ba54d7c667f7deef1d04beed4e7fe6182 --- ext/ota/ota1.test | 60 +++++++++- ext/ota/sqlite3ota.c | 258 +++++++++++++++++++++++++++++++++++++------ manifest | 18 +-- manifest.uuid | 2 +- src/delete.c | 3 + src/vdbeblob.c | 24 ++-- 6 files changed, 304 insertions(+), 61 deletions(-) diff --git a/ext/ota/ota1.test b/ext/ota/ota1.test index 12afc6e3f1..22b8fa66cd 100644 --- a/ext/ota/ota1.test +++ b/ext/ota/ota1.test @@ -32,16 +32,21 @@ proc create_ota1 {filename} { return $filename } -# Create an empty target database suitable for the OTA created by -# [create_ota1]. +# Create a simple OTA database. That expects to write to a table: # # CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); # -proc create_target1 {filename} { +proc create_ota4 {filename} { forcedelete $filename - sqlite3 target1 $filename - target1 eval { CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c) } - target1 close + sqlite3 ota1 $filename + ota1 eval { + CREATE TABLE data_t1(a, b, c, ota_control); + INSERT INTO data_t1 VALUES(1, 2, 3, 0); + INSERT INTO data_t1 VALUES(2, NULL, 5, 1); + INSERT INTO data_t1 VALUES(3, 8, 9, 0); + INSERT INTO data_t1 VALUES(4, NULL, 11, 1); + } + ota1 close return $filename } @@ -176,6 +181,49 @@ foreach {tn errcode errmsg schema} { do_test 3.$tn.4 { dbcksum db main } $cksum } +#------------------------------------------------------------------------- +# +foreach {tn2 cmd} {1 run_ota 2 step_ota} { + foreach {tn schema} { + 1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + } + 2 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + CREATE INDEX i1 ON t1(b); + } + 3 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + CREATE INDEX i1 ON t1(b); + CREATE INDEX i2 ON t1(c, b); + CREATE INDEX i3 ON t1(c, b, c); + } + } { + reset_db + execsql $schema + execsql { + INSERT INTO t1 VALUES(2, 'hello', 'world'); + INSERT INTO t1 VALUES(4, 'hello', 'planet'); + INSERT INTO t1 VALUES(6, 'hello', 'xyz'); + } + + do_test 4.$tn2.$tn.1 { + create_ota4 ota.db + $cmd test.db ota.db + } {SQLITE_DONE} + + do_execsql_test 4.$tn2.$tn.2 { + SELECT * FROM t1 ORDER BY a ASC; + } { + 1 2 3 + 3 8 9 + 6 hello xyz + } + + do_execsql_test 4.$tn2.$tn.3 { PRAGMA integrity_check } ok + } +} + finish_test diff --git a/ext/ota/sqlite3ota.c b/ext/ota/sqlite3ota.c index f0fbd28d1b..62b1c4ab34 100644 --- a/ext/ota/sqlite3ota.c +++ b/ext/ota/sqlite3ota.c @@ -77,6 +77,7 @@ struct OtaObjIter { int nCol; /* Number of columns in current object */ sqlite3_stmt *pSelect; /* Source data */ sqlite3_stmt *pInsert; /* Statement for INSERT operations */ + sqlite3_stmt *pDelete; /* Statement for DELETE ops */ }; /* @@ -189,6 +190,7 @@ static void otaObjIterFinalize(OtaObjIter *pIter){ sqlite3_finalize(pIter->pIdxIter); sqlite3_finalize(pIter->pSelect); sqlite3_finalize(pIter->pInsert); + sqlite3_finalize(pIter->pDelete); otaObjIterFreeCols(pIter); memset(pIter, 0, sizeof(OtaObjIter)); } @@ -208,8 +210,10 @@ static int otaObjIterNext(sqlite3ota *p, OtaObjIter *pIter){ /* Free any SQLite statements used while processing the previous object */ sqlite3_finalize(pIter->pSelect); sqlite3_finalize(pIter->pInsert); + sqlite3_finalize(pIter->pDelete); pIter->pSelect = 0; pIter->pInsert = 0; + pIter->pDelete = 0; pIter->nCol = 0; if( pIter->bCleanup ){ @@ -305,6 +309,32 @@ static char *otaQuoteName(const char *zName){ return zRet; } +/* +** Argument zFmt is a sqlite3_mprintf() style format string. The trailing +** arguments are the usual subsitution values. This function performs +** the printf() style substitutions and executes the result as an SQL +** statement on the OTA handles database. +** +** If an error occurs, an error code and error message is stored in the +** OTA handle. If an error has already occurred when this function is +** called, it is a no-op. +*/ +static int otaMPrintfExec(sqlite3ota *p, const char *zFmt, ...){ + va_list ap; + va_start(ap, zFmt); + if( p->rc==SQLITE_OK ){ + char *zSql = sqlite3_vmprintf(zFmt, ap); + if( zSql==0 ){ + p->rc = SQLITE_NOMEM; + }else{ + p->rc = sqlite3_exec(p->db, zSql, 0, 0, &p->zErrmsg); + sqlite3_free(zSql); + } + } + va_end(ap); + return p->rc; +} + /* ** If they are not already populated, populate the pIter->azTblCol[], ** pIter->abTblPk[] and pIter->nTblCol variables according to the table @@ -380,6 +410,48 @@ static char *otaObjIterGetCollist( return zList; } +static char *otaObjIterGetOldlist( + sqlite3ota *p, + OtaObjIter *pIter +){ + char *zList = 0; + if( p->rc==SQLITE_OK ){ + const char *zSep = ""; + int i; + for(i=0; inTblCol; i++){ + zList = sqlite3_mprintf("%z%sold.%s", zList, zSep, pIter->azTblCol[i]); + zSep = ", "; + if( zList==0 ){ + p->rc = SQLITE_NOMEM; + break; + } + } + } + return zList; +} + +static char *otaObjIterGetWhere( + sqlite3ota *p, + OtaObjIter *pIter +){ + char *zList = 0; + if( p->rc==SQLITE_OK ){ + const char *zSep = ""; + int i; + for(i=0; inTblCol; i++){ + if( pIter->abTblPk[i] ){ + zList = sqlite3_mprintf("%z%s%s=?", zList, zSep, pIter->azTblCol[i]); + zSep = " AND "; + if( zList==0 ){ + p->rc = SQLITE_NOMEM; + break; + } + } + } + } + return zList; +} + static char *otaObjIterGetBindlist(sqlite3ota *p, int nBind){ char *zRet = 0; if( p->rc==SQLITE_OK ){ @@ -423,25 +495,37 @@ static int otaObjIterPrepareAll( if( zIdx ){ int *aiCol; /* Column map */ - /* Create the index writer */ + /* Create the index writers */ if( p->rc==SQLITE_OK ){ p->rc = sqlite3_index_writer( p->db, 0, zIdx, &pIter->pInsert, &aiCol, &pIter->nCol ); } + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3_index_writer( + p->db, 1, zIdx, &pIter->pDelete, &aiCol, &pIter->nCol + ); + } /* Create the SELECT statement to read keys in sorted order */ zCollist = otaObjIterGetCollist(p, pIter, pIter->nCol, aiCol); if( p->rc==SQLITE_OK ){ p->rc = prepareFreeAndCollectError(p->db, &pIter->pSelect, pz, sqlite3_mprintf( - "SELECT %s FROM ota.'data_%q' ORDER BY %s%s", - zCollist, pIter->zTbl, zCollist, zLimit + "SELECT %s, ota_control FROM ota.'data_%q' " + "UNION ALL " + "SELECT %s, ota_control FROM ota.'ota_tmp_%q' " + "ORDER BY %s%s", + zCollist, pIter->zTbl, + zCollist, pIter->zTbl, + zCollist, zLimit ) ); } }else{ char *zBindings = otaObjIterGetBindlist(p, pIter->nTblCol); + char *zWhere = otaObjIterGetWhere(p, pIter); + char *zOldlist = otaObjIterGetOldlist(p, pIter); zCollist = otaObjIterGetCollist(p, pIter, pIter->nTblCol, 0); pIter->nCol = pIter->nTblCol; @@ -449,7 +533,7 @@ static int otaObjIterPrepareAll( if( p->rc==SQLITE_OK ){ p->rc = prepareFreeAndCollectError(p->db, &pIter->pSelect, pz, sqlite3_mprintf( - "SELECT %s FROM ota.'data_%q'%s", + "SELECT %s, ota_control FROM ota.'data_%q'%s", zCollist, pIter->zTbl, zLimit) ); } @@ -463,6 +547,31 @@ static int otaObjIterPrepareAll( ) ); } + + /* Create the DELETE statement to write to the target PK b-tree */ + if( p->rc==SQLITE_OK ){ + p->rc = prepareFreeAndCollectError(p->db, &pIter->pDelete, pz, + sqlite3_mprintf( + "DELETE FROM main.%Q WHERE %s", pIter->zTbl, zWhere + ) + ); + } + + if( p->rc==SQLITE_OK ){ + otaMPrintfExec(p, + "CREATE TABLE IF NOT EXISTS ota.'ota_tmp_%q' AS " + "SELECT * FROM ota.'data_%q' WHERE 0;" + "CREATE TEMP TRIGGER ota_delete_%q BEFORE DELETE ON main.%Q " + "BEGIN " + " INSERT INTO 'ota_tmp_%q'(ota_control, %s) VALUES(2, %s);" + "END;" + , pIter->zTbl, pIter->zTbl, pIter->zTbl, pIter->zTbl, pIter->zTbl, + zCollist, zOldlist + ); + } + + sqlite3_free(zWhere); + sqlite3_free(zOldlist); sqlite3_free(zBindings); } sqlite3_free(zCollist); @@ -472,6 +581,72 @@ static int otaObjIterPrepareAll( return p->rc; } +#define OTA_INSERT 1 +#define OTA_DELETE 2 +#define OTA_IDX_DELETE 3 +#define OTA_UPDATE 4 + +/* +** The SELECT statement iterating through the keys for the current object +** (p->objiter.pSelect) currently points to a valid row. However, there +** is something wrong with the ota_control value in the ota_control value +** stored in the (p->nCol+1)'th column. Set the error code and error message +** of the OTA handle to something reflecting this. +*/ +static void otaBadControlError(sqlite3ota *p){ + p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf("Invalid ota_control value"); +} + +/* +** The SELECT statement iterating through the keys for the current object +** (p->objiter.pSelect) currently points to a valid row. This function +** determines the type of operation requested by this row and returns +** one of the following values to indicate the result: +** +** * OTA_INSERT +** * OTA_DELETE +** * OTA_IDX_DELETE +** * OTA_UPDATE +** +** If OTA_UPDATE is returned, then output variable *pzMask is set to +** point to the text value indicating the columns to update. +** +** If the ota_control field contains an invalid value, an error code and +** message are left in the OTA handle and zero returned. +*/ +static int otaStepType(sqlite3ota *p, const char **pzMask){ + int iCol = p->objiter.nCol; /* Index of ota_control column */ + int res = 0; /* Return value */ + + switch( sqlite3_column_type(p->objiter.pSelect, iCol) ){ + case SQLITE_INTEGER: { + int iVal = sqlite3_column_int(p->objiter.pSelect, iCol); + if( iVal==0 ){ + res = OTA_INSERT; + }else if( iVal==1 ){ + res = OTA_DELETE; + }else if( iVal==2 ){ + res = OTA_IDX_DELETE; + } + break; + } + + case SQLITE_TEXT: + *pzMask = (const char*)sqlite3_column_text(p->objiter.pSelect, iCol); + res = OTA_UPDATE; + break; + + default: + break; + } + + if( res==0 ){ + otaBadControlError(p); + } + return res; +} + /* ** This function does the work for an sqlite3ota_step() call. ** @@ -485,15 +660,49 @@ static int otaObjIterPrepareAll( */ static int otaStep(sqlite3ota *p){ OtaObjIter *pIter = &p->objiter; + const char *zMask = 0; int i; + int eType = otaStepType(p, &zMask); - for(i=0; inCol; i++){ - sqlite3_value *pVal = sqlite3_column_value(pIter->pSelect, i); - sqlite3_bind_value(pIter->pInsert, i+1, pVal); + if( eType ){ + assert( eType!=OTA_UPDATE || pIter->zIdx==0 ); + + if( pIter->zIdx==0 && eType==OTA_IDX_DELETE ){ + otaBadControlError(p); + } + else if( eType==OTA_INSERT || eType==OTA_IDX_DELETE ){ + sqlite3_stmt *pWriter; + assert( eType!=OTA_UPDATE ); + + pWriter = (eType==OTA_INSERT)?pIter->pInsert:pIter->pDelete; + for(i=0; inCol; i++){ + sqlite3_value *pVal = sqlite3_column_value(pIter->pSelect, i); + sqlite3_bind_value(pWriter, i+1, pVal); + } + sqlite3_step(pWriter); + p->rc = resetAndCollectError(pWriter, &p->zErrmsg); + } + else if( eType==OTA_DELETE && pIter->zIdx==0 ){ + int iVar = 1; + assert( pIter->zIdx==0 ); + assert( pIter->nCol==pIter->nTblCol ); + for(i=0; inCol; i++){ + if( pIter->abTblPk[i] ){ + sqlite3_value *pVal = sqlite3_column_value(pIter->pSelect, i); + sqlite3_bind_value(pIter->pDelete, iVar++, pVal); + } + } + sqlite3_step(pIter->pDelete); + p->rc = resetAndCollectError(pIter->pDelete, &p->zErrmsg); + }else if( eType==OTA_UPDATE ){ + p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf("not yet"); + }else{ + /* no-op */ + assert( eType==OTA_DELETE && pIter->zIdx ); + } } - sqlite3_step(pIter->pInsert); - p->rc = resetAndCollectError(pIter->pInsert, &p->zErrmsg); return p->rc; } @@ -506,7 +715,10 @@ int sqlite3ota_step(sqlite3ota *p){ while( p && p->rc==SQLITE_OK && pIter->zTbl ){ if( pIter->bCleanup ){ - /* this is where cleanup of the ota_xxx table will happen... */ + /* Clean up the ota_tmp_xxx table for the previous table. It + ** cannot be dropped as there are currently active SQL statements. + ** But the contents can be deleted. */ + otaMPrintfExec(p, "DELETE FROM ota.'ota_tmp_%q'", pIter->zTbl); }else{ otaObjIterPrepareAll(p, pIter, 0); @@ -532,32 +744,6 @@ int sqlite3ota_step(sqlite3ota *p){ return p->rc; } -/* -** Argument zFmt is a sqlite3_mprintf() style format string. The trailing -** arguments are the usual subsitution values. This function performs -** the printf() style substitutions and executes the result as an SQL -** statement on the OTA handles database. -** -** If an error occurs, an error code and error message is stored in the -** OTA handle. If an error has already occurred when this function is -** called, it is a no-op. -*/ -static int otaMPrintfExec(sqlite3ota *p, const char *zFmt, ...){ - va_list ap; - va_start(ap, zFmt); - if( p->rc==SQLITE_OK ){ - char *zSql = sqlite3_vmprintf(zFmt, ap); - if( zSql==0 ){ - p->rc = SQLITE_NOMEM; - }else{ - p->rc = sqlite3_exec(p->db, zSql, 0, 0, &p->zErrmsg); - sqlite3_free(zSql); - } - } - va_end(ap); - return p->rc; -} - static void otaSaveTransactionState(sqlite3ota *p){ otaMPrintfExec(p, "INSERT OR REPLACE INTO ota.ota_state(rowid, tbl, idx, row, progress)" diff --git a/manifest b/manifest index 8d8f48b13b..46bc695b1a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Switch\sback\sto\susing\sa\ssingle\sdatabase\sconnection\sin\ssqlite3ota.c. -D 2014-09-05T19:52:42.359 +C Add\ssupport\sfor\sdelete\soperations\sto\sthe\sota\sextension. +D 2014-09-06T20:19:38.006 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in cf57f673d77606ab0f2d9627ca52a9ba1464146a F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -122,9 +122,9 @@ F ext/misc/vfslog.c fe40fab5c077a40477f7e5eba994309ecac6cc95 F ext/misc/vtshim.c babb0dc2bf116029e3e7c9a618b8a1377045303e F ext/misc/wholenumber.c 784b12543d60702ebdd47da936e278aa03076212 F ext/ota/ota.c d37097e92a005d3915883adefbb93019ea6f8841 -F ext/ota/ota1.test 0bbdffa5cb4c4bc26be5dae55c834830c7e8e5e3 +F ext/ota/ota1.test 47317179125b5e65289a9f59753c9f895186e6d5 F ext/ota/ota2.test 13f76922446c62ed96192e938b8e625ebf0142fa -F ext/ota/sqlite3ota.c 3ddf5f8122f9ab3270541f61bde5d95ef7b631d5 +F ext/ota/sqlite3ota.c ceb0f77dc6a958d299f532319f6477e5599dc59d F ext/ota/sqlite3ota.h 545f0008b5f02f2595899cb9841caddada5c17c0 F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 F ext/rtree/rtree.c 57bec53e1a677ab74217fe1f20a58c3a47261d6b @@ -181,7 +181,7 @@ F src/callback.c b97d0695ffcf6a8710ee445ffe56ee387d4d8a6f F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac F src/ctime.c 0231df905e2c4abba4483ee18ffc05adc321df2a F src/date.c 593c744b2623971e45affd0bde347631bdfa4625 -F src/delete.c 5adcd322c6b08fc25d215d780ca62cebce66304d +F src/delete.c 3c2a375c0329247d01222170ae19ad8a52ecbf9a F src/expr.c e1691ab0fe6be7247ef073b0038fb8ecd9944fad F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb F src/fkey.c 8d81a780ad78d16ec9082585758a8f1d6bf02ca3 @@ -295,7 +295,7 @@ F src/vdbe.h c63fad052c9e7388d551e556e119c0bcf6bebdf8 F src/vdbeInt.h cdc8e421f85beb1ac9b4669ec5beadab6faa15e0 F src/vdbeapi.c 09677a53dd8c71bcd670b0bd073bb9aefa02b441 F src/vdbeaux.c cef5d34a64ae3a65b56d96d3fd663246ec8e1c36 -F src/vdbeblob.c 0bc9d22578d87ad9ff1c16e20a36863326f34fd7 +F src/vdbeblob.c b1b8b2cd86617db009f027f116b335f86e95b617 F src/vdbemem.c 921d5468a68ac06f369810992e84ca22cc730a62 F src/vdbesort.c 7c45bfcd823f30d172bbbc1b9f51ef4402fbfe8d F src/vdbetrace.c 6f52bc0c51e144b7efdcfb2a8f771167a8816767 @@ -1198,7 +1198,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 98387f05697526c7740e91d8a846a31f77639406 -R cd4982d2f447b7a01026d09e8721c98f +P 3c2f4a078132992e33cc675173c84f8385af9cb5 +R eeedfe1c92df64758f3e97de2d55d43b U dan -Z 303c253ac0315bace60d825e70dc4fdd +Z 3317be7135040205f21e9375db1283ea diff --git a/manifest.uuid b/manifest.uuid index 3a256e520f..457a56e402 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3c2f4a078132992e33cc675173c84f8385af9cb5 \ No newline at end of file +f988234ba54d7c667f7deef1d04beed4e7fe6182 \ No newline at end of file diff --git a/src/delete.c b/src/delete.c index af83903c42..19c1ed01a8 100644 --- a/src/delete.c +++ b/src/delete.c @@ -730,6 +730,9 @@ void sqlite3GenerateRowIndexDelete( Vdbe *v; /* The prepared statement under construction */ Index *pPk; /* PRIMARY KEY index, or NULL for rowid tables */ + /* Skip this if we are in OTA mode */ + if( pParse->db->flags & SQLITE_OtaMode ) return; + v = pParse->pVdbe; pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ diff --git a/src/vdbeblob.c b/src/vdbeblob.c index 11e018e19c..5e809fc78f 100644 --- a/src/vdbeblob.c +++ b/src/vdbeblob.c @@ -546,17 +546,23 @@ int sqlite3_index_writer( sqlite3VdbeAddOp2(v, OP_Variable, i, i); } regRec = ++pParse->nMem; - sqlite3VdbeAddOp3(v, OP_MakeRecord, 1, pIdx->nColumn, regRec); - /* If this is a UNIQUE index, check the constraint. */ - if( pIdx->onError ){ - int addr = sqlite3VdbeAddOp4Int(v, OP_NoConflict, 0, 0, 1, pIdx->nKeyCol); - sqlite3UniqueConstraint(pParse, SQLITE_ABORT, pIdx); - sqlite3VdbeJumpHere(v, addr); + if( bDelete==0 ){ + sqlite3VdbeAddOp3(v, OP_MakeRecord, 1, pIdx->nColumn, regRec); + + /* If this is a UNIQUE index, check the constraint. */ + if( pIdx->onError ){ + int addr = sqlite3VdbeAddOp4Int(v, OP_NoConflict, 0, 0, 1, pIdx->nKeyCol); + sqlite3UniqueConstraint(pParse, SQLITE_ABORT, pIdx); + sqlite3VdbeJumpHere(v, addr); + } + + /* Code the IdxInsert to write to the b-tree index. */ + sqlite3VdbeAddOp2(v, OP_IdxInsert, 0, regRec); + }else{ + /* Code the IdxDelete to remove the entry from the b-tree index. */ + sqlite3VdbeAddOp3(v, OP_IdxDelete, 0, 1, pIdx->nColumn); } - - /* Code the IdxInsert to write to the b-tree index. */ - sqlite3VdbeAddOp2(v, OP_IdxInsert, 0, regRec); sqlite3FinishCoding(pParse); index_writer_out: