Index: src/build.c ================================================================== --- src/build.c +++ src/build.c @@ -154,11 +154,13 @@ for(iDb=0, mask=1; iDbnDb; mask<<=1, iDb++){ if( (mask & pParse->cookieMask)==0 ) continue; sqlite3VdbeUsesBtree(v, iDb); sqlite3VdbeAddOp2(v,OP_Transaction, iDb, (mask & pParse->writeMask)!=0); if( db->init.busy==0 ){ - sqlite3VdbeAddOp2(v,OP_VerifyCookie, iDb, pParse->cookieValue[iDb]); + sqlite3VdbeAddOp3(v, OP_VerifyCookie, + iDb, pParse->cookieValue[iDb], + db->aDb[iDb].pSchema->iGeneration); } } #ifndef SQLITE_OMIT_VIRTUALTABLE { int i; Index: src/callback.c ================================================================== --- src/callback.c +++ src/callback.c @@ -425,11 +425,14 @@ sqlite3DeleteTable(0, pTab); } sqlite3HashClear(&temp1); sqlite3HashClear(&pSchema->fkeyHash); pSchema->pSeqTab = 0; - pSchema->flags &= ~DB_SchemaLoaded; + if( pSchema->flags & DB_SchemaLoaded ){ + pSchema->iGeneration++; + pSchema->flags &= ~DB_SchemaLoaded; + } } /* ** Find and return the schema associated with a BTree. Create ** a new one if necessary. Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -670,10 +670,11 @@ /* ** An instance of the following structure stores a database schema. */ struct Schema { int schema_cookie; /* Database schema version number for this file */ + int iGeneration; /* Generation counter. Incremented with each change */ Hash tblHash; /* All tables indexed by name */ Hash idxHash; /* All (named) indices indexed by name */ Hash trigHash; /* All triggers indexed by name */ Hash fkeyHash; /* All foreign keys by referenced table name */ Table *pSeqTab; /* The sqlite_sequence table used by AUTOINCREMENT */ Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -2888,14 +2888,16 @@ p->expired = 0; } break; } -/* Opcode: VerifyCookie P1 P2 * +/* Opcode: VerifyCookie P1 P2 P3 * * ** ** Check the value of global database parameter number 0 (the -** schema version) and make sure it is equal to P2. +** schema version) and make sure it is equal to P2 and that the +** generation counter on the local schema parse equals P3. +** ** P1 is the database number which is 0 for the main database file ** and 1 for the file holding temporary tables and some higher number ** for auxiliary databases. ** ** The cookie changes its value whenever the database schema changes. @@ -2906,20 +2908,23 @@ ** to be executed (to establish a read lock) before this opcode is ** invoked. */ case OP_VerifyCookie: { int iMeta; + int iGen; Btree *pBt; + assert( pOp->p1>=0 && pOp->p1nDb ); assert( (p->btreeMask & (1<p1))!=0 ); pBt = db->aDb[pOp->p1].pBt; if( pBt ){ sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&iMeta); + iGen = db->aDb[pOp->p1].pSchema->iGeneration; }else{ iMeta = 0; } - if( iMeta!=pOp->p2 ){ + if( iMeta!=pOp->p2 || iGen!=pOp->p3 ){ sqlite3DbFree(db, p->zErrMsg); p->zErrMsg = sqlite3DbStrDup(db, "database schema has changed"); /* If the schema-cookie from the database file matches the cookie ** stored with the in-memory representation of the schema, do ** not reload the schema from the database file. Index: src/vdbeblob.c ================================================================== --- src/vdbeblob.c +++ src/vdbeblob.c @@ -264,10 +264,11 @@ sqlite3VdbeChangeP2(v, 0, flags); /* Configure the OP_VerifyCookie */ sqlite3VdbeChangeP1(v, 1, iDb); sqlite3VdbeChangeP2(v, 1, pTab->pSchema->schema_cookie); + sqlite3VdbeChangeP3(v, 1, pTab->pSchema->iGeneration); /* Make sure a mutex is held on the table to be accessed */ sqlite3VdbeUsesBtree(v, iDb); /* Configure the OP_TableLock instruction */ Index: test/capi3.test ================================================================== --- test/capi3.test +++ test/capi3.test @@ -649,15 +649,15 @@ db cache flush sqlite3_close $DB } {SQLITE_BUSY} do_test capi3-6.2 { sqlite3_step $STMT -} {SQLITE_ROW} -check_data $STMT capi3-6.3 {INTEGER} {1} {1.0} {1} +} {SQLITE_ERROR} +#check_data $STMT capi3-6.3 {INTEGER} {1} {1.0} {1} do_test capi3-6.3 { sqlite3_finalize $STMT -} {SQLITE_OK} +} {SQLITE_SCHEMA} do_test capi3-6.4-misuse { db cache flush sqlite3_close $DB } {SQLITE_OK} db close ADDED test/tkt-f7b4edec.test Index: test/tkt-f7b4edec.test ================================================================== --- /dev/null +++ test/tkt-f7b4edec.test @@ -0,0 +1,74 @@ +# 2011 March 18 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# +# This file implements tests to verify that ticket +# [f7b4edece25c994857dc139207f55a53c8319fae] has been fixed. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Open two database connections to the same database file in +# shared cache mode. Create update hooks that will fire on +# each connection. +# +db close +set ::enable_shared_cache [sqlite3_enable_shared_cache 1] +sqlite3 db1 test.db +sqlite3 db2 test.db +unset -nocomplain HOOKS +set HOOKS {} +proc update_hook {args} { lappend ::HOOKS $args } +db1 update_hook update_hook +db2 update_hook update_hook + +# Create a prepared statement +# +do_test tkt-f7b4edec-1 { + execsql { CREATE TABLE t1(x, y); } db1 + execsql { INSERT INTO t1 VALUES(1, 2) } db1 + set ::HOOKS +} {{INSERT main t1 1}} + +# In the second database connection cause the schema to be reparsed +# without changing the schema cookie. +# +set HOOKS {} +do_test tkt-f7b4edec-2 { + execsql { + BEGIN; + DROP TABLE t1; + CREATE TABLE t1(x, y); + ROLLBACK; + } db2 + set ::HOOKS +} {} + +# Rerun the prepared statement that was created prior to the +# schema reparse. Verify that the update-hook gives the correct +# output. +# +set HOOKS {} +do_test tkt-f7b4edec-3 { + execsql { INSERT INTO t1 VALUES(1, 2) } db1 + set ::HOOKS +} {{INSERT main t1 2}} + +# Be sure to restore the original shared-cache mode setting before +# returning. +# +db1 close +db2 close +sqlite3_enable_shared_cache $::enable_shared_cache + + +finish_test