diff --git a/ext/ota/ota10.test b/ext/ota/ota10.test new file mode 100644 index 0000000000..bca34d68cb --- /dev/null +++ b/ext/ota/ota10.test @@ -0,0 +1,122 @@ +# 2014 August 30 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source $testdir/tester.tcl +set ::testprefix ota10 + + +#-------------------------------------------------------------------- +# Test that UPDATE commands work even if the input columns are in a +# different order to the output columns. +# +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + INSERT INTO t1 VALUES(1, 'b', 'c'); +} + +proc apply_ota {sql} { + forcedelete ota.db + sqlite3 db2 ota.db + db2 eval $sql + db2 close + sqlite3ota ota test.db ota.db + while { [ota step]=="SQLITE_OK" } {} + ota close +} + +do_test 1.1 { + apply_ota { + CREATE TABLE data_t1(a, c, b, ota_control); + INSERT INTO data_t1 VALUES(1, 'xxx', NULL, '.x.'); + } + db eval { SELECT * FROM t1 } +} {1 b xxx} + +#-------------------------------------------------------------------- +# Test that the hidden languageid column of an fts4 table can be +# written. +# +ifcapable fts3 { + do_execsql_test 2.0 { + CREATE VIRTUAL TABLE ft USING fts4(a, b, languageid='langid'); + } + do_test 2.1 { + apply_ota { + CREATE TABLE data_ft(a, b, ota_rowid, langid, ota_control); + INSERT INTO data_ft VALUES('a', 'b', 22, 1, 0); -- insert + INSERT INTO data_ft VALUES('a', 'b', 23, 10, 0); -- insert + INSERT INTO data_ft VALUES('a', 'b', 24, 100, 0); -- insert + } + db eval { SELECT a, b, rowid, langid FROM ft } + } [list {*}{ + a b 22 1 + a b 23 10 + a b 24 100 + }] + + # Or not - this data_xxx table has no langid column, so langid + # defaults to 0. + # + do_test 2.2 { + apply_ota { + CREATE TABLE data_ft(a, b, ota_rowid, ota_control); + INSERT INTO data_ft VALUES('a', 'b', 25, 0); -- insert + } + db eval { SELECT a, b, rowid, langid FROM ft } + } [list {*}{ + a b 22 1 + a b 23 10 + a b 24 100 + a b 25 0 + }] + + # Update langid. + # + do_test 2.3 { + apply_ota { + CREATE TABLE data_ft(a, b, ota_rowid, langid, ota_control); + INSERT INTO data_ft VALUES(NULL, NULL, 23, 50, '..x'); + INSERT INTO data_ft VALUES(NULL, NULL, 25, 500, '..x'); + } + db eval { SELECT a, b, rowid, langid FROM ft } + } [list {*}{ + a b 22 1 + a b 23 50 + a b 24 100 + a b 25 500 + }] +} + +#-------------------------------------------------------------------- +# Test that if writing a hidden virtual table column is an error, +# attempting to do so via ota is also an error. +# +ifcapable fts3 { + do_execsql_test 3.0 { + CREATE VIRTUAL TABLE xt USING fts4(a); + } + do_test 3.1 { + list [catch { + apply_ota { + CREATE TABLE data_xt(a, xt, ota_rowid, ota_control); + INSERT INTO data_xt VALUES('a', 'b', 1, 0); + } + } msg] $msg + } {1 {SQLITE_ERROR - SQL logic error or missing database}} +} + + + +finish_test diff --git a/ext/ota/ota3.test b/ext/ota/ota3.test index 09d1bab083..8921c12b35 100644 --- a/ext/ota/ota3.test +++ b/ext/ota/ota3.test @@ -96,7 +96,7 @@ do_test 2.1 { } db2 close list [catch { run_ota test.db ota.db } msg] $msg -} {1 {SQLITE_ERROR - no such column: c}} +} {1 {SQLITE_ERROR - column missing from data_x1: c}} do_execsql_test 2.2 { PRAGMA integrity_check; diff --git a/ext/ota/sqlite3ota.c b/ext/ota/sqlite3ota.c index 6c3c99434e..b1bc1d10cc 100644 --- a/ext/ota/sqlite3ota.c +++ b/ext/ota/sqlite3ota.c @@ -223,7 +223,6 @@ static void otaObjIterFreeCols(OtaObjIter *pIter){ sqlite3_free(pIter->azTblCol[i]); } sqlite3_free(pIter->azTblCol); - sqlite3_free(pIter->abTblPk); pIter->azTblCol = 0; pIter->abTblPk = 0; pIter->nTblCol = 0; @@ -369,6 +368,24 @@ static char *otaQuoteName(const char *zName){ return zRet; } +/* +** Argument zName points to a column name. Argument zQuoted also points +** to a column name, but one that has been quoted using otaQuoteName(). +** Return true if the column names are the same, or false otherwise. +*/ +static int otaMatchName(const char *zName, const char *zQuoted){ + const char *p = zName; + const char *q = &zQuoted[1]; + while( 1 ){ + if( *q=='`' ) q++; + if( sqlite3_strnicmp(q, p, 1) ) return 0; + if( !*q ) break; + p++; + q++; + } + return 1; +} + /* ** Argument zFmt is a sqlite3_mprintf() style format string. The trailing ** arguments are the usual subsitution values. This function performs @@ -396,21 +413,22 @@ static int otaMPrintfExec(sqlite3ota *p, const char *zFmt, ...){ } /* -** Increase the size of the pIter->azTblCol[] and abTblPk[] arrays so that +** Allocate and zero the pIter->azTblCol[] and abTblPk[] arrays so that ** there is room for at least nCol elements. If an OOM occurs, store an ** error code in the OTA handle passed as the first argument. */ -static void otaExtendIterArrays(sqlite3ota *p, OtaObjIter *pIter, int nCol){ - assert( p->rc==SQLITE_OK ); - if( (nCol % 8)==0 ){ - unsigned char *abNew; - int nByte = sizeof(char*) * (nCol+8); - char **azNew = (char**)sqlite3_realloc(pIter->azTblCol, nByte); - abNew = (unsigned char*)sqlite3_realloc(pIter->abTblPk, nCol+8); +static void otaAllocateIterArrays(sqlite3ota *p, OtaObjIter *pIter, int nCol){ + int nByte = sizeof(char*) * nCol + sizeof(unsigned char*) * nCol; + char **azNew; - if( azNew ) pIter->azTblCol = azNew; - if( abNew ) pIter->abTblPk = abNew; - if( azNew==0 || abNew==0 ) p->rc = SQLITE_NOMEM; + assert( p->rc==SQLITE_OK ); + azNew = (char**)sqlite3_malloc(nByte); + if( azNew ){ + memset(azNew, 0, nByte); + pIter->azTblCol = azNew; + pIter->abTblPk = (unsigned char*)&pIter->azTblCol[nCol]; + }else{ + p->rc = SQLITE_NOMEM; } } @@ -451,30 +469,60 @@ static int otaIsVtab(sqlite3ota *p, const char *zTab){ */ static int otaObjIterGetCols(sqlite3ota *p, OtaObjIter *pIter){ if( pIter->azTblCol==0 ){ - sqlite3_stmt *pStmt; - char *zSql; + sqlite3_stmt *pStmt = 0; int nCol = 0; int bSeenPk = 0; + int i; /* for() loop iterator variable */ int rc2; /* sqlite3_finalize() return value */ assert( pIter->bRowid==0 && pIter->bVtab==0 ); - zSql = sqlite3_mprintf("PRAGMA main.table_info(%Q)", pIter->zTbl); - p->rc = prepareFreeAndCollectError(p->db, &pStmt, &p->zErrmsg, zSql); - while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ - otaExtendIterArrays(p, pIter, nCol); - if( p->rc==SQLITE_OK ){ - const char *zName = (const char*)sqlite3_column_text(pStmt, 1); - int iPk = sqlite3_column_int(pStmt, 5); - pIter->abTblPk[nCol] = (iPk!=0); - if( iPk ) bSeenPk = 1; - if( iPk<0 ) pIter->bRowid = 1; - pIter->azTblCol[nCol] = otaQuoteName(zName); - if( pIter->azTblCol[nCol]==0 ) p->rc = SQLITE_NOMEM; - nCol++; + /* Populate the azTblCol[] and nTblCol variables based on the columns + ** of the input table. Ignore any input table columns that begin with + ** "ota_". */ + p->rc = prepareFreeAndCollectError(p->db, &pStmt, &p->zErrmsg, + sqlite3_mprintf("SELECT * FROM 'data_%q'", pIter->zTbl) + ); + if( p->rc==SQLITE_OK ){ + nCol = sqlite3_column_count(pStmt); + otaAllocateIterArrays(p, pIter, nCol); + } + for(i=0; p->rc==SQLITE_OK && iazTblCol[pIter->nTblCol++] = zCopy; + if( zCopy==0 ) p->rc = SQLITE_NOMEM; + } + } + sqlite3_finalize(pStmt); + pStmt = 0; + + /* Check that all non-HIDDEN columns in the destination table are also + ** present in the input table. Populate the abTblPk[] array at the + ** same time. */ + if( p->rc==SQLITE_OK ){ + p->rc = prepareFreeAndCollectError(p->db, &pStmt, &p->zErrmsg, + sqlite3_mprintf("PRAGMA main.table_info(%Q)", pIter->zTbl) + ); + } + while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + const char *zName = (const char*)sqlite3_column_text(pStmt, 1); + for(i=0; inTblCol; i++){ + if( otaMatchName(zName, pIter->azTblCol[i]) ) break; + } + if( i==pIter->nTblCol ){ + p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf("column missing from data_%q: %s", + pIter->zTbl, zName + ); + }else{ + int iPk = sqlite3_column_int(pStmt, 5); + pIter->abTblPk[i] = (iPk!=0); + if( iPk ) bSeenPk = 1; + if( iPk<0 ) pIter->bRowid = 1; } } - pIter->nTblCol = nCol; rc2 = sqlite3_finalize(pStmt); if( p->rc==SQLITE_OK ) p->rc = rc2; diff --git a/ext/ota/sqlite3ota.h b/ext/ota/sqlite3ota.h index 8a201f25bd..a652316ca2 100644 --- a/ext/ota/sqlite3ota.h +++ b/ext/ota/sqlite3ota.h @@ -106,6 +106,19 @@ ** ** CREATE TABLE data_ft1(a, b, ota_rowid, ota_control); ** +** All non-hidden columns (i.e. all columns matched by "SELECT *") of the +** target table must be present in the input table. For virtual tables, +** hidden columns are optional - they are updated by OTA if present in +** the input table, or not otherwise. For example, to write to an fts4 +** table with a hidden languageid column such as: +** +** CREATE VIRTUAL TABLE ft1 USING fts4(a, b, languageid='langid'); +** +** Either of the following input table schemas may be used: +** +** CREATE TABLE data_ft1(a, b, langid, ota_rowid, ota_control); +** CREATE TABLE data_ft1(a, b, ota_rowid, ota_control); +** ** For each row to INSERT into the target database as part of the OTA ** update, the corresponding data_% table should contain a single record ** with the "ota_control" column set to contain integer value 0. The diff --git a/manifest b/manifest index 5d142cd5c1..00072e021c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sSQLITE_ENABLE_OTA\spre-processor\sdirectives\sso\sthat\sthis\sbranch\smay\sbe\scompiled\swith\sor\swithout\sOTA. -D 2014-11-22T09:09:50.320 +C Update\sota\sso\sthat\sthe\shidden\scolumns\sof\svirtual\stables\smay\sbe\swritten. +D 2014-11-27T18:09:46.630 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in a226317fdf3f4c895fb3cfedc355b4d0868ce1fb F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -126,8 +126,9 @@ 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/ota10.test ab815dff9cef7248c504f06b888627d236f25e9c F ext/ota/ota2.test 4568c2671d19dbde789fb9091d727a2e94880128 -F ext/ota/ota3.test 215dd4a8e238567e0f890a5139b6fdf5494ef311 +F ext/ota/ota3.test 71bd8cc0cf8d7e7d9bb11a1fcc238320a5a9d8c8 F ext/ota/ota4.test 60f897f329a6782ef2f24862640acf3c52e48077 F ext/ota/ota5.test ad0799daf8923ddebffe75ae8c5504ca90b7fadb F ext/ota/ota6.test 82f1f757ec9b2ad07d6de4060b8e3ba8e44dfdd3 @@ -135,8 +136,8 @@ F ext/ota/ota7.test 1fe2c5761705374530e29f70c39693076028221a F ext/ota/ota8.test cd70e63a0c29c45c0906692827deafa34638feda F ext/ota/ota9.test d9ad30ccb4e08f878e382876fe67752309538af9 F ext/ota/otafault.test be02466863015a583cc0ceb6aca871a5e6f7a71b -F ext/ota/sqlite3ota.c accfada2ab182dc52225e7345f656520a4e8db22 -F ext/ota/sqlite3ota.h 04577b00c456aacb99be9c8b55572a6e3ca9aa27 +F ext/ota/sqlite3ota.c f1e930690ec2bcb72138301ebf05ea2ffd4450de +F ext/ota/sqlite3ota.h 8dc9c812e61f47f497336a45f820bee88b33a2c5 F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 F ext/rtree/rtree.c 57bec53e1a677ab74217fe1f20a58c3a47261d6b F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e @@ -1236,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 7ef44c5b5bd30bcc4ef59ed172b9ce9ac6a843f6 -R a969a1e9314cd2ef179f3839efd0b8ec +P 600cefdd4d29c1de4d107fa7ddeb76a18edce4f5 +R 19cf180a791a327f1f4fb68723a586ef U dan -Z bba2f4cd69d571c58d439710133b8bb2 +Z 7646a22500cce7c7ef211b057e34ff27 diff --git a/manifest.uuid b/manifest.uuid index 3c2a061073..0c798bdfcc 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -600cefdd4d29c1de4d107fa7ddeb76a18edce4f5 \ No newline at end of file +ccee999649d0fa1d48e53847542f4cbe05e3d694 \ No newline at end of file