// was:sqlite3PagerOpen public static RC Open(VirtualFileSystem pVfs, out Pager ppPager, string zFilename, int nExtra, PAGEROPEN flags, VFSOPEN vfsFlags, Action<PgHdr> xReinit, Func<object> memPageBuilder) { Pager pPager = null; // Pager object to allocate and return byte memDb = 0; // True if this is an in-memory file bool readOnly = false; // True if this is a read-only file string zPathname = null; // Full path to database file //int nPathname = 0; // Number of bytes in zPathname bool useJournal = (flags & PAGEROPEN.OMIT_JOURNAL) == 0; // False to omit journal bool noReadlock = (flags & PAGEROPEN.NO_READLOCK) != 0; // True to omit read-lock int pcacheSize = PCache.sqlite3PcacheSize(); // Bytes to allocate for PCache uint szPageDflt = SQLITE_DEFAULT_PAGE_SIZE; // Default page size string zUri = null; // URI args to copy int nUri = 0; // Number of bytes of URI args at *zUri // Figure out how much space is required for each journal file-handle (there are two of them, the main journal and the sub-journal). This // is the maximum space required for an in-memory journal file handle and a regular journal file-handle. Note that a "regular journal-handle" // may be a wrapper capable of caching the first portion of the journal file in memory to implement the atomic-write optimization (see // source file journal.c). int journalFileSize = SysEx.ROUND8(sqlite3JournalSize(pVfs) > MemJournalFile.sqlite3MemJournalSize() ? sqlite3JournalSize(pVfs) : MemJournalFile.sqlite3MemJournalSize()); // Bytes to allocate for each journal fd // Set the output variable to NULL in case an error occurs. ppPager = null; #if !SQLITE_OMIT_MEMORYDB if ((flags & PAGEROPEN.MEMORY) != 0) { memDb = 1; zFilename = null; } #endif // Compute and store the full pathname in an allocated buffer pointed to by zPathname, length nPathname. Or, if this is a temporary file, // leave both nPathname and zPathname set to 0. var rc = RC.OK; if (!string.IsNullOrEmpty(zFilename)) { rc = pVfs.xFullPathname(zFilename, out zPathname); var z = zUri = zFilename; nUri = zUri.Length; if (rc == RC.OK && zPathname.Length + 8 > pVfs.mxPathname) // This branch is taken when the journal path required by the database being opened will be more than pVfs.mxPathname // bytes in length. This means the database cannot be opened, as it will not be possible to open the journal file or even // check for a hot-journal before reading. rc = SysEx.SQLITE_CANTOPEN_BKPT(); if (rc != RC.OK) return rc; } // Allocate memory for the Pager structure, PCache object, the three file descriptors, the database file name and the journal // file name. The layout in memory is as follows: // Pager object (sizeof(Pager) bytes) // PCache object (sqlite3PcacheSize() bytes) // Database file handle (pVfs.szOsFile bytes) // Sub-journal file handle (journalFileSize bytes) // Main journal file handle (journalFileSize bytes) // Database file name (nPathname+1 bytes) // Journal file name (nPathname+8+1 bytes) pPager = new Pager(memPageBuilder); pPager.pPCache = new PCache(); pPager.fd = new VirtualFile(); pPager.sjfd = new VirtualFile(); pPager.jfd = new VirtualFile(); // Fill in the Pager.zFilename and Pager.zJournal buffers, if required. if (zPathname != null) { Debug.Assert(zPathname.Length > 0); pPager.zFilename = zPathname.ToString(); zUri = pPager.zFilename; pPager.zJournal = pPager.zFilename + "-journal"; #if !SQLITE_OMIT_WAL pPager.zWal = &pPager.zJournal[nPathname + 8 + 1]; memcpy(pPager.zWal, zPathname, nPathname); memcpy(&pPager.zWal[nPathname], "-wal", 4); #endif } else pPager.zFilename = string.Empty; pPager.pVfs = pVfs; pPager.vfsFlags = vfsFlags; // Open the pager file. var tempFile = LOCKINGMODE.NORMAL; // True for temp files (incl. in-memory files) if (!string.IsNullOrEmpty(zFilename)) { VFSOPEN fout = 0; // VFS flags returned by xOpen() rc = FileEx.sqlite3OsOpen(pVfs, zFilename, pPager.fd, vfsFlags, ref fout); Debug.Assert(0 == memDb); readOnly = (fout & VFSOPEN.READONLY) != 0; // If the file was successfully opened for read/write access, choose a default page size in case we have to create the // database file. The default page size is the maximum of: // + SQLITE_DEFAULT_PAGE_SIZE, // + The value returned by sqlite3OsSectorSize() // + The largest page size that can be written atomically. if (rc == RC.OK && !readOnly) { pPager.setSectorSize(); Debug.Assert(SQLITE_DEFAULT_PAGE_SIZE <= SQLITE_MAX_DEFAULT_PAGE_SIZE); if (szPageDflt < pPager.sectorSize) szPageDflt = (pPager.sectorSize > SQLITE_MAX_DEFAULT_PAGE_SIZE ? SQLITE_MAX_DEFAULT_PAGE_SIZE : (uint)pPager.sectorSize); #if SQLITE_ENABLE_ATOMIC_WRITE int iDc = sqlite3OsDeviceCharacteristics(pPager.fd); Debug.Assert(SQLITE_IOCAP_ATOMIC512 == (512 >> 8)); Debug.Assert(SQLITE_IOCAP_ATOMIC64K == (65536 >> 8)); Debug.Assert(SQLITE_MAX_DEFAULT_PAGE_SIZE <= 65536); for (var ii = szPageDflt; ii <= SQLITE_MAX_DEFAULT_PAGE_SIZE; ii = ii * 2) if (iDc & (SQLITE_IOCAP_ATOMIC | (ii >> 8))) szPageDflt = ii; #endif } } else { // If a temporary file is requested, it is not opened immediately. In this case we accept the default page size and delay actually // opening the file until the first call to OsWrite(). // This branch is also run for an in-memory database. An in-memory database is the same as a temp-file that is never written out to // disk and uses an in-memory rollback journal. tempFile = LOCKINGMODE.EXCLUSIVE; pPager.eState = PAGER.READER; pPager.eLock = VFSLOCK.EXCLUSIVE; readOnly = (vfsFlags & VFSOPEN.READONLY) != 0; } // The following call to PagerSetPagesize() serves to set the value of Pager.pageSize and to allocate the Pager.pTmpSpace buffer. if (rc == RC.OK) { Debug.Assert(pPager.memDb == 0); rc = pPager.SetPageSize(ref szPageDflt, -1); } // If an error occurred in either of the blocks above, free the Pager structure and close the file. if (rc != RC.OK) { Debug.Assert(null == pPager.pTmpSpace); FileEx.sqlite3OsClose(pPager.fd); return rc; } // Initialize the PCache object. Debug.Assert(nExtra < 1000); nExtra = SysEx.ROUND8(nExtra); PCache.Open((int)szPageDflt, nExtra, (memDb == 0), (memDb == 0 ? (Func<object, PgHdr, RC>)pagerStress : null), pPager, pPager.pPCache); PAGERTRACE("OPEN {0} {1}", FILEHANDLEID(pPager.fd), pPager.zFilename); SysEx.IOTRACE("OPEN {0:x} {1}", pPager.GetHashCode(), pPager.zFilename); pPager.useJournal = (byte)(useJournal ? 1 : 0); pPager.noReadlock = (byte)(noReadlock && readOnly ? 1 : 0); pPager.mxPgno = SQLITE_MAX_PAGE_COUNT; #if false Debug.Assert(pPager.state == (tempFile != 0 ? PAGER.EXCLUSIVE : PAGER.UNLOCK)); #endif pPager.tempFile = tempFile != 0; Debug.Assert(tempFile == LOCKINGMODE.NORMAL || tempFile == LOCKINGMODE.EXCLUSIVE); pPager.exclusiveMode = tempFile != 0; pPager.changeCountDone = pPager.tempFile; pPager.memDb = memDb; pPager.readOnly = readOnly; Debug.Assert(useJournal || pPager.tempFile); pPager.noSync = pPager.tempFile; pPager.fullSync = pPager.noSync; pPager.syncFlags = (pPager.noSync ? 0 : VirtualFile.SYNC.NORMAL); pPager.ckptSyncFlags = pPager.syncFlags; pPager.nExtra = (ushort)nExtra; pPager.journalSizeLimit = SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT; Debug.Assert(pPager.fd.IsOpen || tempFile != 0); pPager.setSectorSize(); if (!useJournal) pPager.journalMode = JOURNALMODE.OFF; else if (memDb != 0) pPager.journalMode = JOURNALMODE.MEMORY; pPager.xReiniter = xReinit; ppPager = pPager; return RC.OK; }
// was:sqlite3BtreeOpen public static RC Open(VirtualFileSystem pVfs, string zFilename, sqlite3 db, ref Btree rTree, OPEN flags, VFSOPEN vfsFlags) { Btree p; // Handle to return var rc = RC.OK; byte nReserve; // Byte of unused space on each page var zDbHeader = new byte[100]; // Database header content // True if opening an ephemeral, temporary database */ bool isTempDb = string.IsNullOrEmpty(zFilename); // Set the variable isMemdb to true for an in-memory database, or false for a file-based database. #if SQLITE_OMIT_MEMORYDB var isMemdb = false; #else var isMemdb = (zFilename == ":memory:" || isTempDb && db.sqlite3TempInMemory()); #endif Debug.Assert(db != null); Debug.Assert(pVfs != null); Debug.Assert(MutexEx.Held(db.Mutex)); Debug.Assert(((uint)flags & 0xff) == (uint)flags); // flags fit in 8 bits // Only a BTREE_SINGLE database can be BTREE_UNORDERED Debug.Assert((flags & OPEN.UNORDERED) == 0 || (flags & OPEN.SINGLE) != 0); // A BTREE_SINGLE database is always a temporary and/or ephemeral Debug.Assert((flags & OPEN.SINGLE) == 0 || isTempDb); if ((db.flags & sqlite3b.SQLITE.NoReadlock) != 0) flags |= OPEN.NO_READLOCK; if (isMemdb) flags |= OPEN.MEMORY; if ((vfsFlags & VFSOPEN.MAIN_DB) != 0 && (isMemdb || isTempDb)) vfsFlags = (vfsFlags & ~VFSOPEN.MAIN_DB) | VFSOPEN.TEMP_DB; p = new Btree(); p.InTransaction = TRANS.NONE; p.DB = db; #if !SQLITE_OMIT_SHARED_CACHE p.Locks.Tree = p; p.Locks.TableID = 1; #endif BtShared shared = null; // Shared part of btree structure sqlite3_mutex mutexOpen = null; // Prevents a race condition. #if !SQLITE_OMIT_SHARED_CACHE && !SQLITE_OMIT_DISKIO // If this Btree is a candidate for shared cache, try to find an existing BtShared object that we can share with if (!isMemdb && !isTempDb) { if ((vfsFlags & VFSOPEN.SHAREDCACHE) != 0) { p.Sharable = true; string zPathname; rc = pVfs.xFullPathname(zFilename, out zPathname); mutexOpen = MutexEx.sqlite3MutexAlloc(MUTEX.STATIC_OPEN); MutexEx.sqlite3_mutex_enter(mutexOpen); var mutexShared = MutexEx.sqlite3MutexAlloc(MUTEX.STATIC_MASTER); MutexEx.sqlite3_mutex_enter(mutexShared); for (shared = SysEx.getGLOBAL<BtShared>(s_sqlite3SharedCacheList); shared != null; shared = shared.Next) { Debug.Assert(shared.nRef > 0); if (string.Equals(zPathname, shared.Pager.sqlite3PagerFilename) && shared.Pager.sqlite3PagerVfs == pVfs) { for (var iDb = db.DBs - 1; iDb >= 0; iDb--) { var existingTree = db.AllocDBs[iDb].Tree; if (existingTree != null && existingTree.Shared == shared) { MutexEx.sqlite3_mutex_leave(mutexShared); MutexEx.sqlite3_mutex_leave(mutexOpen); p = null; return RC.CONSTRAINT; } } p.Shared = shared; shared.nRef++; break; } } MutexEx.sqlite3_mutex_leave(mutexShared); } #if DEBUG else // In debug mode, we mark all persistent databases as sharable even when they are not. This exercises the locking code and // gives more opportunity for asserts(sqlite3_mutex_held()) statements to find locking problems. p.Sharable = true; #endif } #endif if (shared == null) { // The following asserts make sure that structures used by the btree are the right size. This is to guard against size changes that result // when compiling on a different architecture. Debug.Assert(sizeof(long) == 8 || sizeof(long) == 4); Debug.Assert(sizeof(ulong) == 8 || sizeof(ulong) == 4); Debug.Assert(sizeof(uint) == 4); Debug.Assert(sizeof(ushort) == 2); Debug.Assert(sizeof(Pgno) == 4); shared = new BtShared(); rc = Pager.Open(pVfs, out shared.Pager, zFilename, EXTRA_SIZE, (Pager.PAGEROPEN)flags, vfsFlags, pageReinit, () => new MemPage()); if (rc == RC.OK) rc = shared.Pager.ReadFileHeader(zDbHeader.Length, zDbHeader); if (rc != RC.OK) goto btree_open_out; shared.OpenFlags = flags; shared.DB = db; shared.Pager.SetBusyHandler(btreeInvokeBusyHandler, shared); p.Shared = shared; shared.Cursors = null; shared.Page1 = null; shared.ReadOnly = shared.Pager.IsReadonly; #if SQLITE_SECURE_DELETE pBt.secureDelete = true; #endif shared.PageSize = (uint)((zDbHeader[16] << 8) | (zDbHeader[17] << 16)); if (shared.PageSize < 512 || shared.PageSize > Pager.SQLITE_MAX_PAGE_SIZE || ((shared.PageSize - 1) & shared.PageSize) != 0) { shared.PageSize = 0; #if !SQLITE_OMIT_AUTOVACUUM // If the magic name ":memory:" will create an in-memory database, then leave the autoVacuum mode at 0 (do not auto-vacuum), even if // SQLITE_DEFAULT_AUTOVACUUM is true. On the other hand, if SQLITE_OMIT_MEMORYDB has been defined, then ":memory:" is just a // regular file-name. In this case the auto-vacuum applies as per normal. if (zFilename != string.Empty && !isMemdb) { shared.AutoVacuum = (AUTOVACUUM.DEFAULT != AUTOVACUUM.NONE); shared.IncrVacuum = (AUTOVACUUM.DEFAULT == AUTOVACUUM.INCR); } #endif nReserve = 0; } else { nReserve = zDbHeader[20]; shared.PageSizeFixed = true; #if !SQLITE_OMIT_AUTOVACUUM shared.AutoVacuum = ConvertEx.Get4(zDbHeader, 36 + 4 * 4) != 0; shared.IncrVacuum = ConvertEx.Get4(zDbHeader, 36 + 7 * 4) != 0; #endif } rc = shared.Pager.SetPageSize(ref shared.PageSize, nReserve); if (rc != RC.OK) goto btree_open_out; shared.UsableSize = (ushort)(shared.PageSize - nReserve); Debug.Assert((shared.PageSize & 7) == 0); // 8-byte alignment of pageSize #if !SQLITE_OMIT_SHARED_CACHE && !SQLITE_OMIT_DISKIO // Add the new BtShared object to the linked list sharable BtShareds. if (p.Sharable) { sqlite3_mutex mutexShared; shared.nRef = 1; mutexShared = MutexEx.sqlite3MutexAlloc(MUTEX.STATIC_MASTER); if (MutexEx.SQLITE_THREADSAFE && MutexEx.WantsCoreMutex) shared.Mutex = MutexEx.sqlite3MutexAlloc(MUTEX.FAST); MutexEx.sqlite3_mutex_enter(mutexShared); shared.Next = SysEx.getGLOBAL<BtShared>(s_sqlite3SharedCacheList); SysEx.setGLOBAL<BtShared>(s_sqlite3SharedCacheList, shared); MutexEx.sqlite3_mutex_leave(mutexShared); } #endif } #if !SQLITE_OMIT_SHARED_CACHE && !SQLITE_OMIT_DISKIO // If the new Btree uses a sharable pBtShared, then link the new Btree into the list of all sharable Btrees for the same connection. // The list is kept in ascending order by pBt address. Btree existingTree2; if (p.Sharable) for (var i = 0; i < db.DBs; i++) if ((existingTree2 = db.AllocDBs[i].Tree) != null && existingTree2.Sharable) { while (existingTree2.Prev != null) { existingTree2 = existingTree2.Prev; } if (p.Shared.Version < existingTree2.Shared.Version) { p.Next = existingTree2; p.Prev = null; existingTree2.Prev = p; } else { while (existingTree2.Next != null && existingTree2.Next.Shared.Version < p.Shared.Version) existingTree2 = existingTree2.Next; p.Next = existingTree2.Next; p.Prev = existingTree2; if (p.Next != null) p.Next.Prev = p; existingTree2.Next = p; } break; } #endif rTree = p; // btree_open_out: if (rc != RC.OK) { if (shared != null && shared.Pager != null) shared.Pager.Close(); shared = null; p = null; rTree = null; } else { // If the B-Tree was successfully opened, set the pager-cache size to the default value. Except, when opening on an existing shared pager-cache, // do not change the pager-cache size. if (p.GetSchema(0, null, null) == null) p.Shared.Pager.SetCacheSize(SQLITE_DEFAULT_CACHE_SIZE); } if (mutexOpen != null) { Debug.Assert(MutexEx.Held(mutexOpen)); MutexEx.sqlite3_mutex_leave(mutexOpen); } return rc; }