static void SchemaIsValid(Parse parse) { Context ctx = parse.Ctx; Debug.Assert(parse.CheckSchema != 0); Debug.Assert(MutexEx.Held(ctx.Mutex)); for (int db = 0; db < ctx.DBs.length; db++) { bool openedTransaction = false; // True if a transaction is opened Btree bt = ctx.DBs[db].Bt; // Btree database to read cookie from if (bt == null) { continue; } // 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 immediately after reading the meta-value. if (!bt.IsInReadTrans()) { RC rc = bt.BeginTrans(0); if (rc == RC.NOMEM || rc == RC.IOERR_NOMEM) { ctx.MallocFailed = true; } if (rc != RC.OK) { return; } openedTransaction = true; } // Read the schema cookie from the database. If it does not match the value stored as part of the in-memory schema representation, // set Parse.rc to SQLITE_SCHEMA. uint cookie; bt.GetMeta(Btree.META.SCHEMA_VERSION, ref cookie); Debug.Assert(Btree.SchemaMutexHeld(ctx, db, null)); if (cookie != ctx.DBs[db].Schema.SchemaCookie) { ResetOneSchema(ctx, db); parse.RC = RC.SCHEMA; } // Close the transaction, if one was opened. if (openedTransaction) { bt.Commit(); } } }
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) Main.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; }