static void DetachFunc_(FuncContext fctx, int notUsed1, Mem[] argv) { Context ctx = sqlite3_context_db_handle(fctx); string name = Mem_Text(argv[0]); if (name == null) { name = string.Empty; } StringBuilder err = new StringBuilder(200); int i; Context.DB db = null; for (i = 0; i < ctx.DBs.length; i++) { db = ctx.DBs[i]; if (db.Bt == null) { continue; } if (string.Equals(db.Name, name, StringComparison.OrdinalIgnoreCase)) { break; } } if (i >= ctx.DBs.length) { err.AppendFormat("no such database: %s", name); goto detach_error; } if (i < 2) { err.AppendFormat("cannot detach database %s", name); goto detach_error; } if (!ctx.AutoCommit) { err.Append("cannot DETACH database within transaction"); goto detach_error; } if (db.Bt.IsInReadTrans() || db.Bt.IsInBackup()) { err.Append("database %s is locked", name); goto detach_error; } db.Bt.Close(); db.Bt = null; db.Schema = null; sqlite3ResetInternalSchema(ctx, -1); return; detach_error: sqlite3_result_error(fctx, err.ToString(), -1); }
static void OpenStatTable(Parse parse, int db, int statCur, string where_, string whereType) { int[] roots = new int[] { 0, 0 }; byte[] createTbls = new byte[] { 0, 0 }; Context ctx = parse.Ctx; Vdbe v = parse.GetVdbe(); if (v == null) { return; } Debug.Assert(Btree.HoldsAllMutexes(ctx)); Debug.Assert(v.Ctx == ctx); Context.DB dbObj = ctx.DBs[db]; for (int i = 0; i < _tables.Length; i++) { string tableName = _tables[i].Name; Table stat; if ((stat = Parse.FindTable(ctx, tableName, dbObj.Name)) == null) { // The sqlite_stat[12] table does not exist. Create it. Note that a side-effect of the CREATE TABLE statement is to leave the rootpage // of the new table in register pParse.regRoot. This is important because the OpenWrite opcode below will be needing it. parse.NestedParse("CREATE TABLE %Q.%s(%s)", dbObj.Name, tableName, _tables[i].Cols); roots[i] = parse.RegRoot; createTbls[i] = Vdbe::OPFLAG_P2ISREG; } else { // The table already exists. If zWhere is not NULL, delete all entries associated with the table zWhere. If zWhere is NULL, delete the // entire contents of the table. roots[i] = stat.Id; sqlite3TableLock(parse, db, roots[i], 1, tableName); if (where_ == null) { parse.NestedParse("DELETE FROM %Q.%s WHERE %s=%Q", dbObj.Name, tableName, whereType, where_); } else { v.AddOp2(OP.Clear, roots[i], db); // The sqlite_stat[12] table already exists. Delete all rows. } } } // Open the sqlite_stat[12] tables for writing. for (int i = 0; i < _tables.Length; i++) { v.AddOp3(OP.OpenWrite, statCur + i, roots[i], db); v.ChangeP4(-1, 3, Vdbe.P4T.INT32); v.ChangeP5(createTbls[i]); } }
//ctx.pDfltColl = sqlite3FindCollSeq( ctx, SQLITE_UTF8, "BINARY", 0 ); public static RC InitOne(Context ctx, int db, ref string errMsg) { Debug.Assert(db >= 0 && db < ctx.DBs.length); Debug.Assert(ctx.DBs[db].Schema != null); Debug.Assert(MutexEx.Held(ctx.Mutex)); Debug.Assert(db == 1 || ctx.DBs[db].Bt.HoldsMutex()); RC rc; int i; // zMasterSchema and zInitScript are set to point at the master schema and initialisation script appropriate for the database being // initialized. zMasterName is the name of the master table. string masterSchema = (E.OMIT_TEMPDB == 0 && db == 1 ? temp_master_schema : master_schema); string masterName = E.SCHEMA_TABLE(db); // Construct the schema tables. string[] args = new string[4]; args[0] = masterName; args[1] = "1"; args[2] = masterSchema; args[3] = null; InitData initData = new InitData(); initData.Ctx = ctx; initData.Db = db; initData.RC = RC.OK; initData.ErrMsg = errMsg; InitCallback(initData, 3, args, null); if (initData.RC != 0) { rc = initData.RC; goto error_out; } Table table = Parse.FindTable(ctx, masterName, ctx.DBs[db].Name); if (C._ALWAYS(table != null)) { table.TabFlags |= TF.Readonly; } // Create a cursor to hold the database open Context.DB dbAsObj = ctx.DBs[db]; if (dbAsObj.Bt == null) { if (E.OMIT_TEMPDB == 0 && C._ALWAYS(db == 1)) { E.DbSetProperty(ctx, 1, SCHEMA.SchemaLoaded); } return(RC.OK); } // If there is not already a read-only (or read-write) transaction opened on the b-tree database, open one now. If a transaction is opened, it // will be closed before this function returns. bool openedTransaction = false; dbAsObj.Bt.Enter(); if (!dbAsObj.Bt.IsInReadTrans()) { rc = dbAsObj.Bt.BeginTrans(0); if (rc != RC.OK) { C._setstring(ref errMsg, ctx, "%s", Main.ErrStr(rc)); goto initone_error_out; } openedTransaction = true; } // Get the database meta information. // Meta values are as follows: // meta[0] Schema cookie. Changes with each schema change. // meta[1] File format of schema layer. // meta[2] Size of the page cache. // meta[3] Largest rootpage (auto/incr_vacuum mode) // meta[4] Db text encoding. 1:UTF-8 2:UTF-16LE 3:UTF-16BE // meta[5] User version // meta[6] Incremental vacuum mode // meta[7] unused // meta[8] unused // meta[9] unused // Note: The #defined SQLITE_UTF* symbols in sqliteInt.h correspond to the possible values of meta[4]. uint[] meta = new uint[5]; for (i = 0; i < meta.Length; i++) { dbAsObj.Bt.GetMeta((Btree.META)i + 1, ref meta[i]); } dbAsObj.Schema.SchemaCookie = (int)meta[(int)Btree.META.SCHEMA_VERSION - 1]; // If opening a non-empty database, check the text encoding. For the main database, set sqlite3.enc to the encoding of the main database. // For an attached ctx, it is an error if the encoding is not the same as sqlite3.enc. if (meta[(int)Btree.META.TEXT_ENCODING - 1] != 0) // text encoding { if (db == 0) { #if !OMIT_UTF16 // If opening the main database, set ENC(db). TEXTENCODE encoding = (TEXTENCODE)(meta[(int)Btree.META.TEXT_ENCODING - 1] & 3); if (encoding == 0) { encoding = TEXTENCODE.UTF8; } E.CTXENCODE(ctx, encoding); #else E.CTXENCODE(ctx, TEXTENCODE_UTF8); #endif } else { // If opening an attached database, the encoding much match ENC(db) if ((TEXTENCODE)meta[(int)Btree.META.TEXT_ENCODING - 1] != E.CTXENCODE(ctx)) { C._setstring(ref errMsg, ctx, "attached databases must use the same text encoding as main database"); rc = RC.ERROR; goto initone_error_out; } } } else { E.DbSetProperty(ctx, db, SCHEMA.Empty); } dbAsObj.Schema.Encode = E.CTXENCODE(ctx); if (dbAsObj.Schema.CacheSize == 0) { dbAsObj.Schema.CacheSize = DEFAULT_CACHE_SIZE; dbAsObj.Bt.SetCacheSize(dbAsObj.Schema.CacheSize); } // file_format==1 Version 3.0.0. // file_format==2 Version 3.1.3. // ALTER TABLE ADD COLUMN // file_format==3 Version 3.1.4. // ditto but with non-NULL defaults // file_format==4 Version 3.3.0. // DESC indices. Boolean constants dbAsObj.Schema.FileFormat = (byte)meta[(int)Btree.META.FILE_FORMAT - 1]; if (dbAsObj.Schema.FileFormat == 0) { dbAsObj.Schema.FileFormat = 1; } if (dbAsObj.Schema.FileFormat > MAX_FILE_FORMAT) { C._setstring(ref errMsg, ctx, "unsupported file format"); rc = RC.ERROR; goto initone_error_out; } // Ticket #2804: When we open a database in the newer file format, clear the legacy_file_format pragma flag so that a VACUUM will // not downgrade the database and thus invalidate any descending indices that the user might have created. if (db == 0 && meta[(int)Btree.META.FILE_FORMAT - 1] >= 4) { ctx.Flags &= ~Context.FLAG.LegacyFileFmt; } // Read the schema information out of the schema tables Debug.Assert(ctx.Init.Busy); { string sql = C._mtagprintf(ctx, "SELECT name, rootpage, sql FROM '%q'.%s ORDER BY rowid", ctx.DBs[db].Name, masterName); #if !OMIT_AUTHORIZATION { Func <object, int, string, string, string, string, ARC> auth = ctx.Auth; ctx.Auth = null; #endif rc = sqlite3_exec(ctx, sql, InitCallback, initData, 0); //: errMsg = initData.ErrMsg; #if !OMIT_AUTHORIZATION ctx.Auth = auth; } #endif if (rc == RC.OK) { rc = initData.RC; } C._tagfree(ctx, ref sql); #if !OMIT_ANALYZE if (rc == RC.OK) { sqlite3AnalysisLoad(ctx, db); } #endif } if (ctx.MallocFailed) { rc = RC.NOMEM; Main.ResetAllSchemasOfConnection(ctx); } if (rc == RC.OK || (ctx.Flags & Context.FLAG.RecoveryMode) != 0) { // 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 will attempt to compile the supplied statement against whatever subset // of the schema was loaded before the error occurred. The primary purpose of this is to allow access to the sqlite_master table // even when its contents have been corrupted. E.DbSetProperty(ctx, db, DB.SchemaLoaded); rc = RC.OK; } // Jump here for an error that occurs after successfully allocating curMain and calling sqlite3BtreeEnter(). For an error that occurs // before that point, jump to error_out. initone_error_out: if (openedTransaction) { dbAsObj.Bt.Commit(); } dbAsObj.Bt.Leave(); error_out: if (rc == RC.NOMEM || rc == RC.IOERR_NOMEM) { ctx.MallocFailed = true; } return(rc); }
static void AttachFunc_(FuncContext fctx, int notUsed1, Mem[] argv) { Context ctx = sqlite3_context_db_handle(fctx); string file = Mem_Text(argv[0]); string name = Mem_Text(argv[1]); if (file == null) { file = ""; } if (name == null) { name = ""; } // Check for the following errors: // * Too many attached databases, // * Transaction currently open // * Specified database name already being used. RC rc = 0; string errDyn = null; if (ctx.DBs.length >= ctx.Limits[(int)LIMIT.ATTACHED] + 2) { errDyn = SysEx.Mprintf(ctx, "too many attached databases - max %d", ctx.Limits[(int)LIMIT.ATTACHED]); goto attach_error; } if (!ctx.AutoCommit) { errDyn = SysEx.Mprintf(ctx, "cannot ATTACH database within transaction"); goto attach_error; } for (int i = 0; i < ctx.DBs.length; i++) { string z = ctx.DBs[i].Name; Debug.Assert(z != null && name != null); if (z.Equals(name, StringComparison.OrdinalIgnoreCase)) { errDyn = SysEx.Mprintf(ctx, "database %s is already in use", name); goto attach_error; } } // Allocate the new entry in the ctx->aDb[] array and initialize the schema hash tables. //: Realloc if (ctx.DBs.data.Length <= ctx.DBs.length) { Array.Resize(ref ctx.DBs.data, ctx.DBs.length + 1); } if (ctx.DBs.data == null) { return; } ctx.DBs[ctx.DBs.length] = new Context.DB(); Context.DB newDB = ctx.DBs[ctx.DBs.length]; //: _memset(newDB, 0, sizeof(*newDB)); // Open the database file. If the btree is successfully opened, use it to obtain the database schema. At this point the schema may or may not be initialized. VSystem.OPEN flags = ctx.OpenFlags; VSystem vfs = null; string path = string.Empty; string err = string.Empty; rc = sqlite3ParseUri(ctx.Vfs.Name, file, ref flags, ref vfs, ref path, ref err); if (rc != RC.OK) { if (rc == RC.NOMEM) { ctx.MallocFailed = true; } sqlite3_result_error(fctx, err, -1); C._free(ref err); return; } Debug.Assert(vfs != null); flags |= VSystem.OPEN.MAIN_DB; rc = Btree.Open(vfs, path, ctx, ref newDB.Bt, 0, flags); C._free(ref path); ctx.DBs.length++; if (rc == RC.CONSTRAINT) { rc = RC.ERROR; errDyn = SysEx.Mprintf(ctx, "database is already attached"); } else if (rc == RC.OK) { newDB.Schema = sqlite3SchemaGet(ctx, newDB.Bt); if (newDB.Schema == null) { rc = RC.NOMEM; } else if (newDB.Schema.FileFormat != 0 && newDB.Schema.Encode != E.CTXENCODE(ctx)) { errDyn = SysEx.Mprintf(ctx, "attached databases must use the same text encoding as main database"); rc = RC.ERROR; } Pager pager = newDB.Bt.get_Pager(); pager.LockingMode(ctx.DefaultLockMode); newDB.Bt.SecureDelete(ctx.DBs[0].Bt.SecureDelete(true)); } newDB.SafetyLevel = 3; newDB.Name = name; if (rc == RC.OK && newDB.Name == null) { rc = RC.NOMEM; } #if HAS_CODEC //extern int sqlite3CodecAttach(sqlite3*, int, const void*, int); //extern void sqlite3CodecGetKey(sqlite3*, int, void**, int*); if (rc == RC.OK) { int keyLength; string key; TYPE t = sqlite3_value_type(argv[2]); switch (t) { case TYPE.INTEGER: case TYPE.FLOAT: errDyn = "Invalid key value"; rc = RC.ERROR; break; case TYPE.TEXT: case TYPE.BLOB: keyLength = Mem.Bytes(argv[2]); key = sqlite3_value_blob(argv[2]).ToString(); rc = sqlite3CodecAttach(ctx, ctx.DBs.length - 1, key, keyLength); break; case TYPE.NULL: // No key specified. Use the key from the main database sqlite3CodecGetKey(ctx, 0, out key, out keyLength); if (keyLength > 0 || ctx.DBs[0].Bt.GetReserve() > 0) { rc = sqlite3CodecAttach(ctx, ctx.DBs.length - 1, key, keyLength); } break; } } #endif // If the file was opened successfully, read the schema for the new database. // If this fails, or if opening the file failed, then close the file and remove the entry from the ctx->aDb[] array. i.e. put everything back the way we found it. if (rc == RC.OK) { Btree.EnterAll(ctx); rc = sqlite3Init(ctx, ref errDyn); Btree.LeaveAll(ctx); } if (rc != 0) { int db = ctx.DBs.length - 1; Debug.Assert(db >= 2); if (ctx.DBs[db].Bt != null) { ctx.DBs[db].Bt.Close(); ctx.DBs[db].Bt = null; ctx.DBs[db].Schema = null; } sqlite3ResetInternalSchema(ctx, -1); ctx.DBs.length = db; if (rc == RC.NOMEM || rc == RC.IOERR_NOMEM) { ctx.MallocFailed = true; C._tagfree(ctx, ref errDyn); errDyn = SysEx.Mprintf(ctx, "out of memory"); } else if (errDyn == null) { errDyn = SysEx.Mprintf(ctx, "unable to open database: %s", file); } goto attach_error; } return; attach_error: // Return an error if we get here if (errDyn != null) { sqlite3_result_error(fctx, errDyn, -1); C._tagfree(ctx, ref errDyn); } if (rc != 0) { sqlite3_result_error_code(fctx, rc); } }
public static RC RunVacuum(ref string errMsg, Context ctx) { if (ctx.AutoCommit == 0) { C._setstring(ref errMsg, ctx, "cannot VACUUM from within a transaction"); return(RC.ERROR); } if (ctx.ActiveVdbeCnt > 1) { C._setstring(ref errMsg, ctx, "cannot VACUUM - SQL statements in progress"); return(RC.ERROR); } // Save the current value of the database flags so that it can be restored before returning. Then set the writable-schema flag, and // disable CHECK and foreign key constraints. int saved_Flags = ctx.Flags; // Saved value of the db.flags int saved_Changes = ctx.Changes; // Saved value of db.nChange int saved_TotalChanges = ctx.TotalChanges; // Saved value of db.nTotalChange Action <object, string> saved_Trace = ctx.Trace; // Saved db->xTrace ctx.Flags |= Context.FLAG.WriteSchema | Context.FLAG.IgnoreChecks | Context.FLAG.PreferBuiltin; ctx.Flags &= ~(Context.FLAG.ForeignKeys | Context.FLAG.ReverseOrder); ctx.Trace = null; Btree main = ctx.DBs[0].Bt; // The database being vacuumed bool isMemDb = sqlite3PagerIsMemdb(main.get_Pager()); // True if vacuuming a :memory: database // Attach the temporary database as 'vacuum_db'. The synchronous pragma can be set to 'off' for this file, as it is not recovered if a crash // occurs anyway. The integrity of the database is maintained by a (possibly synchronous) transaction opened on the main database before // sqlite3BtreeCopyFile() is called. // // An optimisation would be to use a non-journaled pager. (Later:) I tried setting "PRAGMA vacuum_db.journal_mode=OFF" but // that actually made the VACUUM run slower. Very little journalling actually occurs when doing a vacuum since the vacuum_db is initially // empty. Only the journal header is written. Apparently it takes more time to parse and run the PRAGMA to turn journalling off than it does // to write the journal header file. int db = ctx.DBs.length; // Number of attached databases string sql = (sqlite3TempInMemory(ctx) ? "ATTACH ':memory:' AS vacuum_db;" : "ATTACH '' AS vacuum_db;"); // SQL statements RC rc = ExecSql(ctx, errMsg, sql); // Return code from service routines Context.DB dbObj = null; // Database to detach at end of vacuum if (ctx.DBs.length > db) { dbObj = ctx.DBs[ctx.DBs.length - 1]; Debug.Assert(dbObj.Name == "vacuum_db"); } if (rc != RC.OK) { goto end_of_vacuum; } Btree temp = ctx.DBs[ctx.DBs.length - 1].Bt; // The temporary database we vacuum into // The call to execSql() to attach the temp database has left the file locked (as there was more than one active statement when the transaction // to read the schema was concluded. Unlock it here so that this doesn't cause problems for the call to BtreeSetPageSize() below. temp.Commit(); int res = main.GetReserve(); // Bytes of reserved space at the end of each page // A VACUUM cannot change the pagesize of an encrypted database. #if HAS_CODEC if (ctx.NextPagesize != 0) { int nKey; string zKey; sqlite3CodecGetKey(ctx, 0, out zKey, out nKey); if (nKey != 0) { ctx.NextPagesize = 0; } } #endif rc = ExecSql(ctx, errMsg, "PRAGMA vacuum_db.synchronous=OFF"); if (rc != RC.OK) { goto end_of_vacuum; } // Begin a transaction and take an exclusive lock on the main database file. This is done before the sqlite3BtreeGetPageSize(main) call below, // to ensure that we do not try to change the page-size on a WAL database. rc = ExecSql(ctx, errMsg, "BEGIN;"); if (rc != RC.OK) { goto end_of_vacuum; } rc = main.BeginTrans(2); if (rc != RC.OK) { goto end_of_vacuum; } // Do not attempt to change the page size for a WAL database if (main.get_Pager().GetJournalMode() == IPager.JOURNALMODE.WAL) { ctx.NextPagesize = 0; } if (temp.SetPageSize(main.GetPageSize(), res, 0) != 0 || (!isMemDb && temp.SetPageSize(ctx->NextPagesize, res, false) != 0) || C._NEVER(ctx.MallocFailed)) { rc = RC.NOMEM; goto end_of_vacuum; } #if !OMIT_AUTOVACUUM temp.SetAutoVacuum(ctx.NextAutovac >= 0 ? ctx.NextAutovac : main.GetAutoVacuum()); #endif // Query the schema of the main database. Create a mirror schema in the temporary database. rc = ExecExecSql(ctx, errMsg, "SELECT 'CREATE TABLE vacuum_db.' || substr(sql,14) " + " FROM sqlite_master WHERE type='table' AND name!='sqlite_sequence'" + " AND rootpage>0" ); if (rc != RC.OK) { goto end_of_vacuum; } rc = ExecExecSql(ctx, errMsg, "SELECT 'CREATE INDEX vacuum_db.' || substr(sql,14)" + " FROM sqlite_master WHERE sql LIKE 'CREATE INDEX %' "); if (rc != RC.OK) { goto end_of_vacuum; } rc = ExecExecSql(ctx, errMsg, "SELECT 'CREATE UNIQUE INDEX vacuum_db.' || substr(sql,21) " + " FROM sqlite_master WHERE sql LIKE 'CREATE UNIQUE INDEX %'"); if (rc != RC.OK) { goto end_of_vacuum; } // Loop through the tables in the main database. For each, do an "INSERT INTO vacuum_db.xxx SELECT * FROM main.xxx;" to copy // the contents to the temporary database. rc = ExecExecSql(ctx, errMsg, "SELECT 'INSERT INTO vacuum_db.' || quote(name) " + "|| ' SELECT * FROM main.' || quote(name) || ';'" + "FROM main.sqlite_master " + "WHERE type = 'table' AND name!='sqlite_sequence' " + " AND rootpage>0"); if (rc != RC.OK) { goto end_of_vacuum; } // Copy over the sequence table rc = ExecExecSql(ctx, errMsg, "SELECT 'DELETE FROM vacuum_db.' || quote(name) || ';' " + "FROM vacuum_db.sqlite_master WHERE name='sqlite_sequence' "); if (rc != RC.OK) { goto end_of_vacuum; } rc = ExecExecSql(ctx, errMsg, "SELECT 'INSERT INTO vacuum_db.' || quote(name) " + "|| ' SELECT * FROM main.' || quote(name) || ';' " + "FROM vacuum_db.sqlite_master WHERE name=='sqlite_sequence';"); if (rc != RC.OK) { goto end_of_vacuum; } // Copy the triggers, views, and virtual tables from the main database over to the temporary database. None of these objects has any // associated storage, so all we have to do is copy their entries from the SQLITE_MASTER table. rc = ExecSql(ctx, errMsg, "INSERT INTO vacuum_db.sqlite_master " + " SELECT type, name, tbl_name, rootpage, sql" + " FROM main.sqlite_master" + " WHERE type='view' OR type='trigger'" + " OR (type='table' AND rootpage=0)"); if (rc != 0) { goto end_of_vacuum; } // At this point, there is a write transaction open on both the vacuum database and the main database. Assuming no error occurs, // both transactions are closed by this block - the main database transaction by sqlite3BtreeCopyFile() and the other by an explicit // call to sqlite3BtreeCommit(). { // This array determines which meta meta values are preserved in the vacuum. Even entries are the meta value number and odd entries // are an increment to apply to the meta value after the vacuum. The increment is used to increase the schema cookie so that other // connections to the same database will know to reread the schema. Debug.Assert(Btree.IsInTrans(temp)); Debug.Assert(Btree.IsInTrans(main)); // Copy Btree meta values for (int i = 0; i < _runVacuum_copy.Length; i += 2) { // GetMeta() and UpdateMeta() cannot fail in this context because we already have page 1 loaded into cache and marked dirty. uint meta = 0; main.GetMeta(_runVacuum_copy[i], ref meta); rc = temp.UpdateMeta(temp, _runVacuum_copy[i], (uint)(meta + _runVacuum_copy[i + 1])); if (C._NEVER(rc != RC.OK)) { goto end_of_vacuum; } } rc = sqlite3BtreeCopyFile(main, temp); if (rc != RC.OK) { goto end_of_vacuum; } rc = temp.Commit(); if (rc != RC.OK) { goto end_of_vacuum; } #if !SQLITE_OMIT_AUTOVACUUM main.SetAutoVacuum(temp.GetAutoVacuum()); #endif } Debug.Assert(rc == RC.OK); rc = main.SetPageSize(temp.GetPageSize(temp), res, 1); end_of_vacuum: // Restore the original value of ctx->flags ctx.Flags = saved_Flags; ctx.Changes = saved_Changes; ctx.TotalChanges = saved_TotalChanges; ctx.Trace = saved_Trace; sqlite3BtreeSetPageSize(main, -1, -1, 1); // Currently there is an SQL level transaction open on the vacuum database. No locks are held on any other files (since the main file // was committed at the btree level). So it safe to end the transaction by manually setting the autoCommit flag to true and detaching the // vacuum database. The vacuum_db journal file is deleted when the pager is closed by the DETACH. ctx.AutoCommit = 1; if (dbObj != null) { dbObj.Bt.Close(); dbObj.Bt = null; dbObj.Schema = null; } // This both clears the schemas and reduces the size of the db->aDb[] array. sqlite3ResetInternalSchema(ctx, -1); return(rc); }