Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -675,11 +675,11 @@ -DSQLITE_ENABLE_RTREE \ -DSQLITE_ENABLE_FTS4 \ -DSQLITE_EANBLE_FTS5 dbfuzz2: $(TOP)/test/dbfuzz2.c sqlite3.c sqlite3.h - clang-6.0 -I. -g -O0 -fsanitize=fuzzer,undefined -o dbfuzz2 \ + clang-6.0 -I. -g -O0 -fsanitize=fuzzer,undefined,address -o dbfuzz2 \ $(DBFUZZ2_OPTS) $(TOP)/test/dbfuzz2.c sqlite3.c mkdir -p dbfuzz2-dir cp $(TOP)/test/dbfuzz2-seed* dbfuzz2-dir mptester$(TEXE): sqlite3.lo $(TOP)/mptest/mptest.c @@ -1296,10 +1296,13 @@ $(LTLINK) -o $@ $(TOP)/tool/showshm.c changeset$(TEXE): $(TOP)/ext/session/changeset.c sqlite3.lo $(LTLINK) -o $@ $(TOP)/ext/session/changeset.c sqlite3.lo $(TLIBS) +changesetfuzz$(TEXE): $(TOP)/ext/session/changesetfuzz.c sqlite3.lo + $(LTLINK) -o $@ $(TOP)/ext/session/changesetfuzz.c sqlite3.lo $(TLIBS) + rollback-test$(TEXE): $(TOP)/tool/rollback-test.c sqlite3.lo $(LTLINK) -o $@ $(TOP)/tool/rollback-test.c sqlite3.lo $(TLIBS) atrc$(TEXX): $(TOP)/test/atrc.c sqlite3.lo $(LTLINK) -o $@ $(TOP)/test/atrc.c sqlite3.lo $(TLIBS) Index: Makefile.msc ================================================================== --- Makefile.msc +++ Makefile.msc @@ -2446,10 +2446,15 @@ changeset.exe: $(TOP)\ext\session\changeset.c $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_ENABLE_SESSION=1 -DSQLITE_ENABLE_PREUPDATE_HOOK=1 \ $(TOP)\ext\session\changeset.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) +changesetfuzz.exe: $(TOP)\ext\session\changesetfuzz.c $(SQLITE3C) $(SQLITE3H) + $(LTLINK) $(NO_WARN) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_ENABLE_SESSION=1 -DSQLITE_ENABLE_PREUPDATE_HOOK=1 \ + $(TOP)\ext\session\changesetfuzz.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) + fts3view.exe: $(TOP)\ext\fts3\tool\fts3view.c $(SQLITE3C) $(SQLITE3H) $(LTLINK) $(NO_WARN) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ $(TOP)\ext\fts3\tool\fts3view.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) rollback-test.exe: $(TOP)\tool\rollback-test.c $(SQLITE3C) $(SQLITE3H) Index: ext/expert/sqlite3expert.c ================================================================== --- ext/expert/sqlite3expert.c +++ ext/expert/sqlite3expert.c @@ -642,10 +642,11 @@ 0, /* xFindFunction - function overloading */ 0, /* xRename - rename the table */ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ + 0, /* xShadowName */ }; return sqlite3_create_module(p->dbv, "expert", &expertModule, (void*)p); } /* Index: ext/fts3/fts3.c ================================================================== --- ext/fts3/fts3.c +++ ext/fts3/fts3.c @@ -3843,13 +3843,28 @@ assert( p->mxSavepoint >= iSavepoint ); TESTONLY( p->mxSavepoint = iSavepoint ); sqlite3Fts3PendingTermsClear(p); return SQLITE_OK; } + +/* +** Return true if zName is the extension on one of the shadow tables used +** by this module. +*/ +static int fts3ShadowName(const char *zName){ + static const char *azName[] = { + "content", "docsize", "segdir", "segments", "stat", + }; + unsigned int i; + for(i=0; ibase; + *ppIter = (Fts5IndexIter*)pRet; sqlite3Fts5BufferFree(&buf); } return fts5IndexReturn(p); } Index: ext/fts5/fts5_main.c ================================================================== --- ext/fts5/fts5_main.c +++ ext/fts5/fts5_main.c @@ -2642,14 +2642,29 @@ ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); sqlite3_result_text(pCtx, "--FTS5-SOURCE-ID--", -1, SQLITE_TRANSIENT); } + +/* +** Return true if zName is the extension on one of the shadow tables used +** by this module. +*/ +static int fts5ShadowName(const char *zName){ + static const char *azName[] = { + "config", "content", "data", "docsize", "idx" + }; + unsigned int i; + for(i=0; ipIter; i64 *pp = &pCsr->iInstPos; int *po = &pCsr->iInstOff; + assert( sqlite3Fts5IterEof(pIter)==0 ); + assert( pCsr->bEof==0 ); while( eDetail==FTS5_DETAIL_NONE || sqlite3Fts5PoslistNext64(pIter->pData, pIter->nData, po, pp) ){ pCsr->iInstPos = 0; pCsr->iInstOff = 0; rc = sqlite3Fts5IterNextScan(pCsr->pIter); if( rc==SQLITE_OK ){ rc = fts5VocabInstanceNewTerm(pCsr); - if( eDetail==FTS5_DETAIL_NONE ) break; + if( pCsr->bEof || eDetail==FTS5_DETAIL_NONE ) break; } if( rc ){ pCsr->bEof = 1; break; } @@ -753,12 +755,11 @@ /* xFindFunction */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ 0, + /* xShadowName */ 0 }; void *p = (void*)pGlobal; return sqlite3_create_module_v2(db, "fts5vocab", &fts5Vocab, p, 0); } - - Index: ext/fts5/test/fts5aa.test ================================================================== --- ext/fts5/test/fts5aa.test +++ ext/fts5/test/fts5aa.test @@ -407,10 +407,11 @@ } {200} do_execsql_test 15.0 { INSERT INTO t1(t1) VALUES('integrity-check'); } +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 15.1 { UPDATE t1_content SET c1 = 'xyz xyz xyz xyz xyz abc' WHERE rowid = 1; } do_catchsql_test 15.2 { INSERT INTO t1(t1) VALUES('integrity-check'); Index: ext/fts5/test/fts5connect.test ================================================================== --- ext/fts5/test/fts5connect.test +++ ext/fts5/test/fts5connect.test @@ -242,6 +242,5 @@ INSERT INTO ft3(ft3) VALUES('integrity-check'); } } finish_test - Index: ext/fts5/test/fts5corrupt.test ================================================================== --- ext/fts5/test/fts5corrupt.test +++ ext/fts5/test/fts5corrupt.test @@ -39,19 +39,21 @@ db_save do_execsql_test 1.2 { INSERT INTO t1(t1) VALUES('integrity-check') } set segid [lindex [fts5_level_segids t1] 0] +sqlite3_db_config db DEFENSIVE 0 do_test 1.3 { execsql { DELETE FROM t1_data WHERE rowid = fts5_rowid('segment', $segid, 4); } catchsql { INSERT INTO t1(t1) VALUES('integrity-check') } } {1 {database disk image is malformed}} do_test 1.4 { db_restore_and_reopen + sqlite3_db_config db DEFENSIVE 0 execsql { UPDATE t1_data set block = X'00000000' || substr(block, 5) WHERE rowid = fts5_rowid('segment', $segid, 4); } catchsql { INSERT INTO t1(t1) VALUES('integrity-check') } @@ -87,12 +89,12 @@ INSERT INTO t3 VALUES('five o'); } do_execsql_test 3.1 { SELECT * FROM t3 WHERE t3 MATCH 'o' } {{one o} {three o} {five o}} - +sqlite3_db_config db DEFENSIVE 0 do_catchsql_test 3.1 { DELETE FROM t3_content WHERE rowid = 3; SELECT * FROM t3 WHERE t3 MATCH 'o'; } {1 {database disk image is malformed}} finish_test Index: ext/fts5/test/fts5corrupt2.test ================================================================== --- ext/fts5/test/fts5corrupt2.test +++ ext/fts5/test/fts5corrupt2.test @@ -97,10 +97,11 @@ # final leaf to some sizes may create a valid leaf page. # set lrowid [db one {SELECT max(rowid) FROM t1_data WHERE (rowid & $mask)=0}] set nbyte [db one {SELECT length(block) FROM t1_data WHERE rowid=$lrowid}] set all [db eval {SELECT rowid FROM t1}] +sqlite3_db_config db DEFENSIVE 0 for {set i [expr $nbyte-2]} {$i>=0} {incr i -1} { do_execsql_test 2.$i.1 { BEGIN; UPDATE t1_data SET block = substr(block, 1, $i) WHERE rowid=$lrowid; } @@ -246,10 +247,11 @@ } } #-------------------------------------------------------------------- reset_db +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 6.1 { CREATE VIRTUAL TABLE x5 USING fts5(tt); INSERT INTO x5 VALUES('a'); INSERT INTO x5 VALUES('a a'); INSERT INTO x5 VALUES('a a a'); Index: ext/fts5/test/fts5corrupt3.test ================================================================== --- ext/fts5/test/fts5corrupt3.test +++ ext/fts5/test/fts5corrupt3.test @@ -49,10 +49,11 @@ }] set L [db one {SELECT length(block) FROM t1_data WHERE rowid = $rowid}] set {} {} } {} +sqlite3_db_config db DEFENSIVE 0 for {set i 0} {$i < $L} {incr i} { do_test 1.2.$i { catchsql { BEGIN; UPDATE t1_data SET block = substr(block, 1, $i) WHERE id = $rowid; @@ -84,10 +85,11 @@ #------------------------------------------------------------------------- # Test that missing leaf pages are recognized as corruption. # reset_db do_test 3.0 { create_t1 } {} +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 3.1 { SELECT count(*) FROM t1_data; } {105} @@ -156,10 +158,11 @@ #------------------------------------------------------------------------- # Test that segments that end unexpectedly are identified as corruption. # reset_db +sqlite3_db_config db DEFENSIVE 0 do_test 4.0 { execsql { CREATE VIRTUAL TABLE t1 USING fts5(x); INSERT INTO t1(t1, rank) VALUES('pgsz', 32); } @@ -180,10 +183,11 @@ lset var end [expr $end - $i] set struct [binary format c* $var] db close sqlite3 db test.db + sqlite3_db_config db DEFENSIVE 0 db eval { BEGIN; UPDATE t1_data SET block = $struct WHERE id=10; } @@ -255,10 +259,11 @@ } #------------------------------------------------------------------------ # reset_db +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 6.1.0 { CREATE VIRTUAL TABLE t1 USING fts5(a); INSERT INTO t1 VALUES('bbbbb ccccc'); SELECT quote(block) FROM t1_data WHERE rowid>100; } {X'000000180630626262626201020201056363636363010203040A'} @@ -271,10 +276,11 @@ INSERT INTO t1(t1) VALUES('integrity-check'); } {1 {database disk image is malformed}} #------- reset_db +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 6.2.0 { CREATE VIRTUAL TABLE t1 USING fts5(a); INSERT INTO t1(t1, rank) VALUES('pgsz', 32); INSERT INTO t1 VALUES('aa bb cc dd ee'); SELECT pgno, quote(term) FROM t1_idx; @@ -286,10 +292,11 @@ INSERT INTO t1(t1) VALUES('integrity-check'); } {1 {database disk image is malformed}} #------- reset_db +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 6.3.0 { CREATE VIRTUAL TABLE t1 USING fts5(a); INSERT INTO t1 VALUES('abc abcdef abcdefghi'); SELECT quote(block) FROM t1_data WHERE id>100; } {X'0000001C043061626301020204036465660102030703676869010204040808'} @@ -360,10 +367,11 @@ INSERT INTO t5 VALUES( rnddoc(10000) ); INSERT INTO t5(t5) VALUES('optimize'); } } {} +sqlite3_db_config db DEFENSIVE 0 do_test 7.1 { foreach i [db eval { SELECT rowid FROM t5_data WHERE rowid>100 }] { db eval BEGIN db eval {DELETE FROM t5_data WHERE rowid = $i} set r [catchsql { INSERT INTO t5(t5) VALUES('integrity-check')} ] @@ -381,10 +389,11 @@ do_execsql_test 8.1 { CREATE VIRTUAL TABLE t1 USING fts5(x, y); INSERT INTO t1 VALUES('one', 'two'); } +sqlite3_db_config db DEFENSIVE 0 do_test 9.1.1 { set blob "12345678" ;# cookie append blob "0105" ;# 1 level, total of 5 segments append blob "06" ;# write counter append blob "0002" ;# first level has 0 segments merging, 2 other. Index: ext/fts5/test/fts5first.test ================================================================== --- ext/fts5/test/fts5first.test +++ ext/fts5/test/fts5first.test @@ -91,6 +91,5 @@ do_catchsql_test 3.3 { SELECT * FROM x2('^a'); } {1 {fts5: phrase queries are not supported (detail!=full)}} finish_test - Index: ext/fts5/test/fts5integrity.test ================================================================== --- ext/fts5/test/fts5integrity.test +++ ext/fts5/test/fts5integrity.test @@ -69,10 +69,11 @@ } {1 1 1 1 1} do_execsql_test 4.1 { INSERT INTO aa(aa) VALUES('integrity-check'); } +sqlite3_db_config db DEFENSIVE 0 do_catchsql_test 4.2 { BEGIN; UPDATE aa_docsize SET sz = X'44' WHERE rowid = 3; INSERT INTO aa(aa) VALUES('integrity-check'); } {1 {database disk image is malformed}} Index: ext/fts5/test/fts5rank.test ================================================================== --- ext/fts5/test/fts5rank.test +++ ext/fts5/test/fts5rank.test @@ -161,6 +161,5 @@ do_execsql_test 5.1 { SELECT rowid FROM ttt('word') WHERE rowid BETWEEN 30 AND 40 ORDER BY rank; } {30 31 32 33 34 35 36 37 38 39 40} finish_test - Index: ext/fts5/test/fts5rebuild.test ================================================================== --- ext/fts5/test/fts5rebuild.test +++ ext/fts5/test/fts5rebuild.test @@ -37,10 +37,11 @@ do_execsql_test 1.4 { INSERT INTO f1(f1) VALUES('integrity-check'); } {} +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 1.5 { DELETE FROM f1_data; } {} do_catchsql_test 1.6 { Index: ext/fts5/test/fts5rowid.test ================================================================== --- ext/fts5/test/fts5rowid.test +++ ext/fts5/test/fts5rowid.test @@ -68,10 +68,11 @@ set res [db one {SELECT count(*) FROM x1_data}] do_execsql_test 2.3 { SELECT count(fts5_decode(rowid, block)) FROM x1_data; } $res +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 2.4 { UPDATE x1_data SET block = X''; SELECT count(fts5_decode(rowid, block)) FROM x1_data; } $res Index: ext/fts5/test/fts5version.test ================================================================== --- ext/fts5/test/fts5version.test +++ ext/fts5/test/fts5version.test @@ -34,13 +34,14 @@ do_execsql_test 1.3 { SELECT rowid FROM t1 WHERE t1 MATCH 'a'; } {1} +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 1.4 { UPDATE t1_config set v=5 WHERE k='version'; -} +} do_test 1.5 { db close sqlite3 db test.db catchsql { SELECT * FROM t1 WHERE t1 MATCH 'a' } @@ -51,13 +52,14 @@ sqlite3 db test.db catchsql { INSERT INTO t1 VALUES('x y z') } } {1 {invalid fts5 file format (found 5, expected 4) - run 'rebuild'}} do_test 1.7 { + sqlite3_db_config db DEFENSIVE 0 execsql { DELETE FROM t1_config WHERE k='version' } db close sqlite3 db test.db catchsql { SELECT * FROM t1 WHERE t1 MATCH 'a' } } {1 {invalid fts5 file format (found 0, expected 4) - run 'rebuild'}} finish_test Index: ext/fts5/test/fts5vocab.test ================================================================== --- ext/fts5/test/fts5vocab.test +++ ext/fts5/test/fts5vocab.test @@ -418,10 +418,11 @@ if {[detail_is_none]} { set resc [row_to_col $resr] } do_execsql_test 8.1.1 { SELECT * FROM x1_r; } $resr do_execsql_test 8.1.2 { SELECT * FROM x1_c } $resc +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 8.2 { PRAGMA writable_schema = 1; UPDATE sqlite_master SET sql = 'CREATE VIRTUAL TABLE x1 USING fts5(a, detail=%DETAIL%)' WHERE name = 'x1'; @@ -479,6 +480,5 @@ } 0 finish_test - Index: ext/fts5/test/fts5vocab2.test ================================================================== --- ext/fts5/test/fts5vocab2.test +++ ext/fts5/test/fts5vocab2.test @@ -11,11 +11,11 @@ # # The tests in this file focus on testing the fts5vocab module. # source [file join [file dirname [info script]] fts5_common.tcl] -set testprefix fts5vocab +set testprefix fts5vocab2 # If SQLITE_ENABLE_FTS5 is defined, omit this file. ifcapable !fts5 { finish_test return @@ -204,6 +204,5 @@ SELECT * FROM v1; } { } finish_test - Index: ext/misc/amatch.c ================================================================== --- ext/misc/amatch.c +++ ext/misc/amatch.c @@ -1471,11 +1471,12 @@ 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ - 0 /* xRollbackTo */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ /* Index: ext/misc/btreeinfo.c ================================================================== --- ext/misc/btreeinfo.c +++ ext/misc/btreeinfo.c @@ -409,10 +409,11 @@ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ + 0 /* xShadowName */ }; return sqlite3_create_module(db, "sqlite_btreeinfo", &binfo_module, 0); } #ifdef _WIN32 Index: ext/misc/closure.c ================================================================== --- ext/misc/closure.c +++ ext/misc/closure.c @@ -936,11 +936,12 @@ 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ - 0 /* xRollbackTo */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ /* Index: ext/misc/completion.c ================================================================== --- ext/misc/completion.c +++ ext/misc/completion.c @@ -466,11 +466,12 @@ 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ - 0 /* xRollbackTo */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ int sqlite3CompletionVtabInit(sqlite3 *db){ Index: ext/misc/csv.c ================================================================== --- ext/misc/csv.c +++ ext/misc/csv.c @@ -17,13 +17,13 @@ ** ** .load ./csv ** CREATE VIRTUAL TABLE temp.csv USING csv(filename=FILENAME); ** SELECT * FROM csv; ** -** The columns are named "c1", "c2", "c3", ... by default. But the -** application can define its own CREATE TABLE statement as an additional -** parameter. For example: +** The columns are named "c1", "c2", "c3", ... by default. Or the +** application can define its own CREATE TABLE statement using the +** schema= parameter, like this: ** ** CREATE VIRTUAL TABLE temp.csv2 USING csv( ** filename = "../http.log", ** schema = "CREATE TABLE x(date,ipaddr,url,referrer,userAgent)" ** ); @@ -30,13 +30,13 @@ ** ** Instead of specifying a file, the text of the CSV can be loaded using ** the data= parameter. ** ** If the columns=N parameter is supplied, then the CSV file is assumed to have -** N columns. If the columns parameter is omitted, the CSV file is opened -** as soon as the virtual table is constructed and the first row of the CSV -** is read in order to count the tables. +** N columns. If both the columns= and schema= parameters are omitted, then +** the number and names of the columns is determined by the first line of +** the CSV input. ** ** Some extra debugging features (used for testing virtual tables) are available ** if this module is compiled with -DSQLITE_TEST. */ #include @@ -434,10 +434,38 @@ return 0; } return -1; } +/* Check to see if the string is of the form: "TAG = BOOLEAN" or just "TAG". +** If it is, set *pValue to be the value of the boolean ("true" if there is +** not "= BOOLEAN" component) and return non-zero. If the input string +** does not begin with TAG, return zero. +*/ +static int csv_boolean_parameter( + const char *zTag, /* Tag we are looking for */ + int nTag, /* Size of the tag in bytes */ + const char *z, /* Input parameter */ + int *pValue /* Write boolean value here */ +){ + int b; + z = csv_skip_whitespace(z); + if( strncmp(zTag, z, nTag)!=0 ) return 0; + z = csv_skip_whitespace(z + nTag); + if( z[0]==0 ){ + *pValue = 1; + return 1; + } + if( z[0]!='=' ) return 0; + z = csv_skip_whitespace(z+1); + b = csv_boolean(z); + if( b>=0 ){ + *pValue = b; + return 1; + } + return 0; +} /* ** Parameters: ** filename=FILENAME Name of file containing CSV content ** data=TEXT Direct CSV content. @@ -467,10 +495,11 @@ int rc = SQLITE_OK; /* Result code from this routine */ int i, j; /* Loop counters */ #ifdef SQLITE_TEST int tstFlags = 0; /* Value for testflags=N parameter */ #endif + int b; /* Value of a boolean parameter */ int nCol = -99; /* Value of the columns= parameter */ CsvReader sRdr; /* A CSV file reader used to store an error ** message and/or to count the number of columns */ static const char *azParam[] = { "filename", "data", "schema", @@ -491,25 +520,16 @@ if( csv_string_parameter(&sRdr, azParam[j], z, &azPValue[j]) ) break; } if( j=0 ){ csv_errmsg(&sRdr, "more than one 'header' parameter"); goto csvtab_connect_error; } - x = csv_boolean(zValue); - if( x==1 ){ - bHeader = 1; - }else if( x==0 ){ - bHeader = 0; - }else{ - csv_errmsg(&sRdr, "unrecognized argument to 'header': %s", zValue); - goto csvtab_connect_error; - } + bHeader = b; }else #ifdef SQLITE_TEST if( (zValue = csv_parameter("testflags",9,z))!=0 ){ tstFlags = (unsigned int)atoi(zValue); }else @@ -519,57 +539,98 @@ csv_errmsg(&sRdr, "more than one 'columns' parameter"); goto csvtab_connect_error; } nCol = atoi(zValue); if( nCol<=0 ){ - csv_errmsg(&sRdr, "must have at least one column"); + csv_errmsg(&sRdr, "column= value must be positive"); goto csvtab_connect_error; } }else { - csv_errmsg(&sRdr, "unrecognized parameter '%s'", z); + csv_errmsg(&sRdr, "bad parameter: '%s'", z); goto csvtab_connect_error; } } if( (CSV_FILENAME==0)==(CSV_DATA==0) ){ - csv_errmsg(&sRdr, "must either filename= or data= but not both"); + csv_errmsg(&sRdr, "must specify either filename= or data= but not both"); goto csvtab_connect_error; } - if( nCol<=0 && csv_reader_open(&sRdr, CSV_FILENAME, CSV_DATA) ){ + + if( (nCol<=0 || bHeader==1) + && csv_reader_open(&sRdr, CSV_FILENAME, CSV_DATA) + ){ goto csvtab_connect_error; } pNew = sqlite3_malloc( sizeof(*pNew) ); *ppVtab = (sqlite3_vtab*)pNew; if( pNew==0 ) goto csvtab_connect_oom; memset(pNew, 0, sizeof(*pNew)); - if( nCol>0 ){ + if( CSV_SCHEMA==0 ){ + sqlite3_str *pStr = sqlite3_str_new(0); + char *zSep = ""; + int iCol = 0; + sqlite3_str_appendf(pStr, "CREATE TABLE x("); + if( nCol<0 && bHeader<1 ){ + nCol = 0; + do{ + csv_read_one_field(&sRdr); + nCol++; + }while( sRdr.cTerm==',' ); + } + if( nCol>0 && bHeader<1 ){ + for(iCol=0; iCol0 && iColnCol = nCol; - }else{ + sqlite3_str_appendf(pStr, ")"); + CSV_SCHEMA = sqlite3_str_finish(pStr); + if( CSV_SCHEMA==0 ) goto csvtab_connect_oom; + }else if( nCol<0 ){ do{ csv_read_one_field(&sRdr); pNew->nCol++; }while( sRdr.cTerm==',' ); + }else{ + pNew->nCol = nCol; } pNew->zFilename = CSV_FILENAME; CSV_FILENAME = 0; pNew->zData = CSV_DATA; CSV_DATA = 0; #ifdef SQLITE_TEST pNew->tstFlags = tstFlags; #endif - pNew->iStart = bHeader==1 ? ftell(sRdr.in) : 0; - csv_reader_reset(&sRdr); - if( CSV_SCHEMA==0 ){ - char *zSep = ""; - CSV_SCHEMA = sqlite3_mprintf("CREATE TABLE x("); - if( CSV_SCHEMA==0 ) goto csvtab_connect_oom; - for(i=0; inCol; i++){ - CSV_SCHEMA = sqlite3_mprintf("%z%sc%d TEXT",CSV_SCHEMA, zSep, i); - zSep = ","; - } - CSV_SCHEMA = sqlite3_mprintf("%z);", CSV_SCHEMA); - } + if( bHeader!=1 ){ + pNew->iStart = 0; + }else if( pNew->zData ){ + pNew->iStart = (int)sRdr.iIn; + }else{ + pNew->iStart = ftell(sRdr.in); + } + csv_reader_reset(&sRdr); rc = sqlite3_declare_vtab(db, CSV_SCHEMA); - if( rc ) goto csvtab_connect_error; + if( rc ){ + csv_errmsg(&sRdr, "bad schema: '%s' - %s", CSV_SCHEMA, sqlite3_errmsg(db)); + goto csvtab_connect_error; + } for(i=0; iestimatedCost = (double)1000000; pIdxInfo->estimatedRows = 500; for(i=0; inConstraint; i++){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i]; - if( p->usable - && p->iColumn==EXPLN_COLUMN_SQL - && p->op==SQLITE_INDEX_CONSTRAINT_EQ - ){ - pIdxInfo->estimatedCost = 10.0; - pIdxInfo->idxNum = 1; - pIdxInfo->aConstraintUsage[i].argvIndex = 1; - pIdxInfo->aConstraintUsage[i].omit = 1; - break; - } + if( p->iColumn!=EXPLN_COLUMN_SQL ) continue; + if( !p->usable ){ + unusable = 1; + }else if( p->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + idx = i; + } + } + if( idx>=0 ){ + /* There exists a usable == constraint against the SQL column */ + pIdxInfo->estimatedCost = 10.0; + pIdxInfo->idxNum = 1; + pIdxInfo->aConstraintUsage[idx].argvIndex = 1; + pIdxInfo->aConstraintUsage[idx].omit = 1; + }else if( unusable ){ + /* There are unusable constraints against the SQL column. Do not allow + ** this plan to continue forward. */ + return SQLITE_CONSTRAINT; } return SQLITE_OK; } /* @@ -278,10 +290,11 @@ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ + 0, /* xShadowName */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ int sqlite3ExplainVtabInit(sqlite3 *db){ Index: ext/misc/fileio.c ================================================================== --- ext/misc/fileio.c +++ ext/misc/fileio.c @@ -104,11 +104,22 @@ #endif #include #include +/* +** Structure of the fsdir() table-valued function +*/ + /* 0 1 2 3 4 5 */ #define FSDIR_SCHEMA "(name,mode,mtime,data,path HIDDEN,dir HIDDEN)" +#define FSDIR_COLUMN_NAME 0 /* Name of the file */ +#define FSDIR_COLUMN_MODE 1 /* Access mode */ +#define FSDIR_COLUMN_MTIME 2 /* Last modification time */ +#define FSDIR_COLUMN_DATA 3 /* File content */ +#define FSDIR_COLUMN_PATH 4 /* Path to top of search */ +#define FSDIR_COLUMN_DIR 5 /* Path is relative to this directory */ + /* ** Set the result stored by context ctx to a blob containing the ** contents of file zName. */ @@ -693,24 +704,24 @@ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ int i /* Which column to return */ ){ fsdir_cursor *pCur = (fsdir_cursor*)cur; switch( i ){ - case 0: { /* name */ + case FSDIR_COLUMN_NAME: { sqlite3_result_text(ctx, &pCur->zPath[pCur->nBase], -1, SQLITE_TRANSIENT); break; } - case 1: /* mode */ + case FSDIR_COLUMN_MODE: sqlite3_result_int64(ctx, pCur->sStat.st_mode); break; - case 2: /* mtime */ + case FSDIR_COLUMN_MTIME: sqlite3_result_int64(ctx, pCur->sStat.st_mtime); break; - case 3: { /* data */ + case FSDIR_COLUMN_DATA: { mode_t m = pCur->sStat.st_mode; if( S_ISDIR(m) ){ sqlite3_result_null(ctx); #if !defined(_WIN32) && !defined(WIN32) }else if( S_ISLNK(m) ){ @@ -736,10 +747,16 @@ #endif }else{ readFileContents(ctx, pCur->zPath); } } + case FSDIR_COLUMN_PATH: + default: { + /* The FSDIR_COLUMN_PATH and FSDIR_COLUMN_DIR are input parameters. + ** always return their values as NULL */ + break; + } } return SQLITE_OK; } /* @@ -762,10 +779,13 @@ return (pCur->zPath==0); } /* ** xFilter callback. +** +** idxNum==1 PATH parameter only +** idxNum==2 Both PATH and DIR supplied */ static int fsdirFilter( sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr, int argc, sqlite3_value **argv @@ -814,44 +834,67 @@ ** plan. ** ** In this implementation idxNum is used to represent the ** query plan. idxStr is unused. ** -** The query plan is represented by bits in idxNum: +** The query plan is represented by values of idxNum: ** -** (1) start = $value -- constraint exists -** (2) stop = $value -- constraint exists -** (4) step = $value -- constraint exists -** (8) output in descending order +** (1) The path value is supplied by argv[0] +** (2) Path is in argv[0] and dir is in argv[1] */ static int fsdirBestIndex( sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo ){ int i; /* Loop over constraints */ - int idx4 = -1; - int idx5 = -1; + int idxPath = -1; /* Index in pIdxInfo->aConstraint of PATH= */ + int idxDir = -1; /* Index in pIdxInfo->aConstraint of DIR= */ + int seenPath = 0; /* True if an unusable PATH= constraint is seen */ + int seenDir = 0; /* True if an unusable DIR= constraint is seen */ const struct sqlite3_index_constraint *pConstraint; (void)tab; pConstraint = pIdxInfo->aConstraint; for(i=0; inConstraint; i++, pConstraint++){ - if( pConstraint->usable==0 ) continue; - if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; - if( pConstraint->iColumn==4 ) idx4 = i; - if( pConstraint->iColumn==5 ) idx5 = i; - } - - if( idx4<0 ){ - pIdxInfo->idxNum = 0; - pIdxInfo->estimatedCost = (double)(((sqlite3_int64)1) << 50); - }else{ - pIdxInfo->aConstraintUsage[idx4].omit = 1; - pIdxInfo->aConstraintUsage[idx4].argvIndex = 1; - if( idx5>=0 ){ - pIdxInfo->aConstraintUsage[idx5].omit = 1; - pIdxInfo->aConstraintUsage[idx5].argvIndex = 2; + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + switch( pConstraint->iColumn ){ + case FSDIR_COLUMN_PATH: { + if( pConstraint->usable ){ + idxPath = i; + seenPath = 0; + }else if( idxPath<0 ){ + seenPath = 1; + } + break; + } + case FSDIR_COLUMN_DIR: { + if( pConstraint->usable ){ + idxDir = i; + seenDir = 0; + }else if( idxDir<0 ){ + seenDir = 1; + } + break; + } + } + } + if( seenPath || seenDir ){ + /* If input parameters are unusable, disallow this plan */ + return SQLITE_CONSTRAINT; + } + + if( idxPath<0 ){ + pIdxInfo->idxNum = 0; + /* The pIdxInfo->estimatedCost should have been initialized to a huge + ** number. Leave it unchanged. */ + pIdxInfo->estimatedRows = 0x7fffffff; + }else{ + pIdxInfo->aConstraintUsage[idxPath].omit = 1; + pIdxInfo->aConstraintUsage[idxPath].argvIndex = 1; + if( idxDir>=0 ){ + pIdxInfo->aConstraintUsage[idxDir].omit = 1; + pIdxInfo->aConstraintUsage[idxDir].argvIndex = 2; pIdxInfo->idxNum = 2; pIdxInfo->estimatedCost = 10.0; }else{ pIdxInfo->idxNum = 1; pIdxInfo->estimatedCost = 100.0; @@ -886,11 +929,12 @@ 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ - 0 /* xRollbackTo */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ }; int rc = sqlite3_create_module(db, "fsdir", &fsdirModule, 0); return rc; } Index: ext/misc/json1.c ================================================================== --- ext/misc/json1.c +++ ext/misc/json1.c @@ -1992,10 +1992,13 @@ #define JEACH_ATOM 3 #define JEACH_ID 4 #define JEACH_PARENT 5 #define JEACH_FULLKEY 6 #define JEACH_PATH 7 +/* The xBestIndex method assumes that the JSON and ROOT columns are +** the last two columns in the table. Should this ever changes, be +** sure to update the xBestIndex method. */ #define JEACH_JSON 8 #define JEACH_ROOT 9 UNUSED_PARAM(pzErr); UNUSED_PARAM(argv); @@ -2249,39 +2252,58 @@ */ static int jsonEachBestIndex( sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo ){ - int i; - int jsonIdx = -1; - int rootIdx = -1; + int i; /* Loop counter or computed array index */ + int aIdx[2]; /* Index of constraints for JSON and ROOT */ + int unusableMask = 0; /* Mask of unusable JSON and ROOT constraints */ + int idxMask = 0; /* Mask of usable == constraints JSON and ROOT */ const struct sqlite3_index_constraint *pConstraint; - UNUSED_PARAM(tab); - pConstraint = pIdxInfo->aConstraint; - for(i=0; inConstraint; i++, pConstraint++){ - if( pConstraint->usable==0 ) continue; - if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; - switch( pConstraint->iColumn ){ - case JEACH_JSON: jsonIdx = i; break; - case JEACH_ROOT: rootIdx = i; break; - default: /* no-op */ break; - } - } - if( jsonIdx<0 ){ - pIdxInfo->idxNum = 0; - pIdxInfo->estimatedCost = 1e99; - }else{ - pIdxInfo->estimatedCost = 1.0; - pIdxInfo->aConstraintUsage[jsonIdx].argvIndex = 1; - pIdxInfo->aConstraintUsage[jsonIdx].omit = 1; - if( rootIdx<0 ){ - pIdxInfo->idxNum = 1; - }else{ - pIdxInfo->aConstraintUsage[rootIdx].argvIndex = 2; - pIdxInfo->aConstraintUsage[rootIdx].omit = 1; - pIdxInfo->idxNum = 3; + /* This implementation assumes that JSON and ROOT are the last two + ** columns in the table */ + assert( JEACH_ROOT == JEACH_JSON+1 ); + UNUSED_PARAM(tab); + aIdx[0] = aIdx[1] = -1; + pConstraint = pIdxInfo->aConstraint; + for(i=0; inConstraint; i++, pConstraint++){ + int iCol; + int iMask; + if( pConstraint->iColumn < JEACH_JSON ) continue; + iCol = pConstraint->iColumn - JEACH_JSON; + assert( iCol==0 || iCol==1 ); + iMask = 1 << iCol; + if( pConstraint->usable==0 ){ + unusableMask |= iMask; + }else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + aIdx[iCol] = i; + idxMask |= iMask; + } + } + if( (unusableMask & ~idxMask)!=0 ){ + /* If there are any unusable constraints on JSON or ROOT, then reject + ** this entire plan */ + return SQLITE_CONSTRAINT; + } + if( aIdx[0]<0 ){ + /* No JSON input. Leave estimatedCost at the huge value that it was + ** initialized to to discourage the query planner from selecting this + ** plan. */ + pIdxInfo->idxNum = 0; + }else{ + pIdxInfo->estimatedCost = 1.0; + i = aIdx[0]; + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + pIdxInfo->aConstraintUsage[i].omit = 1; + if( aIdx[1]<0 ){ + pIdxInfo->idxNum = 1; /* Only JSON supplied. Plan 1 */ + }else{ + i = aIdx[1]; + pIdxInfo->aConstraintUsage[i].argvIndex = 2; + pIdxInfo->aConstraintUsage[i].omit = 1; + pIdxInfo->idxNum = 3; /* Both JSON and ROOT are supplied. Plan 3 */ } } return SQLITE_OK; } @@ -2386,11 +2408,12 @@ 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ - 0 /* xRollbackTo */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ }; /* The methods of the json_tree virtual table. */ static sqlite3_module jsonTreeModule = { 0, /* iVersion */ @@ -2413,11 +2436,12 @@ 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ - 0 /* xRollbackTo */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ /**************************************************************************** ** The following routines are the only publically visible identifiers in this Index: ext/misc/memstat.c ================================================================== --- ext/misc/memstat.c +++ ext/misc/memstat.c @@ -393,10 +393,11 @@ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ + 0, /* xShadowName */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ int sqlite3MemstatVtabInit(sqlite3 *db){ Index: ext/misc/series.c ================================================================== --- ext/misc/series.c +++ ext/misc/series.c @@ -311,48 +311,49 @@ */ static int seriesBestIndex( sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo ){ - int i; /* Loop over constraints */ - int idxNum = 0; /* The query plan bitmask */ - int startIdx = -1; /* Index of the start= constraint, or -1 if none */ - int stopIdx = -1; /* Index of the stop= constraint, or -1 if none */ - int stepIdx = -1; /* Index of the step= constraint, or -1 if none */ - int nArg = 0; /* Number of arguments that seriesFilter() expects */ - - const struct sqlite3_index_constraint *pConstraint; + int i, j; /* Loop over constraints */ + int idxNum = 0; /* The query plan bitmask */ + int unusableMask = 0; /* Mask of unusable constraints */ + int nArg = 0; /* Number of arguments that seriesFilter() expects */ + int aIdx[3]; /* Constraints on start, stop, and step */ + const struct sqlite3_index_constraint *pConstraint; + + /* This implementation assumes that the start, stop, and step columns + ** are the last three columns in the virtual table. */ + assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 ); + assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 ); + aIdx[0] = aIdx[1] = aIdx[2] = -1; pConstraint = pIdxInfo->aConstraint; for(i=0; inConstraint; i++, pConstraint++){ - if( pConstraint->usable==0 ) continue; - if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; - switch( pConstraint->iColumn ){ - case SERIES_COLUMN_START: - startIdx = i; - idxNum |= 1; - break; - case SERIES_COLUMN_STOP: - stopIdx = i; - idxNum |= 2; - break; - case SERIES_COLUMN_STEP: - stepIdx = i; - idxNum |= 4; - break; - } - } - if( startIdx>=0 ){ - pIdxInfo->aConstraintUsage[startIdx].argvIndex = ++nArg; - pIdxInfo->aConstraintUsage[startIdx].omit= !SQLITE_SERIES_CONSTRAINT_VERIFY; - } - if( stopIdx>=0 ){ - pIdxInfo->aConstraintUsage[stopIdx].argvIndex = ++nArg; - pIdxInfo->aConstraintUsage[stopIdx].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY; - } - if( stepIdx>=0 ){ - pIdxInfo->aConstraintUsage[stepIdx].argvIndex = ++nArg; - pIdxInfo->aConstraintUsage[stepIdx].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY; + int iCol; /* 0 for start, 1 for stop, 2 for step */ + int iMask; /* bitmask for those column */ + if( pConstraint->iColumniColumn - SERIES_COLUMN_START; + assert( iCol>=0 && iCol<=2 ); + iMask = 1 << iCol; + if( pConstraint->usable==0 ){ + unusableMask |= iMask; + continue; + }else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + idxNum |= iMask; + aIdx[iCol] = i; + } + } + for(i=0; i<3; i++){ + if( (j = aIdx[i])>=0 ){ + pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[j].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY; + } + } + if( (unusableMask & ~idxNum)!=0 ){ + /* The start, stop, and step columns are inputs. Therefore if there + ** are unusable constraints on any of start, stop, or step then + ** this plan is unusable */ + return SQLITE_CONSTRAINT; } if( (idxNum & 3)==3 ){ /* Both start= and stop= boundaries are available. This is the ** the preferred case */ pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0)); @@ -363,11 +364,10 @@ } }else{ /* If either boundary is missing, we have to generate a huge span ** of numbers. Make this case very expensive so that the query ** planner will work hard to avoid it. */ - pIdxInfo->estimatedCost = (double)2147483647; pIdxInfo->estimatedRows = 2147483647; } pIdxInfo->idxNum = idxNum; return SQLITE_OK; } Index: ext/misc/stmt.c ================================================================== --- ext/misc/stmt.c +++ ext/misc/stmt.c @@ -264,10 +264,11 @@ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ + 0, /* xShadowName */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ int sqlite3StmtVtabInit(sqlite3 *db){ Index: ext/misc/templatevtab.c ================================================================== --- ext/misc/templatevtab.c +++ ext/misc/templatevtab.c @@ -246,11 +246,12 @@ /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ 0, - /* xRollbackTo */ 0 + /* xRollbackTo */ 0, + /* xShadowName */ 0 }; #ifdef _WIN32 __declspec(dllexport) Index: ext/misc/unionvtab.c ================================================================== --- ext/misc/unionvtab.c +++ ext/misc/unionvtab.c @@ -1348,11 +1348,12 @@ 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ - 0 /* xRollbackTo */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ }; int rc; rc = sqlite3_create_module(db, "unionvtab", &unionModule, 0); if( rc==SQLITE_OK ){ Index: ext/misc/vtablog.c ================================================================== --- ext/misc/vtablog.c +++ ext/misc/vtablog.c @@ -490,10 +490,11 @@ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ + 0, /* xShadowName */ }; #ifdef _WIN32 __declspec(dllexport) #endif Index: ext/misc/zipfile.c ================================================================== --- ext/misc/zipfile.c +++ ext/misc/zipfile.c @@ -1294,29 +1294,30 @@ static int zipfileBestIndex( sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo ){ int i; + int idx = -1; + int unusable = 0; for(i=0; inConstraint; i++){ const struct sqlite3_index_constraint *pCons = &pIdxInfo->aConstraint[i]; - if( pCons->usable==0 ) continue; - if( pCons->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; if( pCons->iColumn!=ZIPFILE_F_COLUMN_IDX ) continue; - break; + if( pCons->usable==0 ){ + unusable = 1; + }else if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + idx = i; + } } - - if( inConstraint ){ - pIdxInfo->aConstraintUsage[i].argvIndex = 1; - pIdxInfo->aConstraintUsage[i].omit = 1; + if( idx>=0 ){ + pIdxInfo->aConstraintUsage[idx].argvIndex = 1; + pIdxInfo->aConstraintUsage[idx].omit = 1; pIdxInfo->estimatedCost = 1000.0; pIdxInfo->idxNum = 1; - }else{ - pIdxInfo->estimatedCost = (double)(((sqlite3_int64)1) << 50); - pIdxInfo->idxNum = 0; + }else if( unusable ){ + return SQLITE_CONSTRAINT; } - return SQLITE_OK; } static ZipfileEntry *zipfileNewEntry(const char *zPath){ ZipfileEntry *pNew; Index: ext/rbu/rbu1.test ================================================================== --- ext/rbu/rbu1.test +++ ext/rbu/rbu1.test @@ -668,6 +668,5 @@ eval $destroy_vfs } finish_test - Index: ext/rbu/rbu10.test ================================================================== --- ext/rbu/rbu10.test +++ ext/rbu/rbu10.test @@ -183,6 +183,5 @@ list [catch { apply_rbu $rbu } msg] $msg } {0 SQLITE_DONE} finish_test - Index: ext/rbu/rbu11.test ================================================================== --- ext/rbu/rbu11.test +++ ext/rbu/rbu11.test @@ -193,6 +193,5 @@ do_test 4.7.2 { list [catch {rbu close} msg] $msg } {1 {SQLITE_ERROR - rbu_state mismatch error}} finish_test - Index: ext/rbu/rbu12.test ================================================================== --- ext/rbu/rbu12.test +++ ext/rbu/rbu12.test @@ -230,6 +230,5 @@ } [list $V1 $V2] } finish_test - Index: ext/rbu/rbu13.test ================================================================== --- ext/rbu/rbu13.test +++ ext/rbu/rbu13.test @@ -60,6 +60,5 @@ a == ( (b<<6) + (c<<5) + (d<<4) + (e<<3) + (f<<2) + (g<<1) + (h<<0) ) } {128} finish_test - Index: ext/rbu/rbu14.test ================================================================== --- ext/rbu/rbu14.test +++ ext/rbu/rbu14.test @@ -90,6 +90,5 @@ integrity_check $tn.4 } finish_test - Index: ext/rbu/rbu3.test ================================================================== --- ext/rbu/rbu3.test +++ ext/rbu/rbu3.test @@ -201,7 +201,5 @@ } 1 do_test 6.1 { sqlite3rbu_internal_test } {} finish_test - - Index: ext/rbu/rbu5.test ================================================================== --- ext/rbu/rbu5.test +++ ext/rbu/rbu5.test @@ -298,9 +298,5 @@ } } finish_test - - - - Index: ext/rbu/rbu6.test ================================================================== --- ext/rbu/rbu6.test +++ ext/rbu/rbu6.test @@ -98,6 +98,5 @@ } {1 t1 5 hello} } finish_test - Index: ext/rbu/rbu7.test ================================================================== --- ext/rbu/rbu7.test +++ ext/rbu/rbu7.test @@ -104,7 +104,5 @@ 3 2 f } } finish_test - - Index: ext/rbu/rbu8.test ================================================================== --- ext/rbu/rbu8.test +++ ext/rbu/rbu8.test @@ -70,6 +70,5 @@ } integrity_check 1.3.3 finish_test - Index: ext/rbu/rbu9.test ================================================================== --- ext/rbu/rbu9.test +++ ext/rbu/rbu9.test @@ -123,6 +123,5 @@ integrity_check 2.$tn.4 } finish_test - Index: ext/rbu/rbuA.test ================================================================== --- ext/rbu/rbuA.test +++ ext/rbu/rbuA.test @@ -78,6 +78,5 @@ list [catch { rbu close } msg] $msg } {1 {SQLITE_ERROR - cannot update wal mode database}} finish_test - Index: ext/rbu/rbuB.test ================================================================== --- ext/rbu/rbuB.test +++ ext/rbu/rbuB.test @@ -57,6 +57,5 @@ db close sqlite3_shutdown test_sqlite3_log sqlite3_initialize finish_test - Index: ext/rbu/rbuC.test ================================================================== --- ext/rbu/rbuC.test +++ ext/rbu/rbuC.test @@ -137,6 +137,5 @@ } finish_test - Index: ext/rbu/rbucollate.test ================================================================== --- ext/rbu/rbucollate.test +++ ext/rbu/rbucollate.test @@ -58,6 +58,5 @@ db eval { SELECT * FROM t1 } } {a one 1 b two 2 c three 3} #forcedelete testrbu.db finish_test - Index: ext/rbu/rbucrash.test ================================================================== --- ext/rbu/rbucrash.test +++ ext/rbu/rbucrash.test @@ -143,6 +143,5 @@ do_rbu_crash_test 2.pre=$nPre.step=$is $nPre $is } } finish_test - Index: ext/rbu/rbucrash2.test ================================================================== --- ext/rbu/rbucrash2.test +++ ext/rbu/rbucrash2.test @@ -101,6 +101,5 @@ rbu close } } finish_test - Index: ext/rbu/rbudiff.test ================================================================== --- ext/rbu/rbudiff.test +++ ext/rbu/rbudiff.test @@ -298,6 +298,5 @@ } } finish_test - Index: ext/rbu/rbudor.test ================================================================== --- ext/rbu/rbudor.test +++ ext/rbu/rbudor.test @@ -54,6 +54,5 @@ do_execsql_test 1.4 { SELECT * FROM t1 } [list 1 $bigA 2 $bigB] finish_test - Index: ext/rbu/rbufault.test ================================================================== --- ext/rbu/rbufault.test +++ ext/rbu/rbufault.test @@ -232,6 +232,5 @@ } } } finish_test - Index: ext/rbu/rbufault2.test ================================================================== --- ext/rbu/rbufault2.test +++ ext/rbu/rbufault2.test @@ -53,6 +53,5 @@ finish_test - Index: ext/rbu/rbufault3.test ================================================================== --- ext/rbu/rbufault3.test +++ ext/rbu/rbufault3.test @@ -93,6 +93,5 @@ } } finish_test - Index: ext/rbu/rbufault4.test ================================================================== --- ext/rbu/rbufault4.test +++ ext/rbu/rbufault4.test @@ -61,6 +61,5 @@ } finish_test - Index: ext/rbu/rbufts.test ================================================================== --- ext/rbu/rbufts.test +++ ext/rbu/rbufts.test @@ -129,6 +129,5 @@ } {1 {SQLITE_ERROR - SQL logic error]}} finish_test - Index: ext/rbu/rbumulti.test ================================================================== --- ext/rbu/rbumulti.test +++ ext/rbu/rbumulti.test @@ -170,6 +170,5 @@ } finish_test - Index: ext/rbu/rbuprogress.test ================================================================== --- ext/rbu/rbuprogress.test +++ ext/rbu/rbuprogress.test @@ -414,6 +414,5 @@ } } finish_test - Index: ext/rbu/rburesume.test ================================================================== --- ext/rbu/rburesume.test +++ ext/rbu/rburesume.test @@ -249,6 +249,5 @@ } {60 ok} db2 close } finish_test - Index: ext/rbu/rbusave.test ================================================================== --- ext/rbu/rbusave.test +++ ext/rbu/rbusave.test @@ -100,6 +100,5 @@ SELECT * FROM t1; SELECT * FROM t2; } {1 one 1 3 3 3 4 4 4 1 one 1 3 3 3 4 4 4} finish_test - Index: ext/rbu/rbusplit.test ================================================================== --- ext/rbu/rbusplit.test +++ ext/rbu/rbusplit.test @@ -90,6 +90,5 @@ 9 9 9 } } finish_test - Index: ext/rbu/rbutemplimit.test ================================================================== --- ext/rbu/rbutemplimit.test +++ ext/rbu/rbutemplimit.test @@ -124,6 +124,5 @@ step_rbu_cachesize test.db test.db2 1000 10 1400000 } {1 SQLITE_FULL} do_test 1.6.2 { info commands rbu } {} finish_test - Index: ext/rbu/rbuvacuum.test ================================================================== --- ext/rbu/rbuvacuum.test +++ ext/rbu/rbuvacuum.test @@ -395,6 +395,5 @@ list [catch { rbu close } msg] $msg } {0 SQLITE_DONE} catch { db close } finish_test - Index: ext/rbu/rbuvacuum2.test ================================================================== --- ext/rbu/rbuvacuum2.test +++ ext/rbu/rbuvacuum2.test @@ -230,6 +230,5 @@ rbu close execsql { PRAGMA integrity_check } } {ok} finish_test - Index: ext/repair/test/checkindex01.test ================================================================== --- ext/repair/test/checkindex01.test +++ ext/repair/test/checkindex01.test @@ -345,7 +345,5 @@ {} 1,1 {} 3,3 } do_index_check_test 7.4 t7i4 { {} 1,1 {} 3,3 } - - Index: ext/rtree/geopoly.c ================================================================== --- ext/rtree/geopoly.c +++ ext/rtree/geopoly.c @@ -1723,11 +1723,11 @@ return 0; } static sqlite3_module geopolyModule = { - 2, /* iVersion */ + 3, /* iVersion */ geopolyCreate, /* xCreate - create a table */ geopolyConnect, /* xConnect - connect to an existing table */ geopolyBestIndex, /* xBestIndex - Determine search strategy */ rtreeDisconnect, /* xDisconnect - Disconnect from a table */ rtreeDestroy, /* xDestroy - Drop a table */ @@ -1746,10 +1746,11 @@ geopolyFindFunction, /* xFindFunction - function overloading */ rtreeRename, /* xRename - rename the table */ rtreeSavepoint, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ + rtreeShadowName /* xShadowName */ }; static int sqlite3_geopoly_init(sqlite3 *db){ int rc = SQLITE_OK; static const struct { Index: ext/rtree/rtree.c ================================================================== --- ext/rtree/rtree.c +++ ext/rtree/rtree.c @@ -3323,12 +3323,28 @@ } return rc; } + +/* +** Return true if zName is the extension on one of the shadow tables used +** by this module. +*/ +static int rtreeShadowName(const char *zName){ + static const char *azName[] = { + "node", "parent", "rowid" + }; + unsigned int i; + for(i=0; i0 AND x1<100 AND x2>0 AND x2<100; @@ -256,6 +258,5 @@ sqlite3_extended_errcode db } {SQLITE_CORRUPT_VTAB} finish_test - Index: ext/rtree/rtreecheck.test ================================================================== --- ext/rtree/rtreecheck.test +++ ext/rtree/rtreecheck.test @@ -59,10 +59,11 @@ INSERT INTO r1 VALUES(2, 6, 6, 6, 6); -- 9 INSERT INTO r1 VALUES(3, 7, 7, 7, 7); -- 15 INSERT INTO r1 VALUES(4, 8, 8, 8, 8); -- 21 INSERT INTO r1 VALUES(5, 9, 9, 9, 9); -- 27 " + sqlite3_db_config db DEFENSIVE 0 } setup_simple_db do_execsql_test 2.1 { SELECT rtreecheck('r1') @@ -110,10 +111,11 @@ CREATE VIRTUAL TABLE r2 USING rtree_i32(id, x1, x2); INSERT INTO r2 VALUES(2, -1*(1<<31), -1*(1<<31)+5); SELECT rtreecheck('r2') } {ok} +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 3.2 { BEGIN; UPDATE r2_node SET data = X'123456'; SELECT rtreecheck('r2')!="ok"; } {1} @@ -138,10 +140,11 @@ WITH x(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM x WHERE i<1000 ) INSERT INTO r3 SELECT i, i, i, i, i FROM x; } +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 5.1 { BEGIN; UPDATE r3_node SET data = set_int32(data, 3, 5000); UPDATE r3_node SET data = set_int32(data, 4, 5000); SELECT rtreecheck('r3')=='ok' @@ -153,6 +156,5 @@ UPDATE r3_node SET data = set_int32(data, 4, 0); SELECT rtreecheck('r3')=='ok' } 0 finish_test - ADDED ext/session/changesetfuzz.c Index: ext/session/changesetfuzz.c ================================================================== --- /dev/null +++ ext/session/changesetfuzz.c @@ -0,0 +1,1240 @@ +/* +** 2018-11-01 +** +** 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 contains code to implement the "changesetfuzz" command +** line utility for fuzzing changeset blobs without corrupting them. +*/ + + +/************************************************************************ +** USAGE: +** +** This program may be invoked in two ways: +** +** changesetfuzz INPUT +** changesetfuzz INPUT SEED N +** +** Argument INPUT must be the name of a file containing a binary changeset. +** In the first form above, this program outputs a human-readable version +** of the same changeset. This is chiefly for debugging. +** +** As well as changesets, this program can also dump and fuzz patchsets. +** The term "changeset" is used for both patchsets and changesets from this +** point on. +** +** In the second form, arguments SEED and N must both be integers. In this +** case, this program writes N binary changesets to disk. Each output +** changeset is a slightly modified - "fuzzed" - version of the input. +** The output changesets are written to files name "INPUT-$n", where $n is +** an integer between 0 and N-1, inclusive. Output changesets are always +** well-formed. Parameter SEED is used to seed the PRNG - any two +** invocations of this program with the same SEED and input changeset create +** the same N output changesets. +** +** The ways in which an input changeset may be fuzzed are as follows: +** +** 1. Any two values within the changeset may be exchanged. +** +** 2. Any TEXT, BLOB, INTEGER or REAL value within the changeset +** may have a single bit of its content flipped. +** +** 3. Any value within a changeset may be replaced by a pseudo-randomly +** generated value. +** +** The above operations never set a PRIMARY KEY column to NULL. Nor do they +** set any value to "undefined", or replace any "undefined" value with +** another. Any such operation risks producing a changeset that is not +** well-formed. +** +** 4. A single change may be duplicated. +** +** 5. A single change may be removed, so long as this does not mean that +** there are zero changes following a table-header within the changeset. +** +** 6. A single change may have its type (INSERT, DELETE, UPDATE) changed. +** If an INSERT is changed to a DELETE (or vice versa), the type is +** simply changed - no other modifications are required. If an INSERT +** or DELETE is changed to an UPDATE, then the single record is duplicated +** (as both the old.* and new.* records of the new UPDATE change). If an +** UPDATE is changed to a DELETE or INSERT, the new.* record is discarded +** and any "undefined" fields replaced with pseudo-randomly generated +** values. +** +** 7. An UPDATE change that modifies N table columns may be modified so +** that it updates N-1 columns, so long as (N>1). +** +** 8. The "indirect" flag may be toggled for any change. +** +** Entire group of changes may also be operated on: +** +** 9. Duplicate an existing group. +** +** 10. Remove an existing group. +** +** 11. The positions of two groups may be exchanged. +** +** There are also schema changes: +** +** 12. A non-PK column may be added to a table. In this case a NULL +** value is appended to all records. +** +** 13. A PK column may be added to a table. In this case a non-NULL +** value is appended to all INSERT, DELETE and UPDATE old.* records. +** An "undefined" is appended to new.* UPDATE records. +** +** 14. A column may be removed from a table, provided that it is not the +** only PRIMARY KEY column in the table. In this case the corresponding +** field is removed from all records. In cases where this leaves an UPDATE +** with no non-PK, non-undefined fields, the entire change is removed. +*/ + +#include "sqlite3.h" +#include +#include +#include +#include +#include + +#define FUZZ_VALUE_SUB 1 /* Replace one value with a copy of another */ +#define FUZZ_VALUE_MOD 2 /* Modify content by 1 bit */ +#define FUZZ_VALUE_RND 3 /* Replace with pseudo-random value */ + +#define FUZZ_CHANGE_DUP 4 /* Duplicate an existing change */ +#define FUZZ_CHANGE_DEL 5 /* Completely remove one change */ +#define FUZZ_CHANGE_TYPE 6 /* Change the type of one change */ +#define FUZZ_CHANGE_FIELD 7 /* Change an UPDATE to modify fewer columns */ +#define FUZZ_CHANGE_INDIRECT 8 /* Toggle the "indirect" flag of a change */ + +#define FUZZ_GROUP_DUP 9 /* Duplicate a change group */ +#define FUZZ_GROUP_DEL 10 /* Delete an entire change group */ +#define FUZZ_GROUP_SWAP 11 /* Exchange the position of two groups */ + +#define FUZZ_COLUMN_ADD 12 /* Add column to table definition */ +#define FUZZ_COLUMN_ADDPK 13 /* Add PK column to table definition */ +#define FUZZ_COLUMN_DEL 14 /* Remove column from table definition */ + + + +typedef unsigned char u8; +typedef sqlite3_uint64 u64; +typedef sqlite3_int64 i64; +typedef unsigned int u32; + +/* +** Show a usage message on stderr then quit. +*/ +static void usage(const char *argv0){ + fprintf(stderr, "Usage: %s FILENAME ?SEED N?\n", argv0); + exit(1); +} + +/* +** Read the content of a disk file into an in-memory buffer +*/ +static void fuzzReadFile(const char *zFilename, int *pSz, void **ppBuf){ + FILE *f; + int sz; + void *pBuf; + f = fopen(zFilename, "rb"); + if( f==0 ){ + fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename); + exit(1); + } + fseek(f, 0, SEEK_END); + sz = (int)ftell(f); + rewind(f); + pBuf = sqlite3_malloc( sz ? sz : 1 ); + if( pBuf==0 ){ + fprintf(stderr, "cannot allocate %d to hold content of \"%s\"\n", + sz, zFilename); + exit(1); + } + if( sz>0 ){ + if( fread(pBuf, sz, 1, f)!=1 ){ + fprintf(stderr, "cannot read all %d bytes of \"%s\"\n", sz, zFilename); + exit(1); + } + fclose(f); + } + *pSz = sz; + *ppBuf = pBuf; +} + +/* +** Write the contents of buffer pBuf, size nBuf bytes, into file zFilename +** on disk. zFilename, if it already exists, is clobbered. +*/ +static void fuzzWriteFile(const char *zFilename, void *pBuf, int nBuf){ + FILE *f; + f = fopen(zFilename, "wb"); + if( f==0 ){ + fprintf(stderr, "cannot open \"%s\" for writing\n", zFilename); + exit(1); + } + if( fwrite(pBuf, nBuf, 1, f)!=1 ){ + fprintf(stderr, "cannot write to \"%s\"\n", zFilename); + exit(1); + } + fclose(f); +} + +static int fuzzCorrupt(){ + return SQLITE_CORRUPT; +} + +/************************************************************************* +** The following block is a copy of the implementation of SQLite function +** sqlite3_randomness. This version has two important differences: +** +** 1. It always uses the same seed. So the sequence of random data output +** is the same for every run of the program. +** +** 2. It is not threadsafe. +*/ +static struct sqlite3PrngType { + unsigned char i, j; /* State variables */ + unsigned char s[256]; /* State variables */ +} sqlite3Prng = { + 0xAF, 0x28, + { + 0x71, 0xF5, 0xB4, 0x6E, 0x80, 0xAB, 0x1D, 0xB8, + 0xFB, 0xB7, 0x49, 0xBF, 0xFF, 0x72, 0x2D, 0x14, + 0x79, 0x09, 0xE3, 0x78, 0x76, 0xB0, 0x2C, 0x0A, + 0x8E, 0x23, 0xEE, 0xDF, 0xE0, 0x9A, 0x2F, 0x67, + 0xE1, 0xBE, 0x0E, 0xA7, 0x08, 0x97, 0xEB, 0x77, + 0x78, 0xBA, 0x9D, 0xCA, 0x49, 0x4C, 0x60, 0x9A, + 0xF6, 0xBD, 0xDA, 0x7F, 0xBC, 0x48, 0x58, 0x52, + 0xE5, 0xCD, 0x83, 0x72, 0x23, 0x52, 0xFF, 0x6D, + 0xEF, 0x0F, 0x82, 0x29, 0xA0, 0x83, 0x3F, 0x7D, + 0xA4, 0x88, 0x31, 0xE7, 0x88, 0x92, 0x3B, 0x9B, + 0x3B, 0x2C, 0xC2, 0x4C, 0x71, 0xA2, 0xB0, 0xEA, + 0x36, 0xD0, 0x00, 0xF1, 0xD3, 0x39, 0x17, 0x5D, + 0x2A, 0x7A, 0xE4, 0xAD, 0xE1, 0x64, 0xCE, 0x0F, + 0x9C, 0xD9, 0xF5, 0xED, 0xB0, 0x22, 0x5E, 0x62, + 0x97, 0x02, 0xA3, 0x8C, 0x67, 0x80, 0xFC, 0x88, + 0x14, 0x0B, 0x15, 0x10, 0x0F, 0xC7, 0x40, 0xD4, + 0xF1, 0xF9, 0x0E, 0x1A, 0xCE, 0xB9, 0x1E, 0xA1, + 0x72, 0x8E, 0xD7, 0x78, 0x39, 0xCD, 0xF4, 0x5D, + 0x2A, 0x59, 0x26, 0x34, 0xF2, 0x73, 0x0B, 0xA0, + 0x02, 0x51, 0x2C, 0x03, 0xA3, 0xA7, 0x43, 0x13, + 0xE8, 0x98, 0x2B, 0xD2, 0x53, 0xF8, 0xEE, 0x91, + 0x7D, 0xE7, 0xE3, 0xDA, 0xD5, 0xBB, 0xC0, 0x92, + 0x9D, 0x98, 0x01, 0x2C, 0xF9, 0xB9, 0xA0, 0xEB, + 0xCF, 0x32, 0xFA, 0x01, 0x49, 0xA5, 0x1D, 0x9A, + 0x76, 0x86, 0x3F, 0x40, 0xD4, 0x89, 0x8F, 0x9C, + 0xE2, 0xE3, 0x11, 0x31, 0x37, 0xB2, 0x49, 0x28, + 0x35, 0xC0, 0x99, 0xB6, 0xD0, 0xBC, 0x66, 0x35, + 0xF7, 0x83, 0x5B, 0xD7, 0x37, 0x1A, 0x2B, 0x18, + 0xA6, 0xFF, 0x8D, 0x7C, 0x81, 0xA8, 0xFC, 0x9E, + 0xC4, 0xEC, 0x80, 0xD0, 0x98, 0xA7, 0x76, 0xCC, + 0x9C, 0x2F, 0x7B, 0xFF, 0x8E, 0x0E, 0xBB, 0x90, + 0xAE, 0x13, 0x06, 0xF5, 0x1C, 0x4E, 0x52, 0xF7 + } +}; + +/* +** Generate and return single random byte +*/ +static unsigned char fuzzRandomByte(void){ + unsigned char t; + sqlite3Prng.i++; + t = sqlite3Prng.s[sqlite3Prng.i]; + sqlite3Prng.j += t; + sqlite3Prng.s[sqlite3Prng.i] = sqlite3Prng.s[sqlite3Prng.j]; + sqlite3Prng.s[sqlite3Prng.j] = t; + t += sqlite3Prng.s[sqlite3Prng.i]; + return sqlite3Prng.s[t]; +} + +/* +** Return N random bytes. +*/ +static void fuzzRandomBlob(int nBuf, unsigned char *zBuf){ + int i; + for(i=0; i0 ); + fuzzRandomBlob(sizeof(ret), (unsigned char*)&ret); + return (ret % nRange); +} + +static u64 fuzzRandomU64(){ + u64 ret; + fuzzRandomBlob(sizeof(ret), (unsigned char*)&ret); + return ret; +} + +static void fuzzRandomSeed(unsigned int iSeed){ + int i; + for(i=0; i<256; i+=4){ + sqlite3Prng.s[i] ^= ((iSeed >> 24) & 0xFF); + sqlite3Prng.s[i+1] ^= ((iSeed >> 16) & 0xFF); + sqlite3Prng.s[i+2] ^= ((iSeed >> 8) & 0xFF); + sqlite3Prng.s[i+3] ^= ((iSeed >> 0) & 0xFF); + } +} +/* +** End of code for generating pseudo-random values. +*************************************************************************/ + +typedef struct FuzzChangeset FuzzChangeset; +typedef struct FuzzChangesetGroup FuzzChangesetGroup; +typedef struct FuzzChange FuzzChange; + +/* +** Object containing partially parsed changeset. +*/ +struct FuzzChangeset { + int bPatchset; /* True for a patchset */ + FuzzChangesetGroup **apGroup; /* Array of groups in changeset */ + int nGroup; /* Number of items in list pGroup */ + u8 **apVal; /* Array of all values in changeset */ + int nVal; /* Number of used slots in apVal[] */ + int nChange; /* Number of changes in changeset */ + int nUpdate; /* Number of UPDATE changes in changeset */ +}; + +/* +** There is one object of this type for each change-group (table header) +** in the input changeset. +*/ +struct FuzzChangesetGroup { + const char *zTab; /* Name of table */ + int nCol; /* Number of columns in table */ + u8 *aPK; /* PK array for this table */ + u8 *aChange; /* Buffer containing array of changes */ + int szChange; /* Size of buffer aChange[] in bytes */ + int nChange; /* Number of changes in buffer aChange[] */ +}; + +/* +** Description of a fuzz change to be applied to a changeset. +*/ +struct FuzzChange { + int eType; /* One of the FUZZ_* constants above */ + int iChange; /* Change or UPDATE to modify */ + int iGroup; /* Group to modify */ + int iDelete; /* Field to remove (FUZZ_COLUMN_DEL) */ + u8 *pSub1; /* Replace this value with pSub2 */ + u8 *pSub2; /* And this one with pSub1 */ + u8 aSub[128]; /* Buffer for substitute value */ + int iCurrent; /* Current change number */ +}; + +/* +** Allocate and return nByte bytes of zeroed memory. +*/ +static void *fuzzMalloc(int nByte){ + void *pRet = sqlite3_malloc(nByte); + if( pRet ){ + memset(pRet, 0, nByte); + } + return pRet; +} + +/* +** Free the buffer indicated by the first argument. This function is used +** to free buffers allocated by fuzzMalloc(). +*/ +static void fuzzFree(void *p){ + sqlite3_free(p); +} + +/* +** Argument p points to a buffer containing an SQLite varint that, assuming the +** input is not corrupt, may be between 0 and 0x7FFFFFFF, inclusive. Before +** returning, this function sets (*pnVal) to the value of that varint, and +** returns the number of bytes of space that it takes up. +*/ +static int fuzzGetVarint(u8 *p, int *pnVal){ + int i; + sqlite3_uint64 nVal = 0; + for(i=0; i<9; i++){ + nVal = (nVal<<7) + (p[i] & 0x7F); + if( (p[i] & 0x80)==0 ){ + i++; + break; + } + } + *pnVal = (int)nVal; + return i; +} + +/* +** Write value nVal into the buffer indicated by argument p as an SQLite +** varint. nVal is guaranteed to be between 0 and (2^21-1), inclusive. +** Return the number of bytes written to buffer p. +*/ +static int fuzzPutVarint(u8 *p, int nVal){ + assert( nVal>0 && nVal<2097152 ); + if( nVal<128 ){ + p[0] = nVal; + return 1; + } + if( nVal<16384 ){ + p[0] = ((nVal >> 7) & 0x7F) | 0x80; + p[1] = (nVal & 0x7F); + return 2; + } + + p[0] = ((nVal >> 14) & 0x7F) | 0x80; + p[1] = ((nVal >> 7) & 0x7F) | 0x80; + p[2] = (nVal & 0x7F); + return 3; +} + +/* +** Read a 64-bit big-endian integer value from buffer aRec[]. Return +** the value read. +*/ +static i64 fuzzGetI64(u8 *aRec){ + return (i64)( + (((u64)aRec[0]) << 56) + + (((u64)aRec[1]) << 48) + + (((u64)aRec[2]) << 40) + + (((u64)aRec[3]) << 32) + + (((u64)aRec[4]) << 24) + + (((u64)aRec[5]) << 16) + + (((u64)aRec[6]) << 8) + + (((u64)aRec[7]) << 0) + ); +} + +/* +** Write value iVal to buffer aRec[] as an unsigned 64-bit big-endian integer. +*/ +static void fuzzPutU64(u8 *aRec, u64 iVal){ + aRec[0] = (iVal>>56) & 0xFF; + aRec[1] = (iVal>>48) & 0xFF; + aRec[2] = (iVal>>40) & 0xFF; + aRec[3] = (iVal>>32) & 0xFF; + aRec[4] = (iVal>>24) & 0xFF; + aRec[5] = (iVal>>16) & 0xFF; + aRec[6] = (iVal>> 8) & 0xFF; + aRec[7] = (iVal) & 0xFF; +} + +/* +** Parse a single table-header from the input. Allocate a new change-group +** object with the results. Return SQLITE_OK if successful, or an error code +** otherwise. +*/ +static int fuzzParseHeader( + FuzzChangeset *pParse, /* Changeset parse object */ + u8 **ppHdr, /* IN/OUT: Iterator */ + u8 *pEnd, /* 1 byte past EOF */ + FuzzChangesetGroup **ppGrp /* OUT: New change-group object */ +){ + int rc = SQLITE_OK; + FuzzChangesetGroup *pGrp; + u8 cHdr = (pParse->bPatchset ? 'P' : 'T'); + + assert( pEnd>(*ppHdr) ); + pGrp = (FuzzChangesetGroup*)fuzzMalloc(sizeof(FuzzChangesetGroup)); + if( !pGrp ){ + rc = SQLITE_NOMEM; + }else{ + u8 *p = *ppHdr; + if( p[0]!=cHdr ){ + rc = fuzzCorrupt(); + }else{ + p++; + p += fuzzGetVarint(p, &pGrp->nCol); + pGrp->aPK = p; + p += pGrp->nCol; + pGrp->zTab = (const char*)p; + p = &p[strlen(p)+1]; + + if( p>=pEnd ){ + rc = fuzzCorrupt(); + } + } + *ppHdr = p; + } + + if( rc!=SQLITE_OK ){ + fuzzFree(pGrp); + pGrp = 0; + } + + *ppGrp = pGrp; + return rc; +} + +/* +** Argument p points to a buffer containing a single changeset-record value. +** This function attempts to determine the size of the value in bytes. If +** successful, it sets (*pSz) to the size and returns SQLITE_OK. Or, if the +** buffer does not contain a valid value, SQLITE_CORRUPT is returned and +** the final value of (*pSz) is undefined. +*/ +static int fuzzChangeSize(u8 *p, int *pSz){ + u8 eType = p[0]; + switch( eType ){ + case 0x00: /* undefined */ + case 0x05: /* null */ + *pSz = 1; + break; + + case 0x01: /* integer */ + case 0x02: /* real */ + *pSz = 9; + break; + + case 0x03: /* text */ + case 0x04: { /* blob */ + int nTxt; + int sz; + sz = fuzzGetVarint(&p[1], &nTxt); + *pSz = 1 + sz + nTxt; + break; + } + + default: + return fuzzCorrupt(); + } + return SQLITE_OK; +} + +/* +** When this function is called, (*ppRec) points to the start of a +** record in a changeset being parsed. This function adds entries +** to the pParse->apVal[] array for all values and advances (*ppRec) +** to one byte past the end of the record. Argument pEnd points to +** one byte past the end of the input changeset. +** +** Argument bPkOnly is true if the record being parsed is part of +** a DELETE record in a patchset. In this case, all non-primary-key +** fields have been omitted from the record. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +static int fuzzParseRecord( + u8 **ppRec, /* IN/OUT: Iterator */ + u8 *pEnd, /* One byte after end of input data */ + FuzzChangeset *pParse, /* Changeset parse context */ + int bPkOnly /* True if non-PK fields omitted */ +){ + int rc = SQLITE_OK; + FuzzChangesetGroup *pGrp = pParse->apGroup[pParse->nGroup-1]; + int i; + u8 *p = *ppRec; + + for(i=0; rc==SQLITE_OK && inCol; i++){ + if( bPkOnly==0 || pGrp->aPK[i] ){ + int sz; + if( p>=pEnd ) break; + if( (pParse->nVal & (pParse->nVal-1))==0 ){ + int nNew = pParse->nVal ? pParse->nVal*2 : 4; + u8 **apNew = (u8**)sqlite3_realloc(pParse->apVal, nNew*sizeof(u8*)); + if( apNew==0 ) return SQLITE_NOMEM; + pParse->apVal = apNew; + } + pParse->apVal[pParse->nVal++] = p; + rc = fuzzChangeSize(p, &sz); + p += sz; + } + } + + if( rc==SQLITE_OK && inCol ){ + rc = fuzzCorrupt(); + } + + *ppRec = p; + return rc; +} + +/* +** Parse the array of changes starting at (*ppData) and add entries for +** all values to the pParse->apVal[] array. Argument pEnd points to one byte +** past the end of the input changeset. If successful, set (*ppData) to point +** to one byte past the end of the change array and return SQLITE_OK. +** Otherwise, return an SQLite error code. The final value of (*ppData) is +** undefined in this case. +*/ +static int fuzzParseChanges(u8 **ppData, u8 *pEnd, FuzzChangeset *pParse){ + u8 cHdr = (pParse->bPatchset ? 'P' : 'T'); + FuzzChangesetGroup *pGrp = pParse->apGroup[pParse->nGroup-1]; + int rc = SQLITE_OK; + u8 *p = *ppData; + + pGrp->aChange = p; + while( rc==SQLITE_OK && pnUpdate++; + if( pParse->bPatchset==0 ){ + rc = fuzzParseRecord(&p, pEnd, pParse, 0); + } + }else if( eOp!=SQLITE_INSERT && eOp!=SQLITE_DELETE ){ + rc = fuzzCorrupt(); + } + if( rc==SQLITE_OK ){ + int bPkOnly = (eOp==SQLITE_DELETE && pParse->bPatchset); + rc = fuzzParseRecord(&p, pEnd, pParse, bPkOnly); + } + pGrp->nChange++; + pParse->nChange++; + } + pGrp->szChange = p - pGrp->aChange; + + *ppData = p; + return rc; +} + +/* +** Parse the changeset stored in buffer pChangeset (nChangeset bytes in +** size). If successful, write the results into (*pParse) and return +** SQLITE_OK. Or, if an error occurs, return an SQLite error code. The +** final state of (*pParse) is undefined in this case. +*/ +static int fuzzParseChangeset( + u8 *pChangeset, /* Buffer containing changeset */ + int nChangeset, /* Size of buffer in bytes */ + FuzzChangeset *pParse /* OUT: Results of parse */ +){ + u8 *pEnd = &pChangeset[nChangeset]; + u8 *p = pChangeset; + int rc = SQLITE_OK; + + memset(pParse, 0, sizeof(FuzzChangeset)); + if( nChangeset>0 ){ + pParse->bPatchset = (pChangeset[0]=='P'); + } + + while( rc==SQLITE_OK && papGroup, sizeof(FuzzChangesetGroup*)*(pParse->nGroup+1) + ); + if( apNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + apNew[pParse->nGroup] = pGrp; + pParse->apGroup = apNew; + pParse->nGroup++; + } + rc = fuzzParseChanges(&p, pEnd, pParse); + } + } + + return rc; +} + +/* +** When this function is called, (*ppRec) points to the first byte of +** a record that is part of change-group pGrp. This function attempts +** to output a human-readable version of the record to stdout and advance +** (*ppRec) to point to the first byte past the end of the record before +** returning. If successful, SQLITE_OK is returned. Otherwise, an SQLite +** error code. +** +** If parameter bPkOnly is non-zero, then all non-primary-key fields have +** been omitted from the record. This occurs for records that are part +** of DELETE changes in patchsets. +*/ +static int fuzzPrintRecord(FuzzChangesetGroup *pGrp, u8 **ppRec, int bPKOnly){ + int rc = SQLITE_OK; + u8 *p = *ppRec; + int i; + const char *zPre = " ("; + + for(i=0; inCol; i++){ + if( bPKOnly==0 || pGrp->aPK[i] ){ + u8 eType = p++[0]; + switch( eType ){ + case 0x00: /* undefined */ + printf("%sn/a", zPre); + break; + + case 0x01: { /* integer */ + sqlite3_int64 iVal = 0; + iVal = fuzzGetI64(p); + printf("%s%lld", zPre, iVal); + p += 8; + break; + } + + case 0x02: { /* real */ + sqlite3_int64 iVal = 0; + double fVal = 0.0; + iVal = fuzzGetI64(p); + memcpy(&fVal, &iVal, 8); + printf("%s%f", zPre, fVal); + p += 8; + break; + } + + case 0x03: /* text */ + case 0x04: { /* blob */ + int nTxt; + int sz; + int i; + p += fuzzGetVarint(p, &nTxt); + printf("%s%s", zPre, eType==0x03 ? "'" : "X'"); + for(i=0; i>4 ]); + printf("%c", aHex[ p[i] & 0x0F ]); + } + } + printf("'"); + p += nTxt; + break; + } + + case 0x05: /* null */ + printf("%sNULL", zPre); + break; + } + zPre = ", "; + } + } + printf(")"); + + *ppRec = p; + return rc; +} + +/* +** Print a human-readable version of the table-header and all changes in the +** change-group passed as the second argument. +*/ +static void fuzzPrintGroup(FuzzChangeset *pParse, FuzzChangesetGroup *pGrp){ + int i; + u8 *p; + + /* The table header */ + printf("TABLE: %s nCol=%d aPK=", pGrp->zTab, pGrp->nCol); + for(i=0; inCol; i++){ + printf("%d", (int)pGrp->aPK[i]); + } + printf("\n"); + + /* The array of changes */ + p = pGrp->aChange; + for(i=0; inChange; i++){ + u8 eType = p[0]; + u8 bIndirect = p[1]; + printf("%s (ind=%d):", + (eType==SQLITE_INSERT) ? "INSERT" : + (eType==SQLITE_DELETE ? "DELETE" : "UPDATE"), + bIndirect + ); + p += 2; + + if( pParse->bPatchset==0 && eType==SQLITE_UPDATE ){ + fuzzPrintRecord(pGrp, &p, 0); + } + fuzzPrintRecord(pGrp, &p, eType==SQLITE_DELETE && pParse->bPatchset); + printf("\n"); + } +} + +/* +** Initialize the object passed as the second parameter with details +** of the change that will be attempted (type of change, to which part of the +** changeset it applies etc.). If successful, return SQLITE_OK. Or, if an +** error occurs, return an SQLite error code. +** +** If a negative value is returned, then the selected change would have +** produced a non-well-formed changeset. In this case the caller should +** call this function again. +*/ +static int fuzzSelectChange(FuzzChangeset *pParse, FuzzChange *pChange){ + int iSub; + + memset(pChange, 0, sizeof(FuzzChange)); + pChange->eType = fuzzRandomInt(FUZZ_COLUMN_DEL) + 1; + + assert( pChange->eType==FUZZ_VALUE_SUB + || pChange->eType==FUZZ_VALUE_MOD + || pChange->eType==FUZZ_VALUE_RND + || pChange->eType==FUZZ_CHANGE_DUP + || pChange->eType==FUZZ_CHANGE_DEL + || pChange->eType==FUZZ_CHANGE_TYPE + || pChange->eType==FUZZ_CHANGE_FIELD + || pChange->eType==FUZZ_CHANGE_INDIRECT + || pChange->eType==FUZZ_GROUP_DUP + || pChange->eType==FUZZ_GROUP_DEL + || pChange->eType==FUZZ_GROUP_SWAP + || pChange->eType==FUZZ_COLUMN_ADD + || pChange->eType==FUZZ_COLUMN_ADDPK + || pChange->eType==FUZZ_COLUMN_DEL + ); + + pChange->iGroup = fuzzRandomInt(pParse->nGroup); + pChange->iChange = fuzzRandomInt(pParse->nChange); + if( pChange->eType==FUZZ_CHANGE_FIELD ){ + if( pParse->nUpdate==0 ) return -1; + pChange->iChange = fuzzRandomInt(pParse->nUpdate); + } + + pChange->iDelete = -1; + if( pChange->eType==FUZZ_COLUMN_DEL ){ + FuzzChangesetGroup *pGrp = pParse->apGroup[pChange->iGroup]; + int i; + pChange->iDelete = fuzzRandomInt(pGrp->nCol); + for(i=pGrp->nCol-1; i>=0; i--){ + if( pGrp->aPK[i] && pChange->iDelete!=i ) break; + } + if( i<0 ) return -1; + } + + if( pChange->eType==FUZZ_GROUP_SWAP ){ + FuzzChangesetGroup *pGrp; + int iGrp = pChange->iGroup; + if( pParse->nGroup==1 ) return -1; + while( iGrp==pChange->iGroup ){ + iGrp = fuzzRandomInt(pParse->nGroup); + } + pGrp = pParse->apGroup[pChange->iGroup]; + pParse->apGroup[pChange->iGroup] = pParse->apGroup[iGrp]; + pParse->apGroup[iGrp] = pGrp; + } + + if( pChange->eType==FUZZ_VALUE_SUB + || pChange->eType==FUZZ_VALUE_MOD + || pChange->eType==FUZZ_VALUE_RND + ){ + iSub = fuzzRandomInt(pParse->nVal); + pChange->pSub1 = pParse->apVal[iSub]; + if( pChange->eType==FUZZ_VALUE_SUB ){ + iSub = fuzzRandomInt(pParse->nVal); + pChange->pSub2 = pParse->apVal[iSub]; + }else{ + pChange->pSub2 = pChange->aSub; + } + + if( pChange->eType==FUZZ_VALUE_RND ){ + pChange->aSub[0] = (u8)(fuzzRandomInt(5) + 1); + switch( pChange->aSub[0] ){ + case 0x01: { /* integer */ + u64 iVal = fuzzRandomU64(); + fuzzPutU64(&pChange->aSub[1], iVal); + break; + } + + case 0x02: { /* real */ + u64 iVal1 = fuzzRandomU64(); + u64 iVal2 = fuzzRandomU64(); + double d = (double)iVal1 / (double)iVal2; + memcpy(&iVal1, &d, sizeof(iVal1)); + fuzzPutU64(&pChange->aSub[1], iVal1); + break; + } + + case 0x03: /* text */ + case 0x04: { /* blob */ + int nByte = fuzzRandomInt(48); + pChange->aSub[1] = nByte; + fuzzRandomBlob(nByte, &pChange->aSub[2]); + if( pChange->aSub[0]==0x03 ){ + int i; + for(i=0; iaSub[2+i] &= 0x7F; + } + } + break; + } + } + } + if( pChange->eType==FUZZ_VALUE_MOD ){ + int sz; + int iMod = -1; + fuzzChangeSize(pChange->pSub1, &sz); + memcpy(pChange->aSub, pChange->pSub1, sz); + switch( pChange->aSub[0] ){ + case 0x01: + case 0x02: + iMod = fuzzRandomInt(8) + 1; + break; + + case 0x03: /* text */ + case 0x04: { /* blob */ + int nByte; + int iFirst = 1 + fuzzGetVarint(&pChange->aSub[1], &nByte); + if( nByte>0 ){ + iMod = fuzzRandomInt(nByte) + iFirst; + } + break; + } + } + + if( iMod>=0 ){ + u8 mask = (1 << fuzzRandomInt(8 - (pChange->aSub[0]==0x03))); + pChange->aSub[iMod] ^= mask; + } + } + } + + return SQLITE_OK; +} + +/* +** Copy a single change from the input to the output changeset, making +** any modifications specified by (*pFuzz). +*/ +static int fuzzCopyChange( + FuzzChangeset *pParse, + int iGrp, + FuzzChange *pFuzz, + u8 **pp, u8 **ppOut /* IN/OUT: Input and output pointers */ +){ + int bPS = pParse->bPatchset; + FuzzChangesetGroup *pGrp = pParse->apGroup[iGrp]; + u8 *p = *pp; + u8 *pOut = *ppOut; + u8 eType = p++[0]; + int iRec; + int nRec = ((eType==SQLITE_UPDATE && !bPS) ? 2 : 1); + int iUndef = -1; + int nUpdate = 0; + + u8 eNew = eType; + if( pFuzz->iCurrent==pFuzz->iChange && pFuzz->eType==FUZZ_CHANGE_TYPE ){ + switch( eType ){ + case SQLITE_INSERT: + eNew = SQLITE_DELETE; + break; + case SQLITE_DELETE: + eNew = SQLITE_UPDATE; + break; + case SQLITE_UPDATE: + eNew = SQLITE_INSERT; + break; + } + } + + if( pFuzz->iCurrent==pFuzz->iChange + && pFuzz->eType==FUZZ_CHANGE_FIELD && eType==SQLITE_UPDATE + ){ + int sz; + int i; + int nDef = 0; + u8 *pCsr = p+1; + for(i=0; inCol; i++){ + if( pCsr[0] && pGrp->aPK[i]==0 ) nDef++; + fuzzChangeSize(pCsr, &sz); + pCsr += sz; + } + if( nDef<=1 ) return -1; + nDef = fuzzRandomInt(nDef); + pCsr = p+1; + for(i=0; inCol; i++){ + if( pCsr[0] && pGrp->aPK[i]==0 ){ + if( nDef==0 ) iUndef = i; + nDef--; + } + fuzzChangeSize(pCsr, &sz); + pCsr += sz; + } + } + + /* Copy the change type and indirect flag. If the fuzz mode is + ** FUZZ_CHANGE_INDIRECT, and the current change is the one selected for + ** fuzzing, invert the indirect flag. */ + *(pOut++) = eNew; + if( pFuzz->eType==FUZZ_CHANGE_INDIRECT && pFuzz->iCurrent==pFuzz->iChange ){ + *(pOut++) = !(*(p++)); + }else{ + *(pOut++) = *(p++); + } + + for(iRec=0; iRecnCol; i++){ + int sz; + u8 *pCopy = p; + + /* If this is a patchset, and the input is a DELETE, then the only + ** fields present are the PK fields. So, if this is not a PK, skip to + ** the next column. If the current fuzz is FUZZ_CHANGE_TYPE, then + ** write a randomly selected value to the output. */ + if( bPS && eType==SQLITE_DELETE && pGrp->aPK[i]==0 ){ + if( eType!=eNew ){ + assert( eNew==SQLITE_UPDATE ); + do { + pCopy = pParse->apVal[fuzzRandomInt(pParse->nVal)]; + }while( pCopy[0]==0x00 ); + fuzzChangeSize(pCopy, &sz); + memcpy(pOut, pCopy, sz); + pOut += sz; + } + continue; + } + + if( p==pFuzz->pSub1 ){ + pCopy = pFuzz->pSub2; + }else if( p==pFuzz->pSub2 ){ + pCopy = pFuzz->pSub1; + }else if( i==iUndef ){ + pCopy = "\0"; + } + + if( pCopy[0]==0x00 && eNew!=eType && eType==SQLITE_UPDATE && iRec==0 ){ + while( pCopy[0]==0x00 ){ + pCopy = pParse->apVal[fuzzRandomInt(pParse->nVal)]; + } + }else if( p[0]==0x00 && pCopy[0]!=0x00 ){ + return -1; + }else{ + if( pGrp->aPK[i]>0 && pCopy[0]==0x05 ) return -1; + } + + if( (pFuzz->iGroup!=iGrp || i!=pFuzz->iDelete) + && (eNew==eType || eType!=SQLITE_UPDATE || iRec==0) + && (eNew==eType || eNew!=SQLITE_DELETE || !bPS || pGrp->aPK[i]) + ){ + fuzzChangeSize(pCopy, &sz); + memcpy(pOut, pCopy, sz); + pOut += sz; + nUpdate += (pGrp->aPK[i]==0 && pCopy[0]!=0x00); + } + + fuzzChangeSize(p, &sz); + p += sz; + } + + if( iGrp==pFuzz->iGroup ){ + if( pFuzz->eType==FUZZ_COLUMN_ADD ){ + if( !bPS || eType!=SQLITE_DELETE ) *(pOut++) = 0x05; + }else if( pFuzz->eType==FUZZ_COLUMN_ADDPK ){ + if( iRec==1 ){ + *(pOut++) = 0x00; + }else{ + u8 *pNew; + int szNew; + do { + pNew = pParse->apVal[fuzzRandomInt(pParse->nVal)]; + }while( pNew[0]==0x00 || pNew[0]==0x05 ); + fuzzChangeSize(pNew, &szNew); + memcpy(pOut, pNew, szNew); + pOut += szNew; + } + } + } + } + + if( pFuzz->iCurrent==pFuzz->iChange ){ + if( pFuzz->eType==FUZZ_CHANGE_DUP ){ + int nByte = pOut - (*ppOut); + memcpy(pOut, *ppOut, nByte); + pOut += nByte; + } + + if( pFuzz->eType==FUZZ_CHANGE_DEL ){ + pOut = *ppOut; + } + if( eNew!=eType && eNew==SQLITE_UPDATE && !bPS ){ + int i; + u8 *pCsr = (*ppOut) + 2; + for(i=0; inCol; i++){ + int sz; + u8 *pCopy = pCsr; + if( pGrp->aPK[i] ) pCopy = "\0"; + fuzzChangeSize(pCopy, &sz); + memcpy(pOut, pCopy, sz); + pOut += sz; + fuzzChangeSize(pCsr, &sz); + pCsr += sz; + } + } + } + + /* If a column is being deleted from this group, and this change was an + ** UPDATE, and there are now no non-PK, non-undefined columns in the + ** change, remove it altogether. */ + if( pFuzz->eType==FUZZ_COLUMN_DEL && pFuzz->iGroup==iGrp + && eType==SQLITE_UPDATE && nUpdate==0 + ){ + pOut = *ppOut; + } + + *pp = p; + *ppOut = pOut; + pFuzz->iCurrent += (eType==SQLITE_UPDATE || pFuzz->eType!=FUZZ_CHANGE_FIELD); + return SQLITE_OK; +} + +/* +** Fuzz the changeset parsed into object pParse and write the results +** to file zOut on disk. Argument pBuf points to a buffer that is guaranteed +** to be large enough to hold the fuzzed changeset. +** +** Return SQLITE_OK if successful, or an SQLite error code if an error occurs. +*/ +static int fuzzDoOneFuzz( + const char *zOut, /* Filename to write modified changeset to */ + u8 *pBuf, /* Buffer to use for modified changeset */ + FuzzChangeset *pParse /* Parse of input changeset */ +){ + FuzzChange change; + int iGrp; + int rc = -1; + + while( rc<0 ){ + u8 *pOut = pBuf; + rc = fuzzSelectChange(pParse, &change); + for(iGrp=0; rc==SQLITE_OK && iGrpnGroup; iGrp++){ + FuzzChangesetGroup *pGrp = pParse->apGroup[iGrp]; + int nTab = strlen(pGrp->zTab) + 1; + int j; + int nRep = 1; + + /* If this is the group to delete for a FUZZ_GROUP_DEL change, jump to + ** the next group. Unless this is the only group in the changeset - in + ** that case this change cannot be applied. + ** + ** Or, if this is a FUZZ_GROUP_DUP, set nRep to 2 to output two + ** copies of the group. */ + if( change.iGroup==iGrp ){ + if( change.eType==FUZZ_GROUP_DEL ){ + if( pParse->nGroup==1 ) rc = -1; + continue; + } + else if( change.eType==FUZZ_GROUP_DUP ){ + nRep = 2; + } + } + + for(j=0; jaChange; + int nCol = pGrp->nCol; + int iPKDel = 0; + if( iGrp==change.iGroup ){ + if( change.eType==FUZZ_COLUMN_ADD + || change.eType==FUZZ_COLUMN_ADDPK + ){ + nCol++; + }else if( change.eType==FUZZ_COLUMN_DEL ){ + nCol--; + iPKDel = pGrp->aPK[change.iDelete]; + } + } + + /* Output a table header */ + pOut++[0] = pParse->bPatchset ? 'P' : 'T'; + pOut += fuzzPutVarint(pOut, nCol); + + for(i=0; inCol; i++){ + if( iGrp!=change.iGroup || i!=change.iDelete ){ + u8 v = pGrp->aPK[i]; + if( iPKDel && v>iPKDel ) v--; + *(pOut++) = v; + } + } + if( nCol>pGrp->nCol ){ + if( change.eType==FUZZ_COLUMN_ADD ){ + *(pOut++) = 0x00; + }else{ + u8 max = 0; + for(i=0; inCol; i++){ + if( pGrp->aPK[i]>max ) max = pGrp->aPK[i]; + } + *(pOut++) = max+1; + } + } + memcpy(pOut, pGrp->zTab, nTab); + pOut += nTab; + + /* Output the change array. */ + pSaved = pOut; + for(i=0; rc==SQLITE_OK && inChange; i++){ + rc = fuzzCopyChange(pParse, iGrp, &change, &p, &pOut); + } + if( pOut==pSaved ) rc = -1; + } + } + if( rc==SQLITE_OK ){ + fuzzWriteFile(zOut, pBuf, pOut-pBuf); + } + } + + return rc; +} + +int main(int argc, char **argv){ + int nRepeat = 0; /* Number of output files */ + int iSeed = 0; /* Value of PRNG seed */ + const char *zInput; /* Name of input file */ + void *pChangeset = 0; /* Input changeset */ + int nChangeset = 0; /* Size of input changeset in bytes */ + int i; /* Current output file */ + FuzzChangeset changeset; /* Partially parsed changeset */ + int rc; + u8 *pBuf = 0; + + if( argc!=4 && argc!=2 ) usage(argv[0]); + zInput = argv[1]; + + fuzzReadFile(zInput, &nChangeset, &pChangeset); + rc = fuzzParseChangeset(pChangeset, nChangeset, &changeset); + + if( rc==SQLITE_OK ){ + if( argc==2 ){ + for(i=0; inSrc==1 ); assert( sqlite3BtreeHoldsAllMutexes(pParse->db) ); pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); if( !pTab ) goto exit_rename_table; - iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema, 0); zDb = db->aDb[iDb].zDbSName; db->mDbFlags |= DBFLAG_PreferBuiltin; /* Get a NULL terminated version of the new table name. */ zName = sqlite3NameFromToken(db, pName); @@ -146,15 +146,15 @@ goto exit_rename_table; } #endif #ifndef SQLITE_OMIT_VIRTUALTABLE - if( sqlite3ViewGetColumnNames(pParse, pTab) ){ + if( sqlite3ViewGetColumnNames(pParse, iDb, pTab) ){ goto exit_rename_table; } if( IsVirtual(pTab) ){ - pVTab = sqlite3GetVTable(db, pTab); + pVTab = sqlite3GetVTable(db, -1, pTab); if( pVTab->pVtab->pModule->xRename==0 ){ pVTab = 0; } } #endif @@ -274,11 +274,11 @@ if( pParse->nErr || db->mallocFailed ) return; pNew = pParse->pNewTable; assert( pNew ); assert( sqlite3BtreeHoldsAllMutexes(db) ); - iDb = sqlite3SchemaToIndex(db, pNew->pSchema); + iDb = sqlite3SchemaToIndex(db, pNew->pSchema, 0); zDb = db->aDb[iDb].zDbSName; zTab = &pNew->zName[16]; /* Skip the "sqlite_altertab_" prefix on the name */ pCol = &pNew->aCol[pNew->nCol-1]; pDflt = pCol->pDflt; pTab = sqlite3FindTable(db, zTab, zDb); @@ -427,11 +427,11 @@ if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ){ goto exit_begin_add_column; } assert( pTab->addColOffset>0 ); - iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + iDb = sqlite3SchemaToIndex(db, pTab->pSchema, 0); /* Put a copy of the Table struct in Parse.pNewTable for the ** sqlite3AddColumn() function and friends to modify. But modify ** the name by adding an "sqlite_altertab_" prefix. By adding this ** prefix, we insure that the name will not collide with an existing @@ -528,11 +528,11 @@ /* Cannot alter a system table */ if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ) goto exit_rename_column; if( SQLITE_OK!=isRealTable(pParse, pTab) ) goto exit_rename_column; /* Which schema holds the table to be altered */ - iSchema = sqlite3SchemaToIndex(db, pTab->pSchema); + iSchema = sqlite3SchemaToIndex(db, pTab->pSchema, 0); assert( iSchema>=0 ); zDb = db->aDb[iSchema].zDbSName; #ifndef SQLITE_OMIT_AUTHORIZATION /* Invoke the authorization callback. */ @@ -1056,17 +1056,18 @@ memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; assert( pNew->pTabSchema ); pParse->pTriggerTab = sqlite3FindTable(db, pNew->table, - db->aDb[sqlite3SchemaToIndex(db, pNew->pTabSchema)].zDbSName + db->aDb[sqlite3SchemaToIndex(db, pNew->pTabSchema, 0)].zDbSName ); pParse->eTriggerOp = pNew->op; /* ALWAYS() because if the table of the trigger does not exist, the ** error would have been hit before this point */ if( ALWAYS(pParse->pTriggerTab) ){ - rc = sqlite3ViewGetColumnNames(pParse, pParse->pTriggerTab); + int iDb = sqlite3SchemaToIndex(db, pParse->pTriggerTab->pSchema, 0); + rc = sqlite3ViewGetColumnNames(pParse, iDb, pParse->pTriggerTab); } /* Resolve symbols in WHEN clause */ if( rc==SQLITE_OK && pNew->pWhen ){ rc = sqlite3ResolveExprNames(&sNC, pNew->pWhen); @@ -1079,42 +1080,45 @@ } if( rc==SQLITE_OK && pStep->zTarget ){ Table *pTarget = sqlite3LocateTable(pParse, 0, pStep->zTarget, zDb); if( pTarget==0 ){ rc = SQLITE_ERROR; - }else if( SQLITE_OK==(rc = sqlite3ViewGetColumnNames(pParse, pTarget)) ){ - SrcList sSrc; - memset(&sSrc, 0, sizeof(sSrc)); - sSrc.nSrc = 1; - sSrc.a[0].zName = pStep->zTarget; - sSrc.a[0].pTab = pTarget; - sNC.pSrcList = &sSrc; - if( pStep->pWhere ){ - rc = sqlite3ResolveExprNames(&sNC, pStep->pWhere); - } - if( rc==SQLITE_OK ){ - rc = sqlite3ResolveExprListNames(&sNC, pStep->pExprList); - } - assert( !pStep->pUpsert || (!pStep->pWhere && !pStep->pExprList) ); - if( pStep->pUpsert ){ - Upsert *pUpsert = pStep->pUpsert; - assert( rc==SQLITE_OK ); - pUpsert->pUpsertSrc = &sSrc; - sNC.uNC.pUpsert = pUpsert; - sNC.ncFlags = NC_UUpsert; - rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget); - if( rc==SQLITE_OK ){ - ExprList *pUpsertSet = pUpsert->pUpsertSet; - rc = sqlite3ResolveExprListNames(&sNC, pUpsertSet); - } - if( rc==SQLITE_OK ){ - rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertWhere); - } - if( rc==SQLITE_OK ){ - rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere); - } - sNC.ncFlags = 0; + }else{ + int iDb = sqlite3SchemaToIndex(db, pTarget->pSchema, 0); + if( SQLITE_OK==(rc = sqlite3ViewGetColumnNames(pParse, iDb, pTarget)) ){ + SrcList sSrc; + memset(&sSrc, 0, sizeof(sSrc)); + sSrc.nSrc = 1; + sSrc.a[0].zName = pStep->zTarget; + sSrc.a[0].pTab = pTarget; + sNC.pSrcList = &sSrc; + if( pStep->pWhere ){ + rc = sqlite3ResolveExprNames(&sNC, pStep->pWhere); + } + if( rc==SQLITE_OK ){ + rc = sqlite3ResolveExprListNames(&sNC, pStep->pExprList); + } + assert( !pStep->pUpsert || (!pStep->pWhere && !pStep->pExprList) ); + if( pStep->pUpsert ){ + Upsert *pUpsert = pStep->pUpsert; + assert( rc==SQLITE_OK ); + pUpsert->pUpsertSrc = &sSrc; + sNC.uNC.pUpsert = pUpsert; + sNC.ncFlags = NC_UUpsert; + rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget); + if( rc==SQLITE_OK ){ + ExprList *pUpsertSet = pUpsert->pUpsertSet; + rc = sqlite3ResolveExprListNames(&sNC, pUpsertSet); + } + if( rc==SQLITE_OK ){ + rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertWhere); + } + if( rc==SQLITE_OK ){ + rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere); + } + sNC.ncFlags = 0; + } } } } } return rc; @@ -1442,11 +1446,11 @@ sqlite3WalkSelect(&sWalker, pTab->pSelect); } }else{ /* Modify any FK definitions to point to the new table. */ #ifndef SQLITE_OMIT_FOREIGN_KEY - if( db->flags & SQLITE_ForeignKeys ){ + if( isLegacy==0 || (db->flags & SQLITE_ForeignKeys) ){ FKey *pFKey; for(pFKey=pTab->pFKey; pFKey; pFKey=pFKey->pNextFrom){ if( sqlite3_stricmp(pFKey->zTo, zOld)==0 ){ renameTokenFind(&sParse, &sCtx, (void*)pFKey->zTo); } @@ -1573,11 +1577,11 @@ else if( sParse.pNewTrigger ){ if( isLegacy==0 ){ rc = renameResolveTrigger(&sParse, bTemp ? 0 : zDb); } if( rc==SQLITE_OK ){ - int i1 = sqlite3SchemaToIndex(db, sParse.pNewTrigger->pTabSchema); + int i1 = sqlite3SchemaToIndex(db, sParse.pNewTrigger->pTabSchema, 0); int i2 = sqlite3FindDbName(db, zDb); if( i1==i2 ) sqlite3_result_int(context, 1); } } } Index: src/analyze.c ================================================================== --- src/analyze.c +++ src/analyze.c @@ -977,10 +977,11 @@ ** Generate code to do an analysis of all indices associated with ** a single table. */ static void analyzeOneTable( Parse *pParse, /* Parser context */ + int iDb, /* Database that contains table pTab */ Table *pTab, /* Table whose indices are to be analyzed */ Index *pOnlyIdx, /* If not NULL, only analyze this one index */ int iStatCur, /* Index of VdbeCursor that writes the sqlite_stat1 table */ int iMem, /* Available memory locations begin here */ int iTab /* Next available cursor */ @@ -990,11 +991,10 @@ int iIdxCur; /* Cursor open on index being analyzed */ int iTabCur; /* Table cursor */ Vdbe *v; /* The virtual machine being built up */ int i; /* Loop counter */ int jZeroRows = -1; /* Jump from here if number of rows is zero */ - int iDb; /* Index of database containing pTab */ u8 needTableCnt = 1; /* True to count the table */ int regNewRowid = iMem++; /* Rowid for the inserted record */ int regStat4 = iMem++; /* Register to hold Stat4Accum object */ int regChng = iMem++; /* Index of changed index field */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 @@ -1021,11 +1021,10 @@ if( sqlite3_strlike("sqlite\\_%", pTab->zName, '\\')==0 ){ /* Do not gather statistics on system tables */ return; } assert( sqlite3BtreeHoldsAllMutexes(db) ); - iDb = sqlite3SchemaToIndex(db, pTab->pSchema); assert( iDb>=0 ); assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); #ifndef SQLITE_OMIT_AUTHORIZATION if( sqlite3AuthCheck(pParse, SQLITE_ANALYZE, pTab->zName, 0, db->aDb[iDb].zDbSName ) ){ @@ -1116,11 +1115,11 @@ ** when building a record to insert into the sample column of ** the sqlite_stat4 table. */ pParse->nMem = MAX(pParse->nMem, regPrev+nColTest); /* Open a read-only cursor on the index being analyzed. */ - assert( iDb==sqlite3SchemaToIndex(db, pIdx->pSchema) ); + assert( db->aDb[iDb].pSchema==pIdx->pSchema ); sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum, iDb); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "%s", pIdx->zName)); /* Invoke the stat_init() function. The arguments are: @@ -1337,46 +1336,45 @@ HashElem *k; int iStatCur; int iMem; int iTab; - sqlite3SchemaWritable(pParse, iDb); sqlite3BeginWriteOperation(pParse, 0, iDb); iStatCur = pParse->nTab; pParse->nTab += 3; openStatTable(pParse, iDb, iStatCur, 0, 0); iMem = pParse->nMem+1; iTab = pParse->nTab; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){ Table *pTab = (Table*)sqliteHashData(k); - analyzeOneTable(pParse, pTab, 0, iStatCur, iMem, iTab); + analyzeOneTable(pParse, iDb, pTab, 0, iStatCur, iMem, iTab); } loadAnalysis(pParse, iDb); } /* ** Generate code that will do an analysis of a single table in ** a database. If pOnlyIdx is not NULL then it is a single index ** in pTab that should be analyzed. */ -static void analyzeTable(Parse *pParse, Table *pTab, Index *pOnlyIdx){ - int iDb; +static void analyzeTable(Parse *pParse, int iDb, Table *pTab, Index *pOnlyIdx){ int iStatCur; assert( pTab!=0 ); assert( sqlite3BtreeHoldsAllMutexes(pParse->db) ); - iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); sqlite3BeginWriteOperation(pParse, 0, iDb); iStatCur = pParse->nTab; pParse->nTab += 3; if( pOnlyIdx ){ openStatTable(pParse, iDb, iStatCur, pOnlyIdx->zName, "idx"); }else{ openStatTable(pParse, iDb, iStatCur, pTab->zName, "tbl"); } - analyzeOneTable(pParse, pTab, pOnlyIdx, iStatCur,pParse->nMem+1,pParse->nTab); + analyzeOneTable(pParse, iDb, pTab, + pOnlyIdx, iStatCur, pParse->nMem+1, pParse->nTab + ); loadAnalysis(pParse, iDb); } /* ** Generate code for the ANALYZE command. The parser calls this routine @@ -1423,13 +1421,13 @@ if( iDb>=0 ){ zDb = pName2->n ? db->aDb[iDb].zDbSName : 0; z = sqlite3NameFromToken(db, pTableName); if( z ){ if( (pIdx = sqlite3FindIndex(db, z, zDb))!=0 ){ - analyzeTable(pParse, pIdx->pTable, pIdx); + analyzeTable(pParse, iDb, pIdx->pTable, pIdx); }else if( (pTab = sqlite3LocateTable(pParse, 0, z, zDb))!=0 ){ - analyzeTable(pParse, pTab, 0); + analyzeTable(pParse, iDb, pTab, 0); } sqlite3DbFree(db, z); } } } Index: src/attach.c ================================================================== --- src/attach.c +++ src/attach.c @@ -98,11 +98,11 @@ if( pVfs==0 ) return; pNew = &db->aDb[db->init.iDb]; if( pNew->pBt ) sqlite3BtreeClose(pNew->pBt); pNew->pBt = 0; pNew->pSchema = 0; - rc = sqlite3BtreeOpen(pVfs, "x", db, &pNew->pBt, 0, SQLITE_OPEN_MAIN_DB); + rc = sqlite3BtreeOpen(pVfs, "x\0", db, &pNew->pBt, 0, SQLITE_OPEN_MAIN_DB); }else{ /* This is a real ATTACH ** ** Check for the following errors: ** Index: src/auth.c ================================================================== --- src/auth.c +++ src/auth.c @@ -151,11 +151,11 @@ int iCol; /* Index of column in table */ assert( pExpr->op==TK_COLUMN || pExpr->op==TK_TRIGGER ); assert( !IN_RENAME_OBJECT || db->xAuth==0 ); if( db->xAuth==0 ) return; - iDb = sqlite3SchemaToIndex(pParse->db, pSchema); + iDb = sqlite3SchemaToIndex(pParse->db, pSchema, 0); /* TODO! */ if( iDb<0 ){ /* An attempt to read a column out of a subquery or other ** temporary table. */ return; } Index: src/btmutex.c ================================================================== --- src/btmutex.c +++ src/btmutex.c @@ -250,11 +250,12 @@ ** db using sqlite3SchemaToIndex(). */ int sqlite3SchemaMutexHeld(sqlite3 *db, int iDb, Schema *pSchema){ Btree *p; assert( db!=0 ); - if( pSchema ) iDb = sqlite3SchemaToIndex(db, pSchema); + if( pSchema && (db->openFlags & SQLITE_OPEN_REUSE_SCHEMA) ) return 1; + if( pSchema ) iDb = sqlite3SchemaToIndex(db, pSchema, 0); assert( iDb>=0 && iDbnDb ); if( !sqlite3_mutex_held(db->mutex) ) return 0; if( iDb==1 ) return 1; p = db->aDb[iDb].pBt; assert( p!=0 ); Index: src/build.c ================================================================== --- src/build.c +++ src/build.c @@ -189,11 +189,11 @@ VdbeComment((v, "usesStmtJournal=%d", pParse->mayAbort && pParse->isMultiWrite)); } #ifndef SQLITE_OMIT_VIRTUALTABLE for(i=0; inVtabLock; i++){ - char *vtab = (char *)sqlite3GetVTable(db, pParse->apVtabLock[i]); + char *vtab = (char *)pParse->apVtabLock[i]; sqlite3VdbeAddOp4(v, OP_VBegin, 0, 0, 0, vtab, P4_VTAB); } pParse->nVtabLock = 0; #endif @@ -264,11 +264,13 @@ } pParse->nested++; memcpy(saveBuf, PARSE_TAIL(pParse), PARSE_TAIL_SZ); memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ); sqlite3RunParser(pParse, zSql, &zErrMsg); - sqlite3DbFree(db, zErrMsg); + if( zErrMsg ){ + sqlite3ErrorMsg(pParse, "%z", zErrMsg); + } sqlite3DbFree(db, zSql); memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ); pParse->nested--; } @@ -393,13 +395,18 @@ Parse *pParse, u32 flags, struct SrcList_item *p ){ const char *zDb; - assert( p->pSchema==0 || p->zDatabase==0 ); - if( p->pSchema ){ - int iDb = sqlite3SchemaToIndex(pParse->db, p->pSchema); + if( pParse->iFixDb && pParse->iFixDb!=2 ){ + zDb = pParse->db->aDb[pParse->iFixDb-1].zDbSName; + assert( p->zDatabase==0 || sqlite3StrICmp(p->zDatabase, zDb)==0 ); + if( p->zDatabase==0 ){ + p->zDatabase = sqlite3DbStrDup(pParse->db, zDb); + } + }else if( p->pSchema ){ + int iDb = sqlite3SchemaToIndex(pParse->db, p->pSchema, 0); zDb = pParse->db->aDb[iDb].zDbSName; }else{ zDb = p->zDatabase; } return sqlite3LocateTable(pParse, flags, p->zName, zDb); @@ -1892,10 +1899,36 @@ }else{ pPk->nColumn = pTab->nCol; } recomputeColumnsNotIndexed(pPk); } + +/* +** Return true if zName is a shadow table name in the current database +** connection. +** +** zName is temporarily modified while this routine is running, but is +** restored to its original value prior to this routine returning. +*/ +static int isShadowTableName(sqlite3 *db, char *zName){ + char *zTail; /* Pointer to the last "_" in zName */ + Table *pTab; /* Table that zName is a shadow of */ + Module *pMod; /* Module for the virtual table */ + + zTail = strrchr(zName, '_'); + if( zTail==0 ) return 0; + *zTail = 0; + pTab = sqlite3FindTable(db, zName, 0); + *zTail = '_'; + if( pTab==0 ) return 0; + if( !IsVirtual(pTab) ) return 0; + pMod = (Module*)sqlite3HashFind(&db->aModule, pTab->azModuleArg[0]); + if( pMod==0 ) return 0; + if( pMod->pModule->iVersion<3 ) return 0; + if( pMod->pModule->xShadowName==0 ) return 0; + return pMod->pModule->xShadowName(zTail+1); +} /* ** This routine is called to report the final ")" that terminates ** a CREATE TABLE statement. ** @@ -1931,10 +1964,14 @@ return; } assert( !db->mallocFailed ); p = pParse->pNewTable; if( p==0 ) return; + + if( pSelect==0 && isShadowTableName(db, p->zName) ){ + p->tabFlags |= TF_Shadow; + } /* If the db->init.busy is 1 it means we are reading the SQL off the ** "sqlite_master" or "sqlite_temp_master" table on the disk. ** So do not write to the disk again. Extract the root page number ** for the table from the db->init.newTnum field. (The page number @@ -1965,11 +2002,11 @@ p->tabFlags |= TF_WithoutRowid | TF_NoVisibleRowid; convertToWithoutRowidTable(pParse, p); } } - iDb = sqlite3SchemaToIndex(db, p->pSchema); + iDb = sqlite3SchemaToIndex(db, p->pSchema, 0); #ifndef SQLITE_OMIT_CHECK /* Resolve names in all CHECK constraint expressions. */ if( p->pCheck ){ @@ -2185,11 +2222,11 @@ } sqlite3StartTable(pParse, pName1, pName2, isTemp, 1, 0, noErr); p = pParse->pNewTable; if( p==0 || pParse->nErr ) goto create_view_fail; sqlite3TwoPartName(pParse, pName1, pName2, &pName); - iDb = sqlite3SchemaToIndex(db, p->pSchema); + iDb = sqlite3SchemaToIndex(db, p->pSchema, 0); sqlite3FixInit(&sFix, pParse, iDb, "view", pName); if( sqlite3FixSelect(&sFix, pSelect) ) goto create_view_fail; /* Make a copy of the entire SELECT statement that defines the view. ** This will force all the Expr.token.z values to be dynamically @@ -2238,11 +2275,11 @@ /* ** The Table structure pTable is really a VIEW. Fill in the names of ** the columns of the view in the pTable structure. Return the number ** of errors. If an error is seen leave an error message in pParse->zErrMsg. */ -int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ +int sqlite3ViewGetColumnNames(Parse *pParse, int iDb, Table *pTable){ Table *pSelTab; /* A fake table from which we get the result set */ Select *pSel; /* Copy of the SELECT that implements the view */ int nErr = 0; /* Number of errors encountered */ int n; /* Temporarily holds the number of cursors assigned */ sqlite3 *db = pParse->db; /* Database connection for malloc errors */ @@ -2255,11 +2292,11 @@ assert( pTable ); #ifndef SQLITE_OMIT_VIRTUALTABLE db->nSchemaLock++; - rc = sqlite3VtabCallConnect(pParse, pTable); + rc = sqlite3VtabCallConnect(pParse, iDb, pTable); db->nSchemaLock--; if( rc ){ return 1; } if( IsVirtual(pTable) ) return 0; @@ -2502,11 +2539,11 @@ } } if( iLargest==0 ){ return; }else{ - int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema, 0); assert( iDb>=0 && iDbdb->nDb ); destroyRootPage(pParse, iLargest, iDb); iDestroyed = iLargest; } } @@ -2629,18 +2666,18 @@ if( pTab==0 ){ if( noErr ) sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase); goto exit_drop_table; } - iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + iDb = sqlite3SchemaToIndex(db, pTab->pSchema, 0); assert( iDb>=0 && iDbnDb ); sqlite3SchemaWritable(pParse, iDb); /* If pTab is a virtual table, call ViewGetColumnNames() to ensure ** it is initialized. */ - if( IsVirtual(pTab) && sqlite3ViewGetColumnNames(pParse, pTab) ){ + if( IsVirtual(pTab) && sqlite3ViewGetColumnNames(pParse, iDb, pTab) ){ goto exit_drop_table; } #ifndef SQLITE_OMIT_AUTHORIZATION { int code; @@ -2657,11 +2694,11 @@ code = SQLITE_DROP_VIEW; } #ifndef SQLITE_OMIT_VIRTUALTABLE }else if( IsVirtual(pTab) ){ code = SQLITE_DROP_VTABLE; - zArg2 = sqlite3GetVTable(db, pTab)->pMod->zName; + zArg2 = sqlite3GetVTable(db, -1, pTab)->pMod->zName; #endif }else{ if( !OMIT_TEMPDB && iDb==1 ){ code = SQLITE_DROP_TEMP_TABLE; }else{ @@ -2878,11 +2915,16 @@ ** created. The register specified by memRootPage contains the ** root page number of the index. If memRootPage is negative, then ** the index already exists and must be cleared before being refilled and ** the root page number of the index is taken from pIndex->tnum. */ -static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ +static void sqlite3RefillIndex( + Parse *pParse, + int iDb, + Index *pIndex, + int memRootPage +){ Table *pTab = pIndex->pTable; /* The table that is indexed */ int iTab = pParse->nTab++; /* Btree cursor used for pTab */ int iIdx = pParse->nTab++; /* Btree cursor used for pIndex */ int iSorter; /* Cursor opened by OpenSorter (if in use) */ int addr1; /* Address of top of loop */ @@ -2891,11 +2933,10 @@ int iPartIdxLabel; /* Jump to this label to skip a row */ Vdbe *v; /* Generate code into this virtual machine */ KeyInfo *pKey; /* KeyInfo for index */ int regRecord; /* Register holding assembled index record */ sqlite3 *db = pParse->db; /* The database connection */ - int iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); #ifndef SQLITE_OMIT_AUTHORIZATION if( sqlite3AuthCheck(pParse, SQLITE_REINDEX, pIndex->zName, 0, db->aDb[iDb].zDbSName ) ){ return; @@ -3025,11 +3066,10 @@ Table *pTab = 0; /* Table to be indexed */ Index *pIndex = 0; /* The index to be created */ char *zName = 0; /* Name of the index */ int nName; /* Number of characters in zName */ int i, j; - DbFixer sFix; /* For assigning database names to pTable */ int sortOrderMask; /* 1 to honor DESC in index. 0 to ignore. */ sqlite3 *db = pParse->db; Db *pDb; /* The specific table containing the indexed database */ int iDb; /* Index of the database that is being written */ Token *pName = 0; /* Unqualified name of the index to create */ @@ -3074,17 +3114,13 @@ iDb = 1; } } #endif - sqlite3FixInit(&sFix, pParse, iDb, "index", pName); - if( sqlite3FixSrcList(&sFix, pTblName) ){ - /* Because the parser constructs pTblName from a single identifier, - ** sqlite3FixSrcList can never fail. */ - assert(0); - } + if( iDb!=1 ) pTblName->a[0].zDatabase = db->aDb[iDb].zDbSName; pTab = sqlite3LocateTableItem(pParse, 0, &pTblName->a[0]); + pTblName->a[0].zDatabase = 0; assert( db->mallocFailed==0 || pTab==0 ); if( pTab==0 ) goto exit_create_index; if( iDb==1 && db->aDb[iDb].pSchema!=pTab->pSchema ){ sqlite3ErrorMsg(pParse, "cannot create a TEMP index on non-TEMP table \"%s\"", @@ -3095,11 +3131,11 @@ }else{ assert( pName==0 ); assert( pStart==0 ); pTab = pParse->pNewTable; if( !pTab ) goto exit_create_index; - iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + iDb = sqlite3SchemaToIndex(db, pTab->pSchema, 0); } pDb = &db->aDb[iDb]; assert( pTab!=0 ); assert( pParse->nErr==0 ); @@ -3518,11 +3554,11 @@ /* Fill the index with data and reparse the schema. Code an OP_Expire ** to invalidate all pre-compiled statements. */ if( pTblName ){ - sqlite3RefillIndex(pParse, pIndex, iMem); + sqlite3RefillIndex(pParse, iDb, pIndex, iMem); sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddParseSchemaOp(pParse, iDb, sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName)); sqlite3VdbeAddOp2(v, OP_Expire, 0, 1); } @@ -3533,11 +3569,11 @@ /* When adding an index to the list of indices for a table, make ** sure all indices labeled OE_Replace come after all those labeled ** OE_Ignore. This is necessary for the correct constraint check ** processing (in sqlite3GenerateConstraintChecks()) as part of - ** UPDATE and INSERT statements. + ** UPDATE and INSERT statements. */ if( db->init.busy || pTblName==0 ){ if( onError!=OE_Replace || pTab->pIndex==0 || pTab->pIndex->onError==OE_Replace){ pIndex->pNext = pTab->pIndex; @@ -3644,11 +3680,11 @@ if( pIndex->idxType!=SQLITE_IDXTYPE_APPDEF ){ sqlite3ErrorMsg(pParse, "index associated with UNIQUE " "or PRIMARY KEY constraint cannot be dropped", 0); goto exit_drop_index; } - iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); + iDb = sqlite3SchemaToIndex(db, pIndex->pSchema, 0); sqlite3SchemaWritable(pParse, iDb); #ifndef SQLITE_OMIT_AUTHORIZATION { int code = SQLITE_DROP_INDEX; Table *pTab = pIndex->pTable; @@ -4378,18 +4414,22 @@ /* ** Recompute all indices of pTab that use the collating sequence pColl. ** If pColl==0 then recompute all indices of pTab. */ #ifndef SQLITE_OMIT_REINDEX -static void reindexTable(Parse *pParse, Table *pTab, char const *zColl){ +static void reindexTable( + Parse *pParse, + int iDb, + Table *pTab, + char const *zColl +){ Index *pIndex; /* An index associated with pTab */ for(pIndex=pTab->pIndex; pIndex; pIndex=pIndex->pNext){ if( zColl==0 || collationMatch(zColl, pIndex) ){ - int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); sqlite3BeginWriteOperation(pParse, 0, iDb); - sqlite3RefillIndex(pParse, pIndex, -1); + sqlite3RefillIndex(pParse, iDb, pIndex, -1); } } } #endif @@ -4409,11 +4449,11 @@ assert( sqlite3BtreeHoldsAllMutexes(db) ); /* Needed for schema access */ for(iDb=0, pDb=db->aDb; iDbnDb; iDb++, pDb++){ assert( pDb!=0 ); for(k=sqliteHashFirst(&pDb->pSchema->tblHash); k; k=sqliteHashNext(k)){ pTab = (Table*)sqliteHashData(k); - reindexTable(pParse, pTab, zColl); + reindexTable(pParse, iDb, pTab, zColl); } } } #endif @@ -4468,19 +4508,19 @@ z = sqlite3NameFromToken(db, pObjName); if( z==0 ) return; zDb = db->aDb[iDb].zDbSName; pTab = sqlite3FindTable(db, z, zDb); if( pTab ){ - reindexTable(pParse, pTab, 0); + reindexTable(pParse, iDb, pTab, 0); sqlite3DbFree(db, z); return; } pIndex = sqlite3FindIndex(db, z, zDb); sqlite3DbFree(db, z); if( pIndex ){ sqlite3BeginWriteOperation(pParse, 0, iDb); - sqlite3RefillIndex(pParse, pIndex, -1); + sqlite3RefillIndex(pParse, iDb, pIndex, -1); return; } sqlite3ErrorMsg(pParse, "unable to identify the object to be reindexed"); } #endif Index: src/callback.c ================================================================== --- src/callback.c +++ src/callback.c @@ -553,15 +553,15 @@ void sqlite3SchemaReuse(sqlite3 *db, int iDb){ Schema *pSchema = db->aDb[iDb].pSchema; Schema *p; assert( pSchema && iDb!=1 ); - sqlite3_mutex_enter( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + sqlite3_mutex_enter( sqlite3_mutex_alloc(SQLITE_MUTEX_SCHEMA_REUSE) ); for(p=sharedSchemaList; p; p=p->pNext){ if( p->cksum==pSchema->cksum - && p->schema_cookie==pSchema->schema_cookie - ){ + && p->schema_cookie==pSchema->schema_cookie + ){ break; } } if( !p ){ /* No matching schema was found. */ @@ -569,11 +569,11 @@ sharedSchemaList = pSchema; }else{ /* Found a matching schema. Increase its ref count. */ p->nRef++; } - sqlite3_mutex_leave( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER) ); + sqlite3_mutex_leave( sqlite3_mutex_alloc(SQLITE_MUTEX_SCHEMA_REUSE) ); /* If a matching schema was found in the shared schema list, free the ** schema object just parsed, and add a pointer to the matching schema ** to the db handle. */ if( p ){ Index: src/dbpage.c ================================================================== --- src/dbpage.c +++ src/dbpage.c @@ -117,13 +117,12 @@ for(i=0; inConstraint; i++){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i]; if( p->iColumn!=DBPAGE_COLUMN_SCHEMA ) continue; if( p->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; if( !p->usable ){ - /* No solution. Use the default SQLITE_BIG_DBL cost */ - pIdxInfo->estimatedRows = 0x7fffffff; - return SQLITE_OK; + /* No solution. */ + return SQLITE_CONSTRAINT; } iPlan = 2; pIdxInfo->aConstraintUsage[i].argvIndex = 1; pIdxInfo->aConstraintUsage[i].omit = 1; break; @@ -405,11 +404,12 @@ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ + 0 /* xShadowName */ }; return sqlite3_create_module(db, "sqlite_dbpage", &dbpage_module, 0); } #elif defined(SQLITE_ENABLE_DBPAGE_VTAB) int sqlite3DbpageRegister(sqlite3 *db){ return SQLITE_OK; } #endif /* SQLITE_ENABLE_DBSTAT_VTAB */ Index: src/dbstat.c ================================================================== --- src/dbstat.c +++ src/dbstat.c @@ -192,21 +192,19 @@ ** idxNum is normally 0, but will be 1 if a schema=? constraint exists. */ static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int i; - pIdxInfo->estimatedCost = 1.0e6; /* Initial cost estimate */ - /* Look for a valid schema=? constraint. If found, change the idxNum to ** 1 and request the value of that constraint be sent to xFilter. And ** lower the cost estimate to encourage the constrained version to be ** used. */ for(i=0; inConstraint; i++){ - if( pIdxInfo->aConstraint[i].usable==0 ) continue; - if( pIdxInfo->aConstraint[i].op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; if( pIdxInfo->aConstraint[i].iColumn!=10 ) continue; + if( pIdxInfo->aConstraint[i].usable==0 ) return SQLITE_CONSTRAINT; + if( pIdxInfo->aConstraint[i].op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; pIdxInfo->idxNum = 1; pIdxInfo->estimatedCost = 1.0; pIdxInfo->aConstraintUsage[i].argvIndex = 1; pIdxInfo->aConstraintUsage[i].omit = 1; break; @@ -395,10 +393,11 @@ assert( nPayload>=(u32)nLocal ); assert( nLocal<=(nUsable-35) ); if( nPayload>(u32)nLocal ){ int j; int nOvfl = ((nPayload - nLocal) + nUsable-4 - 1) / (nUsable - 4); + if( iOff+nLocal>nUsable ) goto statPageIsCorrupt; pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4); pCell->nOvfl = nOvfl; pCell->aOvfl = sqlite3_malloc64(sizeof(u32)*nOvfl); if( pCell->aOvfl==0 ) return SQLITE_NOMEM_BKPT; pCell->aOvfl[0] = sqlite3Get4byte(&aData[iOff+nLocal]); @@ -718,11 +717,12 @@ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ + 0 /* xShadowName */ }; return sqlite3_create_module(db, "dbstat", &dbstat_module, 0); } #elif defined(SQLITE_ENABLE_DBSTAT_VTAB) int sqlite3DbstatRegister(sqlite3 *db){ return SQLITE_OK; } #endif /* SQLITE_ENABLE_DBSTAT_VTAB */ Index: src/delete.c ================================================================== --- src/delete.c +++ src/delete.c @@ -41,37 +41,52 @@ if( sqlite3IndexedByLookup(pParse, pItem) ){ pTab = 0; } return pTab; } + +/* Return true if table pTab is read-only. +** +** A table is read-only if any of the following are true: +** +** 1) It is a virtual table and no implementation of the xUpdate method +** has been provided +** +** 2) It is a system table (i.e. sqlite_master), this call is not +** part of a nested parse and writable_schema pragma has not +** been specified +** +** 3) The table is a shadow table, the database connection is in +** defensive mode, and the current sqlite3_prepare() +** is for a top-level SQL statement. +*/ +static int tabIsReadOnly(Parse *pParse, Table *pTab){ + sqlite3 *db; + if( IsVirtual(pTab) ){ + return sqlite3GetVTable(pParse->db, -1, pTab)->pMod->pModule->xUpdate==0; + } + if( (pTab->tabFlags & (TF_Readonly|TF_Shadow))==0 ) return 0; + db = pParse->db; + if( (pTab->tabFlags & TF_Readonly)!=0 ){ + return sqlite3WritableSchema(db)==0 && pParse->nested==0; + } + assert( pTab->tabFlags & TF_Shadow ); + return (db->flags & SQLITE_Defensive)!=0 + && db->nVdbeExec==0 + && db->pVtabCtx==0; +} /* ** Check to make sure the given table is writable. If it is not ** writable, generate an error message and return 1. If it is ** writable return 0; */ int sqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){ - /* A table is not writable under the following circumstances: - ** - ** 1) It is a virtual table and no implementation of the xUpdate method - ** has been provided, or - ** 2) It is a system table (i.e. sqlite_master), this call is not - ** part of a nested parse and writable_schema pragma has not - ** been specified. - ** - ** In either case leave an error message in pParse and return non-zero. - */ - if( ( IsVirtual(pTab) - && sqlite3GetVTable(pParse->db, pTab)->pMod->pModule->xUpdate==0 ) - || ( (pTab->tabFlags & TF_Readonly)!=0 - && sqlite3WritableSchema(pParse->db)==0 - && pParse->nested==0) - ){ + if( tabIsReadOnly(pParse, pTab) ){ sqlite3ErrorMsg(pParse, "table %s may not be modified", pTab->zName); return 1; } - #ifndef SQLITE_OMIT_VIEW if( !viewOk && pTab->pSelect ){ sqlite3ErrorMsg(pParse,"cannot modify %s because it is a view",pTab->zName); return 1; } @@ -86,10 +101,11 @@ ** pWhere argument is an optional WHERE clause that restricts the ** set of rows in the view that are to be added to the ephemeral table. */ void sqlite3MaterializeView( Parse *pParse, /* Parsing context */ + int iDb, /* Database in which view resides */ Table *pView, /* View definition */ Expr *pWhere, /* Optional WHERE clause to be added */ ExprList *pOrderBy, /* Optional ORDER BY clause */ Expr *pLimit, /* Optional LIMIT clause */ int iCur /* Cursor number for ephemeral table */ @@ -96,11 +112,10 @@ ){ SelectDest dest; Select *pSel; SrcList *pFrom; sqlite3 *db = pParse->db; - int iDb = sqlite3SchemaToIndex(db, pView->pSchema); pWhere = sqlite3ExprDup(db, pWhere, 0); pFrom = sqlite3SrcListAppend(db, 0, 0, 0); if( pFrom ){ assert( pFrom->nSrc==1 ); pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName); @@ -300,22 +315,22 @@ ); pOrderBy = 0; pLimit = 0; } #endif + + iDb = sqlite3SchemaToIndex(db, pTab->pSchema, pTabList->a[0].zDatabase); + assert( iDbnDb ); /* If pTab is really a view, make sure it has been initialized. */ - if( sqlite3ViewGetColumnNames(pParse, pTab) ){ + if( sqlite3ViewGetColumnNames(pParse, iDb, pTab) ){ goto delete_from_cleanup; } - if( sqlite3IsReadOnly(pParse, pTab, (pTrigger?1:0)) ){ goto delete_from_cleanup; } - iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - assert( iDbnDb ); rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, db->aDb[iDb].zDbSName); assert( rcauth==SQLITE_OK || rcauth==SQLITE_DENY || rcauth==SQLITE_IGNORE ); if( rcauth==SQLITE_DENY ){ goto delete_from_cleanup; @@ -348,13 +363,11 @@ /* If we are trying to delete from a view, realize that view into ** an ephemeral table. */ #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) if( isView ){ - sqlite3MaterializeView(pParse, pTab, - pWhere, pOrderBy, pLimit, iTabCur - ); + sqlite3MaterializeView(pParse, iDb, pTab, pWhere, pOrderBy, pLimit,iTabCur); iDataCur = iIdxCur = iTabCur; pOrderBy = 0; pLimit = 0; } #endif @@ -514,12 +527,12 @@ int iAddrOnce = 0; if( eOnePass==ONEPASS_MULTI ){ iAddrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } testcase( IsVirtual(pTab) ); - sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, OPFLAG_FORDELETE, - iTabCur, aToOpen, &iDataCur, &iIdxCur); + sqlite3OpenTableAndIndices(pParse, iDb, pTab, OP_OpenWrite, + OPFLAG_FORDELETE, iTabCur, aToOpen, &iDataCur, &iIdxCur); assert( pPk || IsVirtual(pTab) || iDataCur==iTabCur ); assert( pPk || IsVirtual(pTab) || iIdxCur==iDataCur+1 ); if( eOnePass==ONEPASS_MULTI ) sqlite3VdbeJumpHere(v, iAddrOnce); } @@ -548,12 +561,12 @@ } /* Delete the row */ #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pTab) ){ - const char *pVTab = (const char *)sqlite3GetVTable(db, pTab); - sqlite3VtabMakeWritable(pParse, pTab); + const char *pVTab = (const char *)sqlite3GetVTable(db, iDb, pTab); + sqlite3VtabMakeWritable(pParse, iDb, pTab); assert( eOnePass==ONEPASS_OFF || eOnePass==ONEPASS_SINGLE ); sqlite3MayAbort(pParse); if( eOnePass==ONEPASS_SINGLE ){ sqlite3VdbeAddOp1(v, OP_Close, iTabCur); if( sqlite3IsToplevel(pParse) ){ @@ -564,11 +577,11 @@ sqlite3VdbeChangeP5(v, OE_Abort); }else #endif { int count = (pParse->nested==0); /* True to count changes */ - sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, + sqlite3GenerateRowDelete(pParse, iDb, pTab, pTrigger, iDataCur, iIdxCur, iKey, nKey, count, OE_Default, eOnePass, aiCurOnePass[1]); } /* End of the loop over all rowids/primary-keys. */ if( eOnePass!=ONEPASS_OFF ){ @@ -663,10 +676,11 @@ ** Except, this optimization is disabled if there are BEFORE triggers since ** the trigger body might have moved the cursor. */ void sqlite3GenerateRowDelete( Parse *pParse, /* Parsing context */ + int iDb, /* Database containing pTab */ Table *pTab, /* Table containing the row to be deleted */ Trigger *pTrigger, /* List of triggers to (potentially) fire */ int iDataCur, /* Cursor from which column data is extracted */ int iIdxCur, /* First index cursor */ int iPk, /* First memory cell containing the PRIMARY KEY */ @@ -705,11 +719,11 @@ int addrStart; /* Start of BEFORE trigger programs */ /* TODO: Could use temporary registers here. Also could attempt to ** avoid copying the contents of the rowid register. */ mask = sqlite3TriggerColmask( - pParse, pTrigger, 0, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onconf + pParse, iDb, pTrigger, 0, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onconf ); mask |= sqlite3FkOldmask(pParse, pTab); iOld = pParse->nMem+1; pParse->nMem += (1 + pTab->nCol); @@ -724,11 +738,11 @@ } } /* Invoke BEFORE DELETE trigger programs. */ addrStart = sqlite3VdbeCurrentAddr(v); - sqlite3CodeRowTrigger(pParse, pTrigger, + sqlite3CodeRowTrigger(pParse, iDb, pTrigger, TK_DELETE, 0, TRIGGER_BEFORE, pTab, iOld, onconf, iLabel ); /* If any BEFORE triggers were coded, then seek the cursor to the ** row to be deleted again. It may be that the BEFORE triggers moved @@ -747,11 +761,11 @@ } /* Do FK processing. This call checks that any FK constraints that ** refer to this table (i.e. constraints attached to other tables) ** are not violated by deleting this row. */ - sqlite3FkCheck(pParse, pTab, iOld, 0, 0, 0); + sqlite3FkCheck(pParse, iDb, pTab, iOld, 0, 0, 0); } /* Delete the index and table entries. Skip this step if pTab is really ** a view (in which case the only effect of the DELETE statement is to ** fire the INSTEAD OF triggers). @@ -780,14 +794,14 @@ } /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to ** handle rows (possibly in other tables) that refer via a foreign key ** to the row just deleted. */ - sqlite3FkActions(pParse, pTab, 0, iOld, 0, 0); + sqlite3FkActions(pParse, iDb, pTab, 0, iOld, 0, 0); /* Invoke AFTER DELETE trigger programs. */ - sqlite3CodeRowTrigger(pParse, pTrigger, + sqlite3CodeRowTrigger(pParse, iDb, pTrigger, TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf, iLabel ); /* Jump here if the row had already been deleted before any BEFORE ** trigger programs were invoked. Or if a trigger program throws a Index: src/expr.c ================================================================== --- src/expr.c +++ src/expr.c @@ -2367,11 +2367,11 @@ assert( p->pEList->a[0].pExpr!=0 ); /* Because of isCandidateForInOpt(p) */ assert( p->pSrc!=0 ); /* Because of isCandidateForInOpt(p) */ pTab = p->pSrc->a[0].pTab; /* Code an OP_Transaction and OP_TableLock for . */ - iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + iDb = sqlite3SchemaToIndex(db, pTab->pSchema, p->pSrc->a[0].zDatabase); sqlite3CodeVerifySchema(pParse, iDb); sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); assert(v); /* sqlite3GetVdbe() has always been previously called */ if( nExpr==1 && pEList->a[0].pExpr->iColumn<0 ){ Index: src/fkey.c ================================================================== --- src/fkey.c +++ src/fkey.c @@ -860,29 +860,27 @@ ** described for DELETE. Then again after the original record is deleted ** but before the new record is inserted using the INSERT convention. */ void sqlite3FkCheck( Parse *pParse, /* Parse context */ + int iDb, /* Database containing pTab */ Table *pTab, /* Row is being deleted from this table */ int regOld, /* Previous row data is stored here */ int regNew, /* New row data is stored here */ int *aChange, /* Array indicating UPDATEd columns (or 0) */ int bChngRowid /* True if rowid is UPDATEd */ ){ sqlite3 *db = pParse->db; /* Database handle */ FKey *pFKey; /* Used to iterate through FKs */ - int iDb; /* Index of database containing pTab */ const char *zDb; /* Name of database containing pTab */ int isIgnoreErrors = pParse->disableTriggers; /* Exactly one of regOld and regNew should be non-zero. */ assert( (regOld==0)!=(regNew==0) ); /* If foreign-keys are disabled, this function is a no-op. */ if( (db->flags&SQLITE_ForeignKeys)==0 ) return; - - iDb = sqlite3SchemaToIndex(db, pTab->pSchema); zDb = db->aDb[iDb].zDbSName; /* Loop through all the foreign key constraints for which pTab is the ** child table (the table that the foreign key definition is part of). */ for(pFKey=pTab->pFKey; pFKey; pFKey=pFKey->pNextFrom){ @@ -1357,10 +1355,11 @@ ** This function is called when deleting or updating a row to implement ** any required CASCADE, SET NULL or SET DEFAULT actions. */ void sqlite3FkActions( Parse *pParse, /* Parse context */ + int iDb, /* Database in which pTab resides */ Table *pTab, /* Table being updated or deleted from */ ExprList *pChanges, /* Change-list for UPDATE, NULL for DELETE */ int regOld, /* Address of array containing old row */ int *aChange, /* Array indicating UPDATEd columns (or 0) */ int bChngRowid /* True if rowid is UPDATEd */ @@ -1373,11 +1372,11 @@ FKey *pFKey; /* Iterator variable */ for(pFKey = sqlite3FkReferences(pTab); pFKey; pFKey=pFKey->pNextTo){ if( aChange==0 || fkParentIsModified(pTab, pFKey, aChange, bChngRowid) ){ Trigger *pAct = fkActionTrigger(pParse, pTab, pFKey, pChanges); if( pAct ){ - sqlite3CodeRowTriggerDirect(pParse, pAct, pTab, regOld, OE_Abort, 0); + sqlite3CodeRowTriggerDirect(pParse, iDb, pAct,pTab,regOld,OE_Abort,0); } } } } } Index: src/insert.c ================================================================== --- src/insert.c +++ src/insert.c @@ -166,11 +166,11 @@ static int readsTable(Parse *p, int iDb, Table *pTab){ Vdbe *v = sqlite3GetVdbe(p); int i; int iEnd = sqlite3VdbeCurrentAddr(v); #ifndef SQLITE_OMIT_VIRTUALTABLE - VTable *pVTab = IsVirtual(pTab) ? sqlite3GetVTable(p->db, pTab) : 0; + VTable *pVTab = IsVirtual(pTab) ? sqlite3GetVTable(p->db, iDb, pTab) : 0; #endif for(i=1; inSrc==1 ); pTab = sqlite3SrcListLookup(pParse, pTabList); if( pTab==0 ){ goto insert_cleanup; } - iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + iDb = sqlite3SchemaToIndex(db, pTab->pSchema, pTabList->a[0].zDatabase); assert( iDbnDb ); if( sqlite3AuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, db->aDb[iDb].zDbSName) ){ goto insert_cleanup; } @@ -594,11 +594,11 @@ assert( (pTrigger && tmask) || (pTrigger==0 && tmask==0) ); /* If pTab is really a view, make sure it has been initialized. ** ViewGetColumnNames() is a no-op if pTab is not a view. */ - if( sqlite3ViewGetColumnNames(pParse, pTab) ){ + if( sqlite3ViewGetColumnNames(pParse, iDb, pTab) ){ goto insert_cleanup; } /* Cannot insert into a read-only table. */ @@ -809,11 +809,11 @@ } /* If this is not a view, open the table and and all indices */ if( !isView ){ int nIdx; - nIdx = sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, -1, 0, + nIdx = sqlite3OpenTableAndIndices(pParse, iDb, pTab, OP_OpenWrite, 0, -1, 0, &iDataCur, &iIdxCur); aRegIdx = sqlite3DbMallocRawNN(db, sizeof(int)*(nIdx+1)); if( aRegIdx==0 ){ goto insert_cleanup; } @@ -925,11 +925,11 @@ if( !isView ){ sqlite3TableAffinity(v, pTab, regCols+1); } /* Fire BEFORE or INSTEAD OF triggers */ - sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_BEFORE, + sqlite3CodeRowTrigger(pParse, iDb, pTrigger, TK_INSERT, 0, TRIGGER_BEFORE, pTab, regCols-pTab->nCol-1, onError, endOfLoop); sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol+1); } @@ -1025,24 +1025,25 @@ /* Generate code to check constraints and generate index keys and ** do the insertion. */ #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pTab) ){ - const char *pVTab = (const char *)sqlite3GetVTable(db, pTab); - sqlite3VtabMakeWritable(pParse, pTab); + const char *pVTab = (const char *)sqlite3GetVTable(db, iDb, pTab); + sqlite3VtabMakeWritable(pParse, iDb, pTab); sqlite3VdbeAddOp4(v, OP_VUpdate, 1, pTab->nCol+2, regIns, pVTab, P4_VTAB); sqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError); sqlite3MayAbort(pParse); }else #endif { int isReplace; /* Set to true if constraints may cause a replace */ int bUseSeek; /* True to use OPFLAG_SEEKRESULT */ - sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, - regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0, pUpsert + sqlite3GenerateConstraintChecks(pParse, iDb, pTab, aRegIdx, iDataCur, + iIdxCur, regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, + 0, pUpsert ); - sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0); + sqlite3FkCheck(pParse, iDb, pTab, 0, regIns, 0, 0); /* Set the OPFLAG_USESEEKRESULT flag if either (a) there are no REPLACE ** constraints or (b) there are no triggers and this table is not a ** parent table in a foreign key constraint. It is safe to set the ** flag in the second case as if any REPLACE constraint is hit, an @@ -1065,11 +1066,11 @@ sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1); } if( pTrigger ){ /* Code AFTER triggers */ - sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_AFTER, + sqlite3CodeRowTrigger(pParse, iDb, pTrigger, TK_INSERT, 0, TRIGGER_AFTER, pTab, regData-2-pTab->nCol, onError, endOfLoop); } /* The bottom of the main insertion loop, if the data source ** is a SELECT statement. @@ -1272,10 +1273,11 @@ ** is used. Or if pParse->onError==OE_Default then the onError value ** for the constraint is used. */ void sqlite3GenerateConstraintChecks( Parse *pParse, /* The parser context */ + int iDb, /* Databse that contains pTab */ Table *pTab, /* The table being inserted or updated */ int *aRegIdx, /* Use register aRegIdx[i] for index i. 0 for unused */ int iDataCur, /* Canonical data cursor (main table or PK index) */ int iIdxCur, /* First index cursor */ int regNewData, /* First register in a range holding values to insert */ @@ -1555,12 +1557,12 @@ if( db->flags&SQLITE_RecTriggers ){ pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); } if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){ sqlite3MultiWrite(pParse); - sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, - regNewData, 1, 0, OE_Replace, 1, -1); + sqlite3GenerateRowDelete(pParse, iDb, pTab, pTrigger, iDataCur, + iIdxCur, regNewData, 1, 0, OE_Replace, 1, -1); }else{ #ifdef SQLITE_ENABLE_PREUPDATE_HOOK assert( HasRowid(pTab) ); /* This OP_Delete opcode fires the pre-update-hook only. It does ** not modify the b-tree. It is more efficient to let the coming @@ -1804,11 +1806,11 @@ pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); } if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){ sqlite3MultiWrite(pParse); } - sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, + sqlite3GenerateRowDelete(pParse, iDb, pTab, pTrigger, iDataCur, iIdxCur, regR, nPkField, 0, OE_Replace, (pIdx==pPk ? ONEPASS_SINGLE : ONEPASS_OFF), iThisCur); seenReplace = 1; break; } @@ -1965,20 +1967,20 @@ ** If pTab is a virtual table, then this routine is a no-op and the ** *piDataCur and *piIdxCur values are left uninitialized. */ int sqlite3OpenTableAndIndices( Parse *pParse, /* Parsing context */ + int iDb, /* Database to open pTab in */ Table *pTab, /* Table to be opened */ int op, /* OP_OpenRead or OP_OpenWrite */ u8 p5, /* P5 value for OP_Open* opcodes (except on WITHOUT ROWID) */ int iBase, /* Use this for the table cursor, if there is one */ u8 *aToOpen, /* If not NULL: boolean for each table and index */ int *piDataCur, /* Write the database source cursor number here */ int *piIdxCur /* Write the first index cursor number here */ ){ int i; - int iDb; int iDataCur; Index *pIdx; Vdbe *v; assert( op==OP_OpenRead || op==OP_OpenWrite ); @@ -1987,11 +1989,10 @@ /* This routine is a no-op for virtual tables. Leave the output ** variables *piDataCur and *piIdxCur uninitialized so that valgrind ** can detect if they are used by mistake in the caller. */ return 0; } - iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); v = sqlite3GetVdbe(pParse); assert( v!=0 ); if( iBase<0 ) iBase = pParse->nTab; iDataCur = iBase++; if( piDataCur ) *piDataCur = iDataCur; @@ -2282,11 +2283,11 @@ ** table (tab1) is initially empty. */ #ifdef SQLITE_TEST sqlite3_xferopt_count++; #endif - iDbSrc = sqlite3SchemaToIndex(db, pSrc->pSchema); + iDbSrc = sqlite3SchemaToIndex(db, pSrc->pSchema, pItem->zDatabase); v = sqlite3GetVdbe(pParse); sqlite3CodeVerifySchema(pParse, iDbSrc); iSrc = pParse->nTab++; iDest = pParse->nTab++; regAutoinc = autoIncBegin(pParse, iDbDest, pDest); Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -3064,10 +3064,13 @@ #if defined(SQLITE_ENABLE_FTS3_TOKENIZER) | SQLITE_Fts3Tokenizer #endif #if defined(SQLITE_ENABLE_QPSG) | SQLITE_EnableQPSG +#endif +#if defined(SQLITE_DEFAULT_DEFENSIVE) + | SQLITE_Defensive #endif ; sqlite3HashInit(&db->aCollSeq); #ifndef SQLITE_OMIT_VIRTUALTABLE sqlite3HashInit(&db->aModule); Index: src/pragma.c ================================================================== --- src/pragma.c +++ src/pragma.c @@ -1086,15 +1086,16 @@ Table *pTab; pTab = sqlite3LocateTable(pParse, LOCATE_NOERR, zRight, zDb); if( pTab ){ int i, k; int nHidden = 0; + int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema, zDb); Column *pCol; Index *pPk = sqlite3PrimaryKeyIndex(pTab); pParse->nMem = 7; - sqlite3CodeVerifySchema(pParse, iDb); - sqlite3ViewGetColumnNames(pParse, pTab); + sqlite3CodeVerifySchema(pParse, iTabDb); + sqlite3ViewGetColumnNames(pParse, iTabDb, pTab); for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ int isHidden = IsHiddenColumn(pCol); if( isHidden && pPragma->iArg==0 ){ nHidden++; continue; @@ -1550,11 +1551,11 @@ int iDataCur, iIdxCur; int r1 = -1; if( pTab->tnum<1 ) continue; /* Skip VIEWs or VIRTUAL TABLEs */ pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); - sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, 0, + sqlite3OpenTableAndIndices(pParse, i, pTab, OP_OpenRead, 0, 1, 0, &iDataCur, &iIdxCur); /* reg[7] counts the number of entries in the table. ** reg[8+i] counts the number of entries in the i-th index */ sqlite3VdbeAddOp2(v, OP_Integer, 0, 7); @@ -2475,11 +2476,12 @@ 0, /* xRollback - rollback transaction */ 0, /* xFindFunction - function overloading */ 0, /* xRename - rename the table */ 0, /* xSavepoint */ 0, /* xRelease */ - 0 /* xRollbackTo */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ }; /* ** Check to see if zTabName is really the name of a pragma. If it is, ** then register an eponymous virtual table for that pragma and return Index: src/prepare.c ================================================================== --- src/prepare.c +++ src/prepare.c @@ -506,12 +506,22 @@ ** which database file in db->aDb[] the schema refers to. ** ** If the same database is attached more than once, the first ** attached database is returned. */ -int sqlite3SchemaToIndex(sqlite3 *db, Schema *pSchema){ - int i = -1000000; +int sqlite3SchemaToIndex(sqlite3 *db, Schema *pSchema, const char *zDb){ + int i; + + assert( sqlite3_mutex_held(db->mutex) ); + if( zDb ){ + int i; + for(i=0; inDb; i++){ + if( i==1 ) continue; + if( sqlite3StrICmp(zDb, db->aDb[i].zDbSName)==0 ) return i; + } + assert( sqlite3StrICmp(zDb, "temp")==0 ); + } /* If pSchema is NULL, then return -1000000. This happens when code in ** expr.c is trying to resolve a reference to a transient table (i.e. one ** created by a sub-select). In this case the return value of this ** function should never be used. @@ -519,19 +529,20 @@ ** We return -1000000 instead of the more usual -1 simply because using ** -1000000 as the incorrect index into db->aDb[] is much ** more likely to cause a segfault than -1 (of course there are assert() ** statements too, but it never hurts to play the odds). */ - assert( sqlite3_mutex_held(db->mutex) ); if( pSchema ){ for(i=0; 1; i++){ assert( inDb ); if( db->aDb[i].pSchema==pSchema ){ break; } } assert( i>=0 && inDb ); + }else{ + i = -1000000; } return i; } /* Index: src/resolve.c ================================================================== --- src/resolve.c +++ src/resolve.c @@ -194,10 +194,11 @@ NameContext *pTopNC = pNC; /* First namecontext in the list */ Schema *pSchema = 0; /* Schema of the expression */ int eNewExprOp = TK_COLUMN; /* New value for pExpr->op on success */ Table *pTab = 0; /* Table hold the row */ Column *pCol; /* A column of pTab */ + int iDb = -1; assert( pNC ); /* the name context cannot be NULL. */ assert( zCol ); /* The Z in X.Y.Z cannot be NULL */ assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); @@ -221,10 +222,11 @@ }else{ for(i=0; inDb; i++){ assert( db->aDb[i].zDbSName ); if( sqlite3StrICmp(db->aDb[i].zDbSName,zDb)==0 ){ pSchema = db->aDb[i].pSchema; + iDb = i; break; } } } } @@ -252,12 +254,13 @@ hit = 1; } } if( hit || zTab==0 ) continue; } - if( zDb && pTab->pSchema!=pSchema ){ - continue; + if( zDb && (pTab->pSchema || pSchema) ){ + int ii = sqlite3SchemaToIndex(db, pTab->pSchema, pItem->zDatabase); + if( ii!=iDb ) continue; } if( zTab ){ const char *zTabName = pItem->zAlias ? pItem->zAlias : pTab->zName; assert( zTabName!=0 ); if( sqlite3StrICmp(zTabName, zTab)!=0 ){ Index: src/select.c ================================================================== --- src/select.c +++ src/select.c @@ -1443,11 +1443,11 @@ #ifdef SQLITE_ENABLE_SORTER_REFERENCES /* Open any cursors needed for sorter-reference expressions */ for(i=0; inDefer; i++){ Table *pTab = pSort->aDefer[i].pTab; - int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema, 0); /* TODO */ sqlite3OpenTable(pParse, pSort->aDefer[i].iCsr, iDb, pTab, OP_OpenRead); nRefKey = MAX(nRefKey, pSort->aDefer[i].nKey); } #endif @@ -1711,11 +1711,12 @@ zOrigCol = pTab->aCol[iCol].zName; zType = sqlite3ColumnType(&pTab->aCol[iCol],0); } zOrigTab = pTab->zName; if( pNC->pParse && pTab->pSchema ){ - int iDb = sqlite3SchemaToIndex(pNC->pParse->db, pTab->pSchema); + /* TODO: Fix the following for REUSE schemas */ + int iDb = sqlite3SchemaToIndex(pNC->pParse->db, pTab->pSchema, 0); zOrigDb = pNC->pParse->db->aDb[iDb].zDbSName; } #else assert( iCol==XN_ROWID || (iCol>=0 && iColnCol) ); if( iCol<0 ){ @@ -4884,17 +4885,24 @@ if( !IsVirtual(pTab) && cannotBeFunction(pParse, pFrom) ){ return WRC_Abort; } #if !defined(SQLITE_OMIT_VIEW) || !defined (SQLITE_OMIT_VIRTUALTABLE) if( IsVirtual(pTab) || pTab->pSelect ){ + int iSave = pParse->iFixDb; i16 nCol; - if( sqlite3ViewGetColumnNames(pParse, pTab) ) return WRC_Abort; + int iDb = sqlite3SchemaToIndex(db, pTab->pSchema, pFrom->zDatabase); + pParse->iFixDb = 1 + iDb; + if( sqlite3ViewGetColumnNames(pParse, iDb, pTab) ){ + pParse->iFixDb = iSave; + return WRC_Abort; + } assert( pFrom->pSelect==0 ); pFrom->pSelect = sqlite3SelectDup(db, pTab->pSelect, 0); nCol = pTab->nCol; pTab->nCol = -1; sqlite3WalkSelect(pWalker, pFrom->pSelect); + pParse->iFixDb = iSave; pTab->nCol = nCol; } #endif } @@ -4982,11 +4990,11 @@ if( pSub==0 || (pSub->selFlags & SF_NestedFrom)==0 ){ pSub = 0; if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){ continue; } - iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + iDb = sqlite3SchemaToIndex(db, pTab->pSchema, pFrom->zDatabase); zSchemaName = iDb>=0 ? db->aDb[iDb].zDbSName : "*"; } for(j=0; jnCol; j++){ char *zName = pTab->aCol[j].zName; char *zColname; /* The computed column name */ @@ -6473,11 +6481,13 @@ ** OP_Count instruction is executed either on the intkey table that ** contains the data for table or on one of its indexes. It ** is better to execute the op on an index, as indexes are almost ** always spread across less pages than their corresponding tables. */ - const int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + const int iDb = sqlite3SchemaToIndex( + pParse->db, pTab->pSchema, pTabList->a[0].zDatabase + ); const int iCsr = pParse->nTab++; /* Cursor to scan b-tree */ Index *pIdx; /* Iterator variable */ KeyInfo *pKeyInfo = 0; /* Keyinfo for scanned index */ Index *pBest = 0; /* Best index found so far */ int iRoot = pTab->tnum; /* Root page of scanned b-tree */ Index: src/shell.c.in ================================================================== --- src/shell.c.in +++ src/shell.c.in @@ -1063,11 +1063,11 @@ #define SHELL_OPEN_NORMAL 1 /* Normal database file */ #define SHELL_OPEN_APPENDVFS 2 /* Use appendvfs */ #define SHELL_OPEN_ZIPFILE 3 /* Use the zipfile virtual table */ #define SHELL_OPEN_READONLY 4 /* Open a normal database read-only */ #define SHELL_OPEN_DESERIALIZE 5 /* Open using sqlite3_deserialize() */ -#define SHELL_OPEN_REUSESCHEMA 5 /* Open for schema reuse */ +#define SHELL_OPEN_REUSESCHEMA 6 /* Open for schema reuse */ /* ** These are the allowed shellFlgs values */ #define SHFLG_Pagecache 0x00000001 /* The --pagecache option is used */ @@ -8072,11 +8072,11 @@ ** the shell is compiled with SQLITE_OMIT_COMPLETE. The default assumes ** any arbitrary text is a complete SQL statement. This is not very ** user-friendly, but it does seem to work. */ #ifdef SQLITE_OMIT_COMPLETE -int sqlite3_complete(const char *zSql){ return 1; } +#define sqlite3_complete(x) 1 #endif /* ** Return true if zSql is a complete SQL statement. Return false if it ** ends in the middle of a string literal or C-style comment. Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -2017,10 +2017,11 @@ ** the call worked. ^The [sqlite3_db_config()] interface will return a ** non-zero [error code] if a discontinued or unsupported configuration option ** is invoked. ** **
+** [[SQLITE_DBCONFIG_LOOKASIDE]] **
SQLITE_DBCONFIG_LOOKASIDE
**
^This option takes three additional arguments that determine the ** [lookaside memory allocator] configuration for the [database connection]. ** ^The first argument (the third parameter to [sqlite3_db_config()] is a ** pointer to a memory buffer to use for lookaside memory. @@ -2039,10 +2040,11 @@ ** [sqlite3_db_status](D,[SQLITE_CONFIG_LOOKASIDE],...) is zero. ** Any attempt to change the lookaside memory configuration when lookaside ** memory is in use leaves the configuration unchanged and returns ** [SQLITE_BUSY].)^
** +** [[SQLITE_DBCONFIG_ENABLE_FKEY]] **
SQLITE_DBCONFIG_ENABLE_FKEY
**
^This option is used to enable or disable the enforcement of ** [foreign key constraints]. There should be two additional arguments. ** The first argument is an integer which is 0 to disable FK enforcement, ** positive to enable FK enforcement or negative to leave FK enforcement @@ -2049,10 +2051,11 @@ ** unchanged. The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether FK enforcement is off or on ** following this call. The second parameter may be a NULL pointer, in ** which case the FK enforcement setting is not reported back.
** +** [[SQLITE_DBCONFIG_ENABLE_TRIGGER]] **
SQLITE_DBCONFIG_ENABLE_TRIGGER
**
^This option is used to enable or disable [CREATE TRIGGER | triggers]. ** There should be two additional arguments. ** The first argument is an integer which is 0 to disable triggers, ** positive to enable triggers or negative to leave the setting unchanged. @@ -2059,10 +2062,11 @@ ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether triggers are disabled or enabled ** following this call. The second parameter may be a NULL pointer, in ** which case the trigger setting is not reported back.
** +** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] **
SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
**
^This option is used to enable or disable the two-argument ** version of the [fts3_tokenizer()] function which is part of the ** [FTS3] full-text search engine extension. ** There should be two additional arguments. @@ -2072,10 +2076,11 @@ ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether fts3_tokenizer is disabled or enabled ** following this call. The second parameter may be a NULL pointer, in ** which case the new setting is not reported back.
** +** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]] **
SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION
**
^This option is used to enable or disable the [sqlite3_load_extension()] ** interface independently of the [load_extension()] SQL function. ** The [sqlite3_enable_load_extension()] API enables or disables both the ** C-API [sqlite3_load_extension()] and the SQL function [load_extension()]. @@ -2089,19 +2094,20 @@ ** is written 0 or 1 to indicate whether [sqlite3_load_extension()] interface ** is disabled or enabled following this call. The second parameter may ** be a NULL pointer, in which case the new setting is not reported back. **
** -**
SQLITE_DBCONFIG_MAINDBNAME
+** [[SQLITE_DBCONFIG_MAINDBNAME]]
SQLITE_DBCONFIG_MAINDBNAME
**
^This option is used to change the name of the "main" database ** schema. ^The sole argument is a pointer to a constant UTF8 string ** which will become the new schema name in place of "main". ^SQLite ** does not make a copy of the new main schema name string, so the application ** must ensure that the argument passed into this DBCONFIG option is unchanged ** until after the database connection closes. **
** +** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]] **
SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
**
Usually, when a database in wal mode is closed or detached from a ** database handle, SQLite checks if this will mean that there are now no ** connections at all to the database. If so, it performs a checkpoint ** operation before closing the connection. This option may be used to @@ -2111,11 +2117,11 @@ ** The second parameter is a pointer to an integer ** into which is written 0 or 1 to indicate whether checkpoints-on-close ** have been disabled - 0 if they are not disabled, 1 if they are. **
** -**
SQLITE_DBCONFIG_ENABLE_QPSG
+** [[SQLITE_DBCONFIG_ENABLE_QPSG]]
SQLITE_DBCONFIG_ENABLE_QPSG
**
^(The SQLITE_DBCONFIG_ENABLE_QPSG option activates or deactivates ** the [query planner stability guarantee] (QPSG). When the QPSG is active, ** a single SQL query statement will always use the same algorithm regardless ** of values of [bound parameters].)^ The QPSG disables some query optimizations ** that look at the values of bound parameters, which can make some queries @@ -2127,11 +2133,11 @@ ** unchanged. The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether the QPSG is disabled or enabled ** following this call. **
** -**
SQLITE_DBCONFIG_TRIGGER_EQP
+** [[SQLITE_DBCONFIG_TRIGGER_EQP]]
SQLITE_DBCONFIG_TRIGGER_EQP
**
By default, the output of EXPLAIN QUERY PLAN commands does not ** include output for any operations performed by trigger programs. This ** option is used to set or clear (the default) a flag that governs this ** behavior. The first parameter passed to this operation is an integer - ** positive to enable output for trigger programs, or zero to disable it, @@ -2139,11 +2145,11 @@ ** The second parameter is a pointer to an integer into which is written ** 0 or 1 to indicate whether output-for-triggers has been disabled - 0 if ** it is not disabled, 1 if it is. **
** -**
SQLITE_DBCONFIG_RESET_DATABASE
+** [[SQLITE_DBCONFIG_RESET_DATABASE]]
SQLITE_DBCONFIG_RESET_DATABASE
**
Set the SQLITE_DBCONFIG_RESET_DATABASE flag and then run ** [VACUUM] in order to reset a database back to an empty database ** with no schema and no content. The following process works even for ** a badly corrupted database file: **
    @@ -2159,32 +2165,21 @@ **
** Because resetting a database is destructive and irreversible, the ** process requires the use of this obscure API and multiple steps to help ** ensure that it does not happen by accident. ** -**
SQLITE_DBCONFIG_DEFENSIVE
-**
The SQLITE_DBCONFIG_DEFENSIVE option actives or deactivates the +** [[SQLITE_DBCONFIG_DEFENSIVE]]
SQLITE_DBCONFIG_DEFENSIVE
+**
The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the ** "defensive" flag for a database connection. When the defensive -** flag is enabled, some obscure features of SQLite are disabled in order -** to reduce the attack surface. Applications that run untrusted SQL -** can activate this flag to reduce the risk of zero-day exploits. -**

-** Features disabled by the defensive flag include: +** flag is enabled, language features that allow ordinary SQL to +** deliberately corrupt the database file are disabled. The disabled +** features include but are not limited to the following: **

    -**
  • The [PRAGMA writable_schema=ON] statement. -**
  • Writes to the [sqlite_dbpage] virtual table. +**
  • The [PRAGMA writable_schema=ON] statement. +**
  • Writes to the [sqlite_dbpage] virtual table. +**
  • Direct writes to [shadow tables]. **
-** New restrictions may be added in future releases. -**

-** To be clear: It should never be possible for hostile SQL to cause -** arbitrary memory reads, memory leaks, buffer overflows, assertion -** faults, arbitrary code execution, crashes, or other mischief, regardless -** of the value of the defensive flag. Any occurrance of these problems -** is considered a serious bug and will be fixed promptly. It is not -** necessary to enable the defensive flag in order to make SQLite secure -** against attack. The defensive flag merely provides an additional layer -** of defense against unknown vulnerabilities. **

**
*/ #define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ @@ -6322,10 +6317,13 @@ /* The methods above are in version 1 of the sqlite_module object. Those ** below are for version 2 and greater. */ int (*xSavepoint)(sqlite3_vtab *pVTab, int); int (*xRelease)(sqlite3_vtab *pVTab, int); int (*xRollbackTo)(sqlite3_vtab *pVTab, int); + /* The methods above are in versions 1 and 2 of the sqlite_module object. + ** Those below are for version 3 and greater. */ + int (*xShadowName)(const char*); }; /* ** CAPI3REF: Virtual Table Indexing Information ** KEYWORDS: sqlite3_index_info @@ -8656,10 +8654,11 @@ ** These macros define the various options to the ** [sqlite3_vtab_config()] interface that [virtual table] implementations ** can use to customize and optimize their behavior. ** **
+** [[SQLITE_VTAB_CONSTRAINT_SUPPORT]] **
SQLITE_VTAB_CONSTRAINT_SUPPORT **
Calls of the form ** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported, ** where X is an integer. If X is zero, then the [virtual table] whose ** [xCreate] or [xConnect] method invoked [sqlite3_vtab_config()] does not Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -1057,10 +1057,15 @@ ** cases the parameters are named as per the usual conventions. */ #define UNUSED_PARAMETER(x) (void)(x) #define UNUSED_PARAMETER2(x,y) UNUSED_PARAMETER(x),UNUSED_PARAMETER(y) +/* +** The static mutex with which to protect reusable schemas. +*/ +#define SQLITE_MUTEX_SCHEMA_REUSE SQLITE_MUTEX_STATIC_APP1 + /* ** Forward references to structures */ typedef struct AggInfo AggInfo; typedef struct AuthContext AuthContext; @@ -1934,11 +1939,12 @@ ** The memory for objects of this type is always allocated by ** sqlite3DbMalloc(), using the connection handle stored in VTable.db as ** the first argument. */ struct VTable { - sqlite3 *db; /* Database connection associated with this table */ + sqlite3 *db; /* Database that owns this virtual table */ + Btree *pBt; /* Btree backend associated with this table */ Module *pMod; /* Pointer to module implementation */ sqlite3_vtab *pVtab; /* Pointer to vtab instance */ int nRef; /* Number of pointers to this structure */ u8 bConstraint; /* True if constraints are supported */ int iSavepoint; /* Depth of the SAVEPOINT stack */ @@ -2003,10 +2009,11 @@ #define TF_NoVisibleRowid 0x0040 /* No user-visible "rowid" column */ #define TF_OOOHidden 0x0080 /* Out-of-Order hidden columns */ #define TF_StatsUsed 0x0100 /* Query planner decisions affected by ** Index.aiRowLogEst[] values */ #define TF_HasNotNull 0x0200 /* Contains NOT NULL constraints */ +#define TF_Shadow 0x0400 /* True for a shadow table */ /* ** Test to see whether or not a table is a virtual table. This is ** done as a macro so that it will be optimized out when virtual ** table support is omitted from the build. @@ -3000,10 +3007,11 @@ ** a mask of new.* columns used by the program. */ struct TriggerPrg { Trigger *pTrigger; /* Trigger this program was coded from */ TriggerPrg *pNext; /* Next entry in Parse.pTriggerPrg list */ + int iFixDb; /* Value of Parse.iFixDb when this was coded */ SubProgram *pProgram; /* Program implementing pTrigger/orconf */ int orconf; /* Default ON CONFLICT policy */ u32 aColmask[2]; /* Masks of old.*, new.* columns accessed */ }; @@ -3128,19 +3136,20 @@ Index *pNewIndex; /* An index being constructed by CREATE INDEX */ Trigger *pNewTrigger; /* Trigger under construct by a CREATE TRIGGER */ const char *zAuthContext; /* The 6th parameter to db->xAuth callbacks */ #ifndef SQLITE_OMIT_VIRTUALTABLE Token sArg; /* Complete text of a module argument */ - Table **apVtabLock; /* Pointer to virtual tables needing locking */ + VTable **apVtabLock; /* Pointer to virtual tables needing locking */ #endif Table *pZombieTab; /* List of Table objects to delete after code gen */ TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */ With *pWith; /* Current WITH clause, or NULL */ With *pWithToFree; /* Free this WITH object at the end of the parse */ #ifndef SQLITE_OMIT_ALTERTABLE RenameToken *pRename; /* Tokens subject to renaming by ALTER TABLE */ #endif + int iFixDb; }; #define PARSE_MODE_NORMAL 0 #define PARSE_MODE_DECLARE_VTAB 1 #define PARSE_MODE_RENAME_COLUMN 2 @@ -3888,13 +3897,13 @@ int sqlite3RowSetNext(RowSet*, i64*); void sqlite3CreateView(Parse*,Token*,Token*,Token*,ExprList*,Select*,int,int); #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) - int sqlite3ViewGetColumnNames(Parse*,Table*); + int sqlite3ViewGetColumnNames(Parse*,int,Table*); #else -# define sqlite3ViewGetColumnNames(A,B) 0 +# define sqlite3ViewGetColumnNames(A,B,C) 0 #endif #if SQLITE_MAX_ATTACHED>30 int sqlite3DbMaskAllZero(yDbMask); #endif @@ -4022,24 +4031,24 @@ int sqlite3IsRowid(const char*); #ifdef SQLITE_ENABLE_NORMALIZE int sqlite3IsRowidN(const char*, int); #endif void sqlite3GenerateRowDelete( - Parse*,Table*,Trigger*,int,int,int,i16,u8,u8,u8,int); + Parse*,int,Table*,Trigger*,int,int,int,i16,u8,u8,u8,int); void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int, int*, int); int sqlite3GenerateIndexKey(Parse*, Index*, int, int, int, int*,Index*,int); void sqlite3ResolvePartIdxLabel(Parse*,int); int sqlite3ExprReferencesUpdatedColumn(Expr*,int*,int); -void sqlite3GenerateConstraintChecks(Parse*,Table*,int*,int,int,int,int, +void sqlite3GenerateConstraintChecks(Parse*,int,Table*,int*,int,int,int,int, u8,u8,int,int*,int*,Upsert*); #ifdef SQLITE_ENABLE_NULL_TRIM void sqlite3SetMakeRecordP5(Vdbe*,Table*); #else # define sqlite3SetMakeRecordP5(A,B) #endif void sqlite3CompleteInsertion(Parse*,Table*,int,int,int,int*,int,int,int); -int sqlite3OpenTableAndIndices(Parse*, Table*, int, u8, int, u8*, int*, int*); +int sqlite3OpenTableAndIndices(Parse*, int, Table*, int, u8, int,u8*,int*,int*); void sqlite3BeginWriteOperation(Parse*, int, int); void sqlite3MultiWrite(Parse*); void sqlite3MayAbort(Parse*); void sqlite3HaltConstraint(Parse*, int, int, char*, i8, u8); void sqlite3UniqueConstraint(Parse*, int, Index*); @@ -4060,11 +4069,11 @@ int sqlite3SafetyCheckOk(sqlite3*); int sqlite3SafetyCheckSickOrOk(sqlite3*); void sqlite3ChangeCookie(Parse*, int); #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) -void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); +void sqlite3MaterializeView(Parse*, int, Table*, Expr*, ExprList*,Expr*,int); #endif #ifndef SQLITE_OMIT_TRIGGER void sqlite3BeginTrigger(Parse*, Token*,Token*,int,int,IdList*,SrcList*, Expr*,int, int); @@ -4071,13 +4080,13 @@ void sqlite3FinishTrigger(Parse*, TriggerStep*, Token*); void sqlite3DropTrigger(Parse*, SrcList*, int); void sqlite3DropTriggerPtr(Parse*, Trigger*); Trigger *sqlite3TriggersExist(Parse *, Table*, int, ExprList*, int *pMask); Trigger *sqlite3TriggerList(Parse *, Table *); - void sqlite3CodeRowTrigger(Parse*, Trigger *, int, ExprList*, int, Table *, - int, int, int); - void sqlite3CodeRowTriggerDirect(Parse *, Trigger *, Table *, int, int, int); + void sqlite3CodeRowTrigger(Parse*, int, Trigger *, int, ExprList*, int, + Table *, int, int, int); + void sqlite3CodeRowTriggerDirect(Parse*, int, Trigger*, Table*, int, int,int); void sqliteViewTriggers(Parse*, Table*, Expr*, int, ExprList*); void sqlite3DeleteTriggerStep(sqlite3*, TriggerStep*); TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*, const char*,const char*); TriggerStep *sqlite3TriggerInsertStep(Parse*,Token*, IdList*, @@ -4087,24 +4096,24 @@ const char*,const char*); TriggerStep *sqlite3TriggerDeleteStep(Parse*,Token*, Expr*, const char*,const char*); void sqlite3DeleteTrigger(sqlite3*, Trigger*); void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*); - u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int); + u32 sqlite3TriggerColmask(Parse*,int,Trigger*,ExprList*,int,int,Table*,int); # define sqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p)) # define sqlite3IsToplevel(p) ((p)->pToplevel==0) #else # define sqlite3TriggersExist(B,C,D,E,F) 0 # define sqlite3DeleteTrigger(A,B) # define sqlite3DropTriggerPtr(A,B) # define sqlite3UnlinkAndDeleteTrigger(A,B,C) -# define sqlite3CodeRowTrigger(A,B,C,D,E,F,G,H,I) -# define sqlite3CodeRowTriggerDirect(A,B,C,D,E,F) +# define sqlite3CodeRowTrigger(A,B,C,D,E,F,G,H,I,J) +# define sqlite3CodeRowTriggerDirect(A,B,C,D,E,F,G) # define sqlite3TriggerList(X, Y) 0 # define sqlite3ParseToplevel(p) p # define sqlite3IsToplevel(p) 1 -# define sqlite3TriggerColmask(A,B,C,D,E,F,G) 0 +# define sqlite3TriggerColmask(A,B,C,D,E,F,G,H) 0 #endif int sqlite3JoinType(Parse*, Token*, Token*, Token*); void sqlite3CreateForeignKey(Parse*, ExprList*, Token*, ExprList*, int); void sqlite3DeferForeignKey(Parse*, int); @@ -4291,11 +4300,11 @@ void sqlite3SchemaClear(void *); void sqlite3SchemaUnuse(sqlite3*, int); void sqlite3SchemaReuse(sqlite3*, int); void sqlite3SchemaWritable(Parse*, int); Schema *sqlite3SchemaGet(sqlite3 *, Btree *); -int sqlite3SchemaToIndex(sqlite3 *db, Schema *); +int sqlite3SchemaToIndex(sqlite3 *db, Schema *, const char*); KeyInfo *sqlite3KeyInfoAlloc(sqlite3*,int,int); void sqlite3KeyInfoUnref(KeyInfo*); KeyInfo *sqlite3KeyInfoRef(KeyInfo*); KeyInfo *sqlite3KeyInfoOfIndex(Parse*, Index*); KeyInfo *sqlite3KeyInfoFromExprList(Parse*, ExprList*, int, int); @@ -4379,11 +4388,11 @@ # define sqlite3VtabInSync(db) 0 # define sqlite3VtabLock(X) # define sqlite3VtabUnlock(X) # define sqlite3VtabUnlockList(X) # define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK -# define sqlite3GetVTable(X,Y) ((VTable*)0) +# define sqlite3GetVTable(X,Y,Z) ((VTable*)0) #else void sqlite3VtabClear(sqlite3 *db, Table*); void sqlite3VtabDisconnect(sqlite3 *db, Table *p); int sqlite3VtabSync(sqlite3 *db, Vdbe*); int sqlite3VtabRollback(sqlite3 *db); @@ -4391,11 +4400,11 @@ void sqlite3VtabLock(VTable *); void sqlite3VtabUnlock(VTable *); void sqlite3VtabUnlockList(sqlite3*); int sqlite3VtabSavepoint(sqlite3 *, int, int); void sqlite3VtabImportErrmsg(Vdbe*, sqlite3_vtab*); - VTable *sqlite3GetVTable(sqlite3*, Table*); + VTable *sqlite3GetVTable(sqlite3*, int, Table*); Module *sqlite3VtabCreateModule( sqlite3*, const char*, const sqlite3_module*, void*, @@ -4403,17 +4412,17 @@ ); # define sqlite3VtabInSync(db) ((db)->nVTrans>0 && (db)->aVTrans==0) #endif int sqlite3VtabEponymousTableInit(Parse*,Module*); void sqlite3VtabEponymousTableClear(sqlite3*,Module*); -void sqlite3VtabMakeWritable(Parse*,Table*); +void sqlite3VtabMakeWritable(Parse*,int,Table*); void sqlite3VtabBeginParse(Parse*, Token*, Token*, Token*, int); void sqlite3VtabFinishParse(Parse*, Token*); void sqlite3VtabArgInit(Parse*); void sqlite3VtabArgExtend(Parse*, Token*); int sqlite3VtabCallCreate(sqlite3*, int, const char *, char **); -int sqlite3VtabCallConnect(Parse*, Table*); +int sqlite3VtabCallConnect(Parse*, int, Table*); int sqlite3VtabCallDestroy(sqlite3*, int, const char *); int sqlite3VtabBegin(sqlite3 *, VTable *); FuncDef *sqlite3VtabOverloadFunction(sqlite3 *,FuncDef*, int nArg, Expr*); sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*); int sqlite3VdbeParameterIndex(Vdbe*, const char*, int); @@ -4458,19 +4467,19 @@ ** OMIT_FOREIGN_KEY is not, only some of the functions are no-oped. In ** this case foreign keys are parsed, but no other functionality is ** provided (enforcement of FK constraints requires the triggers sub-system). */ #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) - void sqlite3FkCheck(Parse*, Table*, int, int, int*, int); + void sqlite3FkCheck(Parse*, int, Table*, int, int, int*, int); void sqlite3FkDropTable(Parse*, SrcList *, Table*); - void sqlite3FkActions(Parse*, Table*, ExprList*, int, int*, int); + void sqlite3FkActions(Parse*, int, Table*, ExprList*, int, int*, int); int sqlite3FkRequired(Parse*, Table*, int*, int); u32 sqlite3FkOldmask(Parse*, Table*); FKey *sqlite3FkReferences(Table *); #else - #define sqlite3FkActions(a,b,c,d,e,f) - #define sqlite3FkCheck(a,b,c,d,e,f) + #define sqlite3FkActions(a,b,c,d,e,f,g) + #define sqlite3FkCheck(a,b,c,d,e,f,g) #define sqlite3FkDropTable(a,b,c) #define sqlite3FkOldmask(a,b) 0 #define sqlite3FkRequired(a,b,c,d) 0 #define sqlite3FkReferences(a) 0 #endif Index: src/trigger.c ================================================================== --- src/trigger.c +++ src/trigger.c @@ -213,11 +213,11 @@ goto trigger_cleanup; } #ifndef SQLITE_OMIT_AUTHORIZATION if( !IN_RENAME_OBJECT ){ - int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); + int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema, 0); int code = SQLITE_CREATE_TRIGGER; const char *zDb = db->aDb[iTabDb].zDbSName; const char *zDbTrig = isTemp ? db->aDb[1].zDbSName : zDb; if( iTabDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER; if( sqlite3AuthCheck(pParse, code, zName, pTab->zName, zDbTrig) ){ @@ -289,11 +289,11 @@ Token nameToken; /* Trigger name for error reporting */ pParse->pNewTrigger = 0; if( NEVER(pParse->nErr) || !pTrig ) goto triggerfinish_cleanup; zName = pTrig->zName; - iDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema); + iDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema, 0); pTrig->step_list = pStepList; while( pStepList ){ pStepList->pTrig = pTrig; pStepList = pStepList->pNext; } @@ -608,11 +608,11 @@ Table *pTable; Vdbe *v; sqlite3 *db = pParse->db; int iDb; - iDb = sqlite3SchemaToIndex(pParse->db, pTrigger->pSchema); + iDb = sqlite3SchemaToIndex(pParse->db, pTrigger->pSchema, 0); assert( iDb>=0 && iDbnDb ); sqlite3SchemaWritable(pParse, iDb); pTable = tableOfTrigger(pTrigger); assert( pTable ); assert( pTable->pSchema==pTrigger->pSchema || iDb==1 ); @@ -727,24 +727,16 @@ static SrcList *targetSrcList( Parse *pParse, /* The parsing context */ TriggerStep *pStep /* The trigger containing the target token */ ){ sqlite3 *db = pParse->db; - int iDb; /* Index of the database to use */ SrcList *pSrc; /* SrcList to be returned */ pSrc = sqlite3SrcListAppend(db, 0, 0, 0); if( pSrc ){ assert( pSrc->nSrc>0 ); pSrc->a[pSrc->nSrc-1].zName = sqlite3DbStrDup(db, pStep->zTarget); - iDb = sqlite3SchemaToIndex(db, pStep->pTrig->pSchema); - if( iDb==0 || iDb>=2 ){ - const char *zDb; - assert( iDbnDb ); - zDb = db->aDb[iDb].zDbSName; - pSrc->a[pSrc->nSrc-1].zDatabase = sqlite3DbStrDup(db, zDb); - } } return pSrc; } /* @@ -902,10 +894,11 @@ sqlite3VdbeLinkSubProgram(pTop->pVdbe, pProgram); pPrg->pTrigger = pTrigger; pPrg->orconf = orconf; pPrg->aColmask[0] = 0xffffffff; pPrg->aColmask[1] = 0xffffffff; + pPrg->iFixDb = pParse->iFixDb; /* Allocate and populate a new Parse context to use for coding the ** trigger sub-program. */ pSubParse = sqlite3StackAllocZero(db, sizeof(Parse)); if( !pSubParse ) return 0; @@ -915,15 +908,16 @@ pSubParse->pTriggerTab = pTab; pSubParse->pToplevel = pTop; pSubParse->zAuthContext = pTrigger->zName; pSubParse->eTriggerOp = pTrigger->op; pSubParse->nQueryLoop = pParse->nQueryLoop; + pSubParse->iFixDb = pParse->iFixDb; v = sqlite3GetVdbe(pSubParse); if( v ){ - VdbeComment((v, "Start: %s.%s (%s %s%s%s ON %s)", - pTrigger->zName, onErrorText(orconf), + VdbeComment((v, "Start: %s.%s (%d) (%s %s%s%s ON %s)", + pTrigger->zName, onErrorText(orconf), pParse->iFixDb-1, (pTrigger->tr_tm==TRIGGER_BEFORE ? "BEFORE" : "AFTER"), (pTrigger->op==TK_UPDATE ? "UPDATE" : ""), (pTrigger->op==TK_INSERT ? "INSERT" : ""), (pTrigger->op==TK_DELETE ? "DELETE" : ""), pTab->zName @@ -965,10 +959,11 @@ pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg); } pProgram->nMem = pSubParse->nMem; pProgram->nCsr = pSubParse->nTab; pProgram->token = (void *)pTrigger; + pProgram->itoken = pParse->iFixDb; pPrg->aColmask[0] = pSubParse->oldmask; pPrg->aColmask[1] = pSubParse->newmask; sqlite3VdbeDelete(v); } @@ -986,33 +981,39 @@ ** TriggerPrg object exists, a new object is allocated and populated before ** being returned. */ static TriggerPrg *getRowTrigger( Parse *pParse, /* Current parse context */ + int iDb, /* Database containing pTab */ Trigger *pTrigger, /* Trigger to code */ Table *pTab, /* The table trigger pTrigger is attached to */ int orconf /* ON CONFLICT algorithm. */ ){ Parse *pRoot = sqlite3ParseToplevel(pParse); TriggerPrg *pPrg; + int iFixDb = pParse->iFixDb; + pParse->iFixDb = (pTrigger->pSchema==pRoot->db->aDb[1].pSchema)?0:(iDb+1); assert( pTrigger->zName==0 || pTab==tableOfTrigger(pTrigger) ); /* It may be that this trigger has already been coded (or is in the ** process of being coded). If this is the case, then an entry with ** a matching TriggerPrg.pTrigger field will be present somewhere ** in the Parse.pTriggerPrg list. Search for such an entry. */ - for(pPrg=pRoot->pTriggerPrg; - pPrg && (pPrg->pTrigger!=pTrigger || pPrg->orconf!=orconf); - pPrg=pPrg->pNext - ); + for(pPrg=pRoot->pTriggerPrg; pPrg; pPrg=pPrg->pNext){ + if( pPrg->pTrigger==pTrigger + && pPrg->orconf==orconf + && pParse->iFixDb==pPrg->iFixDb + ) break; + } /* If an existing TriggerPrg could not be located, create a new one. */ if( !pPrg ){ pPrg = codeRowTrigger(pParse, pTrigger, pTab, orconf); } + pParse->iFixDb = iFixDb; return pPrg; } /* ** Generate code for the trigger program associated with trigger p on @@ -1020,30 +1021,33 @@ ** function are the same as those described in the header function for ** sqlite3CodeRowTrigger() */ void sqlite3CodeRowTriggerDirect( Parse *pParse, /* Parse context */ + int iDb, /* Database containing pTrigger */ Trigger *p, /* Trigger to code */ Table *pTab, /* The table to code triggers from */ int reg, /* Reg array containing OLD.* and NEW.* values */ int orconf, /* ON CONFLICT policy */ int ignoreJump /* Instruction to jump to for RAISE(IGNORE) */ ){ Vdbe *v = sqlite3GetVdbe(pParse); /* Main VM */ TriggerPrg *pPrg; - pPrg = getRowTrigger(pParse, p, pTab, orconf); + + pPrg = getRowTrigger(pParse, iDb, p, pTab, orconf); assert( pPrg || pParse->nErr || pParse->db->mallocFailed ); /* Code the OP_Program opcode in the parent VDBE. P4 of the OP_Program ** is a pointer to the sub-vdbe containing the trigger program. */ if( pPrg ){ int bRecursive = (p->zName && 0==(pParse->db->flags&SQLITE_RecTriggers)); sqlite3VdbeAddOp4(v, OP_Program, reg, ignoreJump, ++pParse->nMem, (const char *)pPrg->pProgram, P4_SUBPROGRAM); - VdbeComment( - (v, "Call: %s.%s", (p->zName?p->zName:"fkey"), onErrorText(orconf))); + VdbeComment((v, "Call: %s.%s (%d)", + (p->zName?p->zName:"fkey"), onErrorText(orconf), iDb + )); /* Set the P5 operand of the OP_Program instruction to non-zero if ** recursive invocation of this trigger program is disallowed. Recursive ** invocation is disallowed if (a) the sub-program is really a trigger, ** not a foreign key action, and (b) the flag to enable recursive triggers @@ -1092,10 +1096,11 @@ ** is the instruction that control should jump to if a trigger program ** raises an IGNORE exception. */ void sqlite3CodeRowTrigger( Parse *pParse, /* Parse context */ + int iDb, Trigger *pTrigger, /* List of triggers on table pTab */ int op, /* One of TK_UPDATE, TK_INSERT, TK_DELETE */ ExprList *pChanges, /* Changes list for any UPDATE OF triggers */ int tr_tm, /* One of TRIGGER_BEFORE, TRIGGER_AFTER */ Table *pTab, /* The table to code triggers from */ @@ -1122,11 +1127,11 @@ /* Determine whether we should code this trigger */ if( p->op==op && p->tr_tm==tr_tm && checkColumnOverlap(p->pColumns, pChanges) ){ - sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump); + sqlite3CodeRowTriggerDirect(pParse, iDb, p, pTab, reg, orconf,ignoreJump); } } } /* @@ -1154,10 +1159,11 @@ ** tr_tm parameter. Similarly, values accessed by AFTER triggers are only ** included in the returned mask if the TRIGGER_AFTER bit is set in tr_tm. */ u32 sqlite3TriggerColmask( Parse *pParse, /* Parse context */ + int iDb, Trigger *pTrigger, /* List of triggers on table pTab */ ExprList *pChanges, /* Changes list for any UPDATE OF triggers */ int isNew, /* 1 for new.* ref mask, 0 for old.* ref mask */ int tr_tm, /* Mask of TRIGGER_BEFORE|TRIGGER_AFTER */ Table *pTab, /* The table to code triggers from */ @@ -1171,11 +1177,11 @@ for(p=pTrigger; p; p=p->pNext){ if( p->op==op && (tr_tm&p->tr_tm) && checkColumnOverlap(p->pColumns,pChanges) ){ TriggerPrg *pPrg; - pPrg = getRowTrigger(pParse, p, pTab, orconf); + pPrg = getRowTrigger(pParse, iDb, p, pTab, orconf); if( pPrg ){ mask |= pPrg->aColmask[isNew]; } } } Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -16,10 +16,11 @@ #ifndef SQLITE_OMIT_VIRTUALTABLE /* Forward declaration */ static void updateVirtualTable( Parse *pParse, /* The parsing context */ + int iDb, SrcList *pSrc, /* The virtual table to be modified */ Table *pTab, /* The virtual table */ ExprList *pChanges, /* The columns to change in the UPDATE statement */ Expr *pRowidExpr, /* Expression used to recompute the rowid */ int *aXRef, /* Mapping from columns of pTab to entries in pChanges */ @@ -209,11 +210,11 @@ /* Locate the table which we want to update. */ pTab = sqlite3SrcListLookup(pParse, pTabList); if( pTab==0 ) goto update_cleanup; - iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + iDb = sqlite3SchemaToIndex(db, pTab->pSchema, pTabList->a[0].zDatabase); /* Figure out if we have any triggers and if the table being ** updated is a view. */ #ifndef SQLITE_OMIT_TRIGGER @@ -238,11 +239,11 @@ pOrderBy = 0; pLimit = 0; } #endif - if( sqlite3ViewGetColumnNames(pParse, pTab) ){ + if( sqlite3ViewGetColumnNames(pParse, iDb, pTab) ){ goto update_cleanup; } if( sqlite3IsReadOnly(pParse, pTab, tmask) ){ goto update_cleanup; } @@ -414,11 +415,11 @@ /* If we are trying to update a view, realize that view into ** an ephemeral table. */ #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) if( isView ){ - sqlite3MaterializeView(pParse, pTab, + sqlite3MaterializeView(pParse, iDb, pTab, pWhere, pOrderBy, pLimit, iDataCur ); pOrderBy = 0; pLimit = 0; } @@ -432,11 +433,11 @@ } #ifndef SQLITE_OMIT_VIRTUALTABLE /* Virtual tables must be handled separately */ if( IsVirtual(pTab) ){ - updateVirtualTable(pParse, pTabList, pTab, pChanges, pRowidExpr, aXRef, + updateVirtualTable(pParse, iDb, pTabList, pTab, pChanges, pRowidExpr, aXRef, pWhere, onError); goto update_cleanup; } #endif @@ -561,11 +562,11 @@ } if( eOnePass==ONEPASS_MULTI && (nIdx-(aiCurOnePass[1]>=0))>0 ){ addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } - sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur, + sqlite3OpenTableAndIndices(pParse, iDb, pTab, OP_OpenWrite, 0, iBaseCur, aToOpen, 0, 0); if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce); } /* Top of the update loop */ @@ -608,11 +609,11 @@ /* Compute the old pre-UPDATE content of the row being changed, if that ** information is needed */ if( chngPk || hasFK || pTrigger ){ u32 oldmask = (hasFK ? sqlite3FkOldmask(pParse, pTab) : 0); - oldmask |= sqlite3TriggerColmask(pParse, + oldmask |= sqlite3TriggerColmask(pParse, iDb, pTrigger, pChanges, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onError ); for(i=0; inCol; i++){ if( oldmask==0xffffffff || (i<32 && (oldmask & MASKBIT32(i))!=0) @@ -641,11 +642,11 @@ ** the database after the BEFORE triggers are fired anyway (as the trigger ** may have modified them). So not loading those that are not going to ** be used eliminates some redundant opcodes. */ newmask = sqlite3TriggerColmask( - pParse, pTrigger, pChanges, 1, TRIGGER_BEFORE, pTab, onError + pParse, iDb, pTrigger, pChanges, 1, TRIGGER_BEFORE, pTab, onError ); for(i=0; inCol; i++){ if( i==pTab->iPKey ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i); }else{ @@ -670,11 +671,11 @@ /* Fire any BEFORE UPDATE triggers. This happens before constraints are ** verified. One could argue that this is wrong. */ if( tmask&TRIGGER_BEFORE ){ sqlite3TableAffinity(v, pTab, regNew); - sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, + sqlite3CodeRowTrigger(pParse, iDb, pTrigger, TK_UPDATE, pChanges, TRIGGER_BEFORE, pTab, regOldRowid, onError, labelContinue); /* The row-trigger may have deleted the row being updated. In this ** case, jump to the next row. No updates or AFTER triggers are ** required. This behavior - what happens when the row being updated @@ -708,17 +709,17 @@ if( !isView ){ int addr1 = 0; /* Address of jump instruction */ /* Do constraint checks. */ assert( regOldRowid>0 ); - sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, - regNewRowid, regOldRowid, chngKey, onError, labelContinue, &bReplace, - aXRef, 0); + sqlite3GenerateConstraintChecks(pParse, iDb, pTab, aRegIdx, iDataCur, + iIdxCur, regNewRowid, regOldRowid, chngKey, onError, labelContinue, + &bReplace, aXRef, 0); /* Do FK constraint checks. */ if( hasFK ){ - sqlite3FkCheck(pParse, pTab, regOldRowid, 0, aXRef, chngKey); + sqlite3FkCheck(pParse, iDb, pTab, regOldRowid, 0, aXRef, chngKey); } /* Delete the index entries associated with the current record. */ if( bReplace || chngKey ){ if( pPk ){ @@ -760,11 +761,11 @@ if( bReplace || chngKey ){ sqlite3VdbeJumpHere(v, addr1); } if( hasFK ){ - sqlite3FkCheck(pParse, pTab, 0, regNewRowid, aXRef, chngKey); + sqlite3FkCheck(pParse, iDb, pTab, 0, regNewRowid, aXRef, chngKey); } /* Insert the new index entries and the new record. */ sqlite3CompleteInsertion( pParse, pTab, iDataCur, iIdxCur, regNewRowid, aRegIdx, @@ -774,21 +775,21 @@ /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to ** handle rows (possibly in other tables) that refer via a foreign key ** to the row just updated. */ if( hasFK ){ - sqlite3FkActions(pParse, pTab, pChanges, regOldRowid, aXRef, chngKey); + sqlite3FkActions(pParse, iDb, pTab, pChanges, regOldRowid, aXRef,chngKey); } } /* Increment the row counter */ if( regRowCount ){ sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1); } - sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, + sqlite3CodeRowTrigger(pParse, iDb, pTrigger, TK_UPDATE, pChanges, TRIGGER_AFTER, pTab, regOldRowid, onError, labelContinue); /* Repeat the above with the next record to be updated, until ** all record selected by the WHERE clause have been updated. */ @@ -867,10 +868,11 @@ ** stores the same values (A, B and C above) in a register array and ** makes a single invocation of VUpdate. */ static void updateVirtualTable( Parse *pParse, /* The parsing context */ + int iDb, SrcList *pSrc, /* The virtual table to be modified */ Table *pTab, /* The virtual table */ ExprList *pChanges, /* The columns to change in the UPDATE statement */ Expr *pRowid, /* Expression used to recompute the rowid */ int *aXRef, /* Mapping from columns of pTab to entries in pChanges */ @@ -879,11 +881,11 @@ ){ Vdbe *v = pParse->pVdbe; /* Virtual machine under construction */ int ephemTab; /* Table holding the result of the SELECT */ int i; /* Loop counter */ sqlite3 *db = pParse->db; /* Database connection */ - const char *pVTab = (const char*)sqlite3GetVTable(db, pTab); + const char *pVTab = (const char*)sqlite3GetVTable(db, iDb, pTab); WhereInfo *pWInfo; int nArg = 2 + pTab->nCol; /* Number of arguments to VUpdate */ int regArg; /* First register in VUpdate arg array */ int regRec; /* Register in which to assemble record */ int regRowid; /* Register for ephem table rowid */ @@ -970,11 +972,11 @@ ** invoke the VUpdate method. */ for(i=0; inChange; saved_nTotalChange = db->nTotalChange; saved_mTrace = db->mTrace; db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks; db->mDbFlags |= DBFLAG_PreferBuiltin | DBFLAG_Vacuum; - db->flags &= ~(SQLITE_ForeignKeys | SQLITE_ReverseOrder | SQLITE_CountRows); + db->flags &= ~(SQLITE_ForeignKeys | SQLITE_ReverseOrder + | SQLITE_Defensive | SQLITE_CountRows); db->mTrace = 0; zDbMain = db->aDb[iDb].zDbSName; pMain = db->aDb[iDb].pBt; isMemDb = sqlite3PagerIsMemdb(sqlite3BtreePager(pMain)); Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -6036,10 +6036,11 @@ Mem *pMem; /* Used to iterate through memory cells */ Mem *pEnd; /* Last memory cell in new array */ VdbeFrame *pFrame; /* New vdbe frame to execute in */ SubProgram *pProgram; /* Sub-program to execute */ void *t; /* Token identifying trigger */ + int i; /* Second part of token identifying trigger */ pProgram = pOp->p4.pProgram; pRt = &aMem[pOp->p3]; assert( pProgram->nOp>0 ); @@ -6054,11 +6055,14 @@ ** ON CONFLICT algorithm). SubProgram structures associated with a ** single trigger all have the same value for the SubProgram.token ** variable. */ if( pOp->p5 ){ t = pProgram->token; - for(pFrame=p->pFrame; pFrame && pFrame->token!=t; pFrame=pFrame->pParent); + i = pProgram->itoken; + for(pFrame=p->pFrame; + pFrame && (pFrame->token!=t || pFrame->itoken!=i); + pFrame=pFrame->pParent); if( pFrame ) break; } if( p->nFrame>=db->aLimit[SQLITE_LIMIT_TRIGGER_DEPTH] ){ rc = SQLITE_ERROR; @@ -6102,10 +6106,11 @@ pFrame->apCsr = p->apCsr; pFrame->nCursor = p->nCursor; pFrame->aOp = p->aOp; pFrame->nOp = p->nOp; pFrame->token = pProgram->token; + pFrame->itoken = pProgram->itoken; #ifdef SQLITE_ENABLE_STMT_SCANSTATUS pFrame->anExec = p->anExec; #endif #ifdef SQLITE_DEBUG pFrame->iFrameMagic = SQLITE_FRAME_MAGIC; Index: src/vdbe.h ================================================================== --- src/vdbe.h +++ src/vdbe.h @@ -88,10 +88,11 @@ int nOp; /* Elements in aOp[] */ int nMem; /* Number of memory cells required */ int nCsr; /* Number of cursors required */ u8 *aOnce; /* Array of OP_Once flags */ void *token; /* id that may be used to recursive triggers */ + int itoken; /* Second part of id to identify rec. trig. */ SubProgram *pNext; /* Next sub-program already visited */ }; /* ** A smaller version of VdbeOp used for the VdbeAddOpList() function because Index: src/vdbeInt.h ================================================================== --- src/vdbeInt.h +++ src/vdbeInt.h @@ -165,10 +165,11 @@ i64 *anExec; /* Event counters from parent frame */ Mem *aMem; /* Array of memory cells for parent frame */ VdbeCursor **apCsr; /* Array of Vdbe cursors for parent frame */ u8 *aOnce; /* Bitmask used by OP_Once */ void *token; /* Copy of SubProgram.token */ + int itoken; /* Copy of SubProgram.itoken */ i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */ AuxData *pAuxData; /* Linked list of auxdata allocations */ #if SQLITE_DEBUG u32 iFrameMagic; /* magic number for sanity checking */ #endif @@ -469,11 +470,13 @@ int sqlite2BtreeKeyCompare(BtCursor *, const void *, int, int, int *); int sqlite3VdbeIdxKeyCompare(sqlite3*,VdbeCursor*,UnpackedRecord*,int*); int sqlite3VdbeIdxRowid(sqlite3*, BtCursor*, i64*); int sqlite3VdbeExec(Vdbe*); +#ifndef SQLITE_OMIT_EXPLAIN int sqlite3VdbeList(Vdbe*); +#endif int sqlite3VdbeHalt(Vdbe*); int sqlite3VdbeChangeEncoding(Mem *, int); int sqlite3VdbeMemTooBig(Mem*); int sqlite3VdbeMemCopy(Mem*, const Mem*); void sqlite3VdbeMemShallowCopy(Mem*, const Mem*, int); @@ -508,11 +511,13 @@ void sqlite3VdbeMemRelease(Mem *p); int sqlite3VdbeMemFinalize(Mem*, FuncDef*); #ifndef SQLITE_OMIT_WINDOWFUNC int sqlite3VdbeMemAggValue(Mem*, Mem*, FuncDef*); #endif +#ifndef SQLITE_OMIT_EXPLAIN const char *sqlite3OpcodeName(int); +#endif int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve); int sqlite3VdbeMemClearAndResize(Mem *pMem, int n); int sqlite3VdbeCloseStatement(Vdbe *, int); #ifdef SQLITE_DEBUG int sqlite3VdbeFrameIsValid(VdbeFrame*); Index: src/vdbeblob.c ================================================================== --- src/vdbeblob.c +++ src/vdbeblob.c @@ -181,11 +181,11 @@ rc = SQLITE_ERROR; sqlite3BtreeLeaveAll(db); goto blob_open_out; } pBlob->pTab = pTab; - pBlob->zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zDbSName; + pBlob->zDb = db->aDb[sqlite3SchemaToIndex(db,pTab->pSchema,zDb)].zDbSName; /* Now search pTab for the exact column. */ for(iCol=0; iColnCol; iCol++) { if( sqlite3StrICmp(pTab->aCol[iCol].zName, zColumn)==0 ){ break; @@ -268,11 +268,11 @@ {OP_Column, 0, 0, 1}, /* 3 */ {OP_ResultRow, 1, 0, 0}, /* 4 */ {OP_Halt, 0, 0, 0}, /* 5 */ }; Vdbe *v = (Vdbe *)pBlob->pStmt; - int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + int iDb = sqlite3SchemaToIndex(db, pTab->pSchema, zDb); VdbeOp *aOp; sqlite3VdbeAddOp4Int(v, OP_Transaction, iDb, wrFlag, pTab->pSchema->schema_cookie, pTab->pSchema->iGeneration); Index: src/vtab.c ================================================================== --- src/vtab.c +++ src/vtab.c @@ -139,14 +139,19 @@ /* ** pTab is a pointer to a Table structure representing a virtual-table. ** Return a pointer to the VTable object used by connection db to access ** this virtual-table, if one has been created, or NULL otherwise. */ -VTable *sqlite3GetVTable(sqlite3 *db, Table *pTab){ +VTable *sqlite3GetVTable(sqlite3 *db, int iDb, Table *pTab){ VTable *pVtab; assert( IsVirtual(pTab) ); - for(pVtab=pTab->pVTable; pVtab && pVtab->db!=db; pVtab=pVtab->pNext); + if( iDb>=0 ){ + Btree *pBt = db->aDb[iDb].pBt; + for(pVtab=pTab->pVTable; pVtab && pVtab->pBt!=pBt; pVtab=pVtab->pNext); + }else{ + for(pVtab=pTab->pVTable; pVtab && pVtab->db!=db; pVtab=pVtab->pNext); + } return pVtab; } /* ** Decrement the ref-count on a virtual table object. When the ref-count @@ -216,21 +221,23 @@ ** objects without disturbing the rest of the Schema object (which may ** be being used by other shared-cache connections). */ void sqlite3VtabDisconnect(sqlite3 *db, Table *p){ VTable **ppVTab; + VTable **ppNext; assert( IsVirtual(p) ); assert( sqlite3BtreeHoldsAllMutexes(db) ); assert( sqlite3_mutex_held(db->mutex) ); - for(ppVTab=&p->pVTable; *ppVTab; ppVTab=&(*ppVTab)->pNext){ + for(ppVTab=&p->pVTable; *ppVTab; ){ if( (*ppVTab)->db==db ){ VTable *pVTab = *ppVTab; *ppVTab = pVTab->pNext; sqlite3VtabUnlock(pVTab); - break; + }else{ + ppVTab = &(*ppVTab)->pNext; } } } @@ -354,11 +361,11 @@ ** The first invocation, to obtain permission to INSERT a row into the ** sqlite_master table, has already been made by sqlite3StartTable(). ** The second call, to obtain permission to create the table, is made now. */ if( pTable->azModuleArg ){ - int iDb = sqlite3SchemaToIndex(db, pTable->pSchema); + int iDb = sqlite3SchemaToIndex(db, pTable->pSchema, 0); /* TODO */ assert( iDb>=0 ); /* The database the table is being created in */ sqlite3AuthCheck(pParse, SQLITE_CREATE_VTABLE, pTable->zName, pTable->azModuleArg[0], pParse->db->aDb[iDb].zDbSName); } #endif @@ -416,11 +423,11 @@ ** ** The VM register number pParse->regRowid holds the rowid of an ** entry in the sqlite_master table tht was created for this vtab ** by sqlite3StartTable(). */ - iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + iDb = sqlite3SchemaToIndex(db, pTab->pSchema, 0); sqlite3NestedParse(pParse, "UPDATE %Q.%s " "SET type='table', name=%Q, tbl_name=%Q, rootpage=0, sql=%Q " "WHERE rowid=#%d", db->aDb[iDb].zDbSName, MASTER_NAME, @@ -492,10 +499,11 @@ ** pointer to the function to invoke is passed as the fourth parameter ** to this procedure. */ static int vtabCallConstructor( sqlite3 *db, + int iDb, /* Database containing pTab */ Table *pTab, Module *pMod, int (*xConstruct)(sqlite3*,void*,int,const char*const*,sqlite3_vtab**,char**), char **pzErr ){ @@ -504,11 +512,10 @@ int rc; const char *const*azArg = (const char *const*)pTab->azModuleArg; int nArg = pTab->nModuleArg; char *zErr = 0; char *zModuleName; - int iDb; VtabCtx *pCtx; /* Check that the virtual-table is not already being initialized */ for(pCtx=db->pVtabCtx; pCtx; pCtx=pCtx->pPrior){ if( pCtx->pTab==pTab ){ @@ -529,13 +536,13 @@ sqlite3OomFault(db); sqlite3DbFree(db, zModuleName); return SQLITE_NOMEM_BKPT; } pVTable->db = db; + pVTable->pBt = db->aDb[iDb].pBt; pVTable->pMod = pMod; - iDb = sqlite3SchemaToIndex(db, pTab->pSchema); pTab->azModuleArg[1] = db->aDb[iDb].zDbSName; /* Invoke the virtual table constructor */ assert( &db->pVtabCtx ); assert( xConstruct ); @@ -620,18 +627,18 @@ ** of the virtual table pTab. If an error occurs, an error code is returned ** and an error left in pParse. ** ** This call is a no-op if table pTab is not a virtual table. */ -int sqlite3VtabCallConnect(Parse *pParse, Table *pTab){ +int sqlite3VtabCallConnect(Parse *pParse, int iDb, Table *pTab){ sqlite3 *db = pParse->db; const char *zMod; Module *pMod; int rc; assert( pTab ); - if( !IsVirtual(pTab) || sqlite3GetVTable(db, pTab) ){ + if( !IsVirtual(pTab) || sqlite3GetVTable(db, iDb, pTab) ){ return SQLITE_OK; } /* Locate the required virtual table module */ zMod = pTab->azModuleArg[0]; @@ -641,11 +648,11 @@ const char *zModule = pTab->azModuleArg[0]; sqlite3ErrorMsg(pParse, "no such module: %s", zModule); rc = SQLITE_ERROR; }else{ char *zErr = 0; - rc = vtabCallConstructor(db, pTab, pMod, pMod->pModule->xConnect, &zErr); + rc = vtabCallConstructor(db, iDb, pTab, pMod,pMod->pModule->xConnect,&zErr); if( rc!=SQLITE_OK ){ sqlite3ErrorMsg(pParse, "%s", zErr); pParse->rc = rc; } sqlite3DbFree(db, zErr); @@ -712,19 +719,19 @@ */ if( pMod==0 || pMod->pModule->xCreate==0 || pMod->pModule->xDestroy==0 ){ *pzErr = sqlite3MPrintf(db, "no such module: %s", zMod); rc = SQLITE_ERROR; }else{ - rc = vtabCallConstructor(db, pTab, pMod, pMod->pModule->xCreate, pzErr); + rc = vtabCallConstructor(db, iDb, pTab, pMod, pMod->pModule->xCreate,pzErr); } /* Justification of ALWAYS(): The xConstructor method is required to ** create a valid sqlite3_vtab if it returns SQLITE_OK. */ - if( rc==SQLITE_OK && ALWAYS(sqlite3GetVTable(db, pTab)) ){ + if( rc==SQLITE_OK && ALWAYS(sqlite3GetVTable(db, iDb, pTab)) ){ rc = growVTrans(db); if( rc==SQLITE_OK ){ - addToVTrans(db, sqlite3GetVTable(db, pTab)); + addToVTrans(db, sqlite3GetVTable(db, iDb, pTab)); } } return rc; } @@ -1053,11 +1060,11 @@ if( NEVER(pExpr==0) ) return pDef; if( pExpr->op!=TK_COLUMN ) return pDef; pTab = pExpr->y.pTab; if( pTab==0 ) return pDef; if( !IsVirtual(pTab) ) return pDef; - pVtab = sqlite3GetVTable(db, pTab)->pVtab; + pVtab = sqlite3GetVTable(db, -1, pTab)->pVtab; assert( pVtab!=0 ); assert( pVtab->pModule!=0 ); pMod = (sqlite3_module *)pVtab->pModule; if( pMod->xFindFunction==0 ) return pDef; @@ -1102,24 +1109,25 @@ ** Make sure virtual table pTab is contained in the pParse->apVirtualLock[] ** array so that an OP_VBegin will get generated for it. Add pTab to the ** array if it is missing. If pTab is already in the array, this routine ** is a no-op. */ -void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){ +void sqlite3VtabMakeWritable(Parse *pParse, int iDb, Table *pTab){ Parse *pToplevel = sqlite3ParseToplevel(pParse); + VTable *pVTab = sqlite3GetVTable(pParse->db, iDb, pTab); int i, n; - Table **apVtabLock; + VTable **apVtabLock; assert( IsVirtual(pTab) ); for(i=0; inVtabLock; i++){ - if( pTab==pToplevel->apVtabLock[i] ) return; + if( pVTab==pToplevel->apVtabLock[i] ) return; } n = (pToplevel->nVtabLock+1)*sizeof(pToplevel->apVtabLock[0]); apVtabLock = sqlite3_realloc64(pToplevel->apVtabLock, n); if( apVtabLock ){ pToplevel->apVtabLock = apVtabLock; - pToplevel->apVtabLock[pToplevel->nVtabLock++] = pTab; + pToplevel->apVtabLock[pToplevel->nVtabLock++] = pVTab; }else{ sqlite3OomFault(pToplevel->db); } } @@ -1158,11 +1166,11 @@ assert( pTab->nModuleArg==0 ); pTab->iPKey = -1; addModuleArgument(db, pTab, sqlite3DbStrDup(db, pTab->zName)); addModuleArgument(db, pTab, 0); addModuleArgument(db, pTab, sqlite3DbStrDup(db, pTab->zName)); - rc = vtabCallConstructor(db, pTab, pMod, pModule->xConnect, &zErr); + rc = vtabCallConstructor(db, 0, pTab, pMod, pModule->xConnect, &zErr); if( rc ){ sqlite3ErrorMsg(pParse, "%s", zErr); sqlite3DbFree(db, zErr); sqlite3VtabEponymousTableClear(db, pMod); return 0; Index: src/where.c ================================================================== --- src/where.c +++ src/where.c @@ -1029,27 +1029,35 @@ ** The table object reference passed as the second argument to this function ** must represent a virtual table. This function invokes the xBestIndex() ** method of the virtual table with the sqlite3_index_info object that ** comes in as the 3rd argument to this function. ** -** If an error occurs, pParse is populated with an error message and a -** non-zero value is returned. Otherwise, 0 is returned and the output -** part of the sqlite3_index_info structure is left populated. +** If an error occurs, pParse is populated with an error message and an +** appropriate error code is returned. A return of SQLITE_CONSTRAINT from +** xBestIndex is not considered an error. SQLITE_CONSTRAINT indicates that +** the current configuration of "unusable" flags in sqlite3_index_info can +** not result in a valid plan. ** ** Whether or not an error is returned, it is the responsibility of the ** caller to eventually free p->idxStr if p->needToFreeIdxStr indicates ** that this is required. */ -static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ - sqlite3_vtab *pVtab = sqlite3GetVTable(pParse->db, pTab)->pVtab; +static int vtabBestIndex( + Parse *pParse, + struct SrcList_item *pSrc, + sqlite3_index_info *p +){ + Table *pTab = pSrc->pTab; + int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema, pSrc->zDatabase); + sqlite3_vtab *pVtab = sqlite3GetVTable(pParse->db, iDb, pTab)->pVtab; int rc; TRACE_IDX_INPUTS(p); rc = pVtab->pModule->xBestIndex(pVtab, p); TRACE_IDX_OUTPUTS(p); - if( rc!=SQLITE_OK ){ + if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT ){ if( rc==SQLITE_NOMEM ){ sqlite3OomFault(pParse->db); }else if( !pVtab->zErrMsg ){ sqlite3ErrorMsg(pParse, "%s", sqlite3ErrStr(rc)); }else{ @@ -1056,23 +1064,11 @@ sqlite3ErrorMsg(pParse, "%s", pVtab->zErrMsg); } } sqlite3_free(pVtab->zErrMsg); pVtab->zErrMsg = 0; - -#if 0 - /* This error is now caught by the caller. - ** Search for "xBestIndex malfunction" below */ - for(i=0; inConstraint; i++){ - if( !p->aConstraint[i].usable && p->aConstraintUsage[i].argvIndex>0 ){ - sqlite3ErrorMsg(pParse, - "table %s: xBestIndex returned an invalid plan", pTab->zName); - } - } -#endif - - return pParse->nErr; + return rc; } #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 /* @@ -3140,12 +3136,22 @@ pIdxInfo->estimatedRows = 25; pIdxInfo->idxFlags = 0; pIdxInfo->colUsed = (sqlite3_int64)pSrc->colUsed; /* Invoke the virtual table xBestIndex() method */ - rc = vtabBestIndex(pParse, pSrc->pTab, pIdxInfo); - if( rc ) return rc; + rc = vtabBestIndex(pParse, pSrc, pIdxInfo); + if( rc ){ + if( rc==SQLITE_CONSTRAINT ){ + /* If the xBestIndex method returns SQLITE_CONSTRAINT, that means + ** that the particular combination of parameters provided is unusable. + ** Make no entries in the loop table. + */ + WHERETRACE(0xffff, (" ^^^^--- non-viable plan rejected!\n")); + return SQLITE_OK; + } + return rc; + } mxTerm = -1; assert( pNew->nLSlot>=nConstraint ); for(i=0; iaLTerm[i] = 0; pNew->u.vtab.omitMask = 0; @@ -4940,18 +4946,18 @@ int iDb; /* Index of database containing table/index */ struct SrcList_item *pTabItem; pTabItem = &pTabList->a[pLevel->iFrom]; pTab = pTabItem->pTab; - iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + iDb = sqlite3SchemaToIndex(db, pTab->pSchema, pTabItem->zDatabase); pLoop = pLevel->pWLoop; if( (pTab->tabFlags & TF_Ephemeral)!=0 || pTab->pSelect ){ /* Do nothing */ }else #ifndef SQLITE_OMIT_VIRTUALTABLE if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)!=0 ){ - const char *pVTab = (const char *)sqlite3GetVTable(db, pTab); + const char *pVTab = (const char *)sqlite3GetVTable(db, iDb, pTab); int iCur = pTabItem->iCursor; sqlite3VdbeAddOp4(v, OP_VOpen, iCur, 0, 0, pVTab, P4_VTAB); }else if( IsVirtual(pTab) ){ /* noop */ }else Index: src/whereexpr.c ================================================================== --- src/whereexpr.c +++ src/whereexpr.c @@ -407,11 +407,11 @@ if( pCol->op==TK_COLUMN && IsVirtual(pCol->y.pTab) ){ sqlite3_vtab *pVtab; sqlite3_module *pMod; void (*xNotUsed)(sqlite3_context*,int,sqlite3_value**); void *pNotUsed; - pVtab = sqlite3GetVTable(db, pCol->y.pTab)->pVtab; + pVtab = sqlite3GetVTable(db, -1, pCol->y.pTab)->pVtab; assert( pVtab!=0 ); assert( pVtab->pModule!=0 ); pMod = (sqlite3_module *)pVtab->pModule; if( pMod->xFindFunction!=0 ){ i = pMod->xFindFunction(pVtab,2, pExpr->u.zToken, &xNotUsed, &pNotUsed); Index: test/alter2.test ================================================================== --- test/alter2.test +++ test/alter2.test @@ -62,10 +62,11 @@ # proc alter_table {tbl sql {file_format 2}} { sqlite3 dbat test.db set s [string map {' ''} $sql] set t [string map {' ''} $tbl] + sqlite3_db_config dbat DEFENSIVE 0 dbat eval [subst { PRAGMA writable_schema = 1; UPDATE sqlite_master SET sql = '$s' WHERE name = '$t' AND type = 'table'; PRAGMA writable_schema = 0; }] @@ -89,10 +90,11 @@ #----------------------------------------------------------------------- # Some basic tests to make sure short rows are handled. # +sqlite3_db_config db DEFENSIVE 0 do_test alter2-1.1 { execsql { CREATE TABLE abc(a, b); INSERT INTO abc VALUES(1, 2); INSERT INTO abc VALUES(3, 4); Index: test/altercol.test ================================================================== --- test/altercol.test +++ test/altercol.test @@ -551,10 +551,11 @@ DROP TRIGGER tr1; CREATE INDEX x1i ON x1(i); SELECT sql FROM sqlite_master WHERE name='x1i'; } {{CREATE INDEX x1i ON x1(i)}} +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 13.1.4 { PRAGMA writable_schema = 1; UPDATE sqlite_master SET sql = 'CREATE INDEX x1i ON x1(j)' WHERE name='x1i'; } {} Index: test/alterlegacy.test ================================================================== --- test/alterlegacy.test +++ test/alterlegacy.test @@ -465,6 +465,5 @@ ALTER TABLE t1 RENAME TO tt1; } finish_test - Index: test/altertab.test ================================================================== --- test/altertab.test +++ test/altertab.test @@ -503,6 +503,5 @@ SELECT sql FROM sqlite_master WHERE name = 'y'; } {{CREATE VIEW y AS SELECT f2 AS f1 FROM x}} finish_test - Index: test/altertab2.test ================================================================== --- test/altertab2.test +++ test/altertab2.test @@ -39,8 +39,51 @@ INSERT INTO rr VALUES('in', 'tcl'); SELECT * FROM ffff; } {hello world in tcl} } +#------------------------------------------------------------------------- +# Check that table names that appear in REFERENCES clauses are updated +# when a table is renamed unless: +# +# a) "PRAGMA legacy_alter_table" is true, and +# b) "PRAGMA foreign_keys" is false. +# +do_execsql_test 2.0 { + CREATE TABLE p1(a PRIMARY KEY, b); + CREATE TABLE c1(x REFERENCES p1); + CREATE TABLE c2(x, FOREIGN KEY (x) REFERENCES p1); + CREATE TABLE c3(x, FOREIGN KEY (x) REFERENCES p1(a)); +} + +do_execsql_test 2.1 { + ALTER TABLE p1 RENAME TO p2; + SELECT sql FROM sqlite_master WHERE name LIKE 'c%'; +} { + {CREATE TABLE c1(x REFERENCES "p2")} + {CREATE TABLE c2(x, FOREIGN KEY (x) REFERENCES "p2")} + {CREATE TABLE c3(x, FOREIGN KEY (x) REFERENCES "p2"(a))} +} + +do_execsql_test 2.2 { + PRAGMA legacy_alter_table = 1; + ALTER TABLE p2 RENAME TO p3; + SELECT sql FROM sqlite_master WHERE name LIKE 'c%'; +} { + {CREATE TABLE c1(x REFERENCES "p2")} + {CREATE TABLE c2(x, FOREIGN KEY (x) REFERENCES "p2")} + {CREATE TABLE c3(x, FOREIGN KEY (x) REFERENCES "p2"(a))} +} + +do_execsql_test 2.3 { + ALTER TABLE p3 RENAME TO p2; + PRAGMA foreign_keys = 1; + ALTER TABLE p2 RENAME TO p3; + SELECT sql FROM sqlite_master WHERE name LIKE 'c%'; +} { + {CREATE TABLE c1(x REFERENCES "p3")} + {CREATE TABLE c2(x, FOREIGN KEY (x) REFERENCES "p3")} + {CREATE TABLE c3(x, FOREIGN KEY (x) REFERENCES "p3"(a))} +} + finish_test - Index: test/analyze.test ================================================================== --- test/analyze.test +++ test/analyze.test @@ -348,10 +348,11 @@ # This test corrupts the database file so it must be the last test # in the series. # do_test analyze-5.99 { + sqlite3_db_config db DEFENSIVE 0 execsql { PRAGMA writable_schema=on; UPDATE sqlite_master SET sql='nonsense' WHERE name='sqlite_stat1'; } db close Index: test/attach.test ================================================================== --- test/attach.test +++ test/attach.test @@ -727,10 +727,11 @@ } {1 {trigger r5 cannot reference objects in database temp}} } ;# endif subquery ifcapable json1 { do_test attach-5.10 { db close + catch {db2 close} forcedelete test.db sqlite3 db test.db db eval { CREATE TABLE t1(x); CREATE TABLE t2(a,b); Index: test/autoinc.test ================================================================== --- test/autoinc.test +++ test/autoinc.test @@ -695,10 +695,11 @@ # do_test autoinc-12.1 { db close forcedelete test.db sqlite3 db test.db + sqlite3_db_config db DEFENSIVE 0 db eval { CREATE TABLE fake_sequence(name TEXT PRIMARY KEY,seq) WITHOUT ROWID; PRAGMA writable_schema=on; UPDATE sqlite_master SET sql=replace(sql,'fake_','sqlite_'), @@ -716,10 +717,11 @@ } {1 {database disk image is malformed}} do_test autoinc-12.2 { db close forcedelete test.db sqlite3 db test.db + sqlite3_db_config db DEFENSIVE 0 db eval { CREATE TABLE t1(a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT); INSERT INTO t1(b) VALUES('one'); PRAGMA writable_schema=on; UPDATE sqlite_master SET @@ -742,10 +744,11 @@ } do_test autoinc-12.3 { db close forcedelete test.db sqlite3 db test.db + sqlite3_db_config db DEFENSIVE 0 db eval { CREATE TABLE t1(a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT); INSERT INTO t1(b) VALUES('one'); PRAGMA writable_schema=on; UPDATE sqlite_master SET @@ -770,10 +773,11 @@ } set root1 [db one {SELECT rootpage FROM sqlite_master WHERE name='sqlite_sequence'}] set root2 [db one {SELECT rootpage FROM sqlite_master WHERE name='fake'}] + sqlite3_db_config db DEFENSIVE 0 db eval { PRAGMA writable_schema=on; UPDATE sqlite_master SET rootpage=$root2 WHERE name='sqlite_sequence'; UPDATE sqlite_master SET rootpage=$root1 @@ -789,10 +793,11 @@ breakpoint do_test autoinc-12.5 { db close forcedelete test.db sqlite3 db test.db + sqlite3_db_config db DEFENSIVE 0 db eval { CREATE TABLE t1(a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT); INSERT INTO t1(b) VALUES('one'); PRAGMA writable_schema=on; UPDATE sqlite_master SET @@ -808,10 +813,11 @@ } {1 {database disk image is malformed}} do_test autoinc-12.6 { db close forcedelete test.db sqlite3 db test.db + sqlite3_db_config db DEFENSIVE 0 db eval { CREATE TABLE t1(a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT); INSERT INTO t1(b) VALUES('one'); PRAGMA writable_schema=on; UPDATE sqlite_master SET @@ -829,10 +835,11 @@ } {0 ok} do_test autoinc-12.7 { db close forcedelete test.db sqlite3 db test.db + sqlite3_db_config db DEFENSIVE 0 db eval { CREATE TABLE t1(a INTEGER PRIMARY KEY AUTOINCREMENT, b TEXT); INSERT INTO t1(b) VALUES('one'); PRAGMA writable_schema=on; UPDATE sqlite_master SET Index: test/bestindex4.test ================================================================== --- test/bestindex4.test +++ test/bestindex4.test @@ -153,21 +153,23 @@ do_execsql_test 2.0 { CREATE VIRTUAL TABLE x1 USING tcl(vtab_command); CREATE TABLE t1 (x INT PRIMARY KEY); } {} -do_execsql_test 2.1 { - EXPLAIN QUERY PLAN SELECT * FROM t1, x1 WHERE x1.d=t1.x; +do_eqp_test 2.1 { + SELECT * FROM t1, x1 WHERE x1.d=t1.x; } { - 3 0 0 {SCAN TABLE x1 VIRTUAL TABLE INDEX 0:} - 7 0 0 {SEARCH TABLE t1 USING COVERING INDEX sqlite_autoindex_t1_1 (x=?)} + QUERY PLAN + |--SCAN TABLE x1 VIRTUAL TABLE INDEX 0: + `--SEARCH TABLE t1 USING COVERING INDEX sqlite_autoindex_t1_1 (x=?) } -do_execsql_test 2.2 { - EXPLAIN QUERY PLAN SELECT * FROM t1, x1(t1.x) +do_eqp_test 2.2 { + SELECT * FROM t1, x1(t1.x) } { - 3 0 0 {SCAN TABLE t1} - 5 0 0 {SCAN TABLE x1 VIRTUAL TABLE INDEX 555:} + QUERY PLAN + |--SCAN TABLE t1 + `--SCAN TABLE x1 VIRTUAL TABLE INDEX 555: } finish_test Index: test/bestindex5.test ================================================================== --- test/bestindex5.test +++ test/bestindex5.test @@ -245,6 +245,5 @@ do_execsql_test 3.5 { SELECT rowid, * FROM t4 WHERE rowid!=1 OR x!='245'; } {} finish_test - Index: test/capi3.test ================================================================== --- test/capi3.test +++ test/capi3.test @@ -739,10 +739,11 @@ } db close } {} do_test capi3-8.2 { sqlite3 db test.db + sqlite3_db_config db DEFENSIVE 0 execsql { PRAGMA writable_schema=ON; INSERT INTO sqlite_master VALUES(NULL,NULL,NULL,NULL,NULL); } db close @@ -757,10 +758,11 @@ # Build a 5-field row record. The first field is a string 'table', and # subsequent fields are all NULL. db close forcedelete test.db test.db-journal sqlite3 db test.db + sqlite3_db_config db DEFENSIVE 0 execsql { CREATE TABLE t1(a); PRAGMA writable_schema=ON; INSERT INTO sqlite_master VALUES('table',NULL,NULL,NULL,NULL); } Index: test/capi3c.test ================================================================== --- test/capi3c.test +++ test/capi3c.test @@ -684,10 +684,11 @@ } db close } {} do_test capi3c-8.2 { sqlite3 db test.db + sqlite3_db_config db DEFENSIVE 0 execsql { PRAGMA writable_schema=ON; INSERT INTO sqlite_master VALUES(NULL,NULL,NULL,NULL,NULL); } db close @@ -702,10 +703,11 @@ # Build a 5-field row record. The first field is a string 'table', and # subsequent fields are all NULL. db close forcedelete test.db test.db-journal sqlite3 db test.db + sqlite3_db_config db DEFENSIVE 0 execsql { CREATE TABLE t1(a); PRAGMA writable_schema=ON; INSERT INTO sqlite_master VALUES('table',NULL,NULL,NULL,NULL); } Index: test/corrupt.test ================================================================== --- test/corrupt.test +++ test/corrupt.test @@ -130,10 +130,11 @@ } {} do_test corrupt-3.2 { set t1_r [execsql {SELECT rootpage FROM sqlite_master WHERE name = 't1i1'}] set t1i1_r [execsql {SELECT rootpage FROM sqlite_master WHERE name = 't1'}] set cookie [expr [execsql {PRAGMA schema_version}] + 1] + sqlite3_db_config db DEFENSIVE 0 execsql " PRAGMA writable_schema = 1; UPDATE sqlite_master SET rootpage = $t1_r WHERE name = 't1'; UPDATE sqlite_master SET rootpage = $t1i1_r WHERE name = 't1i1'; PRAGMA writable_schema = 0; Index: test/corrupt2.test ================================================================== --- test/corrupt2.test +++ test/corrupt2.test @@ -131,10 +131,11 @@ forcedelete corrupt.db forcedelete corrupt.db-journal forcecopy test.db corrupt.db sqlite3 db2 corrupt.db + sqlite3_db_config db2 DEFENSIVE 0 execsql " $::presql CREATE INDEX a1 ON abc(a); CREATE INDEX a2 ON abc(b); PRAGMA writable_schema = 1; @@ -263,10 +264,11 @@ catch {db close} forcedelete corrupt.db forcedelete corrupt.db-journal sqlite3 db corrupt.db + sqlite3_db_config db DEFENSIVE 0 db eval $::presql eval $A(-tclprep) db eval $A(-sqlprep) db close Index: test/corrupt5.test ================================================================== --- test/corrupt5.test +++ test/corrupt5.test @@ -31,10 +31,11 @@ } # Create a database with a freelist containing at least two pages. # do_test corrupt5-1.1 { + sqlite3_db_config db DEFENSIVE 0 execsql { CREATE TABLE t1(a,b,c); CREATE INDEX i1 ON t1(a,b); PRAGMA writable_schema=ON; UPDATE sqlite_master SET name=NULL, sql=NULL WHERE name='i1'; Index: test/corruptI.test ================================================================== --- test/corruptI.test +++ test/corruptI.test @@ -220,10 +220,11 @@ INSERT INTO t1 VALUES('a', 'A'); INSERT INTO t1 VALUES('b', 'A'); INSERT INTO t1 VALUES('c', 'A'); SELECT name FROM sqlite_master; } {t1 sqlite_autoindex_t1_1} +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 7.1 { PRAGMA writable_schema = 1; DELETE FROM sqlite_master WHERE name = 'sqlite_autoindex_t1_1'; } do_test 7.2 { Index: test/corruptK.test ================================================================== --- test/corruptK.test +++ test/corruptK.test @@ -141,11 +141,11 @@ CREATE TABLE t2(a, b, c); CREATE TABLE t3(a, b, c); CREATE TABLE t4(a, b, c); CREATE TABLE t5(a, b, c); } - + sqlite3_db_config db DEFENSIVE 0 do_execsql_test 3.2 { UPDATE sqlite_dbpage SET data = hex2blob(' 000: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. 010: 04 00 01 01 20 40 20 20 00 00 3e d9 00 00 00 06 .... @ ..>..... 020: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................ Index: test/countofview.test ================================================================== --- test/countofview.test +++ test/countofview.test @@ -39,6 +39,5 @@ select c from t2 union all select f from t3 ) } {3} finish_test - Index: test/csv01.test ================================================================== --- test/csv01.test +++ test/csv01.test @@ -35,10 +35,67 @@ SELECT * FROM t1 WHERE c1='10'; } {9 10 11 12} do_execsql_test 1.2 { SELECT rowid FROM t1; } {1 2 3 4} + +do_execsql_test 1.3 { + DROP TABLE temp.t1; + CREATE VIRTUAL TABLE temp.t1 USING csv( + data= +'a,b,"mix-bloom-eel","soft opinion" +1,2,3,4 +5,6,7,8 +9,10,11,12 +13,14,15,16 +', + header=1 + ); + SELECT * FROM t1 WHERE "soft opinion"=12; +} {9 10 11 12} +do_execsql_test 1.4 { + SELECT name FROM pragma_table_xinfo('t1'); +} {a b mix-bloom-eel {soft opinion}} + +do_execsql_test 1.5 { + DROP TABLE temp.t1; + CREATE VIRTUAL TABLE temp.t1 USING csv( + data= +'a,b,"mix-bloom-eel","soft opinion" +1,2,3,4 +5,6,7,8 +9,10,11,12 +13,14,15,16 +', + header=false + ); + SELECT * FROM t1 WHERE c1='b'; +} {a b mix-bloom-eel {soft opinion}} +do_execsql_test 1.6 { + SELECT name FROM pragma_table_xinfo('t1'); +} {c0 c1 c2 c3} + +do_execsql_test 1.7 { + DROP TABLE temp.t1; + CREATE VIRTUAL TABLE temp.t1 USING csv( + data= +'a,b,"mix-bloom-eel","soft opinion" +1,2,3,4 +5,6,7,8 +9,10,11,12 +13,14,15,16 +', + header, + schema='CREATE TABLE x(x0,x1,x2,x3,x4)', + columns=5 + ); + SELECT * FROM t1 WHERE x1='6'; +} {5 6 7 8 {}} +do_execsql_test 1.8 { + SELECT name FROM pragma_table_xinfo('t1'); +} {x0 x1 x2 x3 x4} + do_execsql_test 2.0 { DROP TABLE t1; CREATE VIRTUAL TABLE temp.t2 USING csv( data= @@ -105,11 +162,11 @@ columns=4, schema= 'CREATE TABLE t3(a,b,c,d,PRIMARY KEY(a,b)) WITHOUT ROWID', testflags=1 ); -} {1 {vtable constructor failed: t4}} +} {1 {bad schema: 'CREATE TABLE t3(a,b,c,d,PRIMARY KEY(a,b)) WITHOUT ROWID' - not an error}} # WITHOUT ROWID tables with a single-column PRIMARY KEY may be writable. do_catchsql_test 4.1 { DROP TABLE IF EXISTS t4; CREATE VIRTUAL TABLE temp.t4 USING csv_wr( @@ -136,11 +193,11 @@ columns=4, schema= 'CREATE TABLE t3(a,b,c,d) WITHOUT ROWID', testflags=1 ); -} {1 {vtable constructor failed: t5}} +} {1 {bad schema: 'CREATE TABLE t3(a,b,c,d) WITHOUT ROWID' - PRIMARY KEY missing on table t3}} # 2018-04-24 # Memory leak reported on the sqlite-users mailing list by Ralf Junker. # do_catchsql_test 4.3 { Index: test/dbpage.test ================================================================== --- test/dbpage.test +++ test/dbpage.test @@ -19,10 +19,11 @@ ifcapable !vtab||!compound { finish_test return } +sqlite3_db_config db DEFENSIVE 0 do_test 100 { execsql { PRAGMA auto_vacuum=0; PRAGMA page_size=4096; PRAGMA journal_mode=WAL; Index: test/default.test ================================================================== --- test/default.test +++ test/default.test @@ -104,10 +104,11 @@ # in the sqlite_master table, for backwards compatibility. # db close forcedelete test.db sqlite3 db test.db +sqlite3_db_config db DEFENSIVE 0 do_execsql_test default-4.0 { CREATE TABLE t1(a TEXT, b TEXT DEFAULT(99)); PRAGMA writable_schema=ON; UPDATE sqlite_master SET sql='CREATE TABLE t1(a TEXT, b TEXT DEFAULT(:xyz))'; } {} Index: test/e_fkey.test ================================================================== --- test/e_fkey.test +++ test/e_fkey.test @@ -2795,16 +2795,18 @@ } } {{CREATE TABLE c(b REFERENCES "parent"(a))}} do_test e_fkey-61.2.2 { execsql { PRAGMA foreign_keys = OFF; + PRAGMA legacy_alter_table = ON; ALTER TABLE p RENAME TO parent; SELECT sql FROM sqlite_master WHERE name = 'c'; } } {{CREATE TABLE c(b REFERENCES p(a))}} do_test e_fkey-61.2.3 { execsql { PRAGMA foreign_keys = ON } + execsql { PRAGMA legacy_alter_table = OFF } } {} do_test e_fkey-61.3.1 { drop_all_tables execsql { Index: test/e_fts3.test ================================================================== --- test/e_fts3.test +++ test/e_fts3.test @@ -675,10 +675,11 @@ ddl_test 10.1.1 { CREATE VIRTUAL TABLE ta USING fts3 } write_test 10.1.2 ta_content { INSERT INTO ta VALUES('During a summer vacation in 1790') } write_test 10.1.3 ta_content { INSERT INTO ta VALUES('Wordsworth went on a walking tour') } +sqlite3_db_config db DEFENSIVE 0 write_test 10.1.4 ta_content { DELETE FROM ta_content WHERE rowid = 2 } read_test 10.1.5 { SELECT * FROM ta WHERE ta MATCH 'summer' } {{During a summer vacation in 1790}} error_test 10.1.6 { Index: test/e_reindex.test ================================================================== --- test/e_reindex.test +++ test/e_reindex.test @@ -42,10 +42,11 @@ # recreate indices from scratch. # # Test this by corrupting some database indexes, running REINDEX, and # observing that the corruption is gone. # +sqlite3_db_config db DEFENSIVE 0 do_execsql_test e_reindex-1.1 { INSERT INTO t1 VALUES(1, 2); INSERT INTO t1 VALUES(3, 4); INSERT INTO t1 VALUES(5, 6); @@ -55,10 +56,11 @@ DELETE FROM sqlite_master WHERE type = 'index'; } {} db close sqlite3 db test.db +sqlite3_db_config db DEFENSIVE 0 do_execsql_test e_reindex-1.2 { DELETE FROM t1 WHERE a = 3; INSERT INTO t1 VALUES(7, 8); INSERT INTO t1 VALUES(9, 10); PRAGMA writable_schema = 1; Index: test/fts3auto.test ================================================================== --- test/fts3auto.test +++ test/fts3auto.test @@ -132,10 +132,11 @@ } # fts3_zero_long_segments TABLE ?LIMIT? # proc fts3_zero_long_segments {tbl limit} { + sqlite3_db_config db DEFENSIVE 0 execsql " UPDATE ${tbl}_segments SET block = zeroblob(length(block)) WHERE length(block)>$limit " Index: test/fts3corrupt.test ================================================================== --- test/fts3corrupt.test +++ test/fts3corrupt.test @@ -21,10 +21,11 @@ # Test that a doclist with a length field that indicates that the doclist # extends past the end of the node on which it resides is correctly identified # as database corruption. # +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 1.0 { CREATE VIRTUAL TABLE t1 USING fts3; INSERT INTO t1 VALUES('hello'); } {} do_test fts3corrupt-1.1 { Index: test/fts3corrupt2.test ================================================================== --- test/fts3corrupt2.test +++ test/fts3corrupt2.test @@ -47,10 +47,11 @@ "ajfqz aethlgir aclcx aowlyvetby aproqm afjlqtkv anebfy akzrcpfrrvw" "aoledfotm aiwlfm aeejlaej anz abgbvk aktfn aayoh anpywgdvgz" "acvmldguld asdvz aqb aeomsyzyu aggylhprbdz asrfkwz auipybpsn agsnszzfb" } +sqlite3_db_config db DEFENSIVE 0 do_test fts3corrupt2-1.0 { execsql BEGIN execsql { CREATE VIRTUAL TABLE t2 USING FTS3(a, b); } execsql { INSERT INTO t2(t2) VALUES('nodesize=32') } foreach d $data { Index: test/fts3corrupt3.test ================================================================== --- test/fts3corrupt3.test +++ test/fts3corrupt3.test @@ -31,10 +31,11 @@ COMMIT; } do_execsql_test 1.1 { SELECT quote(root) from t1_segdir; } {X'00036F6E6509010200010200010200'} +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 1.2 { UPDATE t1_segdir SET root = X'00036F6E650EFFFFFFFFFFFFFFFFFFFFFFFF0200'; } do_catchsql_test 1.3 { SELECT rowid FROM t1 WHERE t1 MATCH 'one' Index: test/fts3corrupt4.test ================================================================== --- test/fts3corrupt4.test +++ test/fts3corrupt4.test @@ -38,10 +38,11 @@ do_execsql_test 1.1 { SELECT quote(root) FROM ft_segdir; } {X'0005616261636B03010200030266740302020003046E646F6E03030200'} +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 1.2 { UPDATE ft_segdir SET root = blob( '0005616261636B03010200 FFFFFFFF0702 66740302020003046E646F6E03030200' ); } @@ -81,10 +82,11 @@ do_execsql_test 2.2 { SELECT quote(block) FROM ft_segments WHERE blockid=2 } {X'00056162633130031F0200'} db func blob blob +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 2.3.1 { UPDATE ft_segments SET block = blob('00056162633130031F0200 FFFFFFFF07FF55 66740302020003046E646F6E03030200') WHERE blockid=2; } {} @@ -131,10 +133,11 @@ do_execsql_test 3.1 { SELECT quote(root) FROM ft_segdir } {X'0101056162633132040136030132030136'} db func blob blob +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 3.2 { UPDATE ft_segdir SET root = blob('0101056162633132FFFFFFFF070236030132030136'); } @@ -141,7 +144,5 @@ do_catchsql_test 3.1 { SELECT * FROM ft WHERE ft MATCH 'abc20' } {1 {database disk image is malformed}} finish_test - - Index: test/fts3cov.test ================================================================== --- test/fts3cov.test +++ test/fts3cov.test @@ -87,10 +87,11 @@ SELECT substr(hex(root), 1, 2) FROM t1_segdir; } } {03} # Test the "missing entry" case: +sqlite3_db_config db DEFENSIVE 0 do_test fts3cov-2.2 { set root [db one {SELECT root FROM t1_segdir}] read_fts3varint [string range $root 1 end] left_child execsql { DELETE FROM t1_segments WHERE blockid = $left_child } } {} @@ -403,10 +404,11 @@ SELECT rowid FROM t15 WHERE t15 MATCH '"abc* def2"' } {1 2} # Test a corruption case. # +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 16.1 { CREATE VIRTUAL TABLE t16 USING fts4; INSERT INTO t16 VALUES('theoretical work to examine the relationship'); INSERT INTO t16 VALUES('solution of our problems on the invisible'); DELETE FROM t16_content WHERE rowid = 2; Index: test/fts3d.test ================================================================== --- test/fts3d.test +++ test/fts3d.test @@ -294,10 +294,11 @@ SELECT level, idx FROM t1_segdir ORDER BY level, idx; } } {{Index already optimal} 1 0} # Even if we move things around, still does nothing. +sqlite3_db_config db DEFENSIVE 0 do_test fts3d-5.1 { execsql { UPDATE t1_segdir SET level = 2 WHERE level = 1 AND idx = 0; SELECT OPTIMIZE(t1) FROM t1 LIMIT 1; SELECT level, idx FROM t1_segdir ORDER BY level, idx; Index: test/fts3defer.test ================================================================== --- test/fts3defer.test +++ test/fts3defer.test @@ -57,10 +57,11 @@ 5 {SELECT rowid FROM t1 WHERE t1 MATCH 'a dog'} {1} } do_select_tests 1.2 $tests +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 1.3 { SELECT count(*) FROM t1_segments WHERE length(block)>10000; UPDATE t1_segments SET block = zeroblob(length(block)) WHERE length(block)>10000; @@ -223,25 +224,28 @@ 3 { set dmt_modes {0 1 2} execsql { CREATE VIRTUAL TABLE t1 USING FTS4 } foreach doc $data { execsql { INSERT INTO t1 VALUES($doc) } } add_empty_records 1000 + sqlite3_db_config db DEFENSIVE 0 execsql $zero_long_doclists } 4 { set dmt_modes 0 execsql { CREATE VIRTUAL TABLE t1 USING FTS4 } foreach doc $data { execsql { INSERT INTO t1 VALUES($doc) } } add_empty_records 1000 execsql "INSERT INTO t1(t1) VALUES('optimize')" + sqlite3_db_config db DEFENSIVE 0 execsql $zero_long_doclists } 5 { set dmt_modes 0 execsql { CREATE VIRTUAL TABLE t1 USING FTS4(matchinfo=fts3) } foreach doc $data { execsql { INSERT INTO t1 VALUES($doc) } } add_empty_records 1000 + sqlite3_db_config db DEFENSIVE 0 execsql $zero_long_doclists } } { execsql { DROP TABLE IF EXISTS t1 } Index: test/fts3defer2.test ================================================================== --- test/fts3defer2.test +++ test/fts3defer2.test @@ -44,10 +44,11 @@ INSERT INTO t1 VALUES(''); INSERT INTO t1 VALUES(''); INSERT INTO t1 VALUES(''); INSERT INTO t1(t1) VALUES('optimize'); } +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 1.1.4 { SELECT count(*) FROM t1_segments WHERE length(block)>10000; UPDATE t1_segments SET block = zeroblob(length(block)) WHERE length(block)>10000; } {2} @@ -96,10 +97,11 @@ 2 { INSERT INTO t2(t2) VALUES('optimize') } 3 { UPDATE t2_segments SET block = zeroblob(length(block)) WHERE length(block)>10000; } } { + sqlite3_db_config db DEFENSIVE 0 execsql $sql do_execsql_test 2.2.$tn.1 { SELECT mit(matchinfo(t2, 'pcxnal')) FROM t2 WHERE t2 MATCH 'a b'; } [list \ @@ -150,13 +152,14 @@ 2 { INSERT INTO t3(t3) VALUES('optimize') } 3 { UPDATE t3_segments SET block = zeroblob(length(block)) WHERE length(block)>10000; } } { + sqlite3_db_config db DEFENSIVE 0 execsql $sql do_execsql_test 2.4.$tn { SELECT docid, mit(matchinfo(t3, 'pcxnal')) FROM t3 WHERE t3 MATCH '"a b c"'; } {1 {1 1 1 4 4 11 912 6} 3 {1 1 1 4 4 11 912 6}} } finish_test Index: test/fts3matchinfo.test ================================================================== --- test/fts3matchinfo.test +++ test/fts3matchinfo.test @@ -276,10 +276,11 @@ do_matchinfo_test 4.3.6 t5 {t5 MATCH 'a OR b'} { s {1 2 1 1} } do_execsql_test 4.4.0.1 { INSERT INTO t5(t5) VALUES('optimize') } ifcapable fts4_deferred { + sqlite3_db_config db DEFENSIVE 0 do_execsql_test 4.4.0.2 { UPDATE t5_segments SET block = zeroblob(length(block)) WHERE length(block)>10000; } @@ -337,10 +338,11 @@ 'this record is used to try to dectect corruption' ); SELECT offsets(t9) FROM t9 WHERE t9 MATCH 'to'; } {{0 0 20 2 0 0 27 2}} +sqlite3_db_config db DEFENSIVE 0 do_catchsql_test 6.2 { UPDATE t9_content SET c0content = 'this record is used to'; SELECT offsets(t9) FROM t9 WHERE t9 MATCH 'to'; } {1 {database disk image is malformed}} @@ -390,10 +392,11 @@ do_execsql_test 8.3 { SELECT mit(matchinfo(t11, 'nxa')) FROM t11 WHERE t11 MATCH 'a*' } {{204 1 3 3 0} {204 1 3 3 0} {204 1 3 3 0}} # Corruption related tests. +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 8.4.1.1 { UPDATE t11_stat SET value = X'0000'; } do_catchsql_test 8.5.1.2 { SELECT mit(matchinfo(t11, 'nxa')) FROM t11 WHERE t11 MATCH 'a*' } {1 {database disk image is malformed}} Index: test/fts3misc.test ================================================================== --- test/fts3misc.test +++ test/fts3misc.test @@ -158,10 +158,11 @@ SELECT count(*) FROM t4 WHERE t4 MATCH '"a b c" OR "c a b"' } {8000} do_execsql_test 4.2 { SELECT quote(value) from t4_stat where id=0 } {X'C03EC0B204C0A608'} + sqlite3_db_config db DEFENSIVE 0 do_execsql_test 4.3 { UPDATE t4_stat SET value = X'C03EC0B204C0A60800' WHERE id=0; } do_catchsql_test 4.4 { SELECT count(*) FROM t4 WHERE t4 MATCH '"a b c" OR "c a b"' Index: test/fts3query.test ================================================================== --- test/fts3query.test +++ test/fts3query.test @@ -165,10 +165,11 @@ 1 "SELECT matchinfo(content) FROM t2 WHERE t2 MATCH 'history'" matchinfo 2 "SELECT offsets(content) FROM t2 WHERE t2 MATCH 'history'" offsets 3 "SELECT snippet(content) FROM t2 WHERE t2 MATCH 'history'" snippet 4 "SELECT optimize(content) FROM t2 WHERE t2 MATCH 'history'" optimize } +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 5.4.0 { UPDATE t2_content SET c0content = X'1234' } do_select_tests 5.4 -errorformat { illegal first argument to %s } { 1 "SELECT matchinfo(content) FROM t2 WHERE t2 MATCH 'history'" matchinfo Index: test/fts3snippet.test ================================================================== --- test/fts3snippet.test +++ test/fts3snippet.test @@ -182,10 +182,11 @@ set off [string first "onehundred " $numbers] do_offsets_test $T.2.2 {onehundred} \ [list 0 0 $off 10 1 0 $off 10] [list 0 0 $off 10] # Test a corruption case: + sqlite3_db_config db DEFENSIVE 0 execsql { UPDATE ft_content SET c1b = 'hello world' WHERE c1b = $numbers } do_error_test $T.2.3 { SELECT offsets(ft) FROM ft WHERE ft MATCH 'onehundred' } {database disk image is malformed} Index: test/fts4check.test ================================================================== --- test/fts4check.test +++ test/fts4check.test @@ -64,10 +64,11 @@ DELETE FROM t1_segdir WHERE level=0 AND idx=( SELECT max(idx) FROM t1_segdir WHERE level=0 ); } } { + sqlite3_db_config db DEFENSIVE 0 do_execsql_test 1.2.1.$tn "BEGIN; $disruption" do_catchsql_test 1.2.2.$tn { INSERT INTO t1 (t1) VALUES('integrity-check') } {1 {database disk image is malformed}} do_execsql_test 1.2.3.$tn "ROLLBACK" @@ -98,10 +99,11 @@ DELETE FROM t2_segdir WHERE level=0 AND idx=( SELECT max(idx) FROM t2_segdir WHERE level=1024 ); } } { + sqlite3_db_config db DEFENSIVE 0 do_execsql_test 2.2.1.$tn "BEGIN; $disruption" do_catchsql_test 2.2.2.$tn { INSERT INTO t2 (t2) VALUES('integrity-check') } {1 {database disk image is malformed}} do_execsql_test 2.2.3.$tn "ROLLBACK" @@ -143,10 +145,11 @@ UPDATE t3_content SET langid=langid+1 WHERE rowid = ( SELECT max(rowid) FROM t3_content ) } } { + sqlite3_db_config db DEFENSIVE 0 do_execsql_test 3.2.1.$tn "BEGIN; $disruption" do_catchsql_test 3.2.2.$tn { INSERT INTO t3 (t3) VALUES('integrity-check') } {1 {database disk image is malformed}} do_execsql_test 3.2.3.$tn "ROLLBACK" @@ -161,10 +164,11 @@ CREATE VIRTUAL TABLE t4 USING fts4(a, b, c, notindexed=b); INSERT INTO t4 VALUES('text one', 'text two', 'text three'); INSERT INTO t4(t4) VALUES('integrity-check'); } +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 4.1 { PRAGMA writable_schema = 1; UPDATE sqlite_master SET sql = 'CREATE VIRTUAL TABLE t4 USING fts4(a, b, c)' WHERE name = 't4'; @@ -195,10 +199,11 @@ do_execsql_test 5.1 { INSERT INTO t5(t5) VALUES('integrity-check'); } {} +sqlite3_db_config db DEFENSIVE 0 do_catchsql_test 5.2 { INSERT INTO t5_content VALUES(5, 'his hardy mountain pony'); INSERT INTO t5(t5) VALUES('integrity-check'); } {1 {database disk image is malformed}} Index: test/fts4growth.test ================================================================== --- test/fts4growth.test +++ test/fts4growth.test @@ -23,10 +23,11 @@ return } source $testdir/genesis.tcl +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 1.1 { CREATE VIRTUAL TABLE x1 USING fts3; } do_test 1.2 { foreach L { {"See here, young man," said Mulga Bill, "from Walgett to the sea,} Index: test/fts4merge.test ================================================================== --- test/fts4merge.test +++ test/fts4merge.test @@ -154,10 +154,11 @@ do_execsql_test 4.4.1 { SELECT quote(value) FROM t4_stat WHERE rowid=1 } {X'0006'} + sqlite3_db_config db DEFENSIVE 0 do_execsql_test 4.4.2 { DELETE FROM t4_stat WHERE rowid=1; INSERT INTO t4(t4) VALUES('merge=1,12'); SELECT level, group_concat(idx, ' ') FROM t4_segdir GROUP BY level; } "0 {0 1 2 3 4 5} 1 0" Index: test/fts4opt.test ================================================================== --- test/fts4opt.test +++ test/fts4opt.test @@ -35,10 +35,11 @@ # %_segdir table so that all segments within each index are on the same # level. This means that the 'merge' command can then be used for an # incremental optimize routine. # proc prepare_for_optimize {db tbl} { + sqlite3_db_config $db DEFENSIVE 0 $db eval [string map [list % $tbl] { BEGIN; CREATE TEMP TABLE tmp_segdir( level, idx, start_block, leaves_end_block, end_block, root ); Index: test/fuzzdata7.db ================================================================== --- test/fuzzdata7.db +++ test/fuzzdata7.db cannot compute difference between binary files Index: test/index.test ================================================================== --- test/index.test +++ test/index.test @@ -624,11 +624,10 @@ catchsql { CREATE TABLE sqlite_t1(a, b, c); } } {1 {object name reserved for internal use: sqlite_t1}} do_test index-18.1.2 { - sqlite3_db_config db DEFENSIVE 1 catchsql { CREATE TABLE sqlite_t1(a, b, c); } } {1 {object name reserved for internal use: sqlite_t1}} sqlite3_db_config db DEFENSIVE 0 Index: test/index3.test ================================================================== --- test/index3.test +++ test/index3.test @@ -81,10 +81,11 @@ # This test corrupts the database file so it must be the last test # in the series. # do_test index3-99.1 { + sqlite3_db_config db DEFENSIVE 0 execsql { PRAGMA writable_schema=on; UPDATE sqlite_master SET sql='nonsense' WHERE name='t1d' } db close Index: test/loadext.test ================================================================== --- test/loadext.test +++ test/loadext.test @@ -59,13 +59,13 @@ {%s: cannot open shared object file: No such file or directory} set dlerror_notadll {%s: file too short} set dlerror_nosymbol {%s: undefined symbol: %s} if {$::tcl_platform(os) eq "Darwin"} { - set dlerror_nosuchfile {dlopen(%s, 10): image not found} - set dlerror_notadll {dlopen(%1$s, 10): no suitable image found.*} - set dlerror_nosymbol {dlsym(XXX, %2$s): symbol not found} + set dlerror_nosuchfile {dlopen.%s, 10.: .*image.*found.*} + set dlerror_notadll {dlopen.%1$s, 10.: .*image.*found.*} + set dlerror_nosymbol {dlsym.XXX, %2$s.: symbol not found} } if {$::tcl_platform(platform) eq "windows"} { set dlerror_nosuchfile {The specified module could not be found.*} set dlerror_notadll {%%1 is not a valid Win32 application.*} Index: test/misc1.test ================================================================== --- test/misc1.test +++ test/misc1.test @@ -657,10 +657,11 @@ # 2015-04-19: NULL pointer dereference on a corrupt schema # db close sqlite3 db :memory: +sqlite3_db_config db DEFENSIVE 0 do_execsql_test misc1-23.1 { CREATE TABLE t1(x); PRAGMA writable_schema=ON; UPDATE sqlite_master SET sql='CREATE table t(d CHECK(T(#0)'; BEGIN; @@ -672,10 +673,11 @@ # 2015-04-19: Faulty assert() statement # db close database_may_be_corrupt sqlite3 db :memory: +sqlite3_db_config db DEFENSIVE 0 do_catchsql_test misc1-23.2 { CREATE TABLE t1(x UNIQUE); PRAGMA writable_schema=ON; UPDATE sqlite_master SET sql='CREATE TABLE IF not EXISTS t(c)'; BEGIN; @@ -683,10 +685,11 @@ ROLLBACK; DROP TABLE F; } {1 {no such table: F}} db close sqlite3 db :memory: +sqlite3_db_config db DEFENSIVE 0 do_catchsql_test misc1-23.3 { CREATE TABLE t1(x UNIQUE); PRAGMA writable_schema=ON; UPDATE sqlite_master SET sql='CREATE table y(a TEXT, a TEXT)'; BEGIN; Index: test/misc4.test ================================================================== --- test/misc4.test +++ test/misc4.test @@ -210,10 +210,11 @@ # 2015-05-15. Error message formatting problem. # db close sqlite3 db :memory: +sqlite3_db_config db DEFENSIVE 0 do_catchsql_test misc4-7.1 { CREATE TABLE t7(x); PRAGMA writable_schema=ON; UPDATE sqlite_master SET sql='CREATE TABLE [M%s%s%s%s%s%s%s%s%s%s%s%s%s'; VACUUM; Index: test/misc5.test ================================================================== --- test/misc5.test +++ test/misc5.test @@ -588,10 +588,11 @@ # Parser stack overflow is silently ignored when it occurs while parsing the # schema and PRAGMA writable_schema is turned on. # do_test misc5-7.2 { sqlite3 db2 :memory: + sqlite3_db_config db2 DEFENSIVE 0 catchsql { CREATE TABLE t1(x UNIQUE); PRAGMA writable_schema=ON; UPDATE sqlite_master SET sql='CREATE table t(o CHECK(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((;VALUES(o)'; BEGIN; Index: test/misc7.test ================================================================== --- test/misc7.test +++ test/misc7.test @@ -432,10 +432,11 @@ # sqlite3_test_control_pending_page [expr ($::sqlite_pending_byte / 1024) + 1] set ::pending_byte_page [expr ($::sqlite_pending_byte / 1024) + 1] sqlite3_test_control_pending_byte $::sqlite_pending_byte do_test misc7-17.3 { + sqlite3_db_config db DEFENSIVE 0 db eval { pragma writable_schema = true; UPDATE sqlite_master SET rootpage = $pending_byte_page WHERE type = 'table' AND name = 't3'; Index: test/mjournal.test ================================================================== --- test/mjournal.test +++ test/mjournal.test @@ -158,6 +158,5 @@ INSERT INTO t4 VALUES(1); COMMIT; } {0} finish_test - Index: test/notnull.test ================================================================== --- test/notnull.test +++ test/notnull.test @@ -604,6 +604,5 @@ do_uses_op_next_test notnull-6.9 "SELECT * FROM t8 WHERE a IS ?" 0 do_uses_op_next_test notnull-6.10 "SELECT * FROM t8 WHERE a IS ?" 0 finish_test - Index: test/pager1.test ================================================================== --- test/pager1.test +++ test/pager1.test @@ -1877,10 +1877,11 @@ } } {} do_test pager1-18.2 { set root [db one "SELECT rootpage FROM sqlite_master"] set lockingpage [expr (0x10000/1024) + 1] + sqlite3_db_config db DEFENSIVE 0 execsql { PRAGMA writable_schema = 1; UPDATE sqlite_master SET rootpage = $lockingpage; } sqlite3 db2 test.db @@ -1929,10 +1930,11 @@ catchsql { SELECT length(x||'') FROM t2 } db2 } {1 {database disk image is malformed}} db2 close do_test pager1-18.5 { sqlite3 db "" + sqlite3_db_config db DEFENSIVE 0 execsql { CREATE TABLE t1(a, b); CREATE TABLE t2(a, b); PRAGMA writable_schema = 1; UPDATE sqlite_master SET rootpage=5 WHERE tbl_name = 't1'; Index: test/parser1.test ================================================================== --- test/parser1.test +++ test/parser1.test @@ -26,10 +26,11 @@ # Verify that a legacy schema in the sqlite_master file is allowed to have # COLLATE, ASC, and DESC keywords on the id list of a FK constraint, and that # those keywords are silently ignored. # +sqlite3_db_config db DEFENSIVE 0 do_execsql_test parser1-1.2 { CREATE TABLE t1( a TEXT PRIMARY KEY, b TEXT, FOREIGN KEY(b) REFERENCES t1(a) Index: test/pragma.test ================================================================== --- test/pragma.test +++ test/pragma.test @@ -495,10 +495,11 @@ } # Verify that PRAGMA integrity_check catches UNIQUE and NOT NULL # constraint violations. # +sqlite3_db_config db DEFENSIVE 0 do_execsql_test pragma-3.20 { CREATE TABLE t1(a,b); CREATE INDEX t1a ON t1(a); INSERT INTO t1 VALUES(1,1),(2,2),(3,3),(2,4),(NULL,5),(NULL,6); PRAGMA writable_schema=ON; Index: test/resetdb.test ================================================================== --- test/resetdb.test +++ test/resetdb.test @@ -63,10 +63,11 @@ } {210 6000 ok delete 8} do_test 200 { # Thoroughly corrupt the database file by overwriting the first # page with randomness. + sqlite3_db_config db DEFENSIVE 0 catchsql { UPDATE sqlite_dbpage SET data=randomblob(4096) WHERE pgno=1; PRAGMA quick_check; } } {1 {unsupported file format}} @@ -123,10 +124,11 @@ PRAGMA page_count; } db2 } {210 26000 ok wal 8192 12} # Corrupt the database again +sqlite3_db_config db DEFENSIVE 0 do_catchsql_test 320 { UPDATE sqlite_dbpage SET data=randomblob(8192) WHERE pgno=1; PRAGMA quick_check } {1 {file is not a database}} @@ -226,10 +228,11 @@ if {[nonzero_reserved_bytes]} { finish_test return } +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 710 { UPDATE sqlite_dbpage SET data= X'53514C69746520666F726D61742033000200030100402020000000000000001300000000000000000000000300000004000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000D00000003017C0001D801AC017C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002E03061715110145696E6465787431626374310443524541544520494E4445582074316263204F4E20743128622C63292A0206171311013F696E64657874316174310343524541544520494E44455820743161204F4E20743128612926010617111101397461626C657431743102435245415445205441424C4520743128612C622C6329' WHERE pgno=1; } Index: test/reuse1.test ================================================================== --- test/reuse1.test +++ test/reuse1.test @@ -88,7 +88,301 @@ 9 { DROP TRIGGER tr1 } 10 { ANALYZE } } { do_catchsql_test 1.5.$tn $sql {1 {attempt to modify read-only schema}} } + +#------------------------------------------------------------------------- +# +reset_db +forcedelete test.db2 +ifcapable fts5 { + do_execsql_test 2.0 { + CREATE VIRTUAL TABLE ft USING fts5(a); + INSERT INTO ft VALUES('one'), ('two'), ('three'); + ATTACH 'test.db2' AS aux; + CREATE VIRTUAL TABLE aux.ft USING fts5(a); + INSERT INTO aux.ft VALUES('aux1'), ('aux2'), ('aux3'); + } + + db close + sqlite3 db test.db -reuse-schema 1 + + do_execsql_test 2.1 { + ATTACH 'test.db2' AS aux; + SELECT * FROM main.ft; + } {one two three} + + do_execsql_test 2.2 { + SELECT * FROM aux.ft; + } {aux1 aux2 aux3} + + do_execsql_test 2.2 { + SELECT * FROM aux.ft_content; + } {1 aux1 2 aux2 3 aux3} +} + +#------------------------------------------------------------------------- +# +reset_db +forcedelete test.db2 +do_execsql_test 3.0 { + CREATE TABLE t1(a PRIMARY KEY, b, c); + CREATE VIEW v1 AS SELECT * FROM t1; + CREATE TRIGGER v1_ins INSTEAD OF INSERT ON v1 BEGIN + INSERT INTO t1 VALUES(new.a, new.b, new.c); + END; + CREATE TRIGGER v1_del INSTEAD OF DELETE ON v1 BEGIN + DELETE FROM t1 WHERE a=old.a; + END; + CREATE TRIGGER v1_up INSTEAD OF UPDATE ON v1 BEGIN + UPDATE t1 SET a=new.a, b=new.b, c=new.c WHERE a=old.a; + END; +} +forcecopy test.db test.db2 + +do_test 3.1 { + sqlite3 db2 test.db2 + execsql { INSERT INTO t1 VALUES(1, 2, 3) } db + execsql { INSERT INTO t1 VALUES(4, 5, 6) } db2 + db2 close + execsql { ATTACH 'test.db2' AS aux; } +} {} + +do_execsql_test 3.2 { + SELECT * FROM main.v1; +} {1 2 3} + +do_execsql_test 3.3 { + SELECT * FROM aux.v1; +} {4 5 6} + +db close +sqlite3 db test.db -reuse-schema 1 + +do_execsql_test 3.4 { ATTACH 'test.db2' AS aux } {} +do_execsql_test 3.5 { SELECT * FROM main.v1 } {1 2 3} +do_execsql_test 3.6 { SELECT * FROM aux.v1 } {4 5 6} + +do_execsql_test 3.7.1 { INSERT INTO aux.t1 VALUES(8, 9, 10); } +do_execsql_test 3.7.2 { SELECT * FROM main.v1 } {1 2 3} +do_execsql_test 3.7.3 { SELECT * FROM aux.v1 } {4 5 6 8 9 10} + +do_execsql_test 3.8.1 { DELETE FROM aux.t1 WHERE b=5 } +do_execsql_test 3.8.2 { SELECT * FROM main.v1 } {1 2 3} +do_execsql_test 3.8.3 { SELECT * FROM aux.v1 } {8 9 10} + +do_execsql_test 3.9.1 { UPDATE aux.t1 SET b='abc' } +do_execsql_test 3.9.2 { SELECT * FROM main.v1 } {1 2 3} +do_execsql_test 3.9.3 { SELECT * FROM aux.v1 } {8 abc 10} + +do_execsql_test 3.10.1 { INSERT INTO aux.v1 VALUES(11, 12, 13) } +do_execsql_test 3.10.2 { SELECT * FROM main.v1 } {1 2 3} +do_execsql_test 3.10.3 { SELECT * FROM aux.v1 } {8 abc 10 11 12 13} + +do_execsql_test 3.11.1 { DELETE FROM aux.v1 WHERE b='abc' } +do_execsql_test 3.11.2 { SELECT * FROM main.v1 } {1 2 3} +do_execsql_test 3.11.3 { SELECT * FROM aux.v1 } {11 12 13} + +do_execsql_test 3.12.1 { UPDATE aux.v1 SET b='def' } +do_execsql_test 3.12.2 { SELECT * FROM main.v1 } {1 2 3} +do_execsql_test 3.12.3 { SELECT * FROM aux.v1 } {11 def 13} + +do_execsql_test 3.13.1 { + CREATE TEMP TRIGGER xyz AFTER INSERT ON aux.t1 BEGIN + INSERT INTO v1 VALUES(new.a, new.b, new.c); + END; +} +do_execsql_test 3.13.2 { + INSERT INTO aux.v1 VALUES('x', 'y', 'z'); +} +do_execsql_test 3.13.3 { + SELECT * FROM v1; +} {1 2 3 x y z} + +#------------------------------------------------------------------------- +# +reset_db +forcedelete test.db2 +do_execsql_test 4.0 { + CREATE TABLE t1(a PRIMARY KEY, b, c UNIQUE); + CREATE TABLE del(a, b, c); + CREATE TRIGGER tr1 AFTER DELETE ON t1 BEGIN + INSERT INTO del VALUES(old.a, old.b, old.c); + END; +} +forcecopy test.db test.db2 + +db close +sqlite3 db test.db -reuse-schema 1 +execsql { + ATTACH 'test.db2' AS aux; + PRAGMA recursive_triggers = 1; +} + +do_execsql_test 4.1 { + INSERT INTO main.t1 VALUES(1, 2, 3); + INSERT INTO aux.t1 VALUES(4, 5, 6); +} + +do_execsql_test 4.2.1 { + INSERT OR REPLACE INTO aux.t1 VALUES('a', 'b', 6); + SELECT * FROM aux.t1; +} {a b 6} +do_execsql_test 4.2.2 { SELECT * FROM aux.del } {4 5 6} +do_execsql_test 4.2.3 { SELECT * FROM main.del } {} + +do_execsql_test 4.3.1 { + INSERT INTO aux.t1 VALUES('x', 'y', 'z'); + UPDATE OR REPLACE aux.t1 SET c='z' WHERE a='a'; +} {} +do_execsql_test 4.3.2 { SELECT * FROM aux.del } {4 5 6 x y z} +do_execsql_test 4.3.3 { SELECT * FROM main.del } {} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 5.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c); + CREATE INDEX i1 ON t1(b); + INSERT INTO t1 VALUES(1, 2, 3), (4, 5, 6); + ANALYZE; + PRAGMA writable_schema = 1; + DELETE FROM sqlite_stat1; +} +db close +forcecopy test.db test.db2 +sqlite3 db test.db -reuse-schema 1 +execsql { ATTACH 'test.db2' AS aux } + +foreach {tn sql} { + 1 { CREATE TABLE t3(x) } + 2 { DROP TABLE t2 } + 3 { CREATE INDEX i2 ON t2(b) } + 4 { DROP INDEX i1 } + 5 { ALTER TABLE t1 ADD COLUMN d } + 6 { ALTER TABLE t1 RENAME TO t3 } + 7 { ALTER TABLE t1 RENAME c TO d } +} { + do_catchsql_test 5.1.$tn $sql {1 {attempt to modify read-only schema}} +} + +do_execsql_test 5.2.1 { ANALYZE aux.t1 } {} +do_execsql_test 5.2.2 { SELECT * FROM aux.sqlite_stat1 } {t1 i1 {2 1}} +do_execsql_test 5.2.3 { SELECT * FROM main.sqlite_stat1 } {} + +do_test 5.3.0 { + sqlite3 db2 test.db2 + db2 eval { + PRAGMA writable_schema = 1; + DELETE FROM sqlite_stat1; + } +} {} + +do_execsql_test 5.3.1 { SELECT * FROM aux.sqlite_stat1 } {} +do_execsql_test 5.3.2 { ANALYZE aux } {} +do_execsql_test 5.3.3 { SELECT * FROM aux.sqlite_stat1 } {t1 i1 {2 1}} +do_execsql_test 5.3.4 { SELECT * FROM main.sqlite_stat1 } {} + +#------------------------------------------------------------------------- +# Attempting to run ANALYZE when the required sqlite_statXX functions +# are missing is an error (because it would modify the database schema). +# +reset_db +do_execsql_test 5.4 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c); + CREATE INDEX i1 ON t1(b); + INSERT INTO t1 VALUES(1, 2, 3), (4, 5, 6); +} +db close +sqlite3 db test.db -reuse-schema 1 +foreach {tn sql} { + 1 { ANALYZE } + 2 { ANALYZE t1 } + 3 { ANALYZE i1 } + 4 { ANALYZE main } + 5 { ANALYZE main.t1 } + 6 { ANALYZE main.i1 } +} { + do_catchsql_test 5.4.$tn $sql {1 {attempt to modify read-only schema}} +} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 6.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); + CREATE VIEW v1 AS SELECT * FROM t1; +} +db close +forcecopy test.db test.db2 +sqlite3 db test.db -reuse-schema 1 +execsql { ATTACH 'test.db2' AS aux } + +do_execsql_test 6.1 { + INSERT INTO main.t1(a) VALUES(1), (2), (3); + INSERT INTO aux.t1(a) VALUES(4), (5), (6); + CREATE TEMP TABLE t2(i,t); + INSERT INTO t2 VALUES(2, 'two'), (5, 'five'); +} + +do_execsql_test 6.2 { + SELECT t FROM t2 WHERE i IN (SELECT a FROM aux.t1) +} {five} +do_execsql_test 6.3 { + SELECT t FROM t2 WHERE i IN (SELECT a FROM aux.v1) +} {five} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 7.0 { + CREATE TABLE p1(a PRIMARY KEY, b); + CREATE TABLE p2(a PRIMARY KEY, b); + CREATE TABLE c1(x REFERENCES p1 ON UPDATE CASCADE ON DELETE CASCADE); +} + +db close +forcecopy test.db test.db2 +sqlite3 db test.db -reuse-schema 1 +execsql { ATTACH 'test.db2' AS aux } + +do_execsql_test 7.1 { + INSERT INTO aux.p1 VALUES(1, 'one'); + INSERT INTO aux.p1 VALUES(2, 'two'); + PRAGMA foreign_keys = on; +} + +do_execsql_test 7.2 { + INSERT INTO aux.c1 VALUES(2); +} + +do_execsql_test 7.3.1 { + PRAGMA foreign_keys = off; + INSERT INTO main.p2 SELECT * FROM aux.p1; +} +do_execsql_test 7.3.2 { + SELECT * FROM main.p2; +} {1 one 2 two} + +do_execsql_test 7.3.3 { + INSERT INTO aux.p2 VALUES(1, 2); +} + +do_execsql_test 7.3.4 { + SELECT main.p2.a FROM main.p2, aux.p2; +} {1 2} + +do_execsql_test 7.3.5 { + SELECT * FROM main.p2, aux.p2; +} {1 one 1 2 2 two 1 2} + +do_execsql_test 7.4 { + SELECT count(*) FROM aux.p2; +} {1} + finish_test + + Index: test/shared8.test ================================================================== --- test/shared8.test +++ test/shared8.test @@ -60,10 +60,11 @@ SELECT * FROM v1; } db1 } {1 i 2 ii 3 iii 4 iv} do_test 1.1 { + sqlite3_db_config db1 DEFENSIVE 0 execsql { PRAGMA writable_schema = 1; DELETE FROM sqlite_master WHERE 1; PRAGMA writable_schema = 0; SELECT * FROM sqlite_master; Index: test/snapshot2.test ================================================================== --- test/snapshot2.test +++ test/snapshot2.test @@ -236,6 +236,5 @@ list [catch { sqlite3_snapshot_open_blob db2 main $snap } msg] $msg } {1 SQLITE_ERROR_SNAPSHOT} finish_test - Index: test/snapshot3.test ================================================================== --- test/snapshot3.test +++ test/snapshot3.test @@ -95,6 +95,5 @@ execsql BEGIN db3 list [catch { sqlite3_snapshot_open_blob db3 main $snap } msg] $msg } {1 SQLITE_ERROR_SNAPSHOT} finish_test - Index: test/snapshot4.test ================================================================== --- test/snapshot4.test +++ test/snapshot4.test @@ -70,6 +70,5 @@ } } {100} finish_test - Index: test/snapshot_up.test ================================================================== --- test/snapshot_up.test +++ test/snapshot_up.test @@ -179,6 +179,5 @@ sqlite3_snapshot_free $::snap1 sqlite3_snapshot_free $::snap2 finish_test - Index: test/swarmvtab3.test ================================================================== --- test/swarmvtab3.test +++ test/swarmvtab3.test @@ -229,6 +229,5 @@ db close forcedelete {*}[glob test.db*] forcedelete {*}[glob test_remote.db*] finish_test - Index: test/swarmvtabfault.test ================================================================== --- test/swarmvtabfault.test +++ test/swarmvtabfault.test @@ -61,6 +61,5 @@ } -test { faultsim_test_result {0 {1 2 9}} {1 {sql error: out of memory}} } finish_test - Index: test/table.test ================================================================== --- test/table.test +++ test/table.test @@ -274,10 +274,11 @@ do_test table-5.2.2 { db close forcedelete test.db sqlite3 db test.db + sqlite3_db_config db DEFENSIVE 0 db eval { CREATE TABLE t0(a,b); CREATE INDEX t ON t0(a); PRAGMA writable_schema=ON; UPDATE sqlite_master SET sql='CREATE TABLE a.b(a UNIQUE'; Index: test/trigger7.test ================================================================== --- test/trigger7.test +++ test/trigger7.test @@ -104,10 +104,11 @@ # This test corrupts the database file so it must be the last test # in the series. # do_test trigger7-99.1 { + sqlite3_db_config db DEFENSIVE 0 execsql { PRAGMA writable_schema=on; UPDATE sqlite_master SET sql='nonsense'; } db close Index: test/triggerE.test ================================================================== --- test/triggerE.test +++ test/triggerE.test @@ -66,10 +66,11 @@ #------------------------------------------------------------------------- # Test that variable references within trigger definitions loaded from # the sqlite_master table are automatically converted to NULL. # +sqlite3_db_config db DEFENSIVE 0 do_execsql_test 2.1 { PRAGMA writable_schema = 1; INSERT INTO sqlite_master VALUES('trigger', 'tr1', 't1', 0, 'CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN INSERT INTO t2 VALUES(?1, ?2); Index: test/unionvtab.test ================================================================== --- test/unionvtab.test +++ test/unionvtab.test @@ -453,6 +453,5 @@ do_execsql_test 5.4 { SELECT * FROM cc WHERE two LIKE '6' } {six 6} finish_test - Index: test/unionvtabfault.test ================================================================== --- test/unionvtabfault.test +++ test/unionvtabfault.test @@ -79,6 +79,5 @@ } finish_test - Index: test/vtab1.test ================================================================== --- test/vtab1.test +++ test/vtab1.test @@ -1251,10 +1251,11 @@ # an error message was set using a call similar to sqlite3_mprintf(zErr), # where zErr is an arbitrary string. This is no good if the string contains # characters that can be mistaken for printf() formatting directives. # do_test vtab1-17.1 { + sqlite3_db_config db DEFENSIVE 0 execsql { PRAGMA writable_schema = 1; INSERT INTO sqlite_master VALUES( 'table', 't3', 't3', 0, 'INSERT INTO "%s%s" VALUES(1)' ); Index: test/wherelimit2.test ================================================================== --- test/wherelimit2.test +++ test/wherelimit2.test @@ -295,6 +295,5 @@ set ::log } {ax a bx b cx c dx d ex a} finish_test - Index: test/window5.test ================================================================== --- test/window5.test +++ test/window5.test @@ -92,6 +92,5 @@ SELECT sum(a) FROM t1; } {21} finish_test - Index: test/windowfault.test ================================================================== --- test/windowfault.test +++ test/windowfault.test @@ -161,6 +161,5 @@ } -test { faultsim_test_result {0 {1 2 5 6 9 10}} } finish_test - Index: test/zipfile.test ================================================================== --- test/zipfile.test +++ test/zipfile.test @@ -759,6 +759,40 @@ do_execsql_test 11.11 { UPDATE z SET name = name || 'suffix'; SELECT name, data FROM z ORDER BY name; } {b0suffix two b2suffix one} + +if {$tcl_platform(platform)!="windows"} { + do_test 12.0 { + catch { file delete -force subdir } + foreach {path sz} { + subdir/x1.txt 143 + subdir/x2.txt 153 + } { + set dir [file dirname $path] + catch { file mkdir $dir } + set fd [open $path w] + puts -nonewline $fd [string repeat 1 $sz] + close $fd + } + } {} + + do_execsql_test 12.1 { + SELECT name FROM fsdir('subdir') ORDER BY 1; + } {subdir subdir/x1.txt subdir/x2.txt} + + do_execsql_test 12.2 { + CREATE TABLE d AS SELECT 'subdir' d; + CREATE TABLE x AS SELECT 1 x; + } + + do_execsql_test 12.4 { + SELECT name FROM d JOIN x JOIN fsdir(d) ORDER BY 1; + } {subdir subdir/x1.txt subdir/x2.txt} + + do_execsql_test 12.5 { + SELECT name FROM d JOIN x JOIN fsdir('.', d) ORDER BY 1; + } {. ./x1.txt ./x2.txt} +} + finish_test Index: test/zipfile2.test ================================================================== --- test/zipfile2.test +++ test/zipfile2.test @@ -239,6 +239,5 @@ UPDATE OR REPLACE zip SET name='test2' WHERE name='test1'; SELECT name FROM zip; } {test2} finish_test - Index: tool/genfkey.test ================================================================== --- tool/genfkey.test +++ tool/genfkey.test @@ -349,6 +349,5 @@ execsql { SELECT * FROM parent; SELECT * FROM child; } } {1 1} - Index: tool/sqldiff.c ================================================================== --- tool/sqldiff.c +++ tool/sqldiff.c @@ -1491,38 +1491,38 @@ } /* ** Write an SQLite value onto out. */ -static void putValue(FILE *out, sqlite3_value *pVal){ - int iDType = sqlite3_value_type(pVal); +static void putValue(FILE *out, sqlite3_stmt *pStmt, int k){ + int iDType = sqlite3_column_type(pStmt, k); sqlite3_int64 iX; double rX; sqlite3_uint64 uX; int j; putc(iDType, out); switch( iDType ){ case SQLITE_INTEGER: - iX = sqlite3_value_int64(pVal); + iX = sqlite3_column_int64(pStmt, k); memcpy(&uX, &iX, 8); for(j=56; j>=0; j-=8) putc((uX>>j)&0xff, out); break; case SQLITE_FLOAT: - rX = sqlite3_value_double(pVal); + rX = sqlite3_column_double(pStmt, k); memcpy(&uX, &rX, 8); for(j=56; j>=0; j-=8) putc((uX>>j)&0xff, out); break; case SQLITE_TEXT: - iX = sqlite3_value_bytes(pVal); + iX = sqlite3_column_bytes(pStmt, k); putsVarint(out, (sqlite3_uint64)iX); - fwrite(sqlite3_value_text(pVal),1,(size_t)iX,out); + fwrite(sqlite3_column_text(pStmt, k),1,(size_t)iX,out); break; case SQLITE_BLOB: - iX = sqlite3_value_bytes(pVal); + iX = sqlite3_column_bytes(pStmt, k); putsVarint(out, (sqlite3_uint64)iX); - fwrite(sqlite3_value_blob(pVal),1,(size_t)iX,out); + fwrite(sqlite3_column_blob(pStmt, k),1,(size_t)iX,out); break; case SQLITE_NULL: break; } } @@ -1648,14 +1648,14 @@ putc(0, out); switch( sqlite3_column_int(pStmt,0) ){ case SQLITE_UPDATE: { for(k=1, i=0; i