예제 #1
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);
        }
예제 #2
0
파일: Backup.cs 프로젝트: BclEx/GpuEx
        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;
        }