Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -3595,10 +3595,22 @@ */ case SQLITE_TESTCTRL_ISINIT: { if( sqlite3GlobalConfig.isInit==0 ) rc = SQLITE_ERROR; break; } + + /* sqlite3_test_control(SQLITE_TESTCTRL_INITMODE, db, busy, iDb, newTnum); + ** + ** Set the db->init.busy, db->init.iDb, and db->init.tnum fields. + */ + case SQLITE_TESTCTRL_INITMODE: { + sqlite3 *db = va_arg(ap, sqlite3*); + db->init.busy = va_arg(ap,int); + db->init.iDb = va_arg(ap,int); + db->init.newTnum = va_arg(ap,int); + break; + } } va_end(ap); #endif /* SQLITE_OMIT_BUILTIN_TEST */ return rc; } Index: src/shell.c ================================================================== --- src/shell.c +++ src/shell.c @@ -3534,10 +3534,11 @@ { "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS }, { "iskeyword", SQLITE_TESTCTRL_ISKEYWORD }, { "scratchmalloc", SQLITE_TESTCTRL_SCRATCHMALLOC }, { "byteorder", SQLITE_TESTCTRL_BYTEORDER }, { "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT }, + { "initmode", SQLITE_TESTCTRL_INITMODE }, }; int testctrl = -1; int rc = 0; int i, n; open_db(p, 0); @@ -3625,10 +3626,22 @@ fprintf(stderr,"Error: testctrl %s takes a single char * option\n", azArg[1]); } break; #endif + + case SQLITE_TESTCTRL_INITMODE: + if( nArg==5 ){ + rc = sqlite3_test_control(testctrl, p->db, + integerValue(azArg[2]), + integerValue(azArg[3]), + integerValue(azArg[4])); + }else{ + fprintf(stderr,"Usage: .testctrl initmode fBusy iDb newTnum\n"); + rc = 1; + } + break; case SQLITE_TESTCTRL_BITVEC_TEST: case SQLITE_TESTCTRL_FAULT_INSTALL: case SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS: case SQLITE_TESTCTRL_SCRATCHMALLOC: Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -6258,11 +6258,12 @@ #define SQLITE_TESTCTRL_NEVER_CORRUPT 20 #define SQLITE_TESTCTRL_VDBE_COVERAGE 21 #define SQLITE_TESTCTRL_BYTEORDER 22 #define SQLITE_TESTCTRL_ISINIT 23 #define SQLITE_TESTCTRL_SORTER_MMAP 24 -#define SQLITE_TESTCTRL_LAST 24 +#define SQLITE_TESTCTRL_INITMODE 25 +#define SQLITE_TESTCTRL_LAST 25 /* ** CAPI3REF: SQLite Runtime Status ** ** ^This interface is used to retrieve runtime status information Index: src/test1.c ================================================================== --- src/test1.c +++ src/test1.c @@ -5912,11 +5912,12 @@ struct Verb { const char *zName; int i; } aVerb[] = { { "SQLITE_TESTCTRL_LOCALTIME_FAULT", SQLITE_TESTCTRL_LOCALTIME_FAULT }, - { "SQLITE_TESTCTRL_SORTER_MMAP", SQLITE_TESTCTRL_SORTER_MMAP }, + { "SQLITE_TESTCTRL_SORTER_MMAP", SQLITE_TESTCTRL_SORTER_MMAP }, + { "SQLITE_TESTCTRL_INITMODE", SQLITE_TESTCTRL_INITMODE }, }; int iVerb; int iFlag; int rc; @@ -5953,10 +5954,25 @@ if( getDbPointer(interp, Tcl_GetString(objv[2]), &db) ) return TCL_ERROR; if( Tcl_GetIntFromObj(interp, objv[3], &val) ) return TCL_ERROR; sqlite3_test_control(SQLITE_TESTCTRL_SORTER_MMAP, db, val); break; } + + case SQLITE_TESTCTRL_INITMODE: { + int fBusy, iDb, newTnum; + sqlite3 *db; + if( objc!=6 ){ + Tcl_WrongNumArgs(interp, 2, objv, "DB fBusy iDb newTnum"); + 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); + break; + } } Tcl_ResetResult(interp); return TCL_OK; } ADDED test/initmode.test Index: test/initmode.test ================================================================== --- /dev/null +++ test/initmode.test @@ -0,0 +1,100 @@ +# 2015-01-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. +# +#*********************************************************************** +# +# 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 +# SQLite handles those as expected. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix initmode + +# Create a bunch of data to sort against +# +do_test initmode-1.0 { + execsql { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c, d NOT NULL); + CREATE INDEX t1b ON t1(b); + CREATE UNIQUE INDEX t1c ON t1(c); + WITH RECURSIVE c(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM c WHERE i<30) + 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'}] + + # Create a shadow 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 + db eval {CREATE TABLE xt1(a,b,c,d)} + sqlite3_test_control SQLITE_TESTCTRL_INITMODE db 0 0 0 + + # Create triggers to record changes to xt1. + # + db eval { + CREATE TEMP TABLE chnglog(desc TEXT); + CREATE TEMP TRIGGER xt1_del AFTER DELETE ON xt1 BEGIN + INSERT INTO chnglog VALUES( + printf('DELETE t1: rowid=%d, a=%s, b=%s, c=%s, d=%s', + old.rowid, quote(old.a), quote(old.b), quote(old.c), + quote(old.d))); + END; + CREATE TEMP TRIGGER xt1_ins AFTER INSERT ON xt1 BEGIN + INSERT INTO chnglog VALUES( + printf('INSERT t1: rowid=%d, a=%s, b=%s, c=%s, d=%s', + new.rowid, quote(new.a), quote(new.b), quote(new.c), + quote(new.d))); + END; + } +} {} + +# The xt1 table has separate xt1.rowid and xt1.a columns. The xt1.rowid +# column corresponds to t1.rowid and t1.a, but the xt1.a column is always +# NULL +# +do_execsql_test initmode-1.1 { + SELECT rowid FROM xt1 WHERE a IS NOT NULL; +} {} +do_execsql_test initmode-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; +} {} + + +# Make changes via the xt1 shadow table. This will not update the +# indexes on t1 nor check the uniqueness constraint on t1.c nor check +# the NOT NULL constraint on t1.d, resulting in a logically inconsistent +# database. +# +do_execsql_test initmode-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; +} [list \ + {DELETE t1: rowid=5, a=NULL, b=1005, c=2005, d=3005} \ + {INSERT t1: rowid=99, a='hello', b=1099, c=2022, d=NULL} \ +] + +do_execsql_test initmode-1.4a { + PRAGMA integrity_check; +} {/NULL value in t1.d/} +do_execsql_test initmode-1.4b { + PRAGMA integrity_check; +} {/row # missing from index t1b/} +do_execsql_test initmode-1.4c { + PRAGMA integrity_check; +} {/row # missing from index t1c/} + +finish_test