| /* |
| ** 2003 Feb 4 |
| ** |
| ** 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: btree_rb.c,v 1.24.2.1 2004/06/26 14:40:05 drh Exp $ |
| ** |
| ** This file implements an in-core database using Red-Black balanced |
| ** binary trees. |
| ** |
| ** It was contributed to SQLite by anonymous on 2003-Feb-04 23:24:49 UTC. |
| */ |
| #include "btree.h" |
| #include "sqliteInt.h" |
| #include <assert.h> |
| |
| /* |
| ** Omit this whole file if the SQLITE_OMIT_INMEMORYDB macro is |
| ** defined. This allows a lot of code to be omitted for installations |
| ** that do not need it. |
| */ |
| #ifndef SQLITE_OMIT_INMEMORYDB |
| |
| |
| typedef struct BtRbTree BtRbTree; |
| typedef struct BtRbNode BtRbNode; |
| typedef struct BtRollbackOp BtRollbackOp; |
| typedef struct Rbtree Rbtree; |
| typedef struct RbtCursor RbtCursor; |
| |
| /* Forward declarations */ |
| static BtOps sqliteRbtreeOps; |
| static BtCursorOps sqliteRbtreeCursorOps; |
| |
| /* |
| * During each transaction (or checkpoint), a linked-list of |
| * "rollback-operations" is accumulated. If the transaction is rolled back, |
| * then the list of operations must be executed (to restore the database to |
| * it's state before the transaction started). If the transaction is to be |
| * committed, just delete the list. |
| * |
| * Each operation is represented as follows, depending on the value of eOp: |
| * |
| * ROLLBACK_INSERT -> Need to insert (pKey, pData) into table iTab. |
| * ROLLBACK_DELETE -> Need to delete the record (pKey) into table iTab. |
| * ROLLBACK_CREATE -> Need to create table iTab. |
| * ROLLBACK_DROP -> Need to drop table iTab. |
| */ |
| struct BtRollbackOp { |
| u8 eOp; |
| int iTab; |
| int nKey; |
| void *pKey; |
| int nData; |
| void *pData; |
| BtRollbackOp *pNext; |
| }; |
| |
| /* |
| ** Legal values for BtRollbackOp.eOp: |
| */ |
| #define ROLLBACK_INSERT 1 /* Insert a record */ |
| #define ROLLBACK_DELETE 2 /* Delete a record */ |
| #define ROLLBACK_CREATE 3 /* Create a table */ |
| #define ROLLBACK_DROP 4 /* Drop a table */ |
| |
| struct Rbtree { |
| BtOps *pOps; /* Function table */ |
| int aMetaData[SQLITE_N_BTREE_META]; |
| |
| int next_idx; /* next available table index */ |
| Hash tblHash; /* All created tables, by index */ |
| u8 isAnonymous; /* True if this Rbtree is to be deleted when closed */ |
| u8 eTransState; /* State of this Rbtree wrt transactions */ |
| |
| BtRollbackOp *pTransRollback; |
| BtRollbackOp *pCheckRollback; |
| BtRollbackOp *pCheckRollbackTail; |
| }; |
| |
| /* |
| ** Legal values for Rbtree.eTransState. |
| */ |
| #define TRANS_NONE 0 /* No transaction is in progress */ |
| #define TRANS_INTRANSACTION 1 /* A transaction is in progress */ |
| #define TRANS_INCHECKPOINT 2 /* A checkpoint is in progress */ |
| #define TRANS_ROLLBACK 3 /* We are currently rolling back a checkpoint or |
| * transaction. */ |
| |
| struct RbtCursor { |
| BtCursorOps *pOps; /* Function table */ |
| Rbtree *pRbtree; |
| BtRbTree *pTree; |
| int iTree; /* Index of pTree in pRbtree */ |
| BtRbNode *pNode; |
| RbtCursor *pShared; /* List of all cursors on the same Rbtree */ |
| u8 eSkip; /* Determines if next step operation is a no-op */ |
| u8 wrFlag; /* True if this cursor is open for writing */ |
| }; |
| |
| /* |
| ** Legal values for RbtCursor.eSkip. |
| */ |
| #define SKIP_NONE 0 /* Always step the cursor */ |
| #define SKIP_NEXT 1 /* The next sqliteRbtreeNext() is a no-op */ |
| #define SKIP_PREV 2 /* The next sqliteRbtreePrevious() is a no-op */ |
| #define SKIP_INVALID 3 /* Calls to Next() and Previous() are invalid */ |
| |
| struct BtRbTree { |
| RbtCursor *pCursors; /* All cursors pointing to this tree */ |
| BtRbNode *pHead; /* Head of the tree, or NULL */ |
| }; |
| |
| struct BtRbNode { |
| int nKey; |
| void *pKey; |
| int nData; |
| void *pData; |
| u8 isBlack; /* true for a black node, 0 for a red node */ |
| BtRbNode *pParent; /* Nodes parent node, NULL for the tree head */ |
| BtRbNode *pLeft; /* Nodes left child, or NULL */ |
| BtRbNode *pRight; /* Nodes right child, or NULL */ |
| |
| int nBlackHeight; /* Only used during the red-black integrity check */ |
| }; |
| |
| /* Forward declarations */ |
| static int memRbtreeMoveto( |
| RbtCursor* pCur, |
| const void *pKey, |
| int nKey, |
| int *pRes |
| ); |
| static int memRbtreeClearTable(Rbtree* tree, int n); |
| static int memRbtreeNext(RbtCursor* pCur, int *pRes); |
| static int memRbtreeLast(RbtCursor* pCur, int *pRes); |
| static int memRbtreePrevious(RbtCursor* pCur, int *pRes); |
| |
| |
| /* |
| ** This routine checks all cursors that point to the same table |
| ** as pCur points to. If any of those cursors were opened with |
| ** wrFlag==0 then this routine returns SQLITE_LOCKED. If all |
| ** cursors point to the same table were opened with wrFlag==1 |
| ** then this routine returns SQLITE_OK. |
| ** |
| ** In addition to checking for read-locks (where a read-lock |
| ** means a cursor opened with wrFlag==0) this routine also NULLs |
| ** out the pNode field of all other cursors. |
| ** This is necessary because an insert |
| ** or delete might change erase the node out from under |
| ** another cursor. |
| */ |
| static int checkReadLocks(RbtCursor *pCur){ |
| RbtCursor *p; |
| assert( pCur->wrFlag ); |
| for(p=pCur->pTree->pCursors; p; p=p->pShared){ |
| if( p!=pCur ){ |
| if( p->wrFlag==0 ) return SQLITE_LOCKED; |
| p->pNode = 0; |
| } |
| } |
| return SQLITE_OK; |
| } |
| |
| /* |
| * The key-compare function for the red-black trees. Returns as follows: |
| * |
| * (key1 < key2) -1 |
| * (key1 == key2) 0 |
| * (key1 > key2) 1 |
| * |
| * Keys are compared using memcmp(). If one key is an exact prefix of the |
| * other, then the shorter key is less than the longer key. |
| */ |
| static int key_compare(void const*pKey1, int nKey1, void const*pKey2, int nKey2) |
| { |
| int mcmp = memcmp(pKey1, pKey2, (nKey1 <= nKey2)?nKey1:nKey2); |
| if( mcmp == 0){ |
| if( nKey1 == nKey2 ) return 0; |
| return ((nKey1 < nKey2)?-1:1); |
| } |
| return ((mcmp>0)?1:-1); |
| } |
| |
| /* |
| * Perform the LEFT-rotate transformation on node X of tree pTree. This |
| * transform is part of the red-black balancing code. |
| * |
| * | | |
| * X Y |
| * / \ / \ |
| * a Y X c |
| * / \ / \ |
| * b c a b |
| * |
| * BEFORE AFTER |
| */ |
| static void leftRotate(BtRbTree *pTree, BtRbNode *pX) |
| { |
| BtRbNode *pY; |
| BtRbNode *pb; |
| pY = pX->pRight; |
| pb = pY->pLeft; |
| |
| pY->pParent = pX->pParent; |
| if( pX->pParent ){ |
| if( pX->pParent->pLeft == pX ) pX->pParent->pLeft = pY; |
| else pX->pParent->pRight = pY; |
| } |
| pY->pLeft = pX; |
| pX->pParent = pY; |
| pX->pRight = pb; |
| if( pb ) pb->pParent = pX; |
| if( pTree->pHead == pX ) pTree->pHead = pY; |
| } |
| |
| /* |
| * Perform the RIGHT-rotate transformation on node X of tree pTree. This |
| * transform is part of the red-black balancing code. |
| * |
| * | | |
| * X Y |
| * / \ / \ |
| * Y c a X |
| * / \ / \ |
| * a b b c |
| * |
| * BEFORE AFTER |
| */ |
| static void rightRotate(BtRbTree *pTree, BtRbNode *pX) |
| { |
| BtRbNode *pY; |
| BtRbNode *pb; |
| pY = pX->pLeft; |
| pb = pY->pRight; |
| |
| pY->pParent = pX->pParent; |
| if( pX->pParent ){ |
| if( pX->pParent->pLeft == pX ) pX->pParent->pLeft = pY; |
| else pX->pParent->pRight = pY; |
| } |
| pY->pRight = pX; |
| pX->pParent = pY; |
| pX->pLeft = pb; |
| if( pb ) pb->pParent = pX; |
| if( pTree->pHead == pX ) pTree->pHead = pY; |
| } |
| |
| /* |
| * A string-manipulation helper function for check_redblack_tree(). If (orig == |
| * NULL) a copy of val is returned. If (orig != NULL) then a copy of the * |
| * concatenation of orig and val is returned. The original orig is deleted |
| * (using sqliteFree()). |
| */ |
| static char *append_val(char * orig, char const * val){ |
| char *z; |
| if( !orig ){ |
| z = sqliteStrDup( val ); |
| } else{ |
| z = 0; |
| sqliteSetString(&z, orig, val, (char*)0); |
| sqliteFree( orig ); |
| } |
| return z; |
| } |
| |
| /* |
| * Append a string representation of the entire node to orig and return it. |
| * This is used to produce debugging information if check_redblack_tree() finds |
| * a problem with a red-black binary tree. |
| */ |
| static char *append_node(char * orig, BtRbNode *pNode, int indent) |
| { |
| char buf[128]; |
| int i; |
| |
| for( i=0; i<indent; i++ ){ |
| orig = append_val(orig, " "); |
| } |
| |
| sprintf(buf, "%p", pNode); |
| orig = append_val(orig, buf); |
| |
| if( pNode ){ |
| indent += 3; |
| if( pNode->isBlack ){ |
| orig = append_val(orig, " B \n"); |
| }else{ |
| orig = append_val(orig, " R \n"); |
| } |
| orig = append_node( orig, pNode->pLeft, indent ); |
| orig = append_node( orig, pNode->pRight, indent ); |
| }else{ |
| orig = append_val(orig, "\n"); |
| } |
| return orig; |
| } |
| |
| /* |
| * Print a representation of a node to stdout. This function is only included |
| * so you can call it from within a debugger if things get really bad. It |
| * is not called from anyplace in the code. |
| */ |
| static void print_node(BtRbNode *pNode) |
| { |
| char * str = append_node(0, pNode, 0); |
| printf("%s", str); |
| |
| /* Suppress a warning message about print_node() being unused */ |
| (void)print_node; |
| } |
| |
| /* |
| * Check the following properties of the red-black tree: |
| * (1) - If a node is red, both of it's children are black |
| * (2) - Each path from a given node to a leaf (NULL) node passes thru the |
| * same number of black nodes |
| * |
| * If there is a problem, append a description (using append_val() ) to *msg. |
| */ |
| static void check_redblack_tree(BtRbTree * tree, char ** msg) |
| { |
| BtRbNode *pNode; |
| |
| /* 0 -> came from parent |
| * 1 -> came from left |
| * 2 -> came from right */ |
| int prev_step = 0; |
| |
| pNode = tree->pHead; |
| while( pNode ){ |
| switch( prev_step ){ |
| case 0: |
| if( pNode->pLeft ){ |
| pNode = pNode->pLeft; |
| }else{ |
| prev_step = 1; |
| } |
| break; |
| case 1: |
| if( pNode->pRight ){ |
| pNode = pNode->pRight; |
| prev_step = 0; |
| }else{ |
| prev_step = 2; |
| } |
| break; |
| case 2: |
| /* Check red-black property (1) */ |
| if( !pNode->isBlack && |
| ( (pNode->pLeft && !pNode->pLeft->isBlack) || |
| (pNode->pRight && !pNode->pRight->isBlack) ) |
| ){ |
| char buf[128]; |
| sprintf(buf, "Red node with red child at %p\n", pNode); |
| *msg = append_val(*msg, buf); |
| *msg = append_node(*msg, tree->pHead, 0); |
| *msg = append_val(*msg, "\n"); |
| } |
| |
| /* Check red-black property (2) */ |
| { |
| int leftHeight = 0; |
| int rightHeight = 0; |
| if( pNode->pLeft ){ |
| leftHeight += pNode->pLeft->nBlackHeight; |
| leftHeight += (pNode->pLeft->isBlack?1:0); |
| } |
| if( pNode->pRight ){ |
| rightHeight += pNode->pRight->nBlackHeight; |
| rightHeight += (pNode->pRight->isBlack?1:0); |
| } |
| if( leftHeight != rightHeight ){ |
| char buf[128]; |
| sprintf(buf, "Different black-heights at %p\n", pNode); |
| *msg = append_val(*msg, buf); |
| *msg = append_node(*msg, tree->pHead, 0); |
| *msg = append_val(*msg, "\n"); |
| } |
| pNode->nBlackHeight = leftHeight; |
| } |
| |
| if( pNode->pParent ){ |
| if( pNode == pNode->pParent->pLeft ) prev_step = 1; |
| else prev_step = 2; |
| } |
| pNode = pNode->pParent; |
| break; |
| default: assert(0); |
| } |
| } |
| } |
| |
| /* |
| * Node pX has just been inserted into pTree (by code in sqliteRbtreeInsert()). |
| * It is possible that pX is a red node with a red parent, which is a violation |
| * of the red-black tree properties. This function performs rotations and |
| * color changes to rebalance the tree |
| */ |
| static void do_insert_balancing(BtRbTree *pTree, BtRbNode *pX) |
| { |
| /* In the first iteration of this loop, pX points to the red node just |
| * inserted in the tree. If the parent of pX exists (pX is not the root |
| * node) and is red, then the properties of the red-black tree are |
| * violated. |
| * |
| * At the start of any subsequent iterations, pX points to a red node |
| * with a red parent. In all other respects the tree is a legal red-black |
| * binary tree. */ |
| while( pX != pTree->pHead && !pX->pParent->isBlack ){ |
| BtRbNode *pUncle; |
| BtRbNode *pGrandparent; |
| |
| /* Grandparent of pX must exist and must be black. */ |
| pGrandparent = pX->pParent->pParent; |
| assert( pGrandparent ); |
| assert( pGrandparent->isBlack ); |
| |
| /* Uncle of pX may or may not exist. */ |
| if( pX->pParent == pGrandparent->pLeft ) |
| pUncle = pGrandparent->pRight; |
| else |
| pUncle = pGrandparent->pLeft; |
| |
| /* If the uncle of pX exists and is red, we do the following: |
| * | | |
| * G(b) G(r) |
| * / \ / \ |
| * U(r) P(r) U(b) P(b) |
| * \ \ |
| * X(r) X(r) |
| * |
| * BEFORE AFTER |
| * pX is then set to G. If the parent of G is red, then the while loop |
| * will run again. */ |
| if( pUncle && !pUncle->isBlack ){ |
| pGrandparent->isBlack = 0; |
| pUncle->isBlack = 1; |
| pX->pParent->isBlack = 1; |
| pX = pGrandparent; |
| }else{ |
| |
| if( pX->pParent == pGrandparent->pLeft ){ |
| if( pX == pX->pParent->pRight ){ |
| /* If pX is a right-child, do the following transform, essentially |
| * to change pX into a left-child: |
| * | | |
| * G(b) G(b) |
| * / \ / \ |
| * P(r) U(b) X(r) U(b) |
| * \ / |
| * X(r) P(r) <-- new X |
| * |
| * BEFORE AFTER |
| */ |
| pX = pX->pParent; |
| leftRotate(pTree, pX); |
| } |
| |
| /* Do the following transform, which balances the tree :) |
| * | | |
| * G(b) P(b) |
| * / \ / \ |
| * P(r) U(b) X(r) G(r) |
| * / \ |
| * X(r) U(b) |
| * |
| * BEFORE AFTER |
| */ |
| assert( pGrandparent == pX->pParent->pParent ); |
| pGrandparent->isBlack = 0; |
| pX->pParent->isBlack = 1; |
| rightRotate( pTree, pGrandparent ); |
| |
| }else{ |
| /* This code is symetric to the illustrated case above. */ |
| if( pX == pX->pParent->pLeft ){ |
| pX = pX->pParent; |
| rightRotate(pTree, pX); |
| } |
| assert( pGrandparent == pX->pParent->pParent ); |
| pGrandparent->isBlack = 0; |
| pX->pParent->isBlack = 1; |
| leftRotate( pTree, pGrandparent ); |
| } |
| } |
| } |
| pTree->pHead->isBlack = 1; |
| } |
| |
| /* |
| * A child of pParent, which in turn had child pX, has just been removed from |
| * pTree (the figure below depicts the operation, Z is being removed). pParent |
| * or pX, or both may be NULL. |
| * | | |
| * P P |
| * / \ / \ |
| * Z X |
| * / \ |
| * X nil |
| * |
| * This function is only called if Z was black. In this case the red-black tree |
| * properties have been violated, and pX has an "extra black". This function |
| * performs rotations and color-changes to re-balance the tree. |
| */ |
| static |
| void do_delete_balancing(BtRbTree *pTree, BtRbNode *pX, BtRbNode *pParent) |
| { |
| BtRbNode *pSib; |
| |
| /* TODO: Comment this code! */ |
| while( pX != pTree->pHead && (!pX || pX->isBlack) ){ |
| if( pX == pParent->pLeft ){ |
| pSib = pParent->pRight; |
| if( pSib && !(pSib->isBlack) ){ |
| pSib->isBlack = 1; |
| pParent->isBlack = 0; |
| leftRotate(pTree, pParent); |
| pSib = pParent->pRight; |
| } |
| if( !pSib ){ |
| pX = pParent; |
| }else if( |
| (!pSib->pLeft || pSib->pLeft->isBlack) && |
| (!pSib->pRight || pSib->pRight->isBlack) ) { |
| pSib->isBlack = 0; |
| pX = pParent; |
| }else{ |
| if( (!pSib->pRight || pSib->pRight->isBlack) ){ |
| if( pSib->pLeft ) pSib->pLeft->isBlack = 1; |
| pSib->isBlack = 0; |
| rightRotate( pTree, pSib ); |
| pSib = pParent->pRight; |
| } |
| pSib->isBlack = pParent->isBlack; |
| pParent->isBlack = 1; |
| if( pSib->pRight ) pSib->pRight->isBlack = 1; |
| leftRotate(pTree, pParent); |
| pX = pTree->pHead; |
| } |
| }else{ |
| pSib = pParent->pLeft; |
| if( pSib && !(pSib->isBlack) ){ |
| pSib->isBlack = 1; |
| pParent->isBlack = 0; |
| rightRotate(pTree, pParent); |
| pSib = pParent->pLeft; |
| } |
| if( !pSib ){ |
| pX = pParent; |
| }else if( |
| (!pSib->pLeft || pSib->pLeft->isBlack) && |
| (!pSib->pRight || pSib->pRight->isBlack) ){ |
| pSib->isBlack = 0; |
| pX = pParent; |
| }else{ |
| if( (!pSib->pLeft || pSib->pLeft->isBlack) ){ |
| if( pSib->pRight ) pSib->pRight->isBlack = 1; |
| pSib->isBlack = 0; |
| leftRotate( pTree, pSib ); |
| pSib = pParent->pLeft; |
| } |
| pSib->isBlack = pParent->isBlack; |
| pParent->isBlack = 1; |
| if( pSib->pLeft ) pSib->pLeft->isBlack = 1; |
| rightRotate(pTree, pParent); |
| pX = pTree->pHead; |
| } |
| } |
| pParent = pX->pParent; |
| } |
| if( pX ) pX->isBlack = 1; |
| } |
| |
| /* |
| * Create table n in tree pRbtree. Table n must not exist. |
| */ |
| static void btreeCreateTable(Rbtree* pRbtree, int n) |
| { |
| BtRbTree *pNewTbl = sqliteMalloc(sizeof(BtRbTree)); |
| sqliteHashInsert(&pRbtree->tblHash, 0, n, pNewTbl); |
| } |
| |
| /* |
| * Log a single "rollback-op" for the given Rbtree. See comments for struct |
| * BtRollbackOp. |
| */ |
| static void btreeLogRollbackOp(Rbtree* pRbtree, BtRollbackOp *pRollbackOp) |
| { |
| assert( pRbtree->eTransState == TRANS_INCHECKPOINT || |
| pRbtree->eTransState == TRANS_INTRANSACTION ); |
| if( pRbtree->eTransState == TRANS_INTRANSACTION ){ |
| pRollbackOp->pNext = pRbtree->pTransRollback; |
| pRbtree->pTransRollback = pRollbackOp; |
| } |
| if( pRbtree->eTransState == TRANS_INCHECKPOINT ){ |
| if( !pRbtree->pCheckRollback ){ |
| pRbtree->pCheckRollbackTail = pRollbackOp; |
| } |
| pRollbackOp->pNext = pRbtree->pCheckRollback; |
| pRbtree->pCheckRollback = pRollbackOp; |
| } |
| } |
| |
| int sqliteRbtreeOpen( |
| const char *zFilename, |
| int mode, |
| int nPg, |
| Btree **ppBtree |
| ){ |
| Rbtree **ppRbtree = (Rbtree**)ppBtree; |
| *ppRbtree = (Rbtree *)sqliteMalloc(sizeof(Rbtree)); |
| if( sqlite_malloc_failed ) goto open_no_mem; |
| sqliteHashInit(&(*ppRbtree)->tblHash, SQLITE_HASH_INT, 0); |
| |
| /* Create a binary tree for the SQLITE_MASTER table at location 2 */ |
| btreeCreateTable(*ppRbtree, 2); |
| if( sqlite_malloc_failed ) goto open_no_mem; |
| (*ppRbtree)->next_idx = 3; |
| (*ppRbtree)->pOps = &sqliteRbtreeOps; |
| /* Set file type to 4; this is so that "attach ':memory:' as ...." does not |
| ** think that the database in uninitialised and refuse to attach |
| */ |
| (*ppRbtree)->aMetaData[2] = 4; |
| |
| return SQLITE_OK; |
| |
| open_no_mem: |
| *ppBtree = 0; |
| return SQLITE_NOMEM; |
| } |
| |
| /* |
| * Create a new table in the supplied Rbtree. Set *n to the new table number. |
| * Return SQLITE_OK if the operation is a success. |
| */ |
| static int memRbtreeCreateTable(Rbtree* tree, int* n) |
| { |
| assert( tree->eTransState != TRANS_NONE ); |
| |
| *n = tree->next_idx++; |
| btreeCreateTable(tree, *n); |
| if( sqlite_malloc_failed ) return SQLITE_NOMEM; |
| |
| /* Set up the rollback structure (if we are not doing this as part of a |
| * rollback) */ |
| if( tree->eTransState != TRANS_ROLLBACK ){ |
| BtRollbackOp *pRollbackOp = sqliteMalloc(sizeof(BtRollbackOp)); |
| if( pRollbackOp==0 ) return SQLITE_NOMEM; |
| pRollbackOp->eOp = ROLLBACK_DROP; |
| pRollbackOp->iTab = *n; |
| btreeLogRollbackOp(tree, pRollbackOp); |
| } |
| |
| return SQLITE_OK; |
| } |
| |
| /* |
| * Delete table n from the supplied Rbtree. |
| */ |
| static int memRbtreeDropTable(Rbtree* tree, int n) |
| { |
| BtRbTree *pTree; |
| assert( tree->eTransState != TRANS_NONE ); |
| |
| memRbtreeClearTable(tree, n); |
| pTree = sqliteHashInsert(&tree->tblHash, 0, n, 0); |
| assert(pTree); |
| assert( pTree->pCursors==0 ); |
| sqliteFree(pTree); |
| |
| if( tree->eTransState != TRANS_ROLLBACK ){ |
| BtRollbackOp *pRollbackOp = sqliteMalloc(sizeof(BtRollbackOp)); |
| if( pRollbackOp==0 ) return SQLITE_NOMEM; |
| pRollbackOp->eOp = ROLLBACK_CREATE; |
| pRollbackOp->iTab = n; |
| btreeLogRollbackOp(tree, pRollbackOp); |
| } |
| |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreeKeyCompare(RbtCursor* pCur, const void *pKey, int nKey, |
| int nIgnore, int *pRes) |
| { |
| assert(pCur); |
| |
| if( !pCur->pNode ) { |
| *pRes = -1; |
| } else { |
| if( (pCur->pNode->nKey - nIgnore) < 0 ){ |
| *pRes = -1; |
| }else{ |
| *pRes = key_compare(pCur->pNode->pKey, pCur->pNode->nKey-nIgnore, |
| pKey, nKey); |
| } |
| } |
| return SQLITE_OK; |
| } |
| |
| /* |
| * Get a new cursor for table iTable of the supplied Rbtree. The wrFlag |
| * parameter indicates that the cursor is open for writing. |
| * |
| * Note that RbtCursor.eSkip and RbtCursor.pNode both initialize to 0. |
| */ |
| static int memRbtreeCursor( |
| Rbtree* tree, |
| int iTable, |
| int wrFlag, |
| RbtCursor **ppCur |
| ){ |
| RbtCursor *pCur; |
| assert(tree); |
| pCur = *ppCur = sqliteMalloc(sizeof(RbtCursor)); |
| if( sqlite_malloc_failed ) return SQLITE_NOMEM; |
| pCur->pTree = sqliteHashFind(&tree->tblHash, 0, iTable); |
| assert( pCur->pTree ); |
| pCur->pRbtree = tree; |
| pCur->iTree = iTable; |
| pCur->pOps = &sqliteRbtreeCursorOps; |
| pCur->wrFlag = wrFlag; |
| pCur->pShared = pCur->pTree->pCursors; |
| pCur->pTree->pCursors = pCur; |
| |
| assert( (*ppCur)->pTree ); |
| return SQLITE_OK; |
| } |
| |
| /* |
| * Insert a new record into the Rbtree. The key is given by (pKey,nKey) |
| * and the data is given by (pData,nData). The cursor is used only to |
| * define what database the record should be inserted into. The cursor |
| * is left pointing at the new record. |
| * |
| * If the key exists already in the tree, just replace the data. |
| */ |
| static int memRbtreeInsert( |
| RbtCursor* pCur, |
| const void *pKey, |
| int nKey, |
| const void *pDataInput, |
| int nData |
| ){ |
| void * pData; |
| int match; |
| |
| /* It is illegal to call sqliteRbtreeInsert() if we are |
| ** not in a transaction */ |
| assert( pCur->pRbtree->eTransState != TRANS_NONE ); |
| |
| /* Make sure some other cursor isn't trying to read this same table */ |
| if( checkReadLocks(pCur) ){ |
| return SQLITE_LOCKED; /* The table pCur points to has a read lock */ |
| } |
| |
| /* Take a copy of the input data now, in case we need it for the |
| * replace case */ |
| pData = sqliteMallocRaw(nData); |
| if( sqlite_malloc_failed ) return SQLITE_NOMEM; |
| memcpy(pData, pDataInput, nData); |
| |
| /* Move the cursor to a node near the key to be inserted. If the key already |
| * exists in the table, then (match == 0). In this case we can just replace |
| * the data associated with the entry, we don't need to manipulate the tree. |
| * |
| * If there is no exact match, then the cursor points at what would be either |
| * the predecessor (match == -1) or successor (match == 1) of the |
| * searched-for key, were it to be inserted. The new node becomes a child of |
| * this node. |
| * |
| * The new node is initially red. |
| */ |
| memRbtreeMoveto( pCur, pKey, nKey, &match); |
| if( match ){ |
| BtRbNode *pNode = sqliteMalloc(sizeof(BtRbNode)); |
| if( pNode==0 ) return SQLITE_NOMEM; |
| pNode->nKey = nKey; |
| pNode->pKey = sqliteMallocRaw(nKey); |
| if( sqlite_malloc_failed ) return SQLITE_NOMEM; |
| memcpy(pNode->pKey, pKey, nKey); |
| pNode->nData = nData; |
| pNode->pData = pData; |
| if( pCur->pNode ){ |
| switch( match ){ |
| case -1: |
| assert( !pCur->pNode->pRight ); |
| pNode->pParent = pCur->pNode; |
| pCur->pNode->pRight = pNode; |
| break; |
| case 1: |
| assert( !pCur->pNode->pLeft ); |
| pNode->pParent = pCur->pNode; |
| pCur->pNode->pLeft = pNode; |
| break; |
| default: |
| assert(0); |
| } |
| }else{ |
| pCur->pTree->pHead = pNode; |
| } |
| |
| /* Point the cursor at the node just inserted, as per SQLite requirements */ |
| pCur->pNode = pNode; |
| |
| /* A new node has just been inserted, so run the balancing code */ |
| do_insert_balancing(pCur->pTree, pNode); |
| |
| /* Set up a rollback-op in case we have to roll this operation back */ |
| if( pCur->pRbtree->eTransState != TRANS_ROLLBACK ){ |
| BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) ); |
| if( pOp==0 ) return SQLITE_NOMEM; |
| pOp->eOp = ROLLBACK_DELETE; |
| pOp->iTab = pCur->iTree; |
| pOp->nKey = pNode->nKey; |
| pOp->pKey = sqliteMallocRaw( pOp->nKey ); |
| if( sqlite_malloc_failed ) return SQLITE_NOMEM; |
| memcpy( pOp->pKey, pNode->pKey, pOp->nKey ); |
| btreeLogRollbackOp(pCur->pRbtree, pOp); |
| } |
| |
| }else{ |
| /* No need to insert a new node in the tree, as the key already exists. |
| * Just clobber the current nodes data. */ |
| |
| /* Set up a rollback-op in case we have to roll this operation back */ |
| if( pCur->pRbtree->eTransState != TRANS_ROLLBACK ){ |
| BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) ); |
| if( pOp==0 ) return SQLITE_NOMEM; |
| pOp->iTab = pCur->iTree; |
| pOp->nKey = pCur->pNode->nKey; |
| pOp->pKey = sqliteMallocRaw( pOp->nKey ); |
| if( sqlite_malloc_failed ) return SQLITE_NOMEM; |
| memcpy( pOp->pKey, pCur->pNode->pKey, pOp->nKey ); |
| pOp->nData = pCur->pNode->nData; |
| pOp->pData = pCur->pNode->pData; |
| pOp->eOp = ROLLBACK_INSERT; |
| btreeLogRollbackOp(pCur->pRbtree, pOp); |
| }else{ |
| sqliteFree( pCur->pNode->pData ); |
| } |
| |
| /* Actually clobber the nodes data */ |
| pCur->pNode->pData = pData; |
| pCur->pNode->nData = nData; |
| } |
| |
| return SQLITE_OK; |
| } |
| |
| /* Move the cursor so that it points to an entry near pKey. |
| ** Return a success code. |
| ** |
| ** *pRes<0 The cursor is left pointing at an entry that |
| ** is smaller than pKey or if the table is empty |
| ** and the cursor is therefore left point to nothing. |
| ** |
| ** *pRes==0 The cursor is left pointing at an entry that |
| ** exactly matches pKey. |
| ** |
| ** *pRes>0 The cursor is left pointing at an entry that |
| ** is larger than pKey. |
| */ |
| static int memRbtreeMoveto( |
| RbtCursor* pCur, |
| const void *pKey, |
| int nKey, |
| int *pRes |
| ){ |
| BtRbNode *pTmp = 0; |
| |
| pCur->pNode = pCur->pTree->pHead; |
| *pRes = -1; |
| while( pCur->pNode && *pRes ) { |
| *pRes = key_compare(pCur->pNode->pKey, pCur->pNode->nKey, pKey, nKey); |
| pTmp = pCur->pNode; |
| switch( *pRes ){ |
| case 1: /* cursor > key */ |
| pCur->pNode = pCur->pNode->pLeft; |
| break; |
| case -1: /* cursor < key */ |
| pCur->pNode = pCur->pNode->pRight; |
| break; |
| } |
| } |
| |
| /* If (pCur->pNode == NULL), then we have failed to find a match. Set |
| * pCur->pNode to pTmp, which is either NULL (if the tree is empty) or the |
| * last node traversed in the search. In either case the relation ship |
| * between pTmp and the searched for key is already stored in *pRes. pTmp is |
| * either the successor or predecessor of the key we tried to move to. */ |
| if( !pCur->pNode ) pCur->pNode = pTmp; |
| pCur->eSkip = SKIP_NONE; |
| |
| return SQLITE_OK; |
| } |
| |
| |
| /* |
| ** Delete the entry that the cursor is pointing to. |
| ** |
| ** The cursor is left pointing at either the next or the previous |
| ** entry. If the cursor is left pointing to the next entry, then |
| ** the pCur->eSkip flag is set to SKIP_NEXT which forces the next call to |
| ** sqliteRbtreeNext() to be a no-op. That way, you can always call |
| ** sqliteRbtreeNext() after a delete and the cursor will be left |
| ** pointing to the first entry after the deleted entry. Similarly, |
| ** pCur->eSkip is set to SKIP_PREV is the cursor is left pointing to |
| ** the entry prior to the deleted entry so that a subsequent call to |
| ** sqliteRbtreePrevious() will always leave the cursor pointing at the |
| ** entry immediately before the one that was deleted. |
| */ |
| static int memRbtreeDelete(RbtCursor* pCur) |
| { |
| BtRbNode *pZ; /* The one being deleted */ |
| BtRbNode *pChild; /* The child of the spliced out node */ |
| |
| /* It is illegal to call sqliteRbtreeDelete() if we are |
| ** not in a transaction */ |
| assert( pCur->pRbtree->eTransState != TRANS_NONE ); |
| |
| /* Make sure some other cursor isn't trying to read this same table */ |
| if( checkReadLocks(pCur) ){ |
| return SQLITE_LOCKED; /* The table pCur points to has a read lock */ |
| } |
| |
| pZ = pCur->pNode; |
| if( !pZ ){ |
| return SQLITE_OK; |
| } |
| |
| /* If we are not currently doing a rollback, set up a rollback op for this |
| * deletion */ |
| if( pCur->pRbtree->eTransState != TRANS_ROLLBACK ){ |
| BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) ); |
| if( pOp==0 ) return SQLITE_NOMEM; |
| pOp->iTab = pCur->iTree; |
| pOp->nKey = pZ->nKey; |
| pOp->pKey = pZ->pKey; |
| pOp->nData = pZ->nData; |
| pOp->pData = pZ->pData; |
| pOp->eOp = ROLLBACK_INSERT; |
| btreeLogRollbackOp(pCur->pRbtree, pOp); |
| } |
| |
| /* First do a standard binary-tree delete (node pZ is to be deleted). How |
| * to do this depends on how many children pZ has: |
| * |
| * If pZ has no children or one child, then splice out pZ. If pZ has two |
| * children, splice out the successor of pZ and replace the key and data of |
| * pZ with the key and data of the spliced out successor. */ |
| if( pZ->pLeft && pZ->pRight ){ |
| BtRbNode *pTmp; |
| int dummy; |
| pCur->eSkip = SKIP_NONE; |
| memRbtreeNext(pCur, &dummy); |
| assert( dummy == 0 ); |
| if( pCur->pRbtree->eTransState == TRANS_ROLLBACK ){ |
| sqliteFree(pZ->pKey); |
| sqliteFree(pZ->pData); |
| } |
| pZ->pData = pCur->pNode->pData; |
| pZ->nData = pCur->pNode->nData; |
| pZ->pKey = pCur->pNode->pKey; |
| pZ->nKey = pCur->pNode->nKey; |
| pTmp = pZ; |
| pZ = pCur->pNode; |
| pCur->pNode = pTmp; |
| pCur->eSkip = SKIP_NEXT; |
| }else{ |
| int res; |
| pCur->eSkip = SKIP_NONE; |
| memRbtreeNext(pCur, &res); |
| pCur->eSkip = SKIP_NEXT; |
| if( res ){ |
| memRbtreeLast(pCur, &res); |
| memRbtreePrevious(pCur, &res); |
| pCur->eSkip = SKIP_PREV; |
| } |
| if( pCur->pRbtree->eTransState == TRANS_ROLLBACK ){ |
| sqliteFree(pZ->pKey); |
| sqliteFree(pZ->pData); |
| } |
| } |
| |
| /* pZ now points at the node to be spliced out. This block does the |
| * splicing. */ |
| { |
| BtRbNode **ppParentSlot = 0; |
| assert( !pZ->pLeft || !pZ->pRight ); /* pZ has at most one child */ |
| pChild = ((pZ->pLeft)?pZ->pLeft:pZ->pRight); |
| if( pZ->pParent ){ |
| assert( pZ == pZ->pParent->pLeft || pZ == pZ->pParent->pRight ); |
| ppParentSlot = ((pZ == pZ->pParent->pLeft) |
| ?&pZ->pParent->pLeft:&pZ->pParent->pRight); |
| *ppParentSlot = pChild; |
| }else{ |
| pCur->pTree->pHead = pChild; |
| } |
| if( pChild ) pChild->pParent = pZ->pParent; |
| } |
| |
| /* pZ now points at the spliced out node. pChild is the only child of pZ, or |
| * NULL if pZ has no children. If pZ is black, and not the tree root, then we |
| * will have violated the "same number of black nodes in every path to a |
| * leaf" property of the red-black tree. The code in do_delete_balancing() |
| * repairs this. */ |
| if( pZ->isBlack ){ |
| do_delete_balancing(pCur->pTree, pChild, pZ->pParent); |
| } |
| |
| sqliteFree(pZ); |
| return SQLITE_OK; |
| } |
| |
| /* |
| * Empty table n of the Rbtree. |
| */ |
| static int memRbtreeClearTable(Rbtree* tree, int n) |
| { |
| BtRbTree *pTree; |
| BtRbNode *pNode; |
| |
| pTree = sqliteHashFind(&tree->tblHash, 0, n); |
| assert(pTree); |
| |
| pNode = pTree->pHead; |
| while( pNode ){ |
| if( pNode->pLeft ){ |
| pNode = pNode->pLeft; |
| } |
| else if( pNode->pRight ){ |
| pNode = pNode->pRight; |
| } |
| else { |
| BtRbNode *pTmp = pNode->pParent; |
| if( tree->eTransState == TRANS_ROLLBACK ){ |
| sqliteFree( pNode->pKey ); |
| sqliteFree( pNode->pData ); |
| }else{ |
| BtRollbackOp *pRollbackOp = sqliteMallocRaw(sizeof(BtRollbackOp)); |
| if( pRollbackOp==0 ) return SQLITE_NOMEM; |
| pRollbackOp->eOp = ROLLBACK_INSERT; |
| pRollbackOp->iTab = n; |
| pRollbackOp->nKey = pNode->nKey; |
| pRollbackOp->pKey = pNode->pKey; |
| pRollbackOp->nData = pNode->nData; |
| pRollbackOp->pData = pNode->pData; |
| btreeLogRollbackOp(tree, pRollbackOp); |
| } |
| sqliteFree( pNode ); |
| if( pTmp ){ |
| if( pTmp->pLeft == pNode ) pTmp->pLeft = 0; |
| else if( pTmp->pRight == pNode ) pTmp->pRight = 0; |
| } |
| pNode = pTmp; |
| } |
| } |
| |
| pTree->pHead = 0; |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreeFirst(RbtCursor* pCur, int *pRes) |
| { |
| if( pCur->pTree->pHead ){ |
| pCur->pNode = pCur->pTree->pHead; |
| while( pCur->pNode->pLeft ){ |
| pCur->pNode = pCur->pNode->pLeft; |
| } |
| } |
| if( pCur->pNode ){ |
| *pRes = 0; |
| }else{ |
| *pRes = 1; |
| } |
| pCur->eSkip = SKIP_NONE; |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreeLast(RbtCursor* pCur, int *pRes) |
| { |
| if( pCur->pTree->pHead ){ |
| pCur->pNode = pCur->pTree->pHead; |
| while( pCur->pNode->pRight ){ |
| pCur->pNode = pCur->pNode->pRight; |
| } |
| } |
| if( pCur->pNode ){ |
| *pRes = 0; |
| }else{ |
| *pRes = 1; |
| } |
| pCur->eSkip = SKIP_NONE; |
| return SQLITE_OK; |
| } |
| |
| /* |
| ** Advance the cursor to the next entry in the database. If |
| ** successful then set *pRes=0. If the cursor |
| ** was already pointing to the last entry in the database before |
| ** this routine was called, then set *pRes=1. |
| */ |
| static int memRbtreeNext(RbtCursor* pCur, int *pRes) |
| { |
| if( pCur->pNode && pCur->eSkip != SKIP_NEXT ){ |
| if( pCur->pNode->pRight ){ |
| pCur->pNode = pCur->pNode->pRight; |
| while( pCur->pNode->pLeft ) |
| pCur->pNode = pCur->pNode->pLeft; |
| }else{ |
| BtRbNode * pX = pCur->pNode; |
| pCur->pNode = pX->pParent; |
| while( pCur->pNode && (pCur->pNode->pRight == pX) ){ |
| pX = pCur->pNode; |
| pCur->pNode = pX->pParent; |
| } |
| } |
| } |
| pCur->eSkip = SKIP_NONE; |
| |
| if( !pCur->pNode ){ |
| *pRes = 1; |
| }else{ |
| *pRes = 0; |
| } |
| |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreePrevious(RbtCursor* pCur, int *pRes) |
| { |
| if( pCur->pNode && pCur->eSkip != SKIP_PREV ){ |
| if( pCur->pNode->pLeft ){ |
| pCur->pNode = pCur->pNode->pLeft; |
| while( pCur->pNode->pRight ) |
| pCur->pNode = pCur->pNode->pRight; |
| }else{ |
| BtRbNode * pX = pCur->pNode; |
| pCur->pNode = pX->pParent; |
| while( pCur->pNode && (pCur->pNode->pLeft == pX) ){ |
| pX = pCur->pNode; |
| pCur->pNode = pX->pParent; |
| } |
| } |
| } |
| pCur->eSkip = SKIP_NONE; |
| |
| if( !pCur->pNode ){ |
| *pRes = 1; |
| }else{ |
| *pRes = 0; |
| } |
| |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreeKeySize(RbtCursor* pCur, int *pSize) |
| { |
| if( pCur->pNode ){ |
| *pSize = pCur->pNode->nKey; |
| }else{ |
| *pSize = 0; |
| } |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreeKey(RbtCursor* pCur, int offset, int amt, char *zBuf) |
| { |
| if( !pCur->pNode ) return 0; |
| if( !pCur->pNode->pKey || ((amt + offset) <= pCur->pNode->nKey) ){ |
| memcpy(zBuf, ((char*)pCur->pNode->pKey)+offset, amt); |
| }else{ |
| memcpy(zBuf, ((char*)pCur->pNode->pKey)+offset, pCur->pNode->nKey-offset); |
| amt = pCur->pNode->nKey-offset; |
| } |
| return amt; |
| } |
| |
| static int memRbtreeDataSize(RbtCursor* pCur, int *pSize) |
| { |
| if( pCur->pNode ){ |
| *pSize = pCur->pNode->nData; |
| }else{ |
| *pSize = 0; |
| } |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreeData(RbtCursor *pCur, int offset, int amt, char *zBuf) |
| { |
| if( !pCur->pNode ) return 0; |
| if( (amt + offset) <= pCur->pNode->nData ){ |
| memcpy(zBuf, ((char*)pCur->pNode->pData)+offset, amt); |
| }else{ |
| memcpy(zBuf, ((char*)pCur->pNode->pData)+offset ,pCur->pNode->nData-offset); |
| amt = pCur->pNode->nData-offset; |
| } |
| return amt; |
| } |
| |
| static int memRbtreeCloseCursor(RbtCursor* pCur) |
| { |
| if( pCur->pTree->pCursors==pCur ){ |
| pCur->pTree->pCursors = pCur->pShared; |
| }else{ |
| RbtCursor *p = pCur->pTree->pCursors; |
| while( p && p->pShared!=pCur ){ p = p->pShared; } |
| assert( p!=0 ); |
| if( p ){ |
| p->pShared = pCur->pShared; |
| } |
| } |
| sqliteFree(pCur); |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreeGetMeta(Rbtree* tree, int* aMeta) |
| { |
| memcpy( aMeta, tree->aMetaData, sizeof(int) * SQLITE_N_BTREE_META ); |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreeUpdateMeta(Rbtree* tree, int* aMeta) |
| { |
| memcpy( tree->aMetaData, aMeta, sizeof(int) * SQLITE_N_BTREE_META ); |
| return SQLITE_OK; |
| } |
| |
| /* |
| * Check that each table in the Rbtree meets the requirements for a red-black |
| * binary tree. If an error is found, return an explanation of the problem in |
| * memory obtained from sqliteMalloc(). Parameters aRoot and nRoot are ignored. |
| */ |
| static char *memRbtreeIntegrityCheck(Rbtree* tree, int* aRoot, int nRoot) |
| { |
| char * msg = 0; |
| HashElem *p; |
| |
| for(p=sqliteHashFirst(&tree->tblHash); p; p=sqliteHashNext(p)){ |
| BtRbTree *pTree = sqliteHashData(p); |
| check_redblack_tree(pTree, &msg); |
| } |
| |
| return msg; |
| } |
| |
| static int memRbtreeSetCacheSize(Rbtree* tree, int sz) |
| { |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreeSetSafetyLevel(Rbtree *pBt, int level){ |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreeBeginTrans(Rbtree* tree) |
| { |
| if( tree->eTransState != TRANS_NONE ) |
| return SQLITE_ERROR; |
| |
| assert( tree->pTransRollback == 0 ); |
| tree->eTransState = TRANS_INTRANSACTION; |
| return SQLITE_OK; |
| } |
| |
| /* |
| ** Delete a linked list of BtRollbackOp structures. |
| */ |
| static void deleteRollbackList(BtRollbackOp *pOp){ |
| while( pOp ){ |
| BtRollbackOp *pTmp = pOp->pNext; |
| sqliteFree(pOp->pData); |
| sqliteFree(pOp->pKey); |
| sqliteFree(pOp); |
| pOp = pTmp; |
| } |
| } |
| |
| static int memRbtreeCommit(Rbtree* tree){ |
| /* Just delete pTransRollback and pCheckRollback */ |
| deleteRollbackList(tree->pCheckRollback); |
| deleteRollbackList(tree->pTransRollback); |
| tree->pTransRollback = 0; |
| tree->pCheckRollback = 0; |
| tree->pCheckRollbackTail = 0; |
| tree->eTransState = TRANS_NONE; |
| return SQLITE_OK; |
| } |
| |
| /* |
| * Close the supplied Rbtree. Delete everything associated with it. |
| */ |
| static int memRbtreeClose(Rbtree* tree) |
| { |
| HashElem *p; |
| memRbtreeCommit(tree); |
| while( (p=sqliteHashFirst(&tree->tblHash))!=0 ){ |
| tree->eTransState = TRANS_ROLLBACK; |
| memRbtreeDropTable(tree, sqliteHashKeysize(p)); |
| } |
| sqliteHashClear(&tree->tblHash); |
| sqliteFree(tree); |
| return SQLITE_OK; |
| } |
| |
| /* |
| * Execute and delete the supplied rollback-list on pRbtree. |
| */ |
| static void execute_rollback_list(Rbtree *pRbtree, BtRollbackOp *pList) |
| { |
| BtRollbackOp *pTmp; |
| RbtCursor cur; |
| int res; |
| |
| cur.pRbtree = pRbtree; |
| cur.wrFlag = 1; |
| while( pList ){ |
| switch( pList->eOp ){ |
| case ROLLBACK_INSERT: |
| cur.pTree = sqliteHashFind( &pRbtree->tblHash, 0, pList->iTab ); |
| assert(cur.pTree); |
| cur.iTree = pList->iTab; |
| cur.eSkip = SKIP_NONE; |
| memRbtreeInsert( &cur, pList->pKey, |
| pList->nKey, pList->pData, pList->nData ); |
| break; |
| case ROLLBACK_DELETE: |
| cur.pTree = sqliteHashFind( &pRbtree->tblHash, 0, pList->iTab ); |
| assert(cur.pTree); |
| cur.iTree = pList->iTab; |
| cur.eSkip = SKIP_NONE; |
| memRbtreeMoveto(&cur, pList->pKey, pList->nKey, &res); |
| assert(res == 0); |
| memRbtreeDelete( &cur ); |
| break; |
| case ROLLBACK_CREATE: |
| btreeCreateTable(pRbtree, pList->iTab); |
| break; |
| case ROLLBACK_DROP: |
| memRbtreeDropTable(pRbtree, pList->iTab); |
| break; |
| default: |
| assert(0); |
| } |
| sqliteFree(pList->pKey); |
| sqliteFree(pList->pData); |
| pTmp = pList->pNext; |
| sqliteFree(pList); |
| pList = pTmp; |
| } |
| } |
| |
| static int memRbtreeRollback(Rbtree* tree) |
| { |
| tree->eTransState = TRANS_ROLLBACK; |
| execute_rollback_list(tree, tree->pCheckRollback); |
| execute_rollback_list(tree, tree->pTransRollback); |
| tree->pTransRollback = 0; |
| tree->pCheckRollback = 0; |
| tree->pCheckRollbackTail = 0; |
| tree->eTransState = TRANS_NONE; |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreeBeginCkpt(Rbtree* tree) |
| { |
| if( tree->eTransState != TRANS_INTRANSACTION ) |
| return SQLITE_ERROR; |
| |
| assert( tree->pCheckRollback == 0 ); |
| assert( tree->pCheckRollbackTail == 0 ); |
| tree->eTransState = TRANS_INCHECKPOINT; |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreeCommitCkpt(Rbtree* tree) |
| { |
| if( tree->eTransState == TRANS_INCHECKPOINT ){ |
| if( tree->pCheckRollback ){ |
| tree->pCheckRollbackTail->pNext = tree->pTransRollback; |
| tree->pTransRollback = tree->pCheckRollback; |
| tree->pCheckRollback = 0; |
| tree->pCheckRollbackTail = 0; |
| } |
| tree->eTransState = TRANS_INTRANSACTION; |
| } |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreeRollbackCkpt(Rbtree* tree) |
| { |
| if( tree->eTransState != TRANS_INCHECKPOINT ) return SQLITE_OK; |
| tree->eTransState = TRANS_ROLLBACK; |
| execute_rollback_list(tree, tree->pCheckRollback); |
| tree->pCheckRollback = 0; |
| tree->pCheckRollbackTail = 0; |
| tree->eTransState = TRANS_INTRANSACTION; |
| return SQLITE_OK; |
| } |
| |
| #ifdef SQLITE_TEST |
| static int memRbtreePageDump(Rbtree* tree, int pgno, int rec) |
| { |
| assert(!"Cannot call sqliteRbtreePageDump"); |
| return SQLITE_OK; |
| } |
| |
| static int memRbtreeCursorDump(RbtCursor* pCur, int* aRes) |
| { |
| assert(!"Cannot call sqliteRbtreeCursorDump"); |
| return SQLITE_OK; |
| } |
| #endif |
| |
| static struct Pager *memRbtreePager(Rbtree* tree) |
| { |
| return 0; |
| } |
| |
| /* |
| ** Return the full pathname of the underlying database file. |
| */ |
| static const char *memRbtreeGetFilename(Rbtree *pBt){ |
| return 0; /* A NULL return indicates there is no underlying file */ |
| } |
| |
| /* |
| ** The copy file function is not implemented for the in-memory database |
| */ |
| static int memRbtreeCopyFile(Rbtree *pBt, Rbtree *pBt2){ |
| return SQLITE_INTERNAL; /* Not implemented */ |
| } |
| |
| static BtOps sqliteRbtreeOps = { |
| (int(*)(Btree*)) memRbtreeClose, |
| (int(*)(Btree*,int)) memRbtreeSetCacheSize, |
| (int(*)(Btree*,int)) memRbtreeSetSafetyLevel, |
| (int(*)(Btree*)) memRbtreeBeginTrans, |
| (int(*)(Btree*)) memRbtreeCommit, |
| (int(*)(Btree*)) memRbtreeRollback, |
| (int(*)(Btree*)) memRbtreeBeginCkpt, |
| (int(*)(Btree*)) memRbtreeCommitCkpt, |
| (int(*)(Btree*)) memRbtreeRollbackCkpt, |
| (int(*)(Btree*,int*)) memRbtreeCreateTable, |
| (int(*)(Btree*,int*)) memRbtreeCreateTable, |
| (int(*)(Btree*,int)) memRbtreeDropTable, |
| (int(*)(Btree*,int)) memRbtreeClearTable, |
| (int(*)(Btree*,int,int,BtCursor**)) memRbtreeCursor, |
| (int(*)(Btree*,int*)) memRbtreeGetMeta, |
| (int(*)(Btree*,int*)) memRbtreeUpdateMeta, |
| (char*(*)(Btree*,int*,int)) memRbtreeIntegrityCheck, |
| (const char*(*)(Btree*)) memRbtreeGetFilename, |
| (int(*)(Btree*,Btree*)) memRbtreeCopyFile, |
| (struct Pager*(*)(Btree*)) memRbtreePager, |
| #ifdef SQLITE_TEST |
| (int(*)(Btree*,int,int)) memRbtreePageDump, |
| #endif |
| }; |
| |
| static BtCursorOps sqliteRbtreeCursorOps = { |
| (int(*)(BtCursor*,const void*,int,int*)) memRbtreeMoveto, |
| (int(*)(BtCursor*)) memRbtreeDelete, |
| (int(*)(BtCursor*,const void*,int,const void*,int)) memRbtreeInsert, |
| (int(*)(BtCursor*,int*)) memRbtreeFirst, |
| (int(*)(BtCursor*,int*)) memRbtreeLast, |
| (int(*)(BtCursor*,int*)) memRbtreeNext, |
| (int(*)(BtCursor*,int*)) memRbtreePrevious, |
| (int(*)(BtCursor*,int*)) memRbtreeKeySize, |
| (int(*)(BtCursor*,int,int,char*)) memRbtreeKey, |
| (int(*)(BtCursor*,const void*,int,int,int*)) memRbtreeKeyCompare, |
| (int(*)(BtCursor*,int*)) memRbtreeDataSize, |
| (int(*)(BtCursor*,int,int,char*)) memRbtreeData, |
| (int(*)(BtCursor*)) memRbtreeCloseCursor, |
| #ifdef SQLITE_TEST |
| (int(*)(BtCursor*,int*)) memRbtreeCursorDump, |
| #endif |
| |
| }; |
| |
| #endif /* SQLITE_OMIT_INMEMORYDB */ |