From 1ffede8c86303506cf7a1536d00b9b5b688ae595 Mon Sep 17 00:00:00 2001 From: drh Date: Fri, 30 Jan 2015 20:59:27 +0000 Subject: [PATCH] Change SQLITE_TESTCTRL_INITMODE to SQLITE_TESTCTRL_IMPOSTER. Revise the order of parameters. Give it the ability to reset the schema parse table so that imposter tables can be erased. FossilOrigin-Name: 42d5601739c90434e5adfda8fa99ef7b903877db --- manifest | 27 +++++------ manifest.uuid | 2 +- src/btree.c | 6 +++ src/build.c | 11 +++-- src/main.c | 25 ++++++++-- src/shell.c | 8 +-- src/sqlite.h.in | 2 +- src/sqliteInt.h | 1 + src/test1.c | 17 ++++--- test/{initmode.test => imposter1.test} | 67 +++++++++++++++++++++----- 10 files changed, 116 insertions(+), 50 deletions(-) rename test/{initmode.test => imposter1.test} (60%) diff --git a/manifest b/manifest index 09d4bc0fa8..f8fe7806e2 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Added\sSQLITE_TESTCTRL_INITMODE\sfor\simproved\stestability. -D 2015-01-30T15:52:26.210 +C Change\sSQLITE_TESTCTRL_INITMODE\sto\sSQLITE_TESTCTRL_IMPOSTER.\s\sRevise\sthe\sorder\nof\sparameters.\s\sGive\sit\sthe\sability\sto\sreset\sthe\sschema\sparse\stable\sso\sthat\nimposter\stables\scan\sbe\serased. +D 2015-01-30T20:59:27.457 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 5407a688f4d77a05c18a8142be8ae5a2829dd610 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -173,10 +173,10 @@ F src/auth.c b56c78ebe40a2110fd361379f7e8162d23f92240 F src/backup.c 7ddee9c7d505e07e959a575b18498f17c71e53ea F src/bitvec.c 19a4ba637bd85f8f63fc8c9bae5ade9fb05ec1cb F src/btmutex.c 49ca66250c7dfa844a4d4cb8272b87420d27d3a5 -F src/btree.c 4c098bb6e8678e4596983862abf78f7a0fcb807e +F src/btree.c 2a1245df0356a229bcd0fd87a8536b5067f16e82 F src/btree.h 94277c1d30c0b75705974bcc8b0c05e79c03d474 F src/btreeInt.h a3d0ae1d511365e1a2b76ad10960dbe55c286f34 -F src/build.c f5cfd7b32216f695b995bbc7c1a395f6d451d11f +F src/build.c eefaa4f1d86bc3c08023a61fdd1e695b47796975 F src/callback.c 7b44ce59674338ad48b0e84e7b72f935ea4f68b0 F src/complete.c 198a0066ba60ab06fc00fba1998d870a4d575463 F src/ctime.c 98f89724adc891a1a4c655bee04e33e716e05887 @@ -195,7 +195,7 @@ F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d F src/legacy.c ba1863ea58c4c840335a84ec276fc2b25e22bc4e F src/lempar.c 7274c97d24bb46631e504332ccd3bd1b37841770 F src/loadext.c 86bd4e2fccd520b748cba52492ab60c4a770f660 -F src/main.c 81ddebf2feb9cbd8c8ea160cdd979503f645d505 +F src/main.c ce38ddcedf33e5530b0e6c592809bb8822a6e8d0 F src/malloc.c 740db54387204c9a2eb67c6d98e68b08e9ef4eab F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c abe6ee469b6c5a35c7f22bfeb9c9bac664a1c987 @@ -230,16 +230,16 @@ F src/random.c ba2679f80ec82c4190062d756f22d0c358180696 F src/resolve.c f6c46d3434439ab2084618d603e6d6dbeb0d6ada F src/rowset.c eccf6af6d620aaa4579bd3b72c1b6395d9e9fa1e F src/select.c 1f2087523007c42900ffcbdeaef06a23ad9329fc -F src/shell.c ed7cf7c29fb1a23d47179affc89cb447868fc976 -F src/sqlite.h.in 6910064681444efb5c467472499b56bb6bcee0f4 +F src/shell.c 22b4406b0b59efd14b3b351a5809dda517df6d30 +F src/sqlite.h.in 54678c21401909f72b221344dd560d285a1ba5eb F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad F src/sqlite3ext.h 17d487c3c91b0b8c584a32fbeb393f6f795eea7d -F src/sqliteInt.h eaf210295b551d4e40e622aec1b2261c0b28f844 +F src/sqliteInt.h c4e05f7489cd300f856e2283d5e61302ce826471 F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d F src/status.c 81712116e826b0089bb221b018929536b2b5406f F src/table.c e7a09215315a978057fb42c640f890160dbcc45e F src/tclsqlite.c b8014393a96a9781bb635c8b1f52fc9b77a2bfcf -F src/test1.c 5dcdade99e77b7b9f7760106c80a83cf50f10e1e +F src/test1.c 90fbedce75330d48d99eadb7d5f4223e86969585 F src/test2.c 577961fe48961b2f2e5c8b56ee50c3f459d3359d F src/test3.c 64d2afdd68feac1bb5e2ffb8226c8c639f798622 F src/test4.c d168f83cc78d02e8d35567bb5630e40dcd85ac1e @@ -630,6 +630,7 @@ F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 F test/hexlit.test f9ecde8145bfc2341573473256c74ae37a200497 F test/hook.test 162d7cef7a2d2b04839fe14402934e6a1b79442f F test/icu.test 70df4faca133254c042d02ae342c0a141f2663f4 +F test/imposter1.test c3f1db2d3db2c24611a6596a3fc0ffc14f1466c8 w test/initmode.test F test/in.test 047c4671328e9032ab95666a67021adbbd36e98e F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75 F test/in3.test 3cbf58c87f4052cee3a58b37b6389777505aa0c0 @@ -655,7 +656,6 @@ F test/index7.test 917cf1e1c7439bb155abbeabec511b28945e157b F test/indexedby.test b2f22f3e693a53813aa3f50b812eb609ba6df1ec F test/indexfault.test 31d4ab9a7d2f6e9616933eb079722362a883eb1d F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7 -F test/initmode.test 38bbaefeb47e034a162856e664a0da3b95e6999b F test/insert.test 38742b5e9601c8f8d76e9b7555f7270288c2d371 F test/insert2.test 4f3a04d168c728ed5ec2c88842e772606c7ce435 F test/insert3.test 1b7db95a03ad9c5013fdf7d6722b6cd66ee55e30 @@ -1238,8 +1238,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 1797158db2a818134c5cba1578f69ed85948b980 3a6e2afe408d2b0c8166d00def2048568169d87a -R 4fa14bf275b60ddd661b9bd3fb0ea0dd -T +closed 3a6e2afe408d2b0c8166d00def2048568169d87a +P 98e029134dc1300d3ecb48b41b5107ec69ba85db +R ae5f14ccaf584de0786e4614152020d2 U drh -Z b68a5e615b7688be627f33067b9af11c +Z c23d2e36f9e88e779a131b19c0ee1b9b diff --git a/manifest.uuid b/manifest.uuid index d367511342..9eba1b1a54 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -98e029134dc1300d3ecb48b41b5107ec69ba85db \ No newline at end of file +42d5601739c90434e5adfda8fa99ef7b903877db \ No newline at end of file diff --git a/src/btree.c b/src/btree.c index f9f76c2ebb..eb5151351c 100644 --- a/src/btree.c +++ b/src/btree.c @@ -175,6 +175,12 @@ static int hasSharedCacheTableLock( for(p=sqliteHashFirst(&pSchema->idxHash); p; p=sqliteHashNext(p)){ Index *pIdx = (Index *)sqliteHashData(p); if( pIdx->tnum==(int)iRoot ){ + if( iTab ){ + /* Two or more indexes share the same root page. There must + ** be imposter tables. So just return true. The assert is not + ** useful in that case. */ + return 1; + } iTab = pIdx->pTable->tnum; } } diff --git a/src/build.c b/src/build.c index f02989bffe..7e3ce1b76a 100644 --- a/src/build.c +++ b/src/build.c @@ -1731,11 +1731,14 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ assert( pPk!=0 ); nPk = pPk->nKeyCol; - /* Make sure every column of the PRIMARY KEY is NOT NULL */ - for(i=0; iaCol[pPk->aiColumn[i]].notNull = 1; + /* Make sure every column of the PRIMARY KEY is NOT NULL. (Except, + ** do not enforce this for imposter tables.) */ + if( !db->init.imposterTable ){ + for(i=0; iaCol[pPk->aiColumn[i]].notNull = 1; + } + pPk->uniqNotNull = 1; } - pPk->uniqNotNull = 1; /* The root page of the PRIMARY KEY is the table root page */ pPk->tnum = pTab->tnum; diff --git a/src/main.c b/src/main.c index 0d6f1be246..11585e7dc5 100644 --- a/src/main.c +++ b/src/main.c @@ -3599,15 +3599,30 @@ int sqlite3_test_control(int op, ...){ break; } - /* sqlite3_test_control(SQLITE_TESTCTRL_INITMODE, db, busy, iDb, newTnum); + /* sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, onOff, tnum); ** - ** Set the db->init.busy, db->init.iDb, and db->init.tnum fields. + ** This test control is used to create imposter tables. "db" is a pointer + ** to the database connection. dbName is the database name (ex: "main" or + ** "temp") which will receive the imposter. "onOff" turns imposter mode on + ** or off. "tnum" is the root page of the b-tree to which the imposter + ** table should connect. + ** + ** Enable imposter mode only when the schema has already been parsed. Then + ** run a single CREATE TABLE statement to construct the imposter table in the + ** parsed schema. Then turn imposter mode back off again. + ** + ** If onOff==0 and tnum>0 then reset the schema for all databases, causing + ** the schema to be reparsed the next time it is needed. This has the + ** effect of erasing all imposter tables. */ - case SQLITE_TESTCTRL_INITMODE: { + case SQLITE_TESTCTRL_IMPOSTER: { sqlite3 *db = va_arg(ap, sqlite3*); - db->init.busy = va_arg(ap,int); - db->init.iDb = va_arg(ap,int); + db->init.iDb = sqlite3FindDbName(db, va_arg(ap,const char*)); + db->init.busy = db->init.imposterTable = va_arg(ap,int); db->init.newTnum = va_arg(ap,int); + if( db->init.busy==0 && db->init.newTnum>0 ){ + sqlite3ResetAllSchemasOfConnection(db); + } break; } } diff --git a/src/shell.c b/src/shell.c index 1a191e0fd0..3130f4c6de 100644 --- a/src/shell.c +++ b/src/shell.c @@ -3536,7 +3536,7 @@ static int do_meta_command(char *zLine, ShellState *p){ { "scratchmalloc", SQLITE_TESTCTRL_SCRATCHMALLOC }, { "byteorder", SQLITE_TESTCTRL_BYTEORDER }, { "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT }, - { "initmode", SQLITE_TESTCTRL_INITMODE }, + { "imposter", SQLITE_TESTCTRL_IMPOSTER }, }; int testctrl = -1; int rc = 0; @@ -3629,14 +3629,14 @@ static int do_meta_command(char *zLine, ShellState *p){ break; #endif - case SQLITE_TESTCTRL_INITMODE: + case SQLITE_TESTCTRL_IMPOSTER: if( nArg==5 ){ rc = sqlite3_test_control(testctrl, p->db, - integerValue(azArg[2]), + azArg[2], integerValue(azArg[3]), integerValue(azArg[4])); }else{ - fprintf(stderr,"Usage: .testctrl initmode fBusy iDb newTnum\n"); + fprintf(stderr,"Usage: .testctrl initmode dbName onoff tnum\n"); rc = 1; } break; diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 722b9235a2..f256cea45d 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -6260,7 +6260,7 @@ int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_BYTEORDER 22 #define SQLITE_TESTCTRL_ISINIT 23 #define SQLITE_TESTCTRL_SORTER_MMAP 24 -#define SQLITE_TESTCTRL_INITMODE 25 +#define SQLITE_TESTCTRL_IMPOSTER 25 #define SQLITE_TESTCTRL_LAST 25 /* diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 379456d5f5..0bc6f679dd 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1087,6 +1087,7 @@ struct sqlite3 { u8 iDb; /* Which db file is being initialized */ u8 busy; /* TRUE if currently initializing */ u8 orphanTrigger; /* Last statement is orphaned TEMP trigger */ + u8 imposterTable; /* Building an imposter table */ } init; int nVdbeActive; /* Number of VDBEs currently running */ int nVdbeRead; /* Number of active VDBEs that read or write */ diff --git a/src/test1.c b/src/test1.c index b9503cf394..a87fcd859d 100644 --- a/src/test1.c +++ b/src/test1.c @@ -5915,7 +5915,7 @@ static int test_test_control( } aVerb[] = { { "SQLITE_TESTCTRL_LOCALTIME_FAULT", SQLITE_TESTCTRL_LOCALTIME_FAULT }, { "SQLITE_TESTCTRL_SORTER_MMAP", SQLITE_TESTCTRL_SORTER_MMAP }, - { "SQLITE_TESTCTRL_INITMODE", SQLITE_TESTCTRL_INITMODE }, + { "SQLITE_TESTCTRL_IMPOSTER", SQLITE_TESTCTRL_IMPOSTER }, }; int iVerb; int iFlag; @@ -5957,18 +5957,19 @@ static int test_test_control( break; } - case SQLITE_TESTCTRL_INITMODE: { - int fBusy, iDb, newTnum; + case SQLITE_TESTCTRL_IMPOSTER: { + int onOff, tnum; + const char *zDbName; sqlite3 *db; if( objc!=6 ){ - Tcl_WrongNumArgs(interp, 2, objv, "DB fBusy iDb newTnum"); + Tcl_WrongNumArgs(interp, 2, objv, "DB dbName onOff tnum"); return TCL_ERROR; } if( getDbPointer(interp, Tcl_GetString(objv[2]), &db) ) return TCL_ERROR; - if( Tcl_GetIntFromObj(interp, objv[3], &fBusy) ) return TCL_ERROR; - if( Tcl_GetIntFromObj(interp, objv[4], &iDb) ) return TCL_ERROR; - if( Tcl_GetIntFromObj(interp, objv[5], &newTnum) ) return TCL_ERROR; - sqlite3_test_control(SQLITE_TESTCTRL_INITMODE, db, fBusy, iDb, newTnum); + zDbName = Tcl_GetString(objv[3]); + if( Tcl_GetIntFromObj(interp, objv[4], &onOff) ) return TCL_ERROR; + if( Tcl_GetIntFromObj(interp, objv[5], &tnum) ) return TCL_ERROR; + sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, zDbName, onOff, tnum); break; } } diff --git a/test/initmode.test b/test/imposter1.test similarity index 60% rename from test/initmode.test rename to test/imposter1.test index ce3f675857..196767be15 100644 --- a/test/initmode.test +++ b/test/imposter1.test @@ -12,17 +12,17 @@ # This file implements tests for SQLite library. # # The focus of this file is adding extra entries in the symbol table -# using sqlite3_test_control(SQLITE_TESTCTRL_INITMODE) and verifying that +# using sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER) and verifying that # SQLite handles those as expected. # set testdir [file dirname $argv0] source $testdir/tester.tcl -set testprefix initmode +set testprefix imposter # Create a bunch of data to sort against # -do_test initmode-1.0 { +do_test imposter-1.0 { execsql { CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, d NOT NULL); CREATE INDEX t1b ON t1(b); @@ -31,15 +31,21 @@ do_test initmode-1.0 { INSERT INTO t1(a,b,c,d) SELECT i,1000+i,2000+i,3000+i FROM c; } set t1_root [db one {SELECT rootpage FROM sqlite_master WHERE name='t1'}] - set t1a_root [db one {SELECT rootpage FROM sqlite_master WHERE name='t1a'}] set t1b_root [db one {SELECT rootpage FROM sqlite_master WHERE name='t1b'}] + set t1c_root [db one {SELECT rootpage FROM sqlite_master WHERE name='t1c'}] - # Create a shadow table that uses the same b-tree as t1 but which does + # Create an imposter table that uses the same b-tree as t1 but which does # not have the indexes # - sqlite3_test_control SQLITE_TESTCTRL_INITMODE db 1 0 $t1_root + sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $t1_root db eval {CREATE TABLE xt1(a,b,c,d)} - sqlite3_test_control SQLITE_TESTCTRL_INITMODE db 0 0 0 + + # And create an imposter table for the t1c index. + sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $t1c_root + db eval {CREATE TABLE xt1c(c,rowid,PRIMARY KEY(c,rowid))WITHOUT ROWID;} + + # Go out of imposter mode for now. + sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0 # Create triggers to record changes to xt1. # @@ -64,10 +70,10 @@ do_test initmode-1.0 { # column corresponds to t1.rowid and t1.a, but the xt1.a column is always # NULL # -do_execsql_test initmode-1.1 { +do_execsql_test imposter-1.1 { SELECT rowid FROM xt1 WHERE a IS NOT NULL; } {} -do_execsql_test initmode-1.2 { +do_execsql_test imposter-1.2 { SELECT a,b,c,d FROM t1 EXCEPT SELECT rowid,b,c,d FROM xt1; SELECT rowid,b,c,d FROM xt1 EXCEPT SELECT a,b,c,d FROM t1; } {} @@ -78,7 +84,7 @@ do_execsql_test initmode-1.2 { # the NOT NULL constraint on t1.d, resulting in a logically inconsistent # database. # -do_execsql_test initmode-1.3 { +do_execsql_test imposter-1.3 { DELETE FROM xt1 WHERE rowid=5; INSERT INTO xt1(rowid,a,b,c,d) VALUES(99,'hello',1099,2022,NULL); SELECT * FROM chnglog ORDER BY rowid; @@ -87,14 +93,49 @@ do_execsql_test initmode-1.3 { {INSERT t1: rowid=99, a='hello', b=1099, c=2022, d=NULL} \ ] -do_execsql_test initmode-1.4a { +do_execsql_test imposter-1.4a { PRAGMA integrity_check; } {/NULL value in t1.d/} -do_execsql_test initmode-1.4b { +do_execsql_test imposter-1.4b { PRAGMA integrity_check; } {/row # missing from index t1b/} -do_execsql_test initmode-1.4c { +do_execsql_test imposter-1.4c { PRAGMA integrity_check; } {/row # missing from index t1c/} +# Cleanup the corruption. +# Then demonstrate that the xt1c imposter table can insert non-unique +# and NULL values into the UNIQUE index. +# +do_execsql_test imposter-2.0 { + DELETE FROM t1; + WITH RECURSIVE c(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM c WHERE i<10) + INSERT INTO t1(a,b,c,d) SELECT i,i,i,i FROM c; + UPDATE xt1c SET c=NULL WHERE rowid=5; + PRAGMA integrity_check; +} {/row # missing from index t1c/} + +do_execsql_test imposter-2.1 { + DELETE FROM t1; + WITH RECURSIVE c(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM c WHERE i<10) + INSERT INTO t1(a,b,c,d) SELECT i,i,i,i FROM c; + UPDATE xt1c SET c=99 WHERE rowid IN (5,7,9); + SELECT c FROM t1 ORDER BY c; +} {1 2 3 4 6 8 10 99 99 99} +do_execsql_test imposter-2.2 { + UPDATE xt1 SET c=99 WHERE rowid IN (5,7,9); + PRAGMA integrity_check; +} {/non-unique entry in index t1c/} + +# Erase the imposter tables +# +do_test imposter-3.1 { + sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 1 + db eval { + DELETE FROM t1 WHERE rowid IN (5,7,9); + PRAGMA integrity_check; + } +} {ok} + + finish_test