static int sqlite3_key(sqlite3 db, string pKey, int nKey) { CODEC_TRACE("sqlite3_key: entered db=%d pKey=%s nKey=%d\n", db, pKey, nKey); /* attach key if db and pKey are not null and nKey is > 0 */ if (db != null && pKey != null) { sqlite3CodecAttach(db, 0, pKey, nKey); // operate only on the main db // // If we are reopening an existing database, redo the header information setup // BtShared pBt = db.aDb[0].pBt.pBt; byte[] zDbHeader = sqlite3MemMalloc(pBt.pageSize); // pBt.pPager.pCodec.buffer; sqlite3PagerReadFileheader(pBt.pPager, zDbHeader.Length, zDbHeader); if (sqlite3Get4byte(zDbHeader) > 0) // Existing Database, need to reset some values { CODEC2(pBt.pPager, zDbHeader, 2, SQLITE_DECRYPT, ref zDbHeader); byte nReserve = zDbHeader[20]; pBt.pageSizeFixed = true; #if !SQLITE_OMIT_AUTOVACUUM pBt.autoVacuum = sqlite3Get4byte(zDbHeader, 36 + 4 * 4) != 0; pBt.incrVacuum = sqlite3Get4byte(zDbHeader, 36 + 7 * 4) != 0; #endif sqlite3PagerSetPagesize(pBt.pPager, ref pBt.pageSize, nReserve); pBt.usableSize = (u16)(pBt.pageSize - nReserve); } return(SQLITE_OK); } return(SQLITE_ERROR); }
static Pid finalDbSize(BtShared bt, Pid origs, Pid frees) { int entrys = (int)(bt.UsableSize / 5); // Number of entries on one ptrmap page Pid ptrmaps = (Pid)((frees - origs + PTRMAP_PAGENO(bt, origs) + entrys) / entrys); // Number of PtrMap pages to be freed Pid fins = origs - frees - ptrmaps; // Return value if (origs > PENDING_BYTE_PAGE(bt) && fins < PENDING_BYTE_PAGE(bt)) fins--; while (PTRMAP_ISPAGE(bt, fins) || fins == PENDING_BYTE_PAGE(bt)) fins--; return fins; }
static RC relocatePage(BtShared bt, MemPage page, PTRMAP type, Pid ptrPageID, Pid freePageID, bool isCommit) { Debug.Assert(type == PTRMAP.OVERFLOW2 || type == PTRMAP.OVERFLOW1 || type == PTRMAP.BTREE || type == PTRMAP.ROOTPAGE); Debug.Assert(MutexEx.Held(bt.Mutex)); Debug.Assert(page.Bt == bt); // Move page iDbPage from its current location to page number iFreePage var lastID = page.ID; TRACE("AUTOVACUUM: Moving %d to free page %d (ptr page %d type %d)\n", lastID, freePageID, ptrPageID, type); Pager pager = bt.Pager; var rc = pager.Movepage(page.DBPage, freePageID, isCommit); if (rc != RC.OK) return rc; page.ID = freePageID; // If pDbPage was a btree-page, then it may have child pages and/or cells that point to overflow pages. The pointer map entries for all these // pages need to be changed. // // If pDbPage is an overflow page, then the first 4 bytes may store a pointer to a subsequent overflow page. If this is the case, then // the pointer map needs to be updated for the subsequent overflow page. if (type == PTRMAP.BTREE || type == PTRMAP.ROOTPAGE) { rc = setChildPtrmaps(page); if (rc != RC.OK) return rc; } else { Pid nextOvfl = ConvertEx.Get4(page.Data); if (nextOvfl != 0) { ptrmapPut(bt, nextOvfl, PTRMAP.OVERFLOW2, freePageID, ref rc); if (rc != RC.OK) return rc; } } // Fix the database pointer on page iPtrPage that pointed at iDbPage so that it points at iFreePage. Also fix the pointer map entry for iPtrPage. if (type != PTRMAP.ROOTPAGE) { var ptrPage = new MemPage(); // The page that contains a pointer to pDbPage rc = btreeGetPage(bt, ptrPageID, ref ptrPage, false); if (rc != RC.OK) return rc; rc = Pager.Write(ptrPage.DBPage); if (rc != RC.OK) { releasePage(ptrPage); return rc; } rc = modifyPagePointer(ptrPage, lastID, freePageID, type); releasePage(ptrPage); if (rc == RC.OK) ptrmapPut(bt, freePageID, type, ptrPageID, ref rc); } return rc; }
static void unlockBtreeIfUnused(BtShared bt) { Debug.Assert(MutexEx.Held(bt.Mutex)); Debug.Assert(bt.Cursor == null || bt.InTransaction > TRANS.NONE); if (bt.InTransaction == TRANS.NONE && bt.Page1 != null) { Debug.Assert(bt.Page1.Data != null); Debug.Assert(bt.Pager.get_Refs() == 1); releasePage(bt.Page1); bt.Page1 = null; } }
static void freeTempSpace(BtShared bt) { PCache.PageFree2(ref bt.TmpSpace); bt.TmpSpace = null; }
static RC ptrmapGet(BtShared bt, Pid key, ref PTRMAP type, ref Pid id) { Debug.Assert(MutexEx.Held(bt.Mutex)); var page = (IPage)new PgHdr(); // The pointer map page var ptrmapIdx = (Pid)PTRMAP_PAGENO(bt, key); // Pointer map page index var rc = bt.Pager.Acquire(ptrmapIdx, ref page, false); if (rc != RC.OK) return rc; var ptrmap = Pager.GetData(page); // Pointer map page data var offset = (int)PTRMAP_PTROFFSET(ptrmapIdx, key); // Offset of entry in pointer map if (offset < 0) { Pager.Unref(page); return SysEx.CORRUPT_BKPT(); } Debug.Assert(offset <= (int)bt.UsableSize - 5); Debug.Assert(type != 0); type = (PTRMAP)ptrmap[offset]; id = ConvertEx.Get4(ptrmap, offset + 1); Pager.Unref(page); if ((byte)type < 1 || (byte)type > 5) return SysEx.CORRUPT_BKPT(); return RC.OK; }
static RC saveAllCursors(BtShared bt, Pid root, BtCursor except) { Debug.Assert(MutexEx.Held(bt.Mutex)); Debug.Assert(except == null || except.Bt == bt); for (var p = bt.Cursor; p != null; p = p.Next) { if (p != except && (root == 0 || p.RootID == root) && p.State == CURSOR.VALID) { var rc = saveCursorPosition(p); if (rc != RC.OK) return rc; } } return RC.OK; }
static RC btreeSetHasContent(BtShared bt, Pid id) { var rc = RC.OK; if (bt.HasContent == null) { Debug.Assert(id <= bt.Pages); bt.HasContent = new Bitvec(bt.Pages); } if (rc == RC.OK && id <= bt.HasContent.Length) rc = bt.HasContent.Set(id); return rc; }
static bool PTRMAP_ISPAGE(BtShared bt, Pid id) { return(PTRMAP_PAGENO((bt), (id)) == (id)); }
static Pid PTRMAP_PAGENO(BtShared bt, Pid id) { return(ptrmapPageno(bt, id)); }
public static Pid PENDING_BYTE_PAGE(BtShared bt) { return(Pager.MJ_PID(bt.Pager)); }
static ushort MX_CELL(BtShared bt) { return((ushort)((bt.PageSize - 8) / 6)); }
static ushort MX_CELL_SIZE(BtShared bt) { return((ushort)(bt.PageSize - 8)); }
//#define PTRMAP_ISPAGE(pBt, pgno) (PTRMAP_PAGENO((pBt),(pgno))==(pgno)) private static bool PTRMAP_ISPAGE(BtShared pBt, uint pgno) { return(PTRMAP_PAGENO(pBt, pgno) == pgno); }
static int countWriteCursors(BtShared bt) { int r = 0; for (var cur = bt.Cursor; cur != null; cur = cur.Next) if (cur.WrFlag && cur.State != CURSOR.FAULT) r++; return r; }
static void invalidateAllOverflowCache(BtShared bt) { Debug.Assert(MutexEx.Held(bt.Mutex)); for (var p = bt.Cursor; p != null; p = p.Next) invalidateOverflowCache(p); }
/* ** 2004 April 6 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** ** This file implements a external (disk-based) database using BTrees. ** For a detailed discussion of BTrees, refer to ** ** Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3: ** "Sorting And Searching", pages 473-480. Addison-Wesley ** Publishing Company, Reading, Massachusetts. ** ** The basic idea is that each page of the file contains N database ** entries and N+1 pointers to subpages. ** ** ---------------------------------------------------------------- ** | Ptr(0) | Key(0) | Ptr(1) | Key(1) | ... | Key(N-1) | Ptr(N) | ** ---------------------------------------------------------------- ** ** All of the keys on the page that Ptr(0) points to have values less ** than Key(0). All of the keys on page Ptr(1) and its subpages have ** values greater than Key(0) and less than Key(1). All of the keys ** on Ptr(N) and its subpages have values greater than Key(N-1). And ** so forth. ** ** Finding a particular key requires reading O(log(M)) pages from the ** disk where M is the number of entries in the tree. ** ** In this implementation, a single file can hold one or more separate ** BTrees. Each BTree is identified by the index of its root page. The ** key and data for any entry are combined to form the "payload". A ** fixed amount of payload can be carried directly on the database ** page. If the payload is larger than the preset amount then surplus ** bytes are stored on overflow pages. The payload for an entry ** and the preceding pointer are combined to form a "Cell". Each ** page has a small header which contains the Ptr(N) pointer and other ** information such as the size of key and data. ** ** FORMAT DETAILS ** ** The file is divided into pages. The first page is called page 1, ** the second is page 2, and so forth. A page number of zero indicates ** "no such page". The page size can be any power of 2 between 512 and 65536. ** Each page can be either a btree page, a freelist page, an overflow ** page, or a pointer-map page. ** ** The first page is always a btree page. The first 100 bytes of the first ** page contain a special header (the "file header") that describes the file. ** The format of the file header is as follows: ** ** OFFSET SIZE DESCRIPTION ** 0 16 Header string: "SQLite format 3\000" ** 16 2 Page size in bytes. ** 18 1 File format write version ** 19 1 File format read version ** 20 1 Bytes of unused space at the end of each page ** 21 1 Max embedded payload fraction ** 22 1 Min embedded payload fraction ** 23 1 Min leaf payload fraction ** 24 4 File change counter ** 28 4 Reserved for future use ** 32 4 First freelist page ** 36 4 Number of freelist pages in the file ** 40 60 15 4-byte meta values passed to higher layers ** ** 40 4 Schema cookie ** 44 4 File format of schema layer ** 48 4 Size of page cache ** 52 4 Largest root-page (auto/incr_vacuum) ** 56 4 1=UTF-8 2=UTF16le 3=UTF16be ** 60 4 User version ** 64 4 Incremental vacuum mode ** 68 4 unused ** 72 4 unused ** 76 4 unused ** ** All of the integer values are big-endian (most significant byte first). ** ** The file change counter is incremented when the database is changed ** This counter allows other processes to know when the file has changed ** and thus when they need to flush their cache. ** ** The max embedded payload fraction is the amount of the total usable ** space in a page that can be consumed by a single cell for standard ** B-tree (non-LEAFDATA) tables. A value of 255 means 100%. The default ** is to limit the maximum cell size so that at least 4 cells will fit ** on one page. Thus the default max embedded payload fraction is 64. ** ** If the payload for a cell is larger than the max payload, then extra ** payload is spilled to overflow pages. Once an overflow page is allocated, ** as many bytes as possible are moved into the overflow pages without letting ** the cell size drop below the min embedded payload fraction. ** ** The min leaf payload fraction is like the min embedded payload fraction ** except that it applies to leaf nodes in a LEAFDATA tree. The maximum ** payload fraction for a LEAFDATA tree is always 100% (or 255) and it ** not specified in the header. ** ** Each btree pages is divided into three sections: The header, the ** cell pointer array, and the cell content area. Page 1 also has a 100-byte ** file header that occurs before the page header. ** ** |----------------| ** | file header | 100 bytes. Page 1 only. ** |----------------| ** | page header | 8 bytes for leaves. 12 bytes for interior nodes ** |----------------| ** | cell pointer | | 2 bytes per cell. Sorted order. ** | array | | Grows downward ** | | v ** |----------------| ** | unallocated | ** | space | ** |----------------| ^ Grows upwards ** | cell content | | Arbitrary order interspersed with freeblocks. ** | area | | and free space fragments. ** |----------------| ** ** The page headers looks like this: ** ** OFFSET SIZE DESCRIPTION ** 0 1 Flags. 1: intkey, 2: zerodata, 4: leafdata, 8: leaf ** 1 2 byte offset to the first freeblock ** 3 2 number of cells on this page ** 5 2 first byte of the cell content area ** 7 1 number of fragmented free bytes ** 8 4 Right child (the Ptr(N) value). Omitted on leaves. ** ** The flags define the format of this btree page. The leaf flag means that ** this page has no children. The zerodata flag means that this page carries ** only keys and no data. The intkey flag means that the key is a integer ** which is stored in the key size entry of the cell header rather than in ** the payload area. ** ** The cell pointer array begins on the first byte after the page header. ** The cell pointer array contains zero or more 2-byte numbers which are ** offsets from the beginning of the page to the cell content in the cell ** content area. The cell pointers occur in sorted order. The system strives ** to keep free space after the last cell pointer so that new cells can ** be easily added without having to defragment the page. ** ** Cell content is stored at the very end of the page and grows toward the ** beginning of the page. ** ** Unused space within the cell content area is collected into a linked list of ** freeblocks. Each freeblock is at least 4 bytes in size. The byte offset ** to the first freeblock is given in the header. Freeblocks occur in ** increasing order. Because a freeblock must be at least 4 bytes in size, ** any group of 3 or fewer unused bytes in the cell content area cannot ** exist on the freeblock chain. A group of 3 or fewer free bytes is called ** a fragment. The total number of bytes in all fragments is recorded. ** in the page header at offset 7. ** ** SIZE DESCRIPTION ** 2 Byte offset of the next freeblock ** 2 Bytes in this freeblock ** ** Cells are of variable length. Cells are stored in the cell content area at ** the end of the page. Pointers to the cells are in the cell pointer array ** that immediately follows the page header. Cells is not necessarily ** contiguous or in order, but cell pointers are contiguous and in order. ** ** Cell content makes use of variable length integers. A variable ** length integer is 1 to 9 bytes where the lower 7 bits of each ** byte are used. The integer consists of all bytes that have bit 8 set and ** the first byte with bit 8 clear. The most significant byte of the integer ** appears first. A variable-length integer may not be more than 9 bytes long. ** As a special case, all 8 bytes of the 9th byte are used as data. This ** allows a 64-bit integer to be encoded in 9 bytes. ** ** 0x00 becomes 0x00000000 ** 0x7f becomes 0x0000007f ** 0x81 0x00 becomes 0x00000080 ** 0x82 0x00 becomes 0x00000100 ** 0x80 0x7f becomes 0x0000007f ** 0x8a 0x91 0xd1 0xac 0x78 becomes 0x12345678 ** 0x81 0x81 0x81 0x81 0x01 becomes 0x10204081 ** ** Variable length integers are used for rowids and to hold the number of ** bytes of key and data in a btree cell. ** ** The content of a cell looks like this: ** ** SIZE DESCRIPTION ** 4 Page number of the left child. Omitted if leaf flag is set. ** var Number of bytes of data. Omitted if the zerodata flag is set. ** var Number of bytes of key. Or the key itself if intkey flag is set. ** * Payload ** 4 First page of the overflow chain. Omitted if no overflow ** ** Overflow pages form a linked list. Each page except the last is completely ** filled with data (pagesize - 4 bytes). The last page can have as little ** as 1 byte of data. ** ** SIZE DESCRIPTION ** 4 Page number of next overflow page ** * Data ** ** Freelist pages come in two subtypes: trunk pages and leaf pages. The ** file header points to the first in a linked list of trunk page. Each trunk ** page points to multiple leaf pages. The content of a leaf page is ** unspecified. A trunk page looks like this: ** ** SIZE DESCRIPTION ** 4 Page number of next trunk page ** 4 Number of leaf pointers on this page ** * zero or more pages numbers of leaves ************************************************************************* ** Included in SQLite3 port to C#-SQLite; 2008 Noah B Hart ** C#-SQLite is an independent reimplementation of the SQLite software library ** ** SQLITE_SOURCE_ID: 2011-05-19 13:26:54 ed1da510a239ea767a01dc332b667119fa3c908e ** ************************************************************************* */ //#include "sqliteInt.h" /* The following value is the maximum cell size assuming a maximum page ** size give above. */ //#define MX_CELL_SIZE(pBt) ((int)(pBt->pageSize-8)) private static int MX_CELL_SIZE(BtShared pBt) { return((int)(pBt.pageSize - 8)); }
static void btreeClearHasContent(BtShared bt) { Bitvec.Destroy(ref bt.HasContent); bt.HasContent = null; }
/* ** 2004 April 6 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** $Id: btreeInt.h,v 1.52 2009/07/15 17:25:46 drh Exp $ ** ************************************************************************* ** Included in SQLite3 port to C#-SQLite; 2008 Noah B Hart ** C#-SQLite is an independent reimplementation of the SQLite software library ** ** $Header$ ************************************************************************* ** ** This file implements a external (disk-based) database using BTrees. ** For a detailed discussion of BTrees, refer to ** ** Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3: ** "Sorting And Searching", pages 473-480. Addison-Wesley ** Publishing Company, Reading, Massachusetts. ** ** The basic idea is that each page of the file contains N database ** entries and N+1 pointers to subpages. ** ** ---------------------------------------------------------------- ** | Ptr(0) | Key(0) | Ptr(1) | Key(1) | ... | Key(N-1) | Ptr(N) | ** ---------------------------------------------------------------- ** ** All of the keys on the page that Ptr(0) points to have values less ** than Key(0). All of the keys on page Ptr(1) and its subpages have ** values greater than Key(0) and less than Key(1). All of the keys ** on Ptr(N) and its subpages have values greater than Key(N-1). And ** so forth. ** ** Finding a particular key requires reading O(log(M)) pages from the ** disk where M is the number of entries in the tree. ** ** In this implementation, a single file can hold one or more separate ** BTrees. Each BTree is identified by the index of its root page. The ** key and data for any entry are combined to form the "payload". A ** fixed amount of payload can be carried directly on the database ** page. If the payload is larger than the preset amount then surplus ** bytes are stored on overflow pages. The payload for an entry ** and the preceding pointer are combined to form a "Cell". Each ** page has a small header which contains the Ptr(N) pointer and other ** information such as the size of key and data. ** ** FORMAT DETAILS ** ** The file is divided into pages. The first page is called page 1, ** the second is page 2, and so forth. A page number of zero indicates ** "no such page". The page size can be anything between 512 and 65536. ** Each page can be either a btree page, a freelist page or an overflow ** page. ** ** The first page is always a btree page. The first 100 bytes of the first ** page contain a special header (the "file header") that describes the file. ** The format of the file header is as follows: ** ** OFFSET SIZE DESCRIPTION ** 0 16 Header string: "SQLite format 3\000" ** 16 2 Page size in bytes. ** 18 1 File format write version ** 19 1 File format read version ** 20 1 Bytes of unused space at the end of each page ** 21 1 Max embedded payload fraction ** 22 1 Min embedded payload fraction ** 23 1 Min leaf payload fraction ** 24 4 File change counter ** 28 4 Reserved for future use ** 32 4 First freelist page ** 36 4 Number of freelist pages in the file ** 40 60 15 4-byte meta values passed to higher layers ** ** 40 4 Schema cookie ** 44 4 File format of schema layer ** 48 4 Size of page cache ** 52 4 Largest root-page (auto/incr_vacuum) ** 56 4 1=UTF-8 2=UTF16le 3=UTF16be ** 60 4 User version ** 64 4 Incremental vacuum mode ** 68 4 unused ** 72 4 unused ** 76 4 unused ** ** All of the integer values are big-endian (most significant byte first). ** ** The file change counter is incremented when the database is changed ** This counter allows other processes to know when the file has changed ** and thus when they need to flush their cache. ** ** The max embedded payload fraction is the amount of the total usable ** space in a page that can be consumed by a single cell for standard ** B-tree (non-LEAFDATA) tables. A value of 255 means 100%. The default ** is to limit the maximum cell size so that at least 4 cells will fit ** on one page. Thus the default max embedded payload fraction is 64. ** ** If the payload for a cell is larger than the max payload, then extra ** payload is spilled to overflow pages. Once an overflow page is allocated, ** as many bytes as possible are moved into the overflow pages without letting ** the cell size drop below the min embedded payload fraction. ** ** The min leaf payload fraction is like the min embedded payload fraction ** except that it applies to leaf nodes in a LEAFDATA tree. The maximum ** payload fraction for a LEAFDATA tree is always 100% (or 255) and it ** not specified in the header. ** ** Each btree pages is divided into three sections: The header, the ** cell pointer array, and the cell content area. Page 1 also has a 100-byte ** file header that occurs before the page header. ** ** |----------------| ** | file header | 100 bytes. Page 1 only. ** |----------------| ** | page header | 8 bytes for leaves. 12 bytes for interior nodes ** |----------------| ** | cell pointer | | 2 bytes per cell. Sorted order. ** | array | | Grows downward ** | | v ** |----------------| ** | unallocated | ** | space | ** |----------------| ^ Grows upwards ** | cell content | | Arbitrary order interspersed with freeblocks. ** | area | | and free space fragments. ** |----------------| ** ** The page headers looks like this: ** ** OFFSET SIZE DESCRIPTION ** 0 1 Flags. 1: intkey, 2: zerodata, 4: leafdata, 8: leaf ** 1 2 byte offset to the first freeblock ** 3 2 number of cells on this page ** 5 2 first byte of the cell content area ** 7 1 number of fragmented free bytes ** 8 4 Right child (the Ptr(N) value). Omitted on leaves. ** ** The flags define the format of this btree page. The leaf flag means that ** this page has no children. The zerodata flag means that this page carries ** only keys and no data. The intkey flag means that the key is a integer ** which is stored in the key size entry of the cell header rather than in ** the payload area. ** ** The cell pointer array begins on the first byte after the page header. ** The cell pointer array contains zero or more 2-byte numbers which are ** offsets from the beginning of the page to the cell content in the cell ** content area. The cell pointers occur in sorted order. The system strives ** to keep free space after the last cell pointer so that new cells can ** be easily added without having to defragment the page. ** ** Cell content is stored at the very end of the page and grows toward the ** beginning of the page. ** ** Unused space within the cell content area is collected into a linked list of ** freeblocks. Each freeblock is at least 4 bytes in size. The byte offset ** to the first freeblock is given in the header. Freeblocks occur in ** increasing order. Because a freeblock must be at least 4 bytes in size, ** any group of 3 or fewer unused bytes in the cell content area cannot ** exist on the freeblock chain. A group of 3 or fewer free bytes is called ** a fragment. The total number of bytes in all fragments is recorded. ** in the page header at offset 7. ** ** SIZE DESCRIPTION ** 2 Byte offset of the next freeblock ** 2 Bytes in this freeblock ** ** Cells are of variable length. Cells are stored in the cell content area at ** the end of the page. Pointers to the cells are in the cell pointer array ** that immediately follows the page header. Cells is not necessarily ** contiguous or in order, but cell pointers are contiguous and in order. ** ** Cell content makes use of variable length integers. A variable ** length integer is 1 to 9 bytes where the lower 7 bits of each ** byte are used. The integer consists of all bytes that have bit 8 set and ** the first byte with bit 8 clear. The most significant byte of the integer ** appears first. A variable-length integer may not be more than 9 bytes long. ** As a special case, all 8 bytes of the 9th byte are used as data. This ** allows a 64-bit integer to be encoded in 9 bytes. ** ** 0x00 becomes 0x00000000 ** 0x7f becomes 0x0000007f ** 0x81 0x00 becomes 0x00000080 ** 0x82 0x00 becomes 0x00000100 ** 0x80 0x7f becomes 0x0000007f ** 0x8a 0x91 0xd1 0xac 0x78 becomes 0x12345678 ** 0x81 0x81 0x81 0x81 0x01 becomes 0x10204081 ** ** Variable length integers are used for rowids and to hold the number of ** bytes of key and data in a btree cell. ** ** The content of a cell looks like this: ** ** SIZE DESCRIPTION ** 4 Page number of the left child. Omitted if leaf flag is set. ** var Number of bytes of data. Omitted if the zerodata flag is set. ** var Number of bytes of key. Or the key itself if intkey flag is set. ** * Payload ** 4 First page of the overflow chain. Omitted if no overflow ** ** Overflow pages form a linked list. Each page except the last is completely ** filled with data (pagesize - 4 bytes). The last page can have as little ** as 1 byte of data. ** ** SIZE DESCRIPTION ** 4 Page number of next overflow page ** * Data ** ** Freelist pages come in two subtypes: trunk pages and leaf pages. The ** file header points to the first in a linked list of trunk page. Each trunk ** page points to multiple leaf pages. The content of a leaf page is ** unspecified. A trunk page looks like this: ** ** SIZE DESCRIPTION ** 4 Page number of next trunk page ** 4 Number of leaf pointers on this page ** * zero or more pages numbers of leaves */ //#include "sqliteInt.h" /* The following value is the maximum cell size assuming a maximum page ** size give above. */ //#define MX_CELL_SIZE(pBt) (pBt.pageSize-8) static int MX_CELL_SIZE(BtShared pBt) { return(pBt.pageSize - 8); }
static Pid ptrmapPageno(BtShared bt, Pid id) { Debug.Assert(MutexEx.Held(bt.Mutex)); if (id < 2) return 0; var pagesPerMapPage = (int)(bt.UsableSize / 5 + 1); var ptrMap = (Pid)((id - 2) / pagesPerMapPage); var ret = (Pid)(ptrMap * pagesPerMapPage) + 2; if (ret == PENDING_BYTE_PAGE(bt)) ret++; return ret; }
/* The maximum number of cells on a single page of the database. This ** assumes a minimum cell size of 6 bytes (4 bytes for the cell itself ** plus 2 bytes for the index to the cell in the page header). Such ** small cells will be rare, but they are possible. */ //#define MX_CELL(pBt) ((pBt.pageSize-8)/6) static int MX_CELL(BtShared pBt) { return((pBt.pageSize - 8) / 6); }
/* ** These macros define the location of the pointer-map entry for a ** database page. The first argument to each is the number of usable ** bytes on each page of the database (often 1024). The second is the ** page number to look up in the pointer map. ** ** PTRMAP_PAGENO returns the database page number of the pointer-map ** page that stores the required pointer. PTRMAP_PTROFFSET returns ** the offset of the requested map entry. ** ** If the pgno argument passed to PTRMAP_PAGENO is a pointer-map page, ** then pgno is returned. So (pgno==PTRMAP_PAGENO(pgsz, pgno)) can be ** used to test if pgno is a pointer-map page. PTRMAP_ISPAGE implements ** this test. */ //#define PTRMAP_PAGENO(pBt, pgno) ptrmapPageno(pBt, pgno) private static uint PTRMAP_PAGENO(BtShared pBt, uint pgno) { return(ptrmapPageno(pBt, pgno)); }
/* ** The database page the PENDING_BYTE occupies. This page is never used. */ //# define PENDING_BYTE_PAGE(pBt) PAGER_MJ_PGNO(pBt) // TODO -- Convert PENDING_BYTE_PAGE to inline static u32 PENDING_BYTE_PAGE(BtShared pBt) { return((u32)PAGER_MJ_PGNO(pBt.pPager)); }
static RC lockBtree(BtShared bt) { Debug.Assert(MutexEx.Held(bt.Mutex)); Debug.Assert(bt.Page1 == null); var rc = bt.Pager.SharedLock(); if (rc != RC.OK) return rc; MemPage page1 = null; // Page 1 of the database file rc = btreeGetPage(bt, 1, ref page1, false); if (rc != RC.OK) return rc; // Do some checking to help insure the file we opened really is a valid database file. Pid pagesHeader; // Number of pages in the database according to hdr Pid pages = pagesHeader = ConvertEx.Get4(page1.Data, 28); // Number of pages in the database Pid pagesFile = 0; // Number of pages in the database file bt.Pager.Pages(out pagesFile); if (pages == 0 || C.memcmp(page1.Data, 24, page1.Data, 92, 4) != 0) pages = pagesFile; if (pages > 0) { var page1Data = page1.Data; rc = RC.NOTADB; if (C.memcmp(page1Data, _magicHeader, 16) != 0) goto page1_init_failed; #if OMIT_WAL if (page1Data[18] > 1) bt.BtsFlags |= BTS.READ_ONLY; if (page1Data[19] > 1) goto page1_init_failed; #else if (page1Data[18] > 2) bt.BtsFlags |= BTS.READ_ONLY; if (page1Data[19] > 2) goto page1_init_failed; // return SQLITE_OK and return without populating BtShared.pPage1. The caller detects this and calls this function again. This is // required as the version of page 1 currently in the page1 buffer may not be the latest version - there may be a newer one in the log file. if (page1Data[19] == 2 && (bt.BtsFlags & BTS.NO_WAL) == 0) { int isOpen = 0; rc = bt.Pager.OpenWal(ref isOpen); if (rc != RC.OK) goto page1_init_failed; else if (isOpen == 0) { releasePage(page1); return RC.OK; } rc = RC.NOTADB; } #endif // The maximum embedded fraction must be exactly 25%. And the minimum embedded fraction must be 12.5% for both leaf-data and non-leaf-data. // The original design allowed these amounts to vary, but as of version 3.6.0, we require them to be fixed. if (C.memcmp(page1Data, 21, "\x0040\x0020\x0020", 3) != 0) // "\100\040\040" goto page1_init_failed; uint pageSize = (uint)((page1Data[16] << 8) | (page1Data[17] << 16)); if (((pageSize - 1) & pageSize) != 0 || pageSize > Pager.MAX_PAGE_SIZE || pageSize <= 256) goto page1_init_failed; Debug.Assert((pageSize & 7) == 0); uint usableSize = pageSize - page1Data[20]; if (pageSize != bt.PageSize) { // After reading the first page of the database assuming a page size of BtShared.pageSize, we have discovered that the page-size is // actually pageSize. Unlock the database, leave pBt->pPage1 at zero and return SQLITE_OK. The caller will call this function // again with the correct page-size. releasePage(page1); bt.UsableSize = usableSize; bt.PageSize = pageSize; freeTempSpace(bt); rc = bt.Pager.SetPageSize(ref bt.PageSize, (int)(pageSize - usableSize)); return rc; } if ((bt.Ctx.Flags & BContext.FLAG.RecoveryMode) == 0 && pages > pagesFile) { rc = SysEx.CORRUPT_BKPT(); goto page1_init_failed; } if (usableSize < 480) goto page1_init_failed; bt.PageSize = pageSize; bt.UsableSize = usableSize; #if !OMIT_AUTOVACUUM bt.AutoVacuum = (ConvertEx.Get4(page1Data, 36 + 4 * 4) != 0); bt.IncrVacuum = (ConvertEx.Get4(page1Data, 36 + 7 * 4) != 0); #endif } // maxLocal is the maximum amount of payload to store locally for a cell. Make sure it is small enough so that at least minFanout // cells can will fit on one page. We assume a 10-byte page header. Besides the payload, the cell must store: // 2-byte pointer to the cell // 4-byte child pointer // 9-byte nKey value // 4-byte nData value // 4-byte overflow page pointer // So a cell consists of a 2-byte pointer, a header which is as much as 17 bytes long, 0 to N bytes of payload, and an optional 4 byte overflow // page pointer. bt.MaxLocal = (ushort)((bt.UsableSize - 12) * 64 / 255 - 23); bt.MinLocal = (ushort)((bt.UsableSize - 12) * 32 / 255 - 23); bt.MaxLeaf = (ushort)(bt.UsableSize - 35); bt.MinLeaf = (ushort)((bt.UsableSize - 12) * 32 / 255 - 23); Debug.Assert(bt.MaxLeaf + 23 <= MX_CELL_SIZE(bt)); bt.Page1 = page1; bt.Pages = pages; return RC.OK; page1_init_failed: releasePage(page1); bt.Page1 = null; return rc; }
/* ** These macros define the location of the pointer-map entry for a ** database page. The first argument to each is the number of usable ** bytes on each page of the database (often 1024). The second is the ** page number to look up in the pointer map. ** ** PTRMAP_PAGENO returns the database page number of the pointer-map ** page that stores the required pointer. PTRMAP_PTROFFSET returns ** the offset of the requested map entry. ** ** If the pgno argument passed to PTRMAP_PAGENO is a pointer-map page, ** then pgno is returned. So (pgno==PTRMAP_PAGENO(pgsz, pgno)) can be ** used to test if pgno is a pointer-map page. PTRMAP_ISPAGE implements ** this test. */ //#define PTRMAP_PAGENO(pBt, pgno) ptrmapPageno(pBt, pgno) static Pgno PTRMAP_PAGENO(BtShared pBt, Pgno pgno) { return(ptrmapPageno(pBt, pgno)); }
static RC newDatabase(BtShared bt) { Debug.Assert(MutexEx.Held(bt.Mutex)); if (bt.Pages > 0) return RC.OK; var p1 = bt.Page1; Debug.Assert(p1 != null); var data = p1.Data; var rc = Pager.Write(p1.DBPage); if (rc != RC.OK) return rc; Buffer.BlockCopy(_magicHeader, 0, data, 0, _magicHeader.Length); Debug.Assert(_magicHeader.Length == 16); data[16] = (byte)((bt.PageSize >> 8) & 0xff); data[17] = (byte)((bt.PageSize >> 16) & 0xff); data[18] = 1; data[19] = 1; Debug.Assert(bt.UsableSize <= bt.PageSize && bt.UsableSize + 255 >= bt.PageSize); data[20] = (byte)(bt.PageSize - bt.UsableSize); data[21] = 64; data[22] = 32; data[23] = 32; //_memset(&data[24], 0, 100 - 24); zeroPage(p1, PTF_INTKEY | PTF_LEAF | PTF_LEAFDATA); bt.BtsFlags |= BTS.PAGESIZE_FIXED; #if !SQLITE_OMIT_AUTOVACUUM ConvertEx.Put4(data, 36 + 4 * 4, bt.AutoVacuum ? 1 : 0); ConvertEx.Put4(data, 36 + 7 * 4, bt.IncrVacuum ? 1 : 0); #endif bt.Pages = 1; data[31] = 1; return RC.OK; }
//#define PTRMAP_ISPAGE(pBt, pgno) (PTRMAP_PAGENO((pBt),(pgno))==(pgno)) static bool PTRMAP_ISPAGE(BtShared pBt, u32 pgno) { return(PTRMAP_PAGENO((pBt), (pgno)) == (pgno)); }
static RC incrVacuumStep(BtShared bt, Pid fins, Pid lastPageID, bool commit) { Debug.Assert(MutexEx.Held(bt.Mutex)); Debug.Assert(lastPageID > fins); if (!PTRMAP_ISPAGE(bt, lastPageID) && lastPageID != PENDING_BYTE_PAGE(bt)) { Pid freesList = ConvertEx.Get4(bt.Page1.Data, 36); // Number of pages still on the free-list if (freesList == 0) return RC.DONE; PTRMAP type = 0; Pid ptrPageID = 0; var rc = ptrmapGet(bt, lastPageID, ref type, ref ptrPageID); if (rc != RC.OK) return rc; if (type == PTRMAP.ROOTPAGE) return SysEx.CORRUPT_BKPT(); if (type == PTRMAP.FREEPAGE) { if (!commit) { // Remove the page from the files free-list. This is not required if bCommit is non-zero. In that case, the free-list will be // truncated to zero after this function returns, so it doesn't matter if it still contains some garbage entries. Pid freePageID = 0; var freePage = new MemPage(); rc = allocateBtreePage(bt, ref freePage, ref freePageID, lastPageID, BTALLOC.EXACT); if (rc != RC.OK) return rc; Debug.Assert(freePageID == lastPageID); releasePage(freePage); } } else { MemPage lastPage = new MemPage(); rc = btreeGetPage(bt, lastPageID, ref lastPage, false); if (rc != RC.OK) return rc; // If bCommit is zero, this loop runs exactly once and page pLastPg is swapped with the first free page pulled off the free list. // // On the other hand, if bCommit is greater than zero, then keep looping until a free-page located within the first nFin pages // of the file is found. BTALLOC mode = BTALLOC.ANY; // Mode parameter for allocateBtreePage() Pid nearID = 0; // nearby parameter for allocateBtreePage() if (!commit) { mode = BTALLOC.LE; nearID = fins; } Pid freePageID = 0; // Index of free page to move pLastPg to do { MemPage freePage = new MemPage(); rc = allocateBtreePage(bt, ref freePage, ref freePageID, nearID, mode); if (rc != RC.OK) { releasePage(lastPage); return rc; } releasePage(freePage); } while (commit && freePageID > fins); Debug.Assert(freePageID < lastPageID); rc = relocatePage(bt, lastPage, type, ptrPageID, freePageID, commit); releasePage(lastPage); if (rc != RC.OK) return rc; } } if (!commit) { do { lastPageID--; } while (lastPageID == PENDING_BYTE_PAGE(bt) || PTRMAP_ISPAGE(bt, lastPageID)); bt.DoTruncate = true; bt.Pages = lastPageID; } return RC.OK; }
static MemPage btreePageFromDbPage(IPage dbPage, Pid id, BtShared bt) { MemPage page = Pager.GetExtra<MemPage>(dbPage); page.Data = Pager.GetData(dbPage); page.DBPage = dbPage; page.Bt = bt; page.ID = id; page.HdrOffset = (byte)(page.ID == 1 ? 100 : 0); return page; }
static RC autoVacuumCommit(BtShared bt) { var pager = bt.Pager; #if DEBUG int refs = pager.get_Refs(); #endif Debug.Assert(MutexEx.Held(bt.Mutex)); invalidateAllOverflowCache(bt); Debug.Assert(bt.AutoVacuum); var rc = RC.OK; if (!bt.IncrVacuum) { Pid origs = btreePagecount(bt); // Database size before freeing if (PTRMAP_ISPAGE(bt, origs) || origs == PENDING_BYTE_PAGE(bt)) { // It is not possible to create a database for which the final page is either a pointer-map page or the pending-byte page. If one // is encountered, this indicates corruption. return SysEx.CORRUPT_BKPT(); } Pid frees = ConvertEx.Get4(bt.Page1.Data, 36); // Number of pages on the freelist initially Pid fins = finalDbSize(bt, origs, frees); // Number of pages in database after autovacuuming if (fins > origs) return SysEx.CORRUPT_BKPT(); for (var freeID = origs; freeID > fins && rc == RC.OK; freeID--) // The next page to be freed rc = incrVacuumStep(bt, fins, freeID, true); if ((rc == RC.DONE || rc == RC.OK) && frees > 0) { rc = Pager.Write(bt.Page1.DBPage); ConvertEx.Put4(bt.Page1.Data, 32, 0); ConvertEx.Put4(bt.Page1.Data, 36, 0); ConvertEx.Put4(bt.Page1.Data, 28, fins); bt.DoTruncate = true; bt.Pages = fins; } if (rc != RC.OK) pager.Rollback(); } #if DEBUG Debug.Assert(refs == pager.get_Refs()); #endif return rc; }
static RC btreeGetPage(BtShared bt, Pid id, ref MemPage page, bool noContent) { Debug.Assert(MutexEx.Held(bt.Mutex)); IPage dbPage = null; var rc = bt.Pager.Acquire(id, ref dbPage, noContent); if (rc != RC.OK) return rc; page = btreePageFromDbPage(dbPage, id, bt); return RC.OK; }
static RC getOverflowPage(BtShared bt, Pid ovfl, out MemPage pageOut, out Pid idNextOut) { Pid next = 0; MemPage page = null; pageOut = null; var rc = RC.OK; Debug.Assert(MutexEx.Held(bt.Mutex)); #if !OMIT_AUTOVACUUM // Try to find the next page in the overflow list using the autovacuum pointer-map pages. Guess that the next page in // the overflow list is page number (ovfl+1). If that guess turns out to be wrong, fall back to loading the data of page // number ovfl to determine the next page number. if (bt.AutoVacuum) { Pid guess = ovfl + 1; while (PTRMAP_ISPAGE(bt, guess) || guess == PENDING_BYTE_PAGE(bt)) guess++; if (guess <= btreePagecount(bt)) { Pid id = 0; PTRMAP type = 0; rc = ptrmapGet(bt, guess, ref type, ref id); if (rc == RC.OK && type == PTRMAP.OVERFLOW2 && id == ovfl) { next = guess; rc = RC.DONE; } } } #endif Debug.Assert(next == 0 || rc == RC.DONE); if (rc == RC.OK) { rc = btreeGetPage(bt, ovfl, ref page, false); Debug.Assert(rc == RC.OK || page == null); if (rc == RC.OK) next = ConvertEx.Get4(page.Data); } idNextOut = next; if (pageOut != null) pageOut = page; else releasePage(page); return (rc == RC.DONE ? RC.OK : rc); }
static MemPage btreePageLookup(BtShared bt, Pid id) { Debug.Assert(MutexEx.Held(bt.Mutex)); var dbPage = bt.Pager.Lookup(id); return (dbPage != null ? btreePageFromDbPage(dbPage, id, bt) : null); }
static void invalidateAllOverflowCache(BtShared bt) { }
static Pid btreePagecount(BtShared bt) { return bt.Pages; }
static bool btreeGetHasContent(BtShared bt, Pid id) { var p = bt.HasContent; return (p != null && (id > p.Length || p.Get(id))); }
static RC getAndInitPage(BtShared bt, Pid id, ref MemPage page) { Debug.Assert(MutexEx.Held(bt.Mutex)); RC rc; if (id > btreePagecount(bt)) rc = SysEx.CORRUPT_BKPT(); else { rc = btreeGetPage(bt, id, ref page, false); if (rc == RC.OK) { rc = btreeInitPage(page); if (rc != RC.OK) releasePage(page); } } Debug.Assert(id != 0 || rc == RC.CORRUPT); return rc; }
static RC allocateBtreePage(BtShared bt, ref MemPage page, ref Pid id, Pid nearby, BTALLOC mode) { Debug.Assert(MutexEx.Held(bt.Mutex)); Debug.Assert(mode == BTALLOC.ANY || (nearby > 0 && IFAUTOVACUUM(bt.AutoVacuum))); MemPage page1 = bt.Page1; Pid maxPage = btreePagecount(bt); // Total size of the database file uint n = ConvertEx.Get4(page1.Data, 36); // Number of pages on the freelist ASSERTCOVERAGE(n == maxPage - 1); if (n >= maxPage) return SysEx.CORRUPT_BKPT(); RC rc; MemPage trunk = null; MemPage prevTrunk = null; if (n > 0) { // There are pages on the freelist. Reuse one of those pages. bool searchList = false; // If the free-list must be searched for // If eMode==BTALLOC_EXACT and a query of the pointer-map shows that the page 'nearby' is somewhere on the free-list, then // the entire-list will be searched for that page. #if !OMIT_AUTOVACUUM if (mode == BTALLOC.EXACT) { if (nearby <= maxPage) { Debug.Assert(nearby > 0); Debug.Assert(bt.AutoVacuum); PTRMAP type = 0; Pid dummy = 0; rc = ptrmapGet(bt, nearby, ref type, ref dummy); if (rc != RC.OK) return rc; if (type == PTRMAP.FREEPAGE) searchList = true; } } else if (mode == BTALLOC.LE) searchList = true; #endif // Decrement the free-list count by 1. Set iTrunk to the index of the first free-list trunk page. iPrevTrunk is initially 1. rc = Pager.Write(page1.DBPage); if (rc != RC.OK) return rc; ConvertEx.Put4(page1.Data, (uint)36, n - 1); // The code within this loop is run only once if the 'searchList' variable is not true. Otherwise, it runs once for each trunk-page on the // free-list until the page 'nearby' is located (eMode==BTALLOC_EXACT) or until a page less than 'nearby' is located (eMode==BTALLOC_LT) Pid trunkID; do { prevTrunk = trunk; if (prevTrunk != null) trunkID = ConvertEx.Get4(prevTrunk.Data, 0); else trunkID = ConvertEx.Get4(page1.Data, 32); ASSERTCOVERAGE(trunkID == maxPage); if (trunkID > maxPage) rc = SysEx.CORRUPT_BKPT(); else rc = btreeGetPage(bt, trunkID, ref trunk, false); if (rc != RC.OK) { trunk = null; goto end_allocate_page; } Debug.Assert(trunk != null); Debug.Assert(trunk.Data != null); uint k = ConvertEx.Get4(trunk.Data, 4); // # of leaves on this trunk page, Number of leaves on the trunk of the freelist if (k == 0 && !searchList) { // The trunk has no leaves and the list is not being searched. So extract the trunk page itself and use it as the newly allocated page Debug.Assert(prevTrunk == null); rc = Pager.Write(trunk.DBPage); if (rc != RC.OK) goto end_allocate_page; id = trunkID; Buffer.BlockCopy(trunk.Data, 0, page1.Data, 32, 4); page = trunk; trunk = null; TRACE("ALLOCATE: %d trunk - %d free pages left\n", id, n - 1); } else if (k > (uint)(bt.UsableSize / 4 - 2)) { // Value of k is out of range. Database corruption rc = SysEx.CORRUPT_BKPT(); goto end_allocate_page; #if !OMIT_AUTOVACUUM } else if (searchList && (nearby == trunkID || (trunkID < nearby && mode == BTALLOC.LE))) { // The list is being searched and this trunk page is the page to allocate, regardless of whether it has leaves. id = trunkID; page = trunk; searchList = false; rc = Pager.Write(trunk.DBPage); if (rc != RC.OK) goto end_allocate_page; if (k == 0) { if (prevTrunk == null) { //memcpy(page1.Data[32], trunk.Data[0], 4); page1.Data[32 + 0] = trunk.Data[0 + 0]; page1.Data[32 + 1] = trunk.Data[0 + 1]; page1.Data[32 + 2] = trunk.Data[0 + 2]; page1.Data[32 + 3] = trunk.Data[0 + 3]; } else { rc = Pager.Write(prevTrunk.DBPage); if (rc != RC.OK) goto end_allocate_page; //memcpy(prevTrunk.Data[0], trunk.Data[0], 4); prevTrunk.Data[0 + 0] = trunk.Data[0 + 0]; prevTrunk.Data[0 + 1] = trunk.Data[0 + 1]; prevTrunk.Data[0 + 2] = trunk.Data[0 + 2]; prevTrunk.Data[0 + 3] = trunk.Data[0 + 3]; } } else { // The trunk page is required by the caller but it contains pointers to free-list leaves. The first leaf becomes a trunk // page in this case. Pid newTrunkID = ConvertEx.Get4(trunk.Data, 8); if (newTrunkID > maxPage) { rc = SysEx.CORRUPT_BKPT(); goto end_allocate_page; } ASSERTCOVERAGE(newTrunkID == maxPage); var newTrunk = new MemPage(); rc = btreeGetPage(bt, newTrunkID, ref newTrunk, false); if (rc != RC.OK) goto end_allocate_page; rc = Pager.Write(newTrunk.DBPage); if (rc != RC.OK) { releasePage(newTrunk); goto end_allocate_page; } //memcpy(newTrunk.Data[0], trunk.Data[0], 4); newTrunk.Data[0 + 0] = trunk.Data[0 + 0]; newTrunk.Data[0 + 1] = trunk.Data[0 + 1]; newTrunk.Data[0 + 2] = trunk.Data[0 + 2]; newTrunk.Data[0 + 3] = trunk.Data[0 + 3]; ConvertEx.Put4(newTrunk.Data, (uint)4, (uint)(k - 1)); Buffer.BlockCopy(trunk.Data, 12, newTrunk.Data, 8, (int)(k - 1) * 4); releasePage(newTrunk); if (prevTrunk == null) { Debug.Assert(Pager.Iswriteable(page1.DBPage)); ConvertEx.Put4(page1.Data, (uint)32, newTrunkID); } else { rc = Pager.Write(prevTrunk.DBPage); if (rc != RC.OK) goto end_allocate_page; ConvertEx.Put4(prevTrunk.Data, (uint)0, newTrunkID); } } trunk = null; TRACE("ALLOCATE: %d trunk - %d free pages left\n", id, n - 1); #endif } else if (k > 0) { // Extract a leaf from the trunk byte[] data = trunk.Data; Pid pageID; uint closest; if (nearby > 0) { closest = 0; if (mode == BTALLOC.LE) { for (var i = 0U; i < k; i++) { pageID = ConvertEx.Get4(data, 8 + i * 4); if (pageID <= nearby) { closest = i; break; } } } else { int dist = Math.Abs((int)(ConvertEx.Get4(data, 8) - nearby)); for (var i = 1U; i < k; i++) { int d2 = Math.Abs((int)(ConvertEx.Get4(data, 8 + i * 4) - nearby)); if (d2 < dist) { closest = i; dist = d2; } } } } else closest = 0; pageID = ConvertEx.Get4(data, 8 + closest * 4); ASSERTCOVERAGE(pageID == maxPage); if (pageID > maxPage) { rc = SysEx.CORRUPT_BKPT(); goto end_allocate_page; } ASSERTCOVERAGE(pageID == maxPage); if (!searchList || (pageID == nearby || (pageID < nearby && mode == BTALLOC.LE))) { id = pageID; TRACE("ALLOCATE: %d was leaf %d of %d on trunk %d: %d more free pages\n", id, closest + 1, k, trunk.ID, n - 1); rc = Pager.Write(trunk.DBPage); if (rc != RC.OK) goto end_allocate_page; if (closest < k - 1) Buffer.BlockCopy(data, (int)(4 + k * 4), data, 8 + (int)closest * 4, 4);//memcpy( aData[8 + closest * 4], ref aData[4 + k * 4], 4 ); ConvertEx.Put4(data, (uint)4, (k - 1)); bool noContent = !btreeGetHasContent(bt, id); rc = btreeGetPage(bt, id, ref page, noContent); if (rc == RC.OK) { rc = Pager.Write((page).DBPage); if (rc != RC.OK) releasePage(page); } searchList = false; } } releasePage(prevTrunk); prevTrunk = null; } while (searchList); } else { // Normally, new pages allocated by this block can be requested from the pager layer with the 'no-content' flag set. This prevents the pager // from trying to read the pages content from disk. However, if the current transaction has already run one or more incremental-vacuum // steps, then the page we are about to allocate may contain content that is required in the event of a rollback. In this case, do // not set the no-content flag. This causes the pager to load and journal the current page content before overwriting it. // // Note that the pager will not actually attempt to load or journal content for any page that really does lie past the end of the database // file on disk. So the effects of disabling the no-content optimization here are confined to those pages that lie between the end of the // database image and the end of the database file. bool noContent = !IFAUTOVACUUM(bt.DoTruncate); // There are no pages on the freelist, so append a new page to the database image. rc = Pager.Write(bt.Page1.DBPage); if (rc != RC.OK) return rc; bt.Pages++; if (bt.Pages == PENDING_BYTE_PAGE(bt)) bt.Pages++; #if !OMIT_AUTOVACUUM if (bt.AutoVacuum && PTRMAP_ISPAGE(bt, bt.Pages)) { // If *pPgno refers to a pointer-map page, allocate two new pages at the end of the file instead of one. The first allocated page // becomes a new pointer-map page, the second is used by the caller. TRACE("ALLOCATE: %d from end of file (pointer-map page)\n", bt.Pages); Debug.Assert(bt.Pages != PENDING_BYTE_PAGE(bt)); MemPage pg = null; rc = btreeGetPage(bt, bt.Pages, ref pg, noContent); if (rc == RC.OK) { rc = Pager.Write(pg.DBPage); releasePage(pg); } if (rc != RC.OK) return rc; bt.Pages++; if (bt.Pages == PENDING_BYTE_PAGE(bt)) bt.Pages++; } #endif ConvertEx.Put4(bt.Page1.Data, (uint)28, bt.Pages); id = bt.Pages; Debug.Assert(id != PENDING_BYTE_PAGE(bt)); rc = btreeGetPage(bt, id, ref page, noContent); if (rc != RC.OK) return rc; rc = Pager.Write((page).DBPage); if (rc != RC.OK) releasePage(page); TRACE("ALLOCATE: %d from end of file\n", id); } Debug.Assert(id != PENDING_BYTE_PAGE(bt)); end_allocate_page: releasePage(trunk); releasePage(prevTrunk); if (rc == RC.OK) { if (Pager.get_PageRefs((page).DBPage) > 1) { releasePage(page); return SysEx.CORRUPT_BKPT(); } (page).IsInit = false; } else page = null; Debug.Assert(rc != RC.OK || Pager.Iswriteable((page).DBPage)); return rc; }
public static RC Open(VSystem vfs, string filename, BContext ctx, ref Btree btree, OPEN flags, VSystem.OPEN vfsFlags) { // True if opening an ephemeral, temporary database bool tempDB = string.IsNullOrEmpty(filename); // Set the variable isMemdb to true for an in-memory database, or false for a file-based database. bool memoryDB = (filename == ":memory:") || (tempDB && ctx.TempInMemory()) || (vfsFlags & VSystem.OPEN.MEMORY) != 0; Debug.Assert(ctx != null); Debug.Assert(vfs != null); Debug.Assert(MutexEx.Held(ctx.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 || tempDB); if (memoryDB) flags |= OPEN.MEMORY; if ((vfsFlags & VSystem.OPEN.MAIN_DB) != 0 && (memoryDB || tempDB)) vfsFlags = (vfsFlags & ~VSystem.OPEN.MAIN_DB) | VSystem.OPEN.TEMP_DB; var p = new Btree(); // Handle to return if (p == null) return RC.NOMEM; p.InTrans = TRANS.NONE; p.Ctx = ctx; #if !OMIT_SHARED_CACHE p.Lock.Btree = p; p.Lock.Table = 1; #endif RC rc = RC.OK; // Result code from this function BtShared bt = null; // Shared part of btree structure MutexEx mutexOpen = null; #if !OMIT_SHARED_CACHE && !OMIT_DISKIO // If this Btree is a candidate for shared cache, try to find an existing BtShared object that we can share with if (!tempDB && (!memoryDB || (vfsFlags & VSystem.OPEN.URI) != 0)) if ((vfsFlags & VSystem.OPEN.SHAREDCACHE) != 0) { string fullPathname; p.Sharable_ = true; if (memoryDB) fullPathname = filename; else vfs.FullPathname(filename, out fullPathname); MutexEx mutexShared; #if THREADSAFE mutexOpen = MutexEx.Alloc(MutexEx.MUTEX.STATIC_OPEN); // Prevents a race condition. Ticket #3537 MutexEx.Enter(mutexOpen); mutexShared = MutexEx.Alloc(MutexEx.MUTEX.STATIC_MASTER); MutexEx.Enter(mutexShared); #endif for (bt = _sharedCacheList; bt != null; bt = bt.Next) { Debug.Assert(bt.Refs > 0); if (fullPathname == bt.Pager.get_Filename(false) && bt.Pager.get_Vfs() == vfs) { for (var i = ctx.DBs.length - 1; i >= 0; i--) { var existing = ctx.DBs[i].Bt; if (existing != null && existing.Bt == bt) { MutexEx.Leave(mutexShared); MutexEx.Leave(mutexOpen); fullPathname = null; p = null; return RC.CONSTRAINT; } } p.Bt = bt; bt.Refs++; break; } } MutexEx.Leave(mutexShared); fullPathname = null; } #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 byte reserves; // Byte of unused space on each page var dbHeader = new byte[100]; // Database header content if (bt == 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(Pid) == 4); bt = new BtShared(); if (bt == null) { rc = RC.NOMEM; goto btree_open_out; } rc = Pager.Open(vfs, out bt.Pager, filename, EXTRA_SIZE, (IPager.PAGEROPEN)flags, vfsFlags, pageReinit, null); if (rc == RC.OK) rc = bt.Pager.ReadFileHeader(dbHeader.Length, dbHeader); if (rc != RC.OK) goto btree_open_out; bt.OpenFlags = flags; bt.Ctx = ctx; bt.Pager.SetBusyHandler(btreeInvokeBusyHandler, bt); p.Bt = bt; bt.Cursor = null; bt.Page1 = null; if (bt.Pager.get_Readonly()) bt.BtsFlags |= BTS.READ_ONLY; #if SECURE_DELETE bt.BtsFlags |= BTS.SECURE_DELETE; #endif bt.PageSize = (Pid)((dbHeader[16] << 8) | (dbHeader[17] << 16)); if (bt.PageSize < 512 || bt.PageSize > Pager.MAX_PAGE_SIZE || ((bt.PageSize - 1) & bt.PageSize) != 0) { bt.PageSize = 0; #if !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 (filename != null && !memoryDB) { bt.AutoVacuum = (DEFAULT_AUTOVACUUM != 0); bt.IncrVacuum = (DEFAULT_AUTOVACUUM == AUTOVACUUM.INCR); } #endif reserves = 0; } else { reserves = dbHeader[20]; bt.BtsFlags |= BTS.PAGESIZE_FIXED; #if !OMIT_AUTOVACUUM bt.AutoVacuum = (ConvertEx.Get4(dbHeader, 36 + 4 * 4) != 0); bt.IncrVacuum = (ConvertEx.Get4(dbHeader, 36 + 7 * 4) != 0); #endif } rc = bt.Pager.SetPageSize(ref bt.PageSize, reserves); if (rc != RC.OK) goto btree_open_out; bt.UsableSize = (ushort)(bt.PageSize - reserves); Debug.Assert((bt.PageSize & 7) == 0); // 8-byte alignment of pageSize #if !SHARED_CACHE && !OMIT_DISKIO // Add the new BtShared object to the linked list sharable BtShareds. if (p.Sharable_) { bt.Refs = 1; MutexEx mutexShared; #if THREADSAFE mutexShared = MutexEx.Alloc(MutexEx.MUTEX.STATIC_MASTER); bt.Mutex = MutexEx.Alloc(MutexEx.MUTEX.FAST); #endif MutexEx.Enter(mutexShared); bt.Next = _sharedCacheList; _sharedCacheList = bt; MutexEx.Leave(mutexShared); } #endif } #if !OMIT_SHARED_CACHE && !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. if (p.Sharable_) { Btree sib; for (var i = 0; i < ctx.DBs.length; i++) if ((sib = ctx.DBs[i].Bt) != null && sib.Sharable_) { while (sib.Prev != null) { sib = sib.Prev; } if (p.Bt.AutoID < sib.Bt.AutoID) { p.Next = sib; p.Prev = null; sib.Prev = p; } else { while (sib.Next != null && sib.Next.Bt.AutoID < p.Bt.AutoID) sib = sib.Next; p.Next = sib.Next; p.Prev = sib; if (p.Next != null) p.Next.Prev = p; sib.Next = p; } break; } } #endif btree = p; btree_open_out: if (rc != RC.OK) { if (bt != null && bt.Pager != null) bt.Pager.Close(); bt = null; p = null; btree = 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.Schema(0, null) == null) p.Bt.Pager.SetCacheSize(DEFAULT_CACHE_SIZE); #if THREADSAFE Debug.Assert(MutexEx.Held(mutexOpen)); MutexEx.Leave(mutexOpen); #endif return rc; }
static RC freePage2(BtShared bt, MemPage memPage, Pid pageID) { Debug.Assert(MutexEx.Held(bt.Mutex)); Debug.Assert(pageID > 1); Debug.Assert(memPage == null || memPage.ID == pageID); MemPage page; // Page being freed. May be NULL. MemPage trunk = null; // Free-list trunk page if (memPage != null) { page = memPage; Pager.get_PageRefs(page.DBPage); } else page = btreePageLookup(bt, pageID); // Increment the free page count on pPage1 MemPage page1 = bt.Page1; // Local reference to page 1 RC rc = Pager.Write(page1.DBPage); if (rc != RC.OK) goto freepage_out; int frees = (int)ConvertEx.Get4(page1.Data, 36); // Initial number of pages on free-list ConvertEx.Put4(page1.Data, 36, frees + 1); if ((bt.BtsFlags & BTS.SECURE_DELETE) != 0) { // If the secure_delete option is enabled, then always fully overwrite deleted information with zeros. if ((page == null && (rc = btreeGetPage(bt, pageID, ref page, false)) != RC.OK) || (rc = Pager.Write(page.DBPage)) != RC.OK) goto freepage_out; Array.Clear(page.Data, 0, (int)page.Bt.PageSize); } // If the database supports auto-vacuum, write an entry in the pointer-map to indicate that the page is free. #if !OMIT_AUTOVACUUM if (bt.AutoVacuum) { ptrmapPut(bt, pageID, PTRMAP.FREEPAGE, 0, ref rc); if (rc != RC.OK) goto freepage_out; } #endif // Now manipulate the actual database free-list structure. There are two possibilities. If the free-list is currently empty, or if the first // trunk page in the free-list is full, then this page will become a new free-list trunk page. Otherwise, it will become a leaf of the // first trunk page in the current free-list. This block tests if it is possible to add the page as a new free-list leaf. Pid trunkID = 0; // Page number of free-list trunk page if (frees != 0) { trunkID = ConvertEx.Get4(page1.Data, 32); rc = btreeGetPage(bt, trunkID, ref trunk, false); if (rc != RC.OK) goto freepage_out; uint leafs = ConvertEx.Get4(trunk.Data, 4); // Initial number of leaf cells on trunk page Debug.Assert(bt.UsableSize > 32); if (leafs > (uint)bt.UsableSize / 4 - 2) { rc = SysEx.CORRUPT_BKPT(); goto freepage_out; } if (leafs < (uint)bt.UsableSize / 4 - 8) { // In this case there is room on the trunk page to insert the page being freed as a new leaf. // // Note that the trunk page is not really full until it contains usableSize/4 - 2 entries, not usableSize/4 - 8 entries as we have // coded. But due to a coding error in versions of SQLite prior to 3.6.0, databases with freelist trunk pages holding more than // usableSize/4 - 8 entries will be reported as corrupt. In order to maintain backwards compatibility with older versions of SQLite, // we will continue to restrict the number of entries to usableSize/4 - 8 for now. At some point in the future (once everyone has upgraded // to 3.6.0 or later) we should consider fixing the conditional above to read "usableSize/4-2" instead of "usableSize/4-8". rc = Pager.Write(trunk.DBPage); if (rc == RC.OK) { ConvertEx.Put4(trunk.Data, (uint)4, leafs + 1); ConvertEx.Put4(trunk.Data, (uint)8 + leafs * 4, pageID); if (page != null && (bt.BtsFlags & BTS.SECURE_DELETE) == 0) Pager.DontWrite(page.DBPage); rc = btreeSetHasContent(bt, pageID); } TRACE("FREE-PAGE: %d leaf on trunk page %d\n", pageID, trunk.ID); goto freepage_out; } } // If control flows to this point, then it was not possible to add the the page being freed as a leaf page of the first trunk in the free-list. // Possibly because the free-list is empty, or possibly because the first trunk in the free-list is full. Either way, the page being freed // will become the new first trunk page in the free-list. if (page == null && (rc = btreeGetPage(bt, pageID, ref page, false)) != RC.OK) goto freepage_out; rc = Pager.Write(page.DBPage); if (rc != RC.OK) goto freepage_out; ConvertEx.Put4(page.Data, trunkID); ConvertEx.Put4(page.Data, 4, 0); ConvertEx.Put4(page1.Data, (uint)32, pageID); TRACE("FREE-PAGE: %d new trunk page replacing %d\n", page.ID, trunkID); freepage_out: if (page != null) page.IsInit = false; releasePage(page); releasePage(trunk); return rc; }
static bool removeFromSharingList(BtShared bt) { #if !OMIT_SHARED_CACHE Debug.Assert(MutexEx.Held(bt.Mutex)); #if THREADSAFE var master = MutexEx.Alloc(MutexEx.MUTEX.STATIC_MASTER); #endif var removed = false; MutexEx.Enter(master); bt.Refs--; if (bt.Refs <= 0) { if (_sharedCacheList == bt) _sharedCacheList = bt.Next; else { var list = _sharedCacheList; while (C._ALWAYS(list != null) && list.Next != bt) list = list.Next; if (C._ALWAYS(list != null)) list.Next = bt.Next; } #if THREADSAFE MutexEx.Free(bt.Mutex); #endif removed = true; } MutexEx.Leave(master); return removed; #else return true; #endif }
static void ptrmapPut(BtShared bt, Pid key, PTRMAP type, Pid parent, ref RC rcRef) { if (rcRef != RC.OK) return; Debug.Assert(MutexEx.Held(bt.Mutex)); // The master-journal page number must never be used as a pointer map page Debug.Assert(!PTRMAP_ISPAGE(bt, PENDING_BYTE_PAGE(bt))); Debug.Assert(bt.AutoVacuum); if (key == 0) { rcRef = SysEx.CORRUPT_BKPT(); return; } var ptrmapIdx = PTRMAP_PAGENO(bt, key); // The pointer map page number var page = (IPage)new PgHdr(); // The pointer map page var rc = bt.Pager.Acquire(ptrmapIdx, ref page, false); if (rc != RC.OK) { rcRef = rc; return; } var offset = (int)PTRMAP_PTROFFSET(ptrmapIdx, key); // Offset in pointer map page if (offset < 0) { rcRef = SysEx.CORRUPT_BKPT(); goto ptrmap_exit; } Debug.Assert(offset <= (int)bt.UsableSize - 5); var ptrmap = Pager.GetData(page); // The pointer map page if (type != (PTRMAP)ptrmap[offset] || ConvertEx.Get4(ptrmap, offset + 1) != parent) { TRACE("PTRMAP_UPDATE: %d->(%d,%d)\n", key, type, parent); rcRef = rc = Pager.Write(page); if (rc == RC.OK) { ptrmap[offset] = (byte)type; ConvertEx.Put4(ptrmap, offset + 1, parent); } } ptrmap_exit: Pager.Unref(page); }
static void allocateTempSpace(BtShared bt) { if (bt.TmpSpace == null) bt.TmpSpace = PCache.PageAlloc2((int)bt.PageSize); }
static RC clearDatabasePage(BtShared bt, Pid id, bool freePageFlag, ref int changes) { Debug.Assert(MutexEx.Held(bt.Mutex)); if (id > btreePagecount(bt)) return SysEx.CORRUPT_BKPT(); MemPage page = new MemPage(); var rc = getAndInitPage(bt, id, ref page); if (rc != RC.OK) return rc; for (uint i = 0U; i < page.Cells; i++) { uint cell_ = findCell(page, i); if (!page.Leaf) { rc = clearDatabasePage(bt, ConvertEx.Get4(page.Data, cell_), true, ref changes); if (rc != RC.OK) goto cleardatabasepage_out; } rc = clearCell(page, cell_); if (rc != RC.OK) goto cleardatabasepage_out; } if (!page.Leaf) { rc = clearDatabasePage(bt, ConvertEx.Get4(page.Data, 8), true, ref changes); if (rc != RC.OK) goto cleardatabasepage_out; } else { Debug.Assert(page.IntKey); changes += page.Cells; } if (freePageFlag) freePage(page, ref rc); else if ((rc = Pager.Write(page.DBPage)) == 0) zeroPage(page, page.Data[0] | PTF_LEAF); cleardatabasepage_out: releasePage(page); return rc; }
/* ** The database page the PENDING_BYTE occupies. This page is never used. */ //# define PENDING_BYTE_PAGE(pBt) PAGER_MJ_PGNO(pBt) // TODO -- Convert PENDING_BYTE_PAGE to inline private static uint PENDING_BYTE_PAGE(BtShared pBt) { return(PAGER_MJ_PGNO(pBt.pPager)); }