static void sqlite3MemFreeBtCursor(ref BtCursor pPrior) { if (pPrior == null) { return; } #if FALSE && DEBUG for (int i = mem0.msBtCursor.next - 1; i >= 0; i--) { if (mem0.aBtCursor[i] != null && mem0.aBtCursor[i] == pPrior) { Debugger.Break(); } } #endif mem0.msBtCursor.dealloc++; if (mem0.msBtCursor.next < mem0.aBtCursor.Length - 1) { mem0.aBtCursor[++mem0.msBtCursor.next] = pPrior; if (mem0.msBtCursor.next > mem0.msBtCursor.max) { mem0.msBtCursor.max = mem0.msBtCursor.next; } } //Array.Resize( ref mem0.aBtCursor, (int)(mem0.hw_BtCursor * 1.5 + 1 )); //mem0.aBtCursor[mem0.hw_BtCursor] = pPrior; //mem0.hw_BtCursor = mem0.aBtCursor.Length; pPrior = null; return; }
/* ** Usage: btree_cursor ID TABLENUM WRITEABLE ** ** Create a new cursor. Return the ID for the cursor. */ static int btree_cursor( object NotUsed, Tcl_Interp interp, /* The TCL interpreter that invoked this command */ int argc, /* Number of arguments */ TclObject[] argv /* Text of each argument */ ) { Btree pBt; int iTable = 0; BtCursor pCur; int rc = SQLITE_OK; int wrFlag = 0; StringBuilder zBuf = new StringBuilder(30); if (argc != 4) { TCL.Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0].ToString(), " ID TABLENUM WRITEABLE\""); return(TCL.TCL_ERROR); } pBt = (Btree)sqlite3TestTextToPtr(interp, argv[1].ToString()); if (TCL.Tcl_GetInt(interp, argv[2], out iTable)) { return(TCL.TCL_ERROR); } if (TCL.Tcl_GetBoolean(interp, argv[3], out wrFlag)) { return(TCL.TCL_ERROR); } //pCur = (BtCursor )ckalloc(sqlite3BtreeCursorSize()); pCur = new BtCursor();// memset( pCur, 0, sqlite3BtreeCursorSize() ); sqlite3BtreeEnter(pBt); #if !SQLITE_OMIT_SHARED_CACHE rc = sqlite3BtreeLockTable(pBt, iTable, wrFlag); #endif if (rc == SQLITE_OK) { rc = sqlite3BtreeCursor(pBt, iTable, wrFlag, null, pCur); } sqlite3BtreeLeave(pBt); if (rc != 0) { pCur = null;// ckfree( pCur ); TCL.Tcl_AppendResult(interp, errorName(rc), null); ; return(TCL.TCL_ERROR); } sqlite3_snprintf(30, zBuf, "->%p", pCur); if (TCL.Tcl_CreateCommandPointer(interp, zBuf, pCur)) { return(TCL.TCL_ERROR); } else { TCL.Tcl_AppendResult(interp, zBuf); } return(SQLITE_OK); }
static BtCursor sqlite3MemMallocBtCursor(BtCursor dummy) { BtCursor pBtCursor; mem0.msBtCursor.alloc++; if (mem0.msBtCursor.next > 0 && mem0.aBtCursor[mem0.msBtCursor.next] != null) { pBtCursor = mem0.aBtCursor[mem0.msBtCursor.next]; mem0.aBtCursor[mem0.msBtCursor.next] = null; mem0.msBtCursor.cached++; mem0.msBtCursor.next--; } else { pBtCursor = new BtCursor(); } return(pBtCursor); }
static BtCursor sqlite3MemMallocBtCursor(BtCursor dummy) { BtCursor pBtCursor; mem0.msBtCursor.alloc++; if (mem0.msBtCursor.next > 0 && mem0.aBtCursor[mem0.msBtCursor.next] != null) { pBtCursor = mem0.aBtCursor[mem0.msBtCursor.next]; Debug.Assert(pBtCursor.pNext == null && pBtCursor.pPrev == null && pBtCursor.wrFlag == 0); mem0.aBtCursor[mem0.msBtCursor.next] = null; mem0.msBtCursor.cached++; mem0.msBtCursor.next--; } else { pBtCursor = new BtCursor(); } return(pBtCursor); }
internal MemPage[] Pages = new MemPage[BTCURSOR_MAX_DEPTH]; // Pages from root to current page internal void memset() { Next = Prev = null; KeyInfo = null; RootID = 0; CachedRowID = 0; Info = new CellInfo(); WrFlag = false; AtLast = 0; ValidNKey = false; State = 0; KeyLength = 0; Key = null; SkipNext = 0; #if !OMIT_INCRBLOB IsIncrblobHandle = false; Overflows = null; #endif ID = 0; }
public MemPage[] apPage = new MemPage[BTCURSOR_MAX_DEPTH]; /* Pages from root to current page */ public void Clear() { pNext = null; pPrev = null; pKeyInfo = null; pgnoRoot = 0; cachedRowid = 0; info = new CellInfo(); wrFlag = 0; atLast = 0; validNKey = false; eState = 0; pKey = null; nKey = 0; skipNext = 0; #if NOT_SQLITE_OMIT_INCRBLOB //#if !SQLITE_OMIT_INCRBLOB isIncrblobHandle = false; aOverflow = null; #endif iPage = 0; }
public bool isIncrblobHandle; // True if this cursor is an incr. io handle #endif #region Methods public void Clear() { pNext = null; pPrev = null; pKeyInfo = null; pgnoRoot = 0; cachedRowid = 0; info = new CellInfo(); wrFlag = 0; atLast = 0; validNKey = false; eState = 0; pKey = null; nKey = 0; skipNext = 0; #if !SQLITE_OMIT_INCRBLOB isIncrblobHandle = false; aOverflow = null; #endif iPage = 0; }
public static RC CloseCursor(BtCursor cur) { var btree = cur.Btree; if (btree != null) { var bt = cur.Bt; btree.Enter(); ClearCursor(cur); if (cur.Prev != null) cur.Prev.Next = cur.Next; else bt.Cursor = cur.Next; if (cur.Next != null) cur.Next.Prev = cur.Prev; for (var i = 0; i <= cur.ID; i++) releasePage(cur.Pages[i]); unlockBtreeIfUnused(bt); invalidateOverflowCache(cur); btree.Leave(); } return RC.OK; }
public static RC CursorHasMoved(BtCursor cur, out bool hasMoved) { var rc = restoreCursorPosition(cur); if (rc != RC.OK) { hasMoved = true; return rc; } hasMoved = (cur.State != CURSOR.VALID || cur.SkipNext != 0); return RC.OK; }
// was:invalidateOverflowCache internal static void invalidateOverflowCache(BtCursor pCur) { }
static RC restoreCursorPosition(BtCursor cur) { return (cur.State >= CURSOR.REQUIRESEEK ? btreeRestoreCursorPosition(cur) : RC.OK); }
public static void CursorHints(BtCursor cur, uint mask) { Debug.Assert(mask == BTREE_BULKLOAD || mask == 0); cur.Hints = (byte)mask; }
//# define sqlite3BtreeEnterCursor(X) static void sqlite3BtreeEnterCursor(BtCursor X) { }
public static RC Delete(BtCursor cur) { Btree p = cur.Btree; BtShared bt = p.Bt; Debug.Assert(cursorHoldsMutex(cur)); Debug.Assert(bt.InTransaction == TRANS.WRITE); Debug.Assert((bt.BtsFlags & BTS.READ_ONLY) == 0); Debug.Assert(cur.WrFlag); Debug.Assert(hasSharedCacheTableLock(p, cur.RootID, cur.KeyInfo != null, LOCK.WRITE)); Debug.Assert(!hasReadConflicts(p, cur.RootID)); if (C._NEVER(cur.Idxs[cur.ID] >= cur.Pages[cur.ID].Cells) || C._NEVER(cur.State != CURSOR.VALID)) return RC.ERROR; // Something has gone awry. int cellDepth = cur.ID; // Depth of node containing pCell uint cellIdx = cur.Idxs[cellDepth]; // Index of cell to delete MemPage page = cur.Pages[cellDepth]; // Page to delete cell from uint cell_ = findCell(page, cellIdx); // Pointer to cell to delete // If the page containing the entry to delete is not a leaf page, move the cursor to the largest entry in the tree that is smaller than // the entry being deleted. This cell will replace the cell being deleted from the internal node. The 'previous' entry is used for this instead // of the 'next' entry, as the previous entry is always a part of the sub-tree headed by the child page of the cell being deleted. This makes // balancing the tree following the delete operation easier. RC rc; if (!page.Leaf) { int notUsed = 0; rc = Previous(cur, ref notUsed); if (rc != RC.OK) return rc; } // Save the positions of any other cursors open on this table before making any modifications. Make the page containing the entry to be // deleted writable. Then free any overflow pages associated with the entry and finally remove the cell itself from within the page. rc = saveAllCursors(bt, cur.RootID, cur); if (rc != RC.OK) return rc; // If this is a delete operation to remove a row from a table b-tree, invalidate any incrblob cursors open on the row being deleted. if (cur.KeyInfo == null) invalidateIncrblobCursors(p, cur.Info.Key, false); rc = Pager.Write(page.DBPage); if (rc != RC.OK) return rc; rc = clearCell(page, cell_); dropCell(page, cellIdx, cellSizePtr(page, cell_), ref rc); if (rc != RC.OK) return rc; // If the cell deleted was not located on a leaf page, then the cursor is currently pointing to the largest entry in the sub-tree headed // by the child-page of the cell that was just deleted from an internal node. The cell from the leaf node needs to be moved to the internal // node to replace the deleted cell. if (!page.Leaf) { MemPage leaf = cur.Pages[cur.ID]; Pid n = cur.Pages[cellDepth + 1].ID; cell_ = findCell(leaf, (uint)leaf.Cells - 1); ushort sizeCell = cellSizePtr(leaf, cell_); Debug.Assert(MX_CELL_SIZE(bt) >= sizeCell); //allocateTempSpace(bt); //byte[] tmp = Bt.TmpSpace; rc = Pager.Write(leaf.DBPage); { byte[] cell_4 = new byte[sizeCell + 4]; Buffer.BlockCopy(leaf.Data, (int)cell_ - 4, cell_4, 0, (int)sizeCell + 4); insertCell(page, cellIdx, cell_4, (ushort)(sizeCell + 4), null, n, ref rc); } dropCell(leaf, (uint)(leaf.Cells - 1), sizeCell, ref rc); if (rc != RC.OK) return rc; } // Balance the tree. If the entry deleted was located on a leaf page, then the cursor still points to that page. In this case the first // call to balance() repairs the tree, and the if(...) condition is never true. // // Otherwise, if the entry deleted was on an internal node page, then pCur is pointing to the leaf page from which a cell was removed to // replace the cell deleted from the internal node. This is slightly tricky as the leaf node may be underfull, and the internal node may // be either under or overfull. In this case run the balancing algorithm on the leaf node first. If the balance proceeds far enough up the // tree that we can be sure that any problem in the internal node has been corrected, so be it. Otherwise, after balancing the leaf node, // walk the cursor up the tree to the internal node and balance it as well. rc = balance(cur); if (rc == RC.OK && cur.ID > cellDepth) { while (cur.ID > cellDepth) releasePage(cur.Pages[cur.ID--]); rc = balance(cur); } if (rc == RC.OK) moveToRoot(cur); return rc; }
public static RC PutData(BtCursor cur, uint offset, uint amount, byte[] z) { Debug.Assert(cursorHoldsMutex(cur)); Debug.Assert(MutexEx.Held(cur.Btree.Ctx.Mutex)); Debug.Assert(cur.IsIncrblobHandle); RC rc = restoreCursorPosition(cur); if (rc != RC.OK) return rc; Debug.Assert(cur.State != CURSOR.REQUIRESEEK); if (cur.State != CURSOR.VALID) return RC.ABORT; // Check some assumptions: // (a) the cursor is open for writing, // (b) there is a read/write transaction open, // (c) the connection holds a write-lock on the table (if required), // (d) there are no conflicting read-locks, and // (e) the cursor points at a valid row of an intKey table. if (!cur.WrFlag) return RC.READONLY; Debug.Assert((cur.Bt.BtsFlags & BTS.READ_ONLY) == 0 && cur.Bt.InTransaction == TRANS.WRITE); Debug.Assert(hasSharedCacheTableLock(cur.Btree, cur.RootID, false, LOCK.WRITE)); Debug.Assert(!hasReadConflicts(cur.Btree, cur.RootID)); Debug.Assert(cur.Pages[cur.ID].IntKey); return accessPayload(cur, offset, amount, z, 1); }
public static RC Next_(BtCursor cur, ref int res) { Debug.Assert(cursorHoldsMutex(cur)); var rc = restoreCursorPosition(cur); if (rc != RC.OK) return rc; if (cur.State == CURSOR.INVALID) { res = 1; return RC.OK; } if (cur.SkipNext > 0) { cur.SkipNext = 0; res = 0; return RC.OK; } cur.SkipNext = 0; MemPage page = cur.Pages[cur.ID]; uint idx = ++cur.Idxs[cur.ID]; Debug.Assert(page.IsInit); cur.Info.Size = 0; cur.ValidNKey = false; if (idx >= page.Cells) { if (!page.Leaf) { rc = moveToChild(cur, ConvertEx.Get4(page.Data, page.HdrOffset + 8)); if (rc != RC.OK) return rc; rc = moveToLeftmost(cur); res = 0; return rc; } do { if (cur.ID == 0) { res = 1; cur.State = CURSOR.INVALID; return RC.OK; } moveToParent(cur); page = cur.Pages[cur.ID]; } while (cur.Idxs[cur.ID] >= page.Cells); res = 0; if (page.IntKey) rc = Next_(cur, ref res); else rc = RC.OK; return rc; } res = 0; if (page.Leaf) return RC.OK; rc = moveToLeftmost(cur); return rc; }
public u16[] aiIdx = new u16[BTCURSOR_MAX_DEPTH]; /* Current index in apPage[i] */ public BtCursor Copy() { BtCursor cp = (BtCursor)MemberwiseClone(); return(cp); }
static void btreeReleaseAllCursorPages(BtCursor cur) { for (int i = 0; i <= cur.ID; i++) { releasePage(cur.Pages[i]); cur.Pages[i] = null; } cur.ID = -1; }
public static bool Eof(BtCursor cur) { // TODO: What if the cursor is in CURSOR_REQUIRESEEK but all table entries have been deleted? This API will need to change to return an error code // as well as the boolean result value. return (cur.State != CURSOR.VALID); }
public static RC MovetoUnpacked(BtCursor cur, UnpackedRecord idxKey, long intKey, int biasRight, out int res) { Debug.Assert(cursorHoldsMutex(cur)); Debug.Assert(MutexEx.Held(cur.Btree.Ctx.Mutex)); Debug.Assert((idxKey == null) == (cur.KeyInfo == null)); // If the cursor is already positioned at the point we are trying to move to, then just return without doing any work if (cur.State == CURSOR.VALID && cur.ValidNKey && cur.Pages[0].IntKey) { if (cur.Info.Key == intKey) { res = 0; return RC.OK; } if (cur.AtLast != 0 && cur.Info.Key < intKey) { res = -1; return RC.OK; } } res = -1; var rc = moveToRoot(cur); if (rc != RC.OK) return rc; Debug.Assert(cur.RootID == 0 || cur.Pages[cur.ID] != null); Debug.Assert(cur.RootID == 0 || cur.Pages[cur.ID].IsInit); Debug.Assert(cur.State == CURSOR.INVALID || cur.Pages[cur.ID].Cells > 0); if (cur.State == CURSOR.INVALID) { res = -1; Debug.Assert(cur.RootID == 0 || cur.Pages[cur.ID].Cells == 0); return RC.OK; } Debug.Assert(cur.Pages[0].IntKey || idxKey != null); int c; for (; ; ) { // pPage->nCell must be greater than zero. If this is the root-page the cursor would have been INVALID above and this for(;;) loop // not run. If this is not the root-page, then the moveToChild() routine would have already detected db corruption. Similarly, pPage must // be the right kind (index or table) of b-tree page. Otherwise a moveToChild() or moveToRoot() call would have detected corruption. MemPage page = cur.Pages[cur.ID]; Debug.Assert(page.Cells > 0); Debug.Assert(page.IntKey == (idxKey == null)); uint idx; uint lwr = 0; uint upr = (uint)(page.Cells - 1); if (biasRight != 0) cur.Idxs[cur.ID] = (ushort)(idx = upr); else cur.Idxs[cur.ID] = (ushort)(idx = (upr + lwr) / 2); for (; ; ) { Debug.Assert(idx == cur.Idxs[cur.ID]); cur.Info.Size = 0; uint cell_ = findCell(page, idx) + page.ChildPtrSize; // Pointer to current cell in pPage if (page.IntKey) { if (page.HasData) { uint dummy = 0; cell_ += ConvertEx.GetVarint32(page.Data, cell_, out dummy); } long cellKeyLength = 0; ConvertEx.GetVarint(page.Data, cell_, out cellKeyLength); if (cellKeyLength == intKey) c = 0; else if (cellKeyLength < intKey) c = -1; else c = +1; cur.ValidNKey = true; cur.Info.Key = cellKeyLength; } else { // The maximum supported page-size is 65536 bytes. This means that the maximum number of record bytes stored on an index B-Tree // page is less than 16384 bytes and may be stored as a 2-byte varint. This information is used to attempt to avoid parsing // the entire cell by checking for the cases where the record is stored entirely within the b-tree page by inspecting the first // 2 bytes of the cell. int cellLength = page.Data[cell_ + 0]; if (cellLength <= page.Max1bytePayload) { // This branch runs if the record-size field of the cell is a single byte varint and the record fits entirely on the main b-tree page. c = _vdbe.RecordCompare(cellLength, page.Data, cell_ + 1, idxKey); } else if ((page.Data[cell_ + 1] & 0x80) == 0 && (cellLength = ((cellLength & 0x7f) << 7) + page.Data[cell_ + 1]) <= page.MaxLocal) { // The record-size field is a 2 byte varint and the record fits entirely on the main b-tree page. c = _vdbe.RecordCompare(cellLength, page.Data, cell_ + 2, idxKey); } else { // The record flows over onto one or more overflow pages. In this case the whole cell needs to be parsed, a buffer allocated // and accessPayload() used to retrieve the record into the buffer before VdbeRecordCompare() can be called. var cellBody = new byte[page.Data.Length - cell_ + page.ChildPtrSize]; Buffer.BlockCopy(page.Data, (int)cell_ - page.ChildPtrSize, cellBody, 0, cellBody.Length); btreeParseCellPtr(page, cellBody, ref cur.Info); cellLength = (int)cur.Info.Key; var cellKey = C._alloc(cellLength); rc = accessPayload(cur, 0, (uint)cellLength, cellKey, 0); if (rc != RC.OK) { cellKey = null; goto moveto_finish; } c = _vdbe.RecordCompare(cellLength, cellKey, 0, idxKey); cellKey = null; } } if (c == 0) { if (page.IntKey && !page.Leaf) { lwr = idx; upr = lwr - 1; break; } else { res = 0; rc = RC.OK; goto moveto_finish; } } if (c < 0) lwr = idx + 1; else upr = idx - 1; if (lwr > upr) break; cur.Idxs[cur.ID] = (ushort)(idx = (lwr + upr) / 2); } Debug.Assert(lwr == upr + 1 || (page.IntKey && !page.Leaf)); Debug.Assert(page.IsInit); Pid childID; if (page.Leaf) childID = 0; else if (lwr >= page.Cells) childID = ConvertEx.Get4(page.Data, page.HdrOffset + 8); else childID = ConvertEx.Get4(page.Data, findCell(page, lwr)); if (childID == 0) { Debug.Assert(cur.Idxs[cur.ID] < cur.Pages[cur.ID].Cells); res = (int)c; rc = RC.OK; goto moveto_finish; } cur.Idxs[cur.ID] = (ushort)lwr; cur.Info.Size = 0; cur.ValidNKey = false; rc = moveToChild(cur, childID); if (rc != RC.OK) goto moveto_finish; } moveto_finish: return rc; }
public static RC Last(BtCursor cur, ref int res) { Debug.Assert(cursorHoldsMutex(cur)); Debug.Assert(MutexEx.Held(cur.Btree.Ctx.Mutex)); // If the cursor already points to the last entry, this is a no-op. if (cur.State == CURSOR.VALID && cur.AtLast != 0) { #if DEBUG // This block serves to Debug.Assert() that the cursor really does point to the last entry in the b-tree. for (var ii = 0; ii < cur.ID; ii++) Debug.Assert(cur.Idxs[ii] == cur.Pages[ii].Cells); Debug.Assert(cur.Idxs[cur.ID] == cur.Pages[cur.ID].Cells - 1); Debug.Assert(cur.Pages[cur.ID].Leaf); #endif return RC.OK; } var rc = moveToRoot(cur); if (rc == RC.OK) { if (cur.State == CURSOR.INVALID) { Debug.Assert(cur.Pages[cur.ID].Cells == 0); res = 1; } else { Debug.Assert(cur.State == CURSOR.VALID); res = 0; rc = moveToRightmost(cur); cur.AtLast = (byte)(rc == RC.OK ? 1 : 0); } } return rc; }
public static RC First(BtCursor cur, ref int res) { Debug.Assert(cursorHoldsMutex(cur)); Debug.Assert(MutexEx.Held(cur.Btree.Ctx.Mutex)); var rc = moveToRoot(cur); if (rc == RC.OK) { if (cur.State == CURSOR.INVALID) { Debug.Assert(cur.Pages[cur.ID].Cells == 0); res = 1; } else { Debug.Assert(cur.Pages[cur.ID].Cells > 0); res = 0; rc = moveToLeftmost(cur); } } return rc; }
static RC balance(BtCursor cur) { #if DEBUG int balance_quick_called = 0; int balance_deeper_called = 0; #else int balance_quick_called = 0; int balance_deeper_called = 0; #endif byte[] free = null; int min = (int)cur.Bt.UsableSize * 2 / 3; RC rc = RC.OK; do { int pageID = cur.ID; MemPage page = cur.Pages[pageID]; if (pageID == 0) { if (page.Overflows != 0) { // The root page of the b-tree is overfull. In this case call the balance_deeper() function to create a new child for the root-page // and copy the current contents of the root-page to it. The next iteration of the do-loop will balance the child page. Debug.Assert(balance_deeper_called++ == 0); rc = balance_deeper(page, ref cur.Pages[1]); if (rc == RC.OK) { cur.ID = 1; cur.Idxs[0] = 0; cur.Idxs[1] = 0; Debug.Assert(cur.Pages[1].Overflows != 0); } } else break; } else if (page.Overflows == 0 && page.Frees <= min) break; else { MemPage parent = cur.Pages[pageID - 1]; ushort idx = cur.Idxs[pageID - 1]; rc = Pager.Write(parent.DBPage); if (rc == RC.OK) { #if !OMIT_QUICKBALANCE if (page.HasData && page.Overflows == 1 && page.OvflIdxs[0] == page.Cells && parent.ID != 1 && parent.Cells == idx) { // Call balance_quick() to create a new sibling of pPage on which to store the overflow cell. balance_quick() inserts a new cell // into pParent, which may cause pParent overflow. If this happens, the next interation of the do-loop will balance pParent // use either balance_nonroot() or balance_deeper(). Until this happens, the overflow cell is stored in the aBalanceQuickSpace[] buffer. // // The purpose of the following assert() is to check that only a single call to balance_quick() is made for each call to this // function. If this were not verified, a subtle bug involving reuse of the aBalanceQuickSpace[] might sneak in. Debug.Assert((balance_quick_called++) == 0); rc = balance_quick(parent, page, _balanceQuickSpace); } else #endif { // In this case, call balance_nonroot() to redistribute cells between pPage and up to 2 of its sibling pages. This involves // modifying the contents of pParent, which may cause pParent to become overfull or underfull. The next iteration of the do-loop // will balance the parent page to correct this. // // If the parent page becomes overfull, the overflow cell or cells are stored in the pSpace buffer allocated immediately below. // A subsequent iteration of the do-loop will deal with this by calling balance_nonroot() (balance_deeper() may be called first, // but it doesn't deal with overflow cells - just moves them to a different page). Once this subsequent call to balance_nonroot() // has completed, it is safe to release the pSpace buffer used by the previous call, as the overflow cell data will have been // copied either into the body of a database page or into the new pSpace buffer passed to the latter call to balance_nonroot(). byte[] space = null; //PCache.PageAlloc2((int)cur.Bt.PageSize); rc = balance_nonroot(parent, idx, space, pageID == 1, cur.Hints != 0); if (free != null) { // If pFree is not NULL, it points to the pSpace buffer used by a previous call to balance_nonroot(). Its contents are // now stored either on real database pages or within the new pSpace buffer, so it may be safely freed here. PCache.PageFree2(ref free); } // The pSpace buffer will be freed after the next call to balance_nonroot(), or just before this function returns, whichever comes first. free = space; } } page.Overflows = 0; // The next iteration of the do-loop balances the parent page. releasePage(page); cur.ID--; } } while (rc == RC.OK); if (free != null) PCache.PageFree2(ref free); return rc; }
private static void sqlite3MemFreeBtCursor(ref BtCursor pPrior) { pPrior = null; }
public static RC Insert(BtCursor cur, byte[] key, long keyLength, byte[] data, int dataLength, int zero, int appendBias, int seekResult) { if (cur.State == CURSOR.FAULT) { Debug.Assert(cur.SkipNext != 0); return (RC)cur.SkipNext; } Btree p = cur.Btree; BtShared bt = p.Bt; Debug.Assert(cursorHoldsMutex(cur)); Debug.Assert(cur.WrFlag && bt.InTransaction == TRANS.WRITE && (bt.BtsFlags & BTS.READ_ONLY) == 0); Debug.Assert(hasSharedCacheTableLock(p, cur.RootID, cur.KeyInfo != null, LOCK.WRITE)); // Assert that the caller has been consistent. If this cursor was opened expecting an index b-tree, then the caller should be inserting blob // keys with no associated data. If the cursor was opened expecting an intkey table, the caller should be inserting integer keys with a // blob of associated data. Debug.Assert((key == null) == (cur.KeyInfo == null)); // Save the positions of any other cursors open on this table. // // In some cases, the call to btreeMoveto() below is a no-op. For example, when inserting data into a table with auto-generated integer // keys, the VDBE layer invokes sqlite3BtreeLast() to figure out the integer key to use. It then calls this function to actually insert the // data into the intkey B-Tree. In this case btreeMoveto() recognizes that the cursor is already where it needs to be and returns without // doing any work. To avoid thwarting these optimizations, it is important not to clear the cursor here. RC rc = saveAllCursors(bt, cur.RootID, cur); if (rc != RC.OK) return rc; // If this is an insert into a table b-tree, invalidate any incrblob cursors open on the row being replaced (assuming this is a replace // operation - if it is not, the following is a no-op). if (cur.KeyInfo == null) invalidateIncrblobCursors(p, keyLength, false); int loc = seekResult; // -1: before desired location +1: after if (loc == 0) { rc = btreeMoveto(cur, key, keyLength, appendBias, ref loc); if (rc != RC.OK) return rc; } Debug.Assert(cur.State == CURSOR.VALID || (cur.State == CURSOR.INVALID && loc != 0)); MemPage page = cur.Pages[cur.ID]; Debug.Assert(page.IntKey || keyLength >= 0); Debug.Assert(page.Leaf || !page.IntKey); TRACE("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n", cur.RootID, keyLength, dataLength, page.ID, loc == 0 ? "overwrite" : "new entry"); Debug.Assert(page.IsInit); allocateTempSpace(bt); byte[] newCell = bt.TmpSpace; if (newCell == null) return RC.NOMEM; ushort sizeNew = 0; rc = fillInCell(page, newCell, key, keyLength, data, dataLength, zero, ref sizeNew); if (rc != RC.OK) goto end_insert; Debug.Assert(sizeNew == cellSizePtr(page, newCell)); Debug.Assert(sizeNew <= MX_CELL_SIZE(bt)); uint idx = cur.Idxs[cur.ID]; if (loc == 0) { Debug.Assert(idx < page.Cells); rc = Pager.Write(page.DBPage); if (rc != RC.OK) goto end_insert; uint oldCell_ = findCell(page, idx); if (!page.Leaf) { //_memcpy(newCell, oldCell, 4); newCell[0] = page.Data[oldCell_ + 0]; newCell[1] = page.Data[oldCell_ + 1]; newCell[2] = page.Data[oldCell_ + 2]; newCell[3] = page.Data[oldCell_ + 3]; } ushort sizeOld = cellSizePtr(page, oldCell_); rc = clearCell(page, oldCell_); dropCell(page, idx, sizeOld, ref rc); if (rc != RC.OK) goto end_insert; } else if (loc < 0 && page.Cells > 0) { Debug.Assert(page.Leaf); idx = ++cur.Idxs[cur.ID]; } else Debug.Assert(page.Leaf); insertCell(page, idx, newCell, sizeNew, null, 0, ref rc); Debug.Assert(rc != RC.OK || page.Cells > 0 || page.Overflows > 0); // If no error has occurred and pPage has an overflow cell, call balance() to redistribute the cells within the tree. Since balance() may move // the cursor, zero the BtCursor.info.nSize and BtCursor.validNKey variables. // // Previous versions of SQLite called moveToRoot() to move the cursor back to the root page as balance() used to invalidate the contents // of BtCursor.apPage[] and BtCursor.aiIdx[]. Instead of doing that, set the cursor state to "invalid". This makes common insert operations // slightly faster. // // There is a subtle but important optimization here too. When inserting multiple records into an intkey b-tree using a single cursor (as can // happen while processing an "INSERT INTO ... SELECT" statement), it is advantageous to leave the cursor pointing to the last entry in // the b-tree if possible. If the cursor is left pointing to the last entry in the table, and the next row inserted has an integer key // larger than the largest existing key, it is possible to insert the row without seeking the cursor. This can be a big performance boost. cur.Info.Size = 0; cur.ValidNKey = false; if (rc == RC.OK && page.Overflows != 0) { rc = balance(cur); // Must make sure nOverflow is reset to zero even if the balance() fails. Internal data structure corruption will result otherwise. // Also, set the cursor state to invalid. This stops saveCursorPosition() from trying to save the current position of the cursor. cur.Pages[cur.ID].Overflows = 0; cur.State = CURSOR.INVALID; } Debug.Assert(cur.Pages[cur.ID].Overflows == 0); end_insert: return rc; }
void sqlite3BtreeLeaveCursor(BtCursor *pCur){ sqlite3BtreeLeave(pCur->pBtree); }
public static RC Count(BtCursor cur, ref long entrysRef) { if (cur.RootID == 0) { entrysRef = 0; return RC.OK; } var rc = moveToRoot(cur); // Unless an error occurs, the following loop runs one iteration for each page in the B-Tree structure (not including overflow pages). long entrys = 0; // Value to return in pnEntry while (rc == RC.OK) { // If this is a leaf page or the tree is not an int-key tree, then this page contains countable entries. Increment the entry counter accordingly. MemPage page = cur.Pages[cur.ID]; // Current page of the b-tree if (page.Leaf || !page.IntKey) entrys += page.Cells; // pPage is a leaf node. This loop navigates the cursor so that it points to the first interior cell that it points to the parent of // the next page in the tree that has not yet been visited. The pCur->aiIdx[pCur->iPage] value is set to the index of the parent cell // of the page, or to the number of cells in the page if the next page to visit is the right-child of its parent. // // If all pages in the tree have been visited, return SQLITE_OK to the caller. if (page.Leaf) { do { if (cur.ID == 0) { // All pages of the b-tree have been visited. Return successfully. entrysRef = entrys; return RC.OK; } moveToParent(cur); } while (cur.Idxs[cur.ID] >= cur.Pages[cur.ID].Cells); cur.Idxs[cur.ID]++; page = cur.Pages[cur.ID]; } // Descend to the child node of the cell that the cursor currently points at. This is the right-child if (iIdx==pPage->nCell). uint idx = cur.Idxs[cur.ID]; // Index of child node in parent if (idx == page.Cells) rc = moveToChild(cur, ConvertEx.Get4(page.Data, page.HdrOffset + 8)); else rc = moveToChild(cur, ConvertEx.Get4(page.Data, findCell(page, idx))); } // An error has occurred. Return an error code. return rc; }
/* ** Move data out of a btree key or data field and into a Mem structure. ** The data or key is taken from the entry that pCur is currently pointing ** to. offset and amt determine what portion of the data or key to retrieve. ** key is true to get the key or false to get data. The result is written ** into the pMem element. ** ** The pMem structure is assumed to be uninitialized. Any prior content ** is overwritten without being freed. ** ** If this routine fails for any reason (malloc returns NULL or unable ** to read from the disk) then the pMem is left in an inconsistent state. */ private static int sqlite3VdbeMemFromBtree( BtCursor pCur, /* Cursor pointing at record to retrieve. */ int offset, /* Offset from the start of data to return bytes from. */ int amt, /* Number of bytes to return. */ bool key, /* If true, retrieve from the btree key, not data. */ Mem pMem /* OUT: Return data in this Mem structure. */ ) { byte[] zData; /* Data from the btree layer */ int available = 0; /* Number of bytes available on the local btree page */ int rc = SQLITE_OK; /* Return code */ Debug.Assert(sqlite3BtreeCursorIsValid(pCur)); /* Note: the calls to BtreeKeyFetch() and DataFetch() below assert() ** that both the BtShared and database handle mutexes are held. */ Debug.Assert((pMem.flags & MEM_RowSet) == 0); int outOffset = -1; if (key) { zData = sqlite3BtreeKeyFetch(pCur, ref available, ref outOffset); } else { zData = sqlite3BtreeDataFetch(pCur, ref available, ref outOffset); } Debug.Assert(zData != null); if (offset + amt <= available && (pMem.flags & MEM_Dyn) == 0) { sqlite3VdbeMemRelease(pMem); pMem.zBLOB = sqlite3Malloc(amt); Buffer.BlockCopy(zData, offset, pMem.zBLOB, 0, amt);//pMem.z = &zData[offset]; pMem.flags = MEM_Blob | MEM_Ephem; } else if (SQLITE_OK == (rc = sqlite3VdbeMemGrow(pMem, amt + 2, 0))) { pMem.enc = 0; pMem.type = SQLITE_BLOB; pMem.z = null; pMem.zBLOB = sqlite3Malloc(amt); pMem.flags = MEM_Blob | MEM_Dyn | MEM_Term; if (key) { rc = sqlite3BtreeKey(pCur, (u32)offset, (u32)amt, pMem.zBLOB); } else { rc = sqlite3BtreeData(pCur, (u32)offset, (u32)amt, pMem.zBLOB);//pMem.z = pMem_z ; } //pMem.z[amt] = 0; //pMem.z[amt+1] = 0; if (rc != SQLITE_OK) { sqlite3VdbeMemRelease(pMem); } } pMem.n = amt; sqlite3_free(ref zData); return rc; }
public static void CacheOverflow(BtCursor cur) { Debug.Assert(cursorHoldsMutex(cur)); Debug.Assert(MutexEx.Held(cur.Btree.Ctx.Mutex)); invalidateOverflowCache(cur); cur.IsIncrblobHandle = true; }
//# define sqlite3BtreeLeaveCursor(X) static void sqlite3BtreeLeaveCursor( BtCursor X ) { }
static RC saveCursorPosition(BtCursor cur) { Debug.Assert(cur.State == CURSOR.VALID); Debug.Assert(cur.Key == null); Debug.Assert(cursorHoldsMutex(cur)); var rc = KeySize(cur, ref cur.KeyLength); Debug.Assert(rc == RC.OK); // KeySize() cannot fail // If this is an intKey table, then the above call to BtreeKeySize() stores the integer key in pCur.nKey. In this case this value is // all that is required. Otherwise, if pCur is not open on an intKey table, then malloc space for and store the pCur.nKey bytes of key data. if (!cur.Pages[0].IntKey) { var key = C._alloc((int)cur.KeyLength); rc = Key(cur, 0, (uint)cur.KeyLength, key); if (rc == RC.OK) cur.Key = key; } Debug.Assert(!cur.Pages[0].IntKey || cur.Key == null); if (rc == RC.OK) { btreeReleaseAllCursorPages(cur); cur.State = CURSOR.REQUIRESEEK; } invalidateOverflowCache(cur); return rc; }
//# define sqlite3BtreeLeaveCursor(X) static void sqlite3BtreeLeaveCursor(BtCursor X) { }
private static BtCursor sqlite3MemMallocBtCursor(BtCursor dummy) { return(new BtCursor()); }
public static RC Previous(BtCursor cur, ref int res) { Debug.Assert(cursorHoldsMutex(cur)); var rc = restoreCursorPosition(cur); if (rc != RC.OK) return rc; cur.AtLast = 0; if (cur.State == CURSOR.INVALID) { res = 1; return RC.OK; } if (cur.SkipNext < 0) { cur.SkipNext = 0; res = 0; return RC.OK; } cur.SkipNext = 0; MemPage page = cur.Pages[cur.ID]; Debug.Assert(page.IsInit); if (!page.Leaf) { uint idx = cur.Idxs[cur.ID]; rc = moveToChild(cur, ConvertEx.Get4(page.Data, findCell(page, idx))); if (rc != RC.OK) return rc; rc = moveToRightmost(cur); } else { while (cur.Idxs[cur.ID] == 0) { if (cur.ID == 0) { cur.State = CURSOR.INVALID; res = 1; return RC.OK; } moveToParent(cur); } cur.Info.Size = 0; cur.ValidNKey = false; cur.Idxs[cur.ID]--; page = cur.Pages[cur.ID]; if (page.IntKey && !page.Leaf) rc = Previous(cur, ref res); else rc = RC.OK; } res = 0; return rc; }
/* ** Enter and leave a mutex on a Btree given a cursor owned by that ** Btree. These entry points are used by incremental I/O and can be ** omitted if that module is not used. */ void sqlite3BtreeEnterCursor(BtCursor *pCur){ sqlite3BtreeEnter(pCur->pBtree); }
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; }
/* ** Usage: btree_cursor ID TABLENUM WRITEABLE ** ** Create a new cursor. Return the ID for the cursor. */ static int btree_cursor( object NotUsed, Tcl_Interp interp, /* The TCL interpreter that invoked this command */ int argc, /* Number of arguments */ TclObject[] argv /* Text of each argument */ ) { Btree pBt; int iTable = 0; BtCursor pCur; int rc = SQLITE_OK; int wrFlag = 0; StringBuilder zBuf = new StringBuilder( 30 ); if ( argc != 4 ) { TCL.Tcl_AppendResult( interp, "wrong # args: should be \"", argv[0].ToString(), " ID TABLENUM WRITEABLE\"" ); return TCL.TCL_ERROR; } pBt = (Btree)sqlite3TestTextToPtr( interp, argv[1].ToString() ); if ( TCL.Tcl_GetInt( interp, argv[2], out iTable ) ) return TCL.TCL_ERROR; if ( TCL.Tcl_GetBoolean( interp, argv[3], out wrFlag ) ) return TCL.TCL_ERROR; //pCur = (BtCursor )ckalloc(sqlite3BtreeCursorSize()); pCur = new BtCursor();// memset( pCur, 0, sqlite3BtreeCursorSize() ); sqlite3BtreeEnter( pBt ); #if !SQLITE_OMIT_SHARED_CACHE rc = sqlite3BtreeLockTable( pBt, iTable, wrFlag ); #endif if ( rc == SQLITE_OK ) { rc = sqlite3BtreeCursor( pBt, iTable, wrFlag, null, pCur ); } sqlite3BtreeLeave( pBt ); if ( rc != 0 ) { pCur = null;// ckfree( pCur ); TCL.Tcl_AppendResult( interp, errorName( rc ), null ); ; return TCL.TCL_ERROR; } sqlite3_snprintf( 30, zBuf, "->%p", pCur ); if ( TCL.Tcl_CreateCommandPointer( interp, zBuf, pCur ) ) { return TCL.TCL_ERROR; } else TCL.Tcl_AppendResult( interp, zBuf ); return SQLITE_OK; }
public static void ClearCursor(BtCursor cur) { Debug.Assert(cursorHoldsMutex(cur)); C._free(ref cur.Key); cur.State = CURSOR.INVALID; }
//# define sqlite3BtreeEnterCursor(X) static void sqlite3BtreeEnterCursor( BtCursor X ) { }
static RC btreeMoveto(BtCursor cur, byte[] key, long keyLength, int bias, ref int res) { UnpackedRecord idxKey; // Unpacked index key object free; if (key != null) { Debug.Assert(keyLength == (long)(int)keyLength); idxKey = _vdbe.AllocUnpackedRecord(cur.KeyInfo, null, 0, out free); if (idxKey == null) return RC.NOMEM; _vdbe.RecordUnpack(cur.KeyInfo, (int)keyLength, key, idxKey); } else idxKey = null; var rc = MovetoUnpacked(cur, idxKey, keyLength, (bias != 0 ? 1 : 0), out res); return rc; }
static RC btreeRestoreCursorPosition(BtCursor cur) { Debug.Assert(cursorHoldsMutex(cur)); Debug.Assert(cur.State >= CURSOR.REQUIRESEEK); if (cur.State == CURSOR.FAULT) return (RC)cur.SkipNext; cur.State = CURSOR.INVALID; var rc = btreeMoveto(cur, cur.Key, cur.KeyLength, 0, ref cur.SkipNext); if (rc == RC.OK) { cur.Key = null; Debug.Assert(cur.State == CURSOR.VALID || cur.State == CURSOR.INVALID); } return rc; }