示例#1
0
        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);
        }
示例#2
0
        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]);
            }
        }
示例#3
0
        //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);
        }
示例#4
0
        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);
            }
        }
示例#5
0
        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);
        }