Exemple #1
0
        public static RC Config(Context ctx, VTABLECONFIG op, object arg1)
        {
            RC rc = RC.OK;

            MutexEx.Enter(ctx.Mutex);
            switch (op)
            {
            case VTABLECONFIG.CONSTRAINT:
            {
                VTableContext p = ctx.VTableCtx;
                if (p == null)
                {
                    rc = SysEx.MISUSE_BKPT();
                }
                else
                {
                    Debug.Assert(p.Table == null || (p.Table.TabFlags & TF.Virtual) != 0);
                    p.VTable.Constraint = (bool)arg1;
                }
                break;
            }

            default:
                rc = SysEx.MISUSE_BKPT();
                break;
            }
            if (rc != RC.OK)
            {
                DataEx.Error(ctx, rc, null);
            }
            MutexEx.Leave(ctx.Mutex);
            return(rc);
        }
Exemple #2
0
        public Backup Next;             // Next backup associated with source pager

        static Btree FindBtree(Context errorCtx, Context ctx, string dbName)
        {
            int i = Parse.FindDbName(ctx, dbName);
            if (i == 1)
            {
                RC rc = 0;
                Parse parse = new Parse();
                if (parse == null)
                {
                    DataEx.Error(errorCtx, RC.NOMEM, "out of memory");
                    rc = RC.NOMEM;
                }
                else
                {
                    parse.Ctx = ctx;
                    if (parse.OpenTempDatabase() != 0)
                    {
                        DataEx.Error(errorCtx, parse.RC, "%s", parse.ErrMsg);
                        rc = RC.ERROR;
                    }
                    C._tagfree(errorCtx, ref parse.ErrMsg);
                }
                if (rc != 0)
                    return null;
            }

            if (i < 0)
            {
                DataEx.Error(errorCtx, RC.ERROR, "unknown database %s", dbName);
                return null;
            }
            return ctx.DBs[i].Bt;
        }
Exemple #3
0
        static RC BlobSeekToRow(Incrblob p, long row, out string errOut)
        {
            string err = null; // Error message
            Vdbe   v   = p.Stmt;

            // Set the value of the SQL statements only variable to integer iRow. This is done directly instead of using sqlite3_bind_int64() to avoid
            // triggering asserts related to mutexes.
            Debug.Assert((v.Vars[0].Flags & MEM.Int) != 0);
            v.Vars[0].u.I = row;

            RC rc = v.Step();

            if (rc == RC.ROW)
            {
                uint type = v.Cursors[0].Types[p.Col];
                if (type < 12)
                {
                    err = C._mtagprintf(p.Ctx, "cannot open value of type %s", type == 0 ? "null" : type == 7 ? "real" : "integer");
                    rc  = RC.ERROR;
                    Vdbe.Finalize(v);
                    p.Stmt = null;
                }
                else
                {
                    p.Offset = v.Cursors[0].Offsets[p.Col];
                    p.Bytes  = SerialTypeLen(type);
                    p.Cursor = v.Cursors[0].Cursor;
                    p.Cursor.EnterCursor();
                    Btree.CacheOverflow(p.Cursor);
                    p.Cursor.LeaveCursor();
                }
            }

            if (rc == RC.ROW)
            {
                rc = RC.OK;
            }
            else if (p.Stmt != null)
            {
                rc     = Vdbe.Finalize(p.Stmt);
                p.Stmt = null;
                if (rc == RC.OK)
                {
                    err = C._mtagprintf(p.Ctx, "no such rowid: %lld", row);
                    rc  = RC.ERROR;
                }
                else
                {
                    err = C._mtagprintf(p.Ctx, "%s", DataEx.Errmsg(p.Ctx));
                }
            }

            Debug.Assert(rc != RC.OK || err == null);
            Debug.Assert(rc != RC.ROW && rc != RC.DONE);

            errOut = err;
            return(rc);
        }
Exemple #4
0
        public static RC GetTable(Context db, string sql, ref string[] results, ref int rows, ref int columns, ref string errMsg)
        {
            results = null;
            columns = 0;
            rows    = 0;
            errMsg  = null;
            TabResult r = new TabResult();

            r.ErrMsg        = null;
            r.Rows          = 0;
            r.Columns       = 0;
            r.RC            = RC.OK;
            r.ResultsAlloc  = 20;
            r.Results       = new string[r.ResultsAlloc];
            r.ResultsLength = 1;
            if (r.Results == null)
            {
                return(db.ErrCode = RC.NOMEM);
            }
            r.Results[0] = null;
            RC rc = DataEx.Exec(db, sql, GetTableCallback, r, ref errMsg);

            //Debug.Assert(sizeof(r.Results[0])>= sizeof(r.ResultsLength));
            //r.Results = INT_TO_PTR(r.ResultsLength);
            if (((int)rc & 0xff) == (int)RC.ABORT)
            {
                FreeTable(ref r.Results);
                if (r.ErrMsg != null)
                {
                    if (errMsg != null)
                    {
                        C._free(ref errMsg);
                        errMsg = r.ErrMsg;
                    }
                    C._free(ref r.ErrMsg);
                }
                return(db.ErrCode = r.RC);  // Assume 32-bit assignment is atomic
            }
            C._free(ref r.ErrMsg);
            if (rc != RC.OK)
            {
                FreeTable(ref r.Results);
                return(rc);
            }
            if (r.ResultsAlloc > r.ResultsLength)
            {
                Array.Resize(ref r.Results, r.ResultsLength);
            }
            results = r.Results;
            columns = r.Columns;
            rows    = r.Rows;
            return(rc);
        }
Exemple #5
0
        public static Backup Init(Context destCtx, string destDbName, Context srcCtx, string srcDbName)
        {
            // Lock the source database handle. The destination database handle is not locked in this routine, but it is locked in
            // sqlite3_backup_step(). The user is required to ensure that no other thread accesses the destination handle for the duration
            // of the backup operation.  Any attempt to use the destination database connection while a backup is in progress may cause
            // a malfunction or a deadlock.
            MutexEx.Enter(srcCtx.Mutex);
            MutexEx.Enter(destCtx.Mutex);

            Backup p;
            if (srcCtx == destCtx)
            {
                DataEx.Error(destCtx, RC.ERROR, "source and destination must be distinct");
                p = null;
            }
            else
            {
                // Allocate space for a new sqlite3_backup object... EVIDENCE-OF: R-64852-21591 The sqlite3_backup object is created by a
                // call to sqlite3_backup_init() and is destroyed by a call to sqlite3_backup_finish().
                p = new Backup();
                if (p == null)
                    DataEx.Error(destCtx, RC.NOMEM, null);
            }

            // If the allocation succeeded, populate the new object.
            if (p != null)
            {
                p.Src = FindBtree(destCtx, srcCtx, srcDbName);
                p.Dest = FindBtree(destCtx, destCtx, destDbName);
                p.DestCtx = destCtx;
                p.SrcCtx = srcCtx;
                p.NextId = 1;
                p.IsAttached = false;

                if (p.Src == null || p.Dest == null || SetDestPgsz(p) == RC.NOMEM)
                {
                    // One (or both) of the named databases did not exist or an OOM error was hit.  The error has already been written into the
                    // pDestDb handle.  All that is left to do here is free the sqlite3_backup structure.
                    //C._free(ref p);
                    p = null;
                }
            }
            if (p != null)
                p.Src.Backups++;

            MutexEx.Leave(destCtx.Mutex);
            MutexEx.Leave(srcCtx.Mutex);
            return p;
        }
Exemple #6
0
        public static RC Finalize(Vdbe p)
        {
            if (p == null)
            {
                return(RC.OK); // IMPLEMENTATION-OF: R-57228-12904 Invoking sqlite3_finalize() on a NULL pointer is a harmless no-op.
            }
            Context ctx = p.Ctx;

            if (VdbeSafety(p))
            {
                return(SysEx.MISUSE_BKPT());
            }
            MutexEx.Enter(ctx.Mutex);
            RC rc = p.Finalize();

            rc = SysEx.ApiExit(ctx, rc);
            DataEx.LeaveMutexAndCloseZombie(ctx);
            return(rc);
        }
Exemple #7
0
        public static RC Finish(Backup p)
        {
            // Enter the mutexes 
            if (p == null) return RC.OK;
            Context srcCtx = p.SrcCtx; // Source database connection
            MutexEx.Enter(srcCtx.Mutex);
            p.Src.Enter();
            if (p.DestCtx != null)
                MutexEx.Enter(p.DestCtx.Mutex);

            // Detach this backup from the source pager.
            if (p.DestCtx != null)
                p.Src.Backups--;
            if (p.IsAttached)
            {
                Backup pp = (Backup)p.Src.get_Pager().Backup;
                while (pp != p)
                    pp = (pp).Next;
                p.Src.get_Pager().Backup = p.Next;
            }

            // If a transaction is still open on the Btree, roll it back.
            p.Dest.Rollback(RC.OK);

            // Set the error code of the destination database handle.
            RC rc = (p.RC_ == RC.DONE ? RC.OK : p.RC_);
            DataEx.Error(p.DestCtx, rc, null);

            // Exit the mutexes and free the backup context structure.
            if (p.DestCtx != null)
                DataEx.LeaveMutexAndCloseZombie(p.DestCtx.Mutex);
            p.Src.Leave();
            //// EVIDENCE-OF: R-64852-21591 The sqlite3_backup object is created by a call to sqlite3_backup_init() and is destroyed by a call to sqlite3_backup_finish().
            //if (p.DestCtx != null)
            //    C._free(ref p);
            DataEx.LeaveMutexAndCloseZombie(mutex);
            return rc;
        }
Exemple #8
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._mtagassignf(ref errMsg, ctx, "%s", DataEx.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._mtagassignf(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._mtagassignf(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;
                DataEx.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);
        }
Exemple #9
0
        public RC Step(int pages)
        {
            MutexEx.Enter(SrcCtx.Mutex);
            Src.Enter();
            if (DestCtx != null)
                MutexEx.Enter(DestCtx.Mutex);

            RC rc = RC_;
            if (!IsFatalError(rc))
            {
                Pager srcPager = Src.get_Pager(); // Source pager
                Pager destPager = Dest.get_Pager(); // Dest pager
                Pid srcPage = 0; // Size of source db in pages
                bool closeTrans = false; // True if src db requires unlocking

                // If the source pager is currently in a write-transaction, return SQLITE_BUSY immediately.
                rc = (DestCtx != null && Src.Bt.InTransaction == TRANS.WRITE ? RC.BUSY : RC.OK);

                // Lock the destination database, if it is not locked already.
                if (rc == RC.OK && !DestLocked && (rc = Dest.BeginTrans(2)) == RC.OK)
                {
                    DestLocked = true;
                    Dest.GetMeta(Btree.META.SCHEMA_VERSION, ref DestSchema);
                }

                // If there is no open read-transaction on the source database, open one now. If a transaction is opened here, then it will be closed
                // before this function exits.
                if (rc == RC.OK && !Src.IsInReadTrans())
                {
                    rc = Src.BeginTrans(0);
                    closeTrans = true;
                }

                // Do not allow backup if the destination database is in WAL mode and the page sizes are different between source and destination
                int pgszSrc = Src.GetPageSize(); // Source page size
                int pgszDest = Dest.GetPageSize(); // Destination page size
                IPager.JOURNALMODE destMode = Dest.get_Pager().GetJournalMode(); // Destination journal mode
                if (rc == RC.OK && destMode == IPager.JOURNALMODE.WAL && pgszSrc != pgszDest)
                    rc = RC.READONLY;

                // Now that there is a read-lock on the source database, query the source pager for the number of pages in the database.
                srcPage = Src.LastPage();
                Debug.Assert(srcPage >= 0);
                for (int ii = 0; (pages < 0 || ii < pages) && NextId <= (Pid)srcPage && rc == 0; ii++)
                {
                    Pid srcPg = NextId; // Source page number
                    if (srcPg != Btree.PENDING_BYTE_PAGE(Src.Bt))
                    {
                        IPage srcPgAsObj = null; // Source page object
                        rc = srcPager.Acquire(srcPg, ref srcPgAsObj, false);
                        if (rc == RC.OK)
                        {
                            rc = BackupOnePage(p, srcPg, Pager.GetData(srcPgAsObj), false);
                            Pager.Unref(srcPgAsObj);
                        }
                    }
                    NextId++;
                }
                if (rc == RC.OK)
                {
                    Pagecount = srcPage;
                    Remaining = (srcPage + 1 - NextId);
                    if (NextId > srcPage)
                        rc = RC.DONE;
                    else if (!IsAttached)
                        AttachBackupObject(p);
                }

                // Update the schema version field in the destination database. This is to make sure that the schema-version really does change in
                // the case where the source and destination databases have the same schema version.
                if (rc == RC.DONE)
                {
                    if (srcPage == null)
                    {
                        rc = Dest.NewDb();
                        srcPage = 1;
                    }
                    if (rc == RC.OK || rc == RC.DONE)
                        rc = Dest.UpdateMeta(Btree.META.SCHEMA_VERSION, DestSchema + 1);
                    if (rc == RC.OK)
                    {
                        if (DestCtx != null)
                            DataEx.ResetAllSchemasOfConnection(DestCtx);
                        if (destMode == IPager.JOURNALMODE.WAL)
                            rc = Dest.SetVersion(2);
                    }
                    if (rc == RC.OK)
                    {
                        // Set nDestTruncate to the final number of pages in the destination database. The complication here is that the destination page
                        // size may be different to the source page size. 
                        //
                        // If the source page size is smaller than the destination page size, round up. In this case the call to sqlite3OsTruncate() below will
                        // fix the size of the file. However it is important to call sqlite3PagerTruncateImage() here so that any pages in the 
                        // destination file that lie beyond the nDestTruncate page mark are journalled by PagerCommitPhaseOne() before they are destroyed
                        // by the file truncation.

                        Debug.Assert(pgszSrc == Src.GetPageSize());
                        Debug.Assert(pgszDest == Dest.GetPageSize());
                        Pid destTruncate;
                        if (pgszSrc < pgszDest)
                        {
                            int ratio = pgszDest / pgszSrc;
                            destTruncate = (Pid)((srcPage + ratio - 1) / ratio);
                            if (destTruncate == Btree.PENDING_BYTE_PAGE(Dest.Bt))
                                destTruncate--;
                        }
                        else
                            destTruncate = (Pid)(srcPage * (pgszSrc / pgszDest));
                        Debug.Assert(destTruncate > 0);

                        if (pgszSrc < pgszDest)
                        {
                            // If the source page-size is smaller than the destination page-size, two extra things may need to happen:
                            //
                            //   * The destination may need to be truncated, and
                            //
                            //   * Data stored on the pages immediately following the pending-byte page in the source database may need to be
                            //     copied into the destination database.
                            int size = (int)(pgszSrc * srcPage);
                            VFile file = destPager.get_File();
                            Debug.Assert(file != null);
                            Debug.Assert((long)destTruncate * (long)pgszDest >= size || (destTruncate == (int)(Btree.PENDING_BYTE_PAGE(Dest.Bt) - 1) && size >= VFile.PENDING_BYTE && size <= VFile.PENDING_BYTE + pgszDest));

                            // This block ensures that all data required to recreate the original database has been stored in the journal for pDestPager and the
                            // journal synced to disk. So at this point we may safely modify the database file in any way, knowing that if a power failure
                            // occurs, the original database will be reconstructed from the journal file.
                            uint dstPage;
                            destPager.Pages(out dstPage);
                            for (Pid pg = destTruncate; rc == RC.OK && pg <= (Pid)dstPage; pg++)
                            {
                                if (pg != Btree.PENDING_BYTE_PAGE(Dest.Bt))
                                {
                                    IPage pgAsObj;
                                    rc = destPager.Acquire(pg, ref pgAsObj, false);
                                    if (rc == RC.OK)
                                    {
                                        rc = Pager.Write(pgAsObj);
                                        Pager.Unref(pgAsObj);
                                    }
                                }
                            }
                            if (rc == RC.OK)
                                rc = destPager.CommitPhaseOne(null, true);

                            // Write the extra pages and truncate the database file as required.
                            long end = Math.Min(VFile.PENDING_BYTE + pgszDest, size);
                            for (long off = VFile.PENDING_BYTE + pgszSrc; rc == RC.OK && off < end; off += pgszSrc)
                            {
                                Pid srcPg = (Pid)((off / pgszSrc) + 1);
                                PgHdr srcPgAsObj = null;
                                rc = srcPager.Acquire(srcPg, ref srcPgAsObj, false);
                                if (rc == RC.OK)
                                {
                                    byte[] data = Pager.GetData(srcPgAsObj);
                                    rc = file.Write(data, pgszSrc, off);
                                }
                                Pager.Unref(srcPgAsObj);
                            }
                            if (rc == RC.OK)
                                rc = BackupTruncateFile(file, (int)size);

                            // Sync the database file to disk. 
                            if (rc == RC.OK)
                                rc = destPager.Sync();
                        }
                        else
                        {
                            destPager.TruncateImage(destTruncate);
                            rc = destPager.CommitPhaseOne(null, false);
                        }

                        // Finish committing the transaction to the destination database.
                        if (rc == RC.OK && (rc = Dest.CommitPhaseTwo(false)) == RC.OK)
                            rc = RC.DONE;
                    }
                }

                // If bCloseTrans is true, then this function opened a read transaction on the source database. Close the read transaction here. There is
                // no need to check the return values of the btree methods here, as "committing" a read-only transaction cannot fail.
                if (closeTrans)
                {
#if !DEBUG || COVERAGE_TEST
                    RC rc2 = Src.CommitPhaseOne(null);
                    rc2 |= Src.CommitPhaseTwo(false);
                    Debug.Assert(rc2 == RC.OK);
#else
                    Src.CommitPhaseOne(null);
                    Src.CommitPhaseTwo(false);
#endif
                }

                if (rc == RC.IOERR_NOMEM)
                    rc = RC.NOMEM;
                RC_ = rc;
            }
            if (DestCtx != null)
                MutexEx.Leave(DestCtx.Mutex);
            Src.Leave();
            MutexEx.Leave(SrcCtx.Mutex);
            return rc;
        }