//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); }
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; }