From: dan Date: Sat, 6 Dec 2014 19:30:41 +0000 (+0000) Subject: Allow the ota extension to write to tables with no PRIMARY KEY declaration. X-Git-Tag: version-3.8.11~252^2~71 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7ca1d07aff15812cab4adc9fd403632ad662dcf6;p=thirdparty%2Fsqlite.git Allow the ota extension to write to tables with no PRIMARY KEY declaration. FossilOrigin-Name: ba59a7e2ba97244492cbca9247456df0f3f19248 --- diff --git a/ext/ota/ota1.test b/ext/ota/ota1.test index e6b50c5906..74600ff6ec 100644 --- a/ext/ota/ota1.test +++ b/ext/ota/ota1.test @@ -177,6 +177,9 @@ foreach {tn2 cmd} {1 run_ota 2 step_ota 3 step_ota_uri} { # relaxed so that external PRIMARY KEYs on tables with automatic rowids # are now allowed. # +# UPDATE 2: Tables without any PRIMARY KEY declaration are now allowed. +# However the input table must feature an "ota_rowid" column. +# reset_db create_ota1 ota.db do_execsql_test 2.1 { CREATE TABLE t1(a, b, c) } @@ -186,7 +189,7 @@ do_test 2.2 { } {SQLITE_ERROR} do_test 2.3 { list [catch { ota close } msg] $msg -} {1 {SQLITE_ERROR - table t1 has no PRIMARY KEY}} +} {1 {SQLITE_ERROR - table data_t1 requires ota_rowid column}} reset_db do_execsql_test 2.4 { CREATE TABLE t1(a PRIMARY KEY, b, c) } do_test 2.5 { diff --git a/ext/ota/ota9.test b/ext/ota/ota9.test index 18791b479e..e746d363f1 100644 --- a/ext/ota/ota9.test +++ b/ext/ota/ota9.test @@ -9,7 +9,7 @@ # #*********************************************************************** # -# Test OTA with virtual tables +# Test OTA with virtual tables. And tables with no PRIMARY KEY declarations. # if {![info exists testdir]} { @@ -60,6 +60,68 @@ do_execsql_test 1.2.2 { SELECT rowid, * FROM f1 } { do_execsql_test 1.2.3 { INSERT INTO f1(f1) VALUES('integrity-check') } integrity_check 1.2.4 +#------------------------------------------------------------------------- +# Tables with no PK declaration. +# + +# Run the OTA in file $ota on target database $target until completion. +# +proc run_ota {target ota} { + sqlite3ota ota $target $ota + while { [ota step]=="SQLITE_OK" } {} + ota close +} + +foreach {tn idx} { + 1 { } + 2 { + CREATE INDEX i1 ON t1(a); + } + 3 { + CREATE INDEX i1 ON t1(b, c); + CREATE INDEX i2 ON t1(c, b); + CREATE INDEX i3 ON t1(a, a, a, b, b, b, c, c, c); + } +} { + + reset_db + do_execsql_test 2.$tn.1 { + CREATE TABLE t1(a, b, c); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(4, 5, 6); + INSERT INTO t1(rowid, a, b, c) VALUES(-1, 'a', 'b', 'c'); + INSERT INTO t1(rowid, a, b, c) VALUES(-2, 'd', 'e', 'f'); + } + + db eval $idx + + do_test 2.$tn.2 { + forcedelete ota.db + sqlite3 db2 ota.db + db2 eval { + CREATE TABLE data_t1(ota_rowid, a, b, c, ota_control); + INSERT INTO data_t1 VALUES(3, 'x', 'y', 'z', 0); + INSERT INTO data_t1 VALUES(NULL, 'X', 'Y', 'Z', 0); + INSERT INTO data_t1 VALUES('1', NULL, NULL, NULL, 1); + INSERT INTO data_t1 VALUES(-2, NULL, NULL, 'fff', '..x'); + } + db2 close + } {} + + run_ota test.db ota.db + + do_execsql_test 2.$tn.3 { + SELECT rowid, a, b, c FROM t1 ORDER BY rowid; + } { + -2 d e fff + -1 a b c + 2 4 5 6 + 3 x y z + 4 X Y Z + } + + integrity_check 2.$tn.4 +} finish_test diff --git a/ext/ota/sqlite3ota.c b/ext/ota/sqlite3ota.c index b1bc1d10cc..a3d73ae67b 100644 --- a/ext/ota/sqlite3ota.c +++ b/ext/ota/sqlite3ota.c @@ -104,8 +104,11 @@ struct OtaObjIter { int nTblCol; /* Size of azTblCol[] array */ char **azTblCol; /* Array of quoted column names */ unsigned char *abTblPk; /* Array of flags - true for PK columns */ + int eType; +#if 0 unsigned char bRowid; /* True for implicit IPK tables */ unsigned char bVtab; /* True for a virtual table */ +#endif /* Output variables. zTbl==0 implies EOF. */ int bCleanup; /* True in "cleanup" state */ @@ -124,6 +127,14 @@ struct OtaObjIter { sqlite3_stmt *pUpdate; /* Last update statement (or NULL) */ }; +/* +** Values for OtaObjIter.eType +*/ +#define OTA_PK_REAL 1 /* Table has a real primary key */ +#define OTA_PK_EXTERNAL 2 /* Table has an external primary key index */ +#define OTA_PK_NONE 3 /* Table has no PK (use rowid) */ +#define OTA_PK_VTAB 4 /* Table is a virtual table (use rowid) */ + /* ** OTA handle. */ @@ -228,8 +239,7 @@ static void otaObjIterFreeCols(OtaObjIter *pIter){ pIter->nTblCol = 0; sqlite3_free(pIter->zMask); pIter->zMask = 0; - pIter->bRowid = 0; - pIter->bVtab = 0; + pIter->eType = 0; /* Invalid value */ } /* @@ -471,11 +481,11 @@ static int otaObjIterGetCols(sqlite3ota *p, OtaObjIter *pIter){ if( pIter->azTblCol==0 ){ sqlite3_stmt *pStmt = 0; int nCol = 0; - int bSeenPk = 0; int i; /* for() loop iterator variable */ int rc2; /* sqlite3_finalize() return value */ + int bOtaRowid = 0; /* If input table has column "ota_rowid" */ - assert( pIter->bRowid==0 && pIter->bVtab==0 ); + assert( pIter->eType==0 ); /* Populate the azTblCol[] and nTblCol variables based on the columns ** of the input table. Ignore any input table columns that begin with @@ -494,6 +504,9 @@ static int otaObjIterGetCols(sqlite3ota *p, OtaObjIter *pIter){ pIter->azTblCol[pIter->nTblCol++] = zCopy; if( zCopy==0 ) p->rc = SQLITE_NOMEM; } + else if( 0==sqlite3_stricmp("ota_rowid", zName) ){ + bOtaRowid = 1; + } } sqlite3_finalize(pStmt); pStmt = 0; @@ -519,20 +532,33 @@ static int otaObjIterGetCols(sqlite3ota *p, OtaObjIter *pIter){ }else{ int iPk = sqlite3_column_int(pStmt, 5); pIter->abTblPk[i] = (iPk!=0); - if( iPk ) bSeenPk = 1; - if( iPk<0 ) pIter->bRowid = 1; + if( iPk ){ + pIter->eType = (iPk<0) ? OTA_PK_EXTERNAL : OTA_PK_REAL; + } } } rc2 = sqlite3_finalize(pStmt); if( p->rc==SQLITE_OK ) p->rc = rc2; - if( p->rc==SQLITE_OK && bSeenPk==0 ){ - const char *zTab = pIter->zTbl; - if( otaIsVtab(p, zTab) ){ - pIter->bVtab = 1; - }else{ - p->zErrmsg = sqlite3_mprintf("table %s has no PRIMARY KEY", zTab); + if( p->rc==SQLITE_OK ){ + if( pIter->eType==0 ){ + /* This must either be a virtual table, or a regular table with no + ** PRIMARY KEY declaration whatsoever. */ + if( bOtaRowid==0 ){ + p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf( + "table data_%q requires ota_rowid column", pIter->zTbl + ); + }else if( otaIsVtab(p, pIter->zTbl) ){ + pIter->eType = OTA_PK_VTAB; + }else{ + pIter->eType = OTA_PK_NONE; + } + }else if( bOtaRowid ){ p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf( + "table data_%q may not have ota_rowid column", pIter->zTbl + ); } } } @@ -618,6 +644,17 @@ static char *otaObjIterGetCollist( return zList; } +/* +** Assuming the current table columns are "a", "b" and "c", and the zObj +** paramter is passed "old", return a string of the form: +** +** "old.a, old.b, old.b" +** +** With the column names escaped. +** +** For tables with implicit rowids - OTA_PK_EXTERNAL and OTA_PK_NONE, append +** the text ", old._rowid_" to the returned value. +*/ static char *otaObjIterGetOldlist( sqlite3ota *p, OtaObjIter *pIter, @@ -637,21 +674,31 @@ static char *otaObjIterGetOldlist( } /* For a table with implicit rowids, append "old._rowid_" to the list. */ - if( pIter->bRowid ){ + if( pIter->eType==OTA_PK_EXTERNAL || pIter->eType==OTA_PK_NONE ){ zList = sqlite3_mprintf("%z, %s._rowid_", zList, zObj); } } return zList; } +/* +** Return an expression that can be used in a WHERE clause to match the +** primary key of the current table. For example, if the table is: +** +** CREATE TABLE t1(a, b, c, PRIMARY KEY(b, c)); +** +** Return the string: +** +** "b = ?1 AND c = ?2" +*/ static char *otaObjIterGetWhere( sqlite3ota *p, OtaObjIter *pIter ){ char *zList = 0; if( p->rc==SQLITE_OK ){ - if( pIter->bVtab ){ - zList = otaMPrintfAndCollectError(p, "rowid = ?%d", pIter->nTblCol+1); + if( pIter->eType==OTA_PK_VTAB || pIter->eType==OTA_PK_NONE ){ + zList = otaMPrintfAndCollectError(p, "_rowid_ = ?%d", pIter->nTblCol+1); }else{ const char *zSep = ""; int i; @@ -759,7 +806,7 @@ static int otaObjIterPrepareAll( int *aiCol; /* Column map */ const char **azColl; /* Collation sequences */ - assert( pIter->bVtab==0 ); + assert( pIter->eType!=OTA_PK_VTAB ); /* Create the index writers */ if( p->rc==SQLITE_OK ){ @@ -777,10 +824,10 @@ static int otaObjIterPrepareAll( zCollist = otaObjIterGetCollist(p, pIter, pIter->nCol, aiCol, azColl); if( p->rc==SQLITE_OK ){ char *zSql; - if( pIter->bRowid ){ + if( pIter->eType==OTA_PK_EXTERNAL || pIter->eType==OTA_PK_NONE ){ zSql = sqlite3_mprintf( "SELECT %s, ota_control FROM ota.'ota_tmp_%q' ORDER BY %s%s", - zCollist, pIter->zTbl, + zCollist, pIter->zTbl, zCollist, zLimit ); }else{ @@ -798,11 +845,13 @@ static int otaObjIterPrepareAll( p->rc = prepareFreeAndCollectError(p->db, &pIter->pSelect, pz, zSql); } }else{ + int bOtaRowid = (pIter->eType==OTA_PK_VTAB || pIter->eType==OTA_PK_NONE); const char *zTbl = pIter->zTbl; - char *zBindings = otaObjIterGetBindlist(p, pIter->nTblCol+pIter->bVtab); char *zWhere = otaObjIterGetWhere(p, pIter); char *zOldlist = otaObjIterGetOldlist(p, pIter, "old"); char *zNewlist = otaObjIterGetOldlist(p, pIter, "new"); + char *zBindings = otaObjIterGetBindlist(p, pIter->nTblCol + bOtaRowid); + zCollist = otaObjIterGetCollist(p, pIter, pIter->nTblCol, 0, 0); pIter->nCol = pIter->nTblCol; @@ -811,7 +860,7 @@ static int otaObjIterPrepareAll( p->rc = prepareFreeAndCollectError(p->db, &pIter->pSelect, pz, sqlite3_mprintf( "SELECT %s, ota_control%s FROM ota.'data_%q'%s", - zCollist, (pIter->bVtab ? ", ota_rowid" : ""), zTbl, zLimit + zCollist, (bOtaRowid ? ", ota_rowid" : ""), zTbl, zLimit ) ); } @@ -821,7 +870,7 @@ static int otaObjIterPrepareAll( p->rc = prepareFreeAndCollectError(p->db, &pIter->pInsert, pz, sqlite3_mprintf( "INSERT INTO main.%Q(%s%s) VALUES(%s)", - zTbl, zCollist, (pIter->bVtab ? ", rowid" : ""), zBindings + zTbl, zCollist, (bOtaRowid ? ", _rowid_" : ""), zBindings ) ); } @@ -835,8 +884,11 @@ static int otaObjIterPrepareAll( ); } - if( pIter->bVtab==0 ){ - const char *zOtaRowid = (pIter->bRowid ? ", ota_rowid" : ""); + if( pIter->eType!=OTA_PK_VTAB ){ + const char *zOtaRowid = ""; + if( pIter->eType==OTA_PK_EXTERNAL || pIter->eType==OTA_PK_NONE ){ + zOtaRowid = ", ota_rowid"; + } /* Create the ota_tmp_xxx table and the triggers to populate it. */ otaMPrintfExec(p, @@ -858,12 +910,13 @@ static int otaObjIterPrepareAll( "BEGIN " " INSERT INTO 'ota_tmp_%q'(ota_control, %s%s) VALUES(3, %s);" "END;" - , zTbl, (pIter->bRowid ? ", 0 AS ota_rowid" : ""), zTbl, + , zTbl, (pIter->eType==OTA_PK_EXTERNAL ? ", 0 AS ota_rowid" : "") + , zTbl, zTbl, zTbl, zTbl, zCollist, zOtaRowid, zOldlist, zTbl, zTbl, zTbl, zCollist, zOtaRowid, zOldlist, zTbl, zTbl, zTbl, zCollist, zOtaRowid, zNewlist ); - if( pIter->bRowid ){ + if( pIter->eType==OTA_PK_EXTERNAL || pIter->eType==OTA_PK_NONE ){ otaMPrintfExec(p, "CREATE TEMP TRIGGER ota_insert_%q AFTER INSERT ON main.%Q " "BEGIN " @@ -1074,6 +1127,15 @@ static int otaStepType(sqlite3ota *p, const char **pzMask){ return res; } +#ifdef SQLITE_DEBUG +static void assertColumnName(sqlite3_stmt *pStmt, int iCol, const char *zName){ + const char *zCol = sqlite3_column_name(pStmt, iCol); + assert( 0==sqlite3_stricmp(zName, zCol) ); +} +#else +# define assertColumnName(x,y,z) +#endif + /* ** This function does the work for an sqlite3ota_step() call. ** @@ -1122,13 +1184,17 @@ static int otaStep(sqlite3ota *p){ pVal = sqlite3_column_value(pIter->pSelect, i); sqlite3_bind_value(pWriter, i+1, pVal); } - if( pIter->bVtab ){ - /* For a virtual table, the SELECT statement is: + if( pIter->zIdx==0 + && (pIter->eType==OTA_PK_VTAB || pIter->eType==OTA_PK_NONE) + ){ + /* For a virtual table, or a table with no primary key, the + ** SELECT statement is: ** ** SELECT , ota_control, ota_rowid FROM .... ** ** Hence column_value(pIter->nCol+1). */ + assertColumnName(pIter->pSelect, pIter->nCol+1, "ota_rowid"); pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1); sqlite3_bind_value(pWriter, pIter->nCol+1, pVal); } @@ -1143,7 +1209,9 @@ static int otaStep(sqlite3ota *p){ pVal = sqlite3_column_value(pIter->pSelect, i); sqlite3_bind_value(pUpdate, i+1, pVal); } - if( pIter->bVtab ){ + if( pIter->eType==OTA_PK_VTAB || pIter->eType==OTA_PK_NONE ){ + /* Bind the ota_rowid value to column _rowid_ */ + assertColumnName(pIter->pSelect, pIter->nCol+1, "ota_rowid"); pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1); sqlite3_bind_value(pUpdate, pIter->nCol+1, pVal); } @@ -1195,7 +1263,7 @@ int sqlite3ota_step(sqlite3ota *p){ /* 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. */ - if( pIter->bVtab==0 ){ + if( pIter->eType!=OTA_PK_VTAB ){ otaMPrintfExec(p, "DELETE FROM ota.'ota_tmp_%q'", pIter->zTbl); } }else{ diff --git a/manifest b/manifest index 00072e021c..9e9bafcb13 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Update\sota\sso\sthat\sthe\shidden\scolumns\sof\svirtual\stables\smay\sbe\swritten. -D 2014-11-27T18:09:46.630 +C Allow\sthe\sota\sextension\sto\swrite\sto\stables\swith\sno\sPRIMARY\sKEY\sdeclaration. +D 2014-12-06T19:30:41.673 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in a226317fdf3f4c895fb3cfedc355b4d0868ce1fb F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -125,7 +125,7 @@ F ext/misc/vtshim.c babb0dc2bf116029e3e7c9a618b8a1377045303e F ext/misc/wholenumber.c 784b12543d60702ebdd47da936e278aa03076212 F ext/ota/README.txt 78d4a9f78f567d4bf826cf0f02df6254902562ca F ext/ota/ota.c c11a85af71dccc45976622fe7a51169a481caa91 -F ext/ota/ota1.test a8f9d89c9b2d381a663bcedaa5dd5952cdbd1231 +F ext/ota/ota1.test 64770d76d3dc00c24f9a78ac69e4448708bde985 F ext/ota/ota10.test ab815dff9cef7248c504f06b888627d236f25e9c F ext/ota/ota2.test 4568c2671d19dbde789fb9091d727a2e94880128 F ext/ota/ota3.test 71bd8cc0cf8d7e7d9bb11a1fcc238320a5a9d8c8 @@ -134,9 +134,9 @@ F ext/ota/ota5.test ad0799daf8923ddebffe75ae8c5504ca90b7fadb F ext/ota/ota6.test 82f1f757ec9b2ad07d6de4060b8e3ba8e44dfdd3 F ext/ota/ota7.test 1fe2c5761705374530e29f70c39693076028221a F ext/ota/ota8.test cd70e63a0c29c45c0906692827deafa34638feda -F ext/ota/ota9.test d9ad30ccb4e08f878e382876fe67752309538af9 +F ext/ota/ota9.test d3eee95dd836824d07a22e5efcdb7bf6e869358b F ext/ota/otafault.test be02466863015a583cc0ceb6aca871a5e6f7a71b -F ext/ota/sqlite3ota.c f1e930690ec2bcb72138301ebf05ea2ffd4450de +F ext/ota/sqlite3ota.c 84cab0f965144772068ec0183252ae5e5278f0be F ext/ota/sqlite3ota.h 8dc9c812e61f47f497336a45f820bee88b33a2c5 F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 F ext/rtree/rtree.c 57bec53e1a677ab74217fe1f20a58c3a47261d6b @@ -1237,7 +1237,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 600cefdd4d29c1de4d107fa7ddeb76a18edce4f5 -R 19cf180a791a327f1f4fb68723a586ef +P ccee999649d0fa1d48e53847542f4cbe05e3d694 +R 23b9af0814440584d410f9d611aafb5a U dan -Z 7646a22500cce7c7ef211b057e34ff27 +Z b3bc69908e00e34016dd9eb2dbbe05c2 diff --git a/manifest.uuid b/manifest.uuid index 0c798bdfcc..b63e4f6272 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ccee999649d0fa1d48e53847542f4cbe05e3d694 \ No newline at end of file +ba59a7e2ba97244492cbca9247456df0f3f19248 \ No newline at end of file