Index: src/attach.c ================================================================== --- src/attach.c +++ src/attach.c @@ -214,11 +214,11 @@ if( db->aDb[iDb].pBt ){ sqlite3BtreeClose(db->aDb[iDb].pBt); db->aDb[iDb].pBt = 0; db->aDb[iDb].pSchema = 0; } - sqlite3ResetInternalSchema(db, -1); + sqlite3ResetAllSchemasOfConnection(db); db->nDb = iDb; if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){ db->mallocFailed = 1; sqlite3DbFree(db, zErrDyn); zErrDyn = sqlite3MPrintf(db, "out of memory"); @@ -286,11 +286,11 @@ } sqlite3BtreeClose(pDb->pBt); pDb->pBt = 0; pDb->pSchema = 0; - sqlite3ResetInternalSchema(db, -1); + sqlite3ResetAllSchemasOfConnection(db); return; detach_error: sqlite3_result_error(context, zErr, -1); } Index: src/backup.c ================================================================== --- src/backup.c +++ src/backup.c @@ -412,11 +412,11 @@ */ if( rc==SQLITE_DONE ){ rc = sqlite3BtreeUpdateMeta(p->pDest,1,p->iDestSchema+1); if( rc==SQLITE_OK ){ if( p->pDestDb ){ - sqlite3ResetInternalSchema(p->pDestDb, -1); + sqlite3ResetAllSchemasOfConnection(p->pDestDb); } if( destMode==PAGER_JOURNALMODE_WAL ){ rc = sqlite3BtreeSetVersion(p->pDest, 2); } } Index: src/build.c ================================================================== --- src/build.c +++ src/build.c @@ -392,62 +392,19 @@ } db->flags |= SQLITE_InternChanges; } /* -** Erase all schema information from the in-memory hash tables of -** a single database. This routine is called to reclaim memory -** before the database closes. It is also called during a rollback -** if there were schema changes during the transaction or if a -** schema-cookie mismatch occurs. +** Look through the list of open database files in db->aDb[] and if +** any have been closed, remove them from the list. Reallocate the +** db->aDb[] structure to a smaller size, if possible. ** -** If iDb<0 then reset the internal schema tables for all database -** files. If iDb>=0 then reset the internal schema for only the -** single file indicated. -*/ -void sqlite3ResetInternalSchema(sqlite3 *db, int iDb){ - int i, j; - assert( iDbnDb ); - - if( iDb>=0 ){ - /* Case 1: Reset the single schema identified by iDb */ - Db *pDb = &db->aDb[iDb]; - assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - assert( pDb->pSchema!=0 ); - sqlite3SchemaClear(pDb->pSchema); - - /* If any database other than TEMP is reset, then also reset TEMP - ** since TEMP might be holding triggers that reference tables in the - ** other database. - */ - if( iDb!=1 ){ - pDb = &db->aDb[1]; - assert( pDb->pSchema!=0 ); - sqlite3SchemaClear(pDb->pSchema); - } - return; - } - /* Case 2 (from here to the end): Reset all schemas for all attached - ** databases. */ - assert( iDb<0 ); - sqlite3BtreeEnterAll(db); - for(i=0; inDb; i++){ - Db *pDb = &db->aDb[i]; - if( pDb->pSchema ){ - sqlite3SchemaClear(pDb->pSchema); - } - } - db->flags &= ~SQLITE_InternChanges; - sqlite3VtabUnlockList(db); - sqlite3BtreeLeaveAll(db); - - /* If one or more of the auxiliary database files has been closed, - ** then remove them from the auxiliary database list. We take the - ** opportunity to do this here since we have just deleted all of the - ** schema hash tables and therefore do not have to make any changes - ** to any of those tables. - */ +** Entry 0 (the "main" database) and entry 1 (the "temp" database) +** are never candidates for being collapsed. +*/ +void sqlite3CollapseDatabaseArray(sqlite3 *db){ + int i, j; for(i=j=2; inDb; i++){ struct Db *pDb = &db->aDb[i]; if( pDb->pBt==0 ){ sqlite3DbFree(db, pDb->zName); pDb->zName = 0; @@ -464,10 +421,55 @@ memcpy(db->aDbStatic, db->aDb, 2*sizeof(db->aDb[0])); sqlite3DbFree(db, db->aDb); db->aDb = db->aDbStatic; } } + +/* +** Reset the schema for the database at index iDb. Also reset the +** TEMP schema. +*/ +void sqlite3ResetOneSchema(sqlite3 *db, int iDb){ + Db *pDb; + assert( iDbnDb ); + + /* Case 1: Reset the single schema identified by iDb */ + pDb = &db->aDb[iDb]; + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + assert( pDb->pSchema!=0 ); + sqlite3SchemaClear(pDb->pSchema); + + /* If any database other than TEMP is reset, then also reset TEMP + ** since TEMP might be holding triggers that reference tables in the + ** other database. + */ + if( iDb!=1 ){ + pDb = &db->aDb[1]; + assert( pDb->pSchema!=0 ); + sqlite3SchemaClear(pDb->pSchema); + } + return; +} + +/* +** Erase all schema information from all attached databases (including +** "main" and "temp") for a single database connection. +*/ +void sqlite3ResetAllSchemasOfConnection(sqlite3 *db){ + int i; + sqlite3BtreeEnterAll(db); + for(i=0; inDb; i++){ + Db *pDb = &db->aDb[i]; + if( pDb->pSchema ){ + sqlite3SchemaClear(pDb->pSchema); + } + } + db->flags &= ~SQLITE_InternChanges; + sqlite3VtabUnlockList(db); + sqlite3BtreeLeaveAll(db); + sqlite3CollapseDatabaseArray(db); +} /* ** This routine is called when a commit occurs. */ void sqlite3CommitInternalChanges(sqlite3 *db){ @@ -2761,11 +2763,11 @@ zExtra += nColl; nExtra -= nColl; }else{ zColl = pTab->aCol[j].zColl; if( !zColl ){ - zColl = db->pDfltColl->zName; + zColl = "BINARY"; } } if( !db->init.busy && !sqlite3LocateCollSeq(pParse, zColl) ){ goto exit_create_index; } Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -717,10 +717,34 @@ pDestructor->xDestroy(pDestructor->pUserData); sqlite3DbFree(db, pDestructor); } } } + +/* +** Disconnect all sqlite3_vtab objects that belong to database connection +** db. This is called when db is being closed. +*/ +static void disconnectAllVtab(sqlite3 *db){ +#ifndef SQLITE_OMIT_VIRTUALTABLE + int i; + sqlite3BtreeEnterAll(db); + for(i=0; inDb; i++){ + Schema *pSchema = db->aDb[i].pSchema; + if( db->aDb[i].pSchema ){ + HashElem *p; + for(p=sqliteHashFirst(&pSchema->tblHash); p; p=sqliteHashNext(p)){ + Table *pTab = (Table *)sqliteHashData(p); + if( IsVirtual(pTab) ) sqlite3VtabDisconnect(db, pTab); + } + } + } + sqlite3BtreeLeaveAll(db); +#else + UNUSED_PARAMETER(db); +#endif +} /* ** Close an existing SQLite database */ int sqlite3_close(sqlite3 *db){ @@ -733,14 +757,14 @@ if( !sqlite3SafetyCheckSickOrOk(db) ){ return SQLITE_MISUSE_BKPT; } sqlite3_mutex_enter(db->mutex); - /* Force xDestroy calls on all virtual tables */ - sqlite3ResetInternalSchema(db, -1); + /* Force xDisconnect calls on all virtual tables */ + disconnectAllVtab(db); - /* If a transaction is open, the ResetInternalSchema() call above + /* If a transaction is open, the disconnectAllVtab() call above ** will not have called the xDisconnect() method on any virtual ** tables in the db->aVTrans[] array. The following sqlite3VtabRollback() ** call will do so. We need to do this before the check for active ** SQL statements below, as the v-table implementation may be storing ** some prepared statements internally. @@ -767,10 +791,11 @@ } /* Free any outstanding Savepoint structures. */ sqlite3CloseSavepoints(db); + /* Close all database connections */ for(j=0; jnDb; j++){ struct Db *pDb = &db->aDb[j]; if( pDb->pBt ){ sqlite3BtreeClose(pDb->pBt); pDb->pBt = 0; @@ -777,19 +802,26 @@ if( j!=1 ){ pDb->pSchema = 0; } } } - sqlite3ResetInternalSchema(db, -1); + /* Clear the TEMP schema separately and last */ + if( db->aDb[1].pSchema ){ + sqlite3SchemaClear(db->aDb[1].pSchema); + } + sqlite3VtabUnlockList(db); + + /* Free up the array of auxiliary databases */ + sqlite3CollapseDatabaseArray(db); + assert( db->nDb<=2 ); + assert( db->aDb==db->aDbStatic ); /* Tell the code in notify.c that the connection no longer holds any ** locks and does not require any further unlock-notify callbacks. */ sqlite3ConnectionClosed(db); - assert( db->nDb<=2 ); - assert( db->aDb==db->aDbStatic ); for(j=0; jaFunc.a); j++){ FuncDef *pNext, *pHash, *p; for(p=db->aFunc.a[j]; p; p=pHash){ pHash = p->pHash; while( p ){ @@ -872,11 +904,11 @@ sqlite3VtabRollback(db); sqlite3EndBenignMalloc(); if( db->flags&SQLITE_InternChanges ){ sqlite3ExpirePreparedStatements(db); - sqlite3ResetInternalSchema(db, -1); + sqlite3ResetAllSchemasOfConnection(db); } /* Any deferred constraint violations have now been resolved. */ db->nDeferredCons = 0; Index: src/pragma.c ================================================================== --- src/pragma.c +++ src/pragma.c @@ -116,11 +116,11 @@ "from within a transaction"); return SQLITE_ERROR; } sqlite3BtreeClose(db->aDb[1].pBt); db->aDb[1].pBt = 0; - sqlite3ResetInternalSchema(db, -1); + sqlite3ResetAllSchemasOfConnection(db); } return SQLITE_OK; } #endif /* SQLITE_PAGER_PRAGMAS */ Index: src/prepare.c ================================================================== --- src/prepare.c +++ src/prepare.c @@ -260,11 +260,10 @@ u8 encoding; /* If opening the main database, set ENC(db). */ encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3; if( encoding==0 ) encoding = SQLITE_UTF8; ENC(db) = encoding; - db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, "BINARY", 0); }else{ /* If opening an attached database, the encoding much match ENC(db) */ if( meta[BTREE_TEXT_ENCODING-1]!=ENC(db) ){ sqlite3SetString(pzErrMsg, db, "attached databases must use the same" " text encoding as main database"); @@ -340,11 +339,11 @@ } #endif } if( db->mallocFailed ){ rc = SQLITE_NOMEM; - sqlite3ResetInternalSchema(db, -1); + sqlite3ResetAllSchemasOfConnection(db); } if( rc==SQLITE_OK || (db->flags&SQLITE_RecoveryMode)){ /* Black magic: If the SQLITE_RecoveryMode flag is set, then consider ** the schema loaded, even if errors occurred. In this situation the ** current sqlite3_prepare() operation will fail, but the following one @@ -393,11 +392,11 @@ db->init.busy = 1; for(i=0; rc==SQLITE_OK && inDb; i++){ if( DbHasProperty(db, i, DB_SchemaLoaded) || i==1 ) continue; rc = sqlite3InitOne(db, i, pzErrMsg); if( rc ){ - sqlite3ResetInternalSchema(db, i); + sqlite3ResetOneSchema(db, i); } } /* Once all the other databases have been initialised, load the schema ** for the TEMP database. This is loaded last, as the TEMP database @@ -406,11 +405,11 @@ #ifndef SQLITE_OMIT_TEMPDB if( rc==SQLITE_OK && ALWAYS(db->nDb>1) && !DbHasProperty(db, 1, DB_SchemaLoaded) ){ rc = sqlite3InitOne(db, 1, pzErrMsg); if( rc ){ - sqlite3ResetInternalSchema(db, 1); + sqlite3ResetOneSchema(db, 1); } } #endif db->init.busy = 0; @@ -474,11 +473,11 @@ ** value stored as part of the in-memory schema representation, ** set Parse.rc to SQLITE_SCHEMA. */ sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&cookie); assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( cookie!=db->aDb[iDb].pSchema->schema_cookie ){ - sqlite3ResetInternalSchema(db, iDb); + sqlite3ResetOneSchema(db, iDb); pParse->rc = SQLITE_SCHEMA; } /* Close the transaction, if one was opened. */ if( openedTransaction ){ Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -2707,11 +2707,13 @@ void sqlite3ExprListSetSpan(Parse*,ExprList*,ExprSpan*); void sqlite3ExprListDelete(sqlite3*, ExprList*); int sqlite3Init(sqlite3*, char**); int sqlite3InitCallback(void*, int, char**, char**); void sqlite3Pragma(Parse*,Token*,Token*,Token*,int); -void sqlite3ResetInternalSchema(sqlite3*, int); +void sqlite3ResetAllSchemasOfConnection(sqlite3*); +void sqlite3ResetOneSchema(sqlite3*,int); +void sqlite3CollapseDatabaseArray(sqlite3*); void sqlite3BeginParse(Parse*,int); void sqlite3CommitInternalChanges(sqlite3*); Table *sqlite3ResultSetOfSelect(Parse*,Select*); void sqlite3OpenMasterTable(Parse *, int); void sqlite3StartTable(Parse*,Token*,Token*,int,int,int,int); @@ -3112,10 +3114,11 @@ # define sqlite3VtabUnlockList(X) # define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK # define sqlite3GetVTable(X,Y) ((VTable*)0) #else void sqlite3VtabClear(sqlite3 *db, Table*); + void sqlite3VtabDisconnect(sqlite3 *db, Table *p); int sqlite3VtabSync(sqlite3 *db, char **); int sqlite3VtabRollback(sqlite3 *db); int sqlite3VtabCommit(sqlite3 *db); void sqlite3VtabLock(VTable *); void sqlite3VtabUnlock(VTable *); Index: src/test8.c ================================================================== --- src/test8.c +++ src/test8.c @@ -1298,10 +1298,11 @@ /* ** Decode a pointer to an sqlite3 object. */ extern int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb); +extern const char *sqlite3TestErrorName(int rc); static void moduleDestroy(void *p){ sqlite3_free(p); } @@ -1312,10 +1313,11 @@ ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ int objc, /* Number of arguments */ Tcl_Obj *CONST objv[] /* Command arguments */ ){ + int rc; sqlite3 *db; EchoModule *pMod; if( objc!=2 ){ Tcl_WrongNumArgs(interp, 1, objv, "DB"); return TCL_ERROR; @@ -1323,18 +1325,24 @@ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; /* Virtual table module "echo" */ pMod = sqlite3_malloc(sizeof(EchoModule)); pMod->interp = interp; - sqlite3_create_module_v2(db, "echo", &echoModule, (void*)pMod, moduleDestroy); + rc = sqlite3_create_module_v2( + db, "echo", &echoModule, (void*)pMod, moduleDestroy + ); /* Virtual table module "echo_v2" */ - pMod = sqlite3_malloc(sizeof(EchoModule)); - pMod->interp = interp; - sqlite3_create_module_v2(db, "echo_v2", - &echoModuleV2, (void*)pMod, moduleDestroy - ); + if( rc==SQLITE_OK ){ + pMod = sqlite3_malloc(sizeof(EchoModule)); + pMod->interp = interp; + rc = sqlite3_create_module_v2(db, "echo_v2", + &echoModuleV2, (void*)pMod, moduleDestroy + ); + } + + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); return TCL_OK; } /* ** Tcl interface to sqlite3_declare_vtab, invoked as follows from Tcl: Index: src/vacuum.c ================================================================== --- src/vacuum.c +++ src/vacuum.c @@ -337,11 +337,11 @@ pDb->pSchema = 0; } /* This both clears the schemas and reduces the size of the db->aDb[] ** array. */ - sqlite3ResetInternalSchema(db, -1); + sqlite3ResetAllSchemasOfConnection(db); return rc; } #endif /* SQLITE_OMIT_VACUUM && SQLITE_OMIT_ATTACH */ Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -2745,11 +2745,11 @@ goto abort_due_to_error; } } if( p1==SAVEPOINT_ROLLBACK && (db->flags&SQLITE_InternChanges)!=0 ){ sqlite3ExpirePreparedStatements(db); - sqlite3ResetInternalSchema(db, -1); + sqlite3ResetAllSchemasOfConnection(db); db->flags = (db->flags | SQLITE_InternChanges); } } /* Regardless of whether this is a RELEASE or ROLLBACK, destroy all @@ -3049,11 +3049,11 @@ ** v-table would have to be ready for the sqlite3_vtab structure itself ** to be invalidated whenever sqlite3_step() is called from within ** a v-table method. */ if( db->aDb[pOp->p1].pSchema->schema_cookie!=iMeta ){ - sqlite3ResetInternalSchema(db, pOp->p1); + sqlite3ResetOneSchema(db, pOp->p1); } p->expired = 1; rc = SQLITE_SCHEMA; } @@ -4862,11 +4862,11 @@ if( rc==SQLITE_OK ) rc = initData.rc; sqlite3DbFree(db, zSql); db->init.busy = 0; } } - if( rc ) sqlite3ResetInternalSchema(db, -1); + if( rc ) sqlite3ResetAllSchemasOfConnection(db); if( rc==SQLITE_NOMEM ){ goto no_mem; } break; } @@ -6157,11 +6157,11 @@ pc, p->zSql, p->zErrMsg); sqlite3VdbeHalt(p); if( rc==SQLITE_IOERR_NOMEM ) db->mallocFailed = 1; rc = SQLITE_ERROR; if( resetSchemaOnFault>0 ){ - sqlite3ResetInternalSchema(db, resetSchemaOnFault-1); + sqlite3ResetOneSchema(db, resetSchemaOnFault-1); } /* This is the only way out of this procedure. We have to ** release the mutexes on btrees that were acquired at the ** top. */ Index: src/vtab.c ================================================================== --- src/vtab.c +++ src/vtab.c @@ -20,12 +20,12 @@ ** this struct allocated on the stack. It is used by the implementation of ** the sqlite3_declare_vtab() and sqlite3_vtab_config() APIs, both of which ** are invoked only from within xCreate and xConnect methods. */ struct VtabCtx { - Table *pTab; - VTable *pVTable; + VTable *pVTable; /* The virtual table being constructed */ + Table *pTab; /* The Table object to which the virtual table belongs */ }; /* ** The actual function that does the work of creating a new module. ** This function implements the sqlite3_create_module() and @@ -36,37 +36,39 @@ const char *zName, /* Name assigned to this module */ const sqlite3_module *pModule, /* The definition of the module */ void *pAux, /* Context pointer for xCreate/xConnect */ void (*xDestroy)(void *) /* Module destructor function */ ){ - int rc, nName; - Module *pMod; + int rc = SQLITE_OK; + int nName; sqlite3_mutex_enter(db->mutex); nName = sqlite3Strlen30(zName); - pMod = (Module *)sqlite3DbMallocRaw(db, sizeof(Module) + nName + 1); - if( pMod ){ - Module *pDel; - char *zCopy = (char *)(&pMod[1]); - memcpy(zCopy, zName, nName+1); - pMod->zName = zCopy; - pMod->pModule = pModule; - pMod->pAux = pAux; - pMod->xDestroy = xDestroy; - pDel = (Module *)sqlite3HashInsert(&db->aModule, zCopy, nName, (void*)pMod); - if( pDel && pDel->xDestroy ){ - sqlite3ResetInternalSchema(db, -1); - pDel->xDestroy(pDel->pAux); - } - sqlite3DbFree(db, pDel); - if( pDel==pMod ){ - db->mallocFailed = 1; - } - }else if( xDestroy ){ - xDestroy(pAux); - } - rc = sqlite3ApiExit(db, SQLITE_OK); + if( sqlite3HashFind(&db->aModule, zName, nName) ){ + rc = SQLITE_MISUSE_BKPT; + }else{ + Module *pMod; + pMod = (Module *)sqlite3DbMallocRaw(db, sizeof(Module) + nName + 1); + if( pMod ){ + Module *pDel; + char *zCopy = (char *)(&pMod[1]); + memcpy(zCopy, zName, nName+1); + pMod->zName = zCopy; + pMod->pModule = pModule; + pMod->pAux = pAux; + pMod->xDestroy = xDestroy; + pDel = (Module *)sqlite3HashInsert(&db->aModule,zCopy,nName,(void*)pMod); + assert( pDel==0 || pDel==pMod ); + if( pDel ){ + db->mallocFailed = 1; + sqlite3DbFree(db, pDel); + } + } + } + rc = sqlite3ApiExit(db, rc); + if( rc!=SQLITE_OK && xDestroy ) xDestroy(pAux); + sqlite3_mutex_leave(db->mutex); return rc; } @@ -177,10 +179,35 @@ } assert( !db || pRet ); return pRet; } + +/* +** Table *p is a virtual table. This function removes the VTable object +** for table *p associated with database connection db from the linked +** list in p->pVTab. It also decrements the VTable ref count. This is +** used when closing database connection db to free all of its VTable +** 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; + + assert( IsVirtual(p) ); + assert( sqlite3BtreeHoldsAllMutexes(db) ); + assert( sqlite3_mutex_held(db->mutex) ); + + for(ppVTab=&p->pVTable; *ppVTab; ppVTab=&(*ppVTab)->pNext){ + if( (*ppVTab)->db==db ){ + VTable *pVTab = *ppVTab; + *ppVTab = pVTab->pNext; + sqlite3VtabUnlock(pVTab); + break; + } + } +} /* ** Disconnect all the virtual table objects in the sqlite3.pDisconnect list. ** Index: test/capi3.test ================================================================== --- test/capi3.test +++ test/capi3.test @@ -647,17 +647,22 @@ } {0} do_test capi3-6.1 { db cache flush sqlite3_close $DB } {SQLITE_BUSY} + +# 6.2 and 6.3 used to return SQLITE_ERROR and SQLITE_SCHEMA, respectively. +# But since attempting to close a connection no longer resets the internal +# schema and expires all statements, this is no longer the case. do_test capi3-6.2 { sqlite3_step $STMT -} {SQLITE_ERROR} +} {SQLITE_ROW} #check_data $STMT capi3-6.3 {INTEGER} {1} {1.0} {1} do_test capi3-6.3 { sqlite3_finalize $STMT -} {SQLITE_SCHEMA} +} {SQLITE_OK} + do_test capi3-6.4-misuse { db cache flush sqlite3_close $DB } {SQLITE_OK} db close ADDED test/shared8.test Index: test/shared8.test ================================================================== --- /dev/null +++ test/shared8.test @@ -0,0 +1,113 @@ +# 2012 May 15 +# +# 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. +# +#*********************************************************************** +# +# The tests in this file are intended to show that closing one database +# connection to a shared-cache while there exist other connections (a) +# does not cause the schema to be reloaded and (b) does not cause any +# other problems. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +ifcapable !shared_cache { finish_test ; return } +set testprefix shared8 + +db close +set ::enable_shared_cache [sqlite3_enable_shared_cache 1] +do_test 0.0 { sqlite3_enable_shared_cache } {1} + +proc roman {n} { + array set R {1 i 2 ii 3 iii 4 iv 5 v 6 vi 7 vii 8 viii 9 ix 10 x} + set R($n) +} + +#------------------------------------------------------------------------- +# The following tests work as follows: +# +# 1.0: Open connection [db1] and populate the database. +# +# 1.1: Using "PRAGMA writable_schema", destroy the database schema on +# disk. The schema is still in memory, so it is possible to keep +# using it, but any attempt to reload it from disk will fail. +# +# 1.3-4: Open connection db2. Check that it can see the db schema. Then +# close db1 and check that db2 still works. This shows that closing +# db1 did not reset the in-memory schema. +# +# 1.5-7: Similar to 1.3-4. +# +# 1.8: Close all database connections (deleting the in-memory schema). +# Then open a new connection and check that it cannot read the db. +# +do_test 1.0 { + sqlite3 db1 test.db + db1 func roman roman + execsql { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 1); + INSERT INTO t1 VALUES(2, 2); + INSERT INTO t1 VALUES(3, 3); + INSERT INTO t1 VALUES(4, 4); + CREATE VIEW v1 AS SELECT a, roman(b) FROM t1; + SELECT * FROM v1; + } db1 +} {1 i 2 ii 3 iii 4 iv} + +do_test 1.1 { + execsql { + PRAGMA writable_schema = 1; + DELETE FROM sqlite_master WHERE 1; + PRAGMA writable_schema = 0; + SELECT * FROM sqlite_master; + } db1 +} {} + +do_test 1.2 { + execsql { SELECT * FROM v1 } db1 +} {1 i 2 ii 3 iii 4 iv} + +do_test 1.3 { + sqlite3 db2 test.db + db2 func roman roman + execsql { SELECT * FROM v1 } db2 +} {1 i 2 ii 3 iii 4 iv} + +do_test 1.4 { + db1 close + execsql { SELECT * FROM v1 } db2 +} {1 i 2 ii 3 iii 4 iv} + +do_test 1.5 { + sqlite3 db3 test.db + db3 func roman roman + execsql { SELECT * FROM v1 } db3 +} {1 i 2 ii 3 iii 4 iv} + +do_test 1.6 { + execsql { SELECT * FROM v1 } db2 +} {1 i 2 ii 3 iii 4 iv} + +do_test 1.7 { + db2 close + execsql { SELECT * FROM v1 } db3 +} {1 i 2 ii 3 iii 4 iv} + +do_test 1.8 { + db3 close + sqlite3 db4 test.db + catchsql { SELECT * FROM v1 } db4 +} {1 {no such table: v1}} + + +foreach db {db1 db2 db3 db4} { catch { $db close } } +sqlite3_enable_shared_cache $::enable_shared_cache +finish_test + Index: test/vtab1.test ================================================================== --- test/vtab1.test +++ test/vtab1.test @@ -1272,7 +1272,21 @@ set echo_module {} do_execsql_test 18.$tn.1 $sql $res do_test 18.$tn.2 { lrange $::echo_module 2 end } $filter } do_execsql_test 18.2.x { PRAGMA case_sensitive_like = OFF } + +#------------------------------------------------------------------------- +# Test that an existing module may not be overridden. +# +do_test 19.1 { + sqlite3 db2 test.db + register_echo_module [sqlite3_connection_pointer db2] +} SQLITE_OK +do_test 19.2 { + register_echo_module [sqlite3_connection_pointer db2] +} SQLITE_MISUSE +do_test 19.3 { + db2 close +} {} finish_test