From: paul Date: Tue, 15 Apr 2003 17:22:30 +0000 (+0000) Subject: Added btree_rb.c (CVS 907) X-Git-Tag: version-3.6.10~5161 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=048bd5937ba52a0acd8e5059adbb97a08a93095a;p=thirdparty%2Fsqlite.git Added btree_rb.c (CVS 907) FossilOrigin-Name: 93eb6c52aca8de15a88247ec986c36245527ec7b --- diff --git a/manifest b/manifest index fbf2fde1ae..57fbd3bd03 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Do\snot\srecord\sthe\sinserted\srowid\son\swhen\sdoing\san\sINSERT\swithin\sa\strigger.\nTicket\s#290.\s(CVS\s906) -D 2003-04-15T14:01:43 +C Added\s\sbtree_rb.c\s(CVS\s907) +D 2003-04-15T17:22:30 F Makefile.in df3a4db41a7450468b5fe934d9dd8f723b631249 F Makefile.linux-gcc b86a99c493a5bfb402d1d9178dcdc4bd4b32f906 F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd @@ -23,6 +23,7 @@ F src/attach.c 8c98e2c0ca434b94deca1b8694c72bd0303a9a87 F src/auth.c f37bfc9451b8c1fa52f34adff474560018892729 F src/btree.c 9949031b6087e9d1b43b359b84c68a491086984f F src/btree.h 5cb871546bd6fa58396a6f033e2b29b388241e1b +F src/btree_rb.c 85727e7ba6277a55385bf86ff5da6d1093dd8978 F src/build.c daed1dacdb70e5d4def9df2e34a1cabeeb8467c9 F src/copy.c ddd204d5dddac09d71a07f4ceded4c9926d5512b F src/delete.c 58d698779a6b7f819718ecd45b310a9de8537088 @@ -161,7 +162,7 @@ F www/speed.tcl cb4c10a722614aea76d2c51f32ee43400d5951be F www/sqlite.tcl ae3dcfb077e53833b59d4fcc94d8a12c50a44098 F www/tclsqlite.tcl 1db15abeb446aad0caf0b95b8b9579720e4ea331 F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218 -P 7902e4778ec86e25ad949ae7a6d55b63ac0e85f3 -R dd4a1ed15ab6eb4c2551ae58dad32820 -U drh -Z 207b6a42d8bab1444378577acd311c3c +P 96a717661a3b7108fe0cacb588d81fd8e91eb640 +R cbe58bcf88d6fb97e9a813f970d2df5a +U paul +Z df689c3d6a38a6ae7133718deb76cd51 diff --git a/manifest.uuid b/manifest.uuid index 32850c1cc0..e136ca02f0 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -96a717661a3b7108fe0cacb588d81fd8e91eb640 \ No newline at end of file +93eb6c52aca8de15a88247ec986c36245527ec7b \ No newline at end of file diff --git a/src/btree_rb.c b/src/btree_rb.c new file mode 100644 index 0000000000..f46b92d372 --- /dev/null +++ b/src/btree_rb.c @@ -0,0 +1,1393 @@ +/* +** 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.1 2003/04/15 17:22:30 paul 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. +*/ + +#define SQLITE_NO_BTREE_DEFS + +#include "btree.h" +#include "sqliteInt.h" +#include + +typedef struct BtRbTree BtRbTree; +typedef struct BtRbNode BtRbNode; +typedef struct BtRollbackOp BtRollbackOp; + +/* Forward declarations */ +static BtOps sqliteBtreeOps; +static BtCursorOps sqliteBtreeCursorOps; + +/* + * 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 Btree { + 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 Btree is to be deleted when closed */ + u8 eTransState; /* State of this Btree wrt transactions */ + + BtRollbackOp *pTransRollback; + BtRollbackOp *pCheckRollback; + BtRollbackOp *pCheckRollbackTail; +}; + +/* +** Legal values for Btree.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 BtCursor { + BtCursorOps *pOps; /* Function table */ + Btree *pBtree; + BtRbTree *pTree; + int iTree; /* Index of pTree in pBtree */ + BtRbNode *pNode; + u8 eSkip; /* Determines if next step operation is a no-op */ +}; + +/* +** Legal values for BtCursor.eSkip. +*/ +#define SKIP_NONE 0 /* Always step the cursor */ +#define SKIP_NEXT 1 /* The next sqliteBtreeNext() is a no-op */ +#define SKIP_PREV 2 /* The next sqliteBtreePrevious() is a no-op */ +#define SKIP_INVALID 3 /* Calls to Next() and Previous() are invalid */ + +struct BtRbTree { + 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 sqliteBtreeMoveto(BtCursor* pCur, const void *pKey, int nKey, int *pRes); +static int sqliteBtreeClearTable(Btree* tree, int n); +static int sqliteBtreeNext(BtCursor* pCur, int *pRes); +static int sqliteBtreeLast(BtCursor* pCur, int *pRes); +static int sqliteBtreePrevious(BtCursor* pCur, int *pRes); + +/* + * 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) +{ + if( !orig ){ + return sqliteStrDup( val ); + } else{ + char * ret = 0; + sqliteSetString(&ret, orig, val, 0); + sqliteFree( orig ); + return ret; + } + assert(0); +} + +/* + * 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; iisBlack ){ + 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. + */ +static void print_node(BtRbNode *pNode) +{ + char * str = append_node(0, pNode, 0); + printf(str); +} + +/* + * 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 sqliteBtreeInsert()). + * 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 pBtree. Table n must not exist. + */ +static void btreeCreateTable(Btree* pBtree, int n) +{ + BtRbTree *pNewTbl = sqliteMalloc(sizeof(BtRbTree)); + sqliteHashInsert(&pBtree->tblHash, 0, n, pNewTbl); +} + +/* + * Log a single "rollback-op" for the given Btree. See comments for struct + * BtRollbackOp. + */ +static void btreeLogRollbackOp(Btree* pBtree, BtRollbackOp *pRollbackOp) +{ + assert( pBtree->eTransState == TRANS_INCHECKPOINT || + pBtree->eTransState == TRANS_INTRANSACTION ); + if( pBtree->eTransState == TRANS_INTRANSACTION ){ + pRollbackOp->pNext = pBtree->pTransRollback; + pBtree->pTransRollback = pRollbackOp; + } + if( pBtree->eTransState == TRANS_INCHECKPOINT ){ + if( !pBtree->pCheckRollback ){ + pBtree->pCheckRollbackTail = pRollbackOp; + } + pRollbackOp->pNext = pBtree->pCheckRollback; + pBtree->pCheckRollback = pRollbackOp; + } +} + +int sqliteRBtreeOpen(const char *zFilename, int mode, int nPg, Btree **ppBtree) +{ + int tnum; + *ppBtree = (Btree *)sqliteMalloc(sizeof(Btree)); + sqliteHashInit(&(*ppBtree)->tblHash, SQLITE_HASH_INT, 0); + + /* Create binary trees for tables 0, 1 and 2. SQLite assumes these + * tables always exist. At least I think so? */ + btreeCreateTable(*ppBtree, 0); + btreeCreateTable(*ppBtree, 1); + btreeCreateTable(*ppBtree, 2); + (*ppBtree)->next_idx = 3; + (*ppBtree)->pOps = &sqliteBtreeOps; + /* Set file type to 4; this is so that "attach ':memory:' as ...." does not + ** think that the database in uninitialised and refuse to attach + */ + (*ppBtree)->aMetaData[2] = 4; + + return SQLITE_OK; +} + +/* + * Create a new table in the supplied Btree. Set *n to the new table number. + * Return SQLITE_OK if the operation is a success. + */ +static int sqliteBtreeCreateTable(Btree* tree, int* n) +{ + BtRbTree *pNewTbl; + assert( tree->eTransState != TRANS_NONE ); + + *n = tree->next_idx++; + btreeCreateTable(tree, *n); + + /* 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)); + pRollbackOp->eOp = ROLLBACK_DROP; + pRollbackOp->iTab = *n; + btreeLogRollbackOp(tree, pRollbackOp); + } + + return SQLITE_OK; +} + +/* + * This is currently an alias for sqliteBtreeCreateTable(). There is a note in + * btree.c suggesting that one day indices and tables may be optimized + * differently. + */ +static int sqliteBtreeCreateIndex(Btree* tree, int* n) +{ + return sqliteBtreeCreateTable(tree, n); +} + +/* + * Delete table n from the supplied Btree. + */ +static int sqliteBtreeDropTable(Btree* tree, int n) +{ + BtRbTree *pTree; + assert( tree->eTransState != TRANS_NONE ); + + sqliteBtreeClearTable(tree, n); + pTree = sqliteHashFind(&tree->tblHash, 0, n); + assert(pTree); + sqliteFree(pTree); + sqliteHashInsert(&tree->tblHash, 0, n, 0); + + if( tree->eTransState != TRANS_ROLLBACK ){ + BtRollbackOp *pRollbackOp = sqliteMalloc(sizeof(BtRollbackOp)); + pRollbackOp->eOp = ROLLBACK_CREATE; + pRollbackOp->iTab = n; + btreeLogRollbackOp(tree, pRollbackOp); + } + + return SQLITE_OK; +} + +static int sqliteBtreeKeyCompare(BtCursor* 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 Btree. The wrFlag + * parameter is ignored, all cursors are capable of write-operations. + * + * Note that BtCursor.eSkip and BtCursor.pNode both initialize to 0. + */ +static int sqliteBtreeCursor(Btree* tree, int iTable, int wrFlag, BtCursor **ppCur) +{ + assert(tree); + *ppCur = sqliteMalloc(sizeof(BtCursor)); + (*ppCur)->pTree = sqliteHashFind(&tree->tblHash, 0, iTable); + (*ppCur)->pBtree = tree; + (*ppCur)->iTree = iTable; + (*ppCur)->pOps = &sqliteBtreeCursorOps; + + assert( (*ppCur)->pTree ); + return SQLITE_OK; +} + +/* + * Insert a new record into the Btree. 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 sqliteBtreeInsert(BtCursor* pCur, const void *pKey, int nKey, + const void *pDataInput, int nData) +{ + BtRbNode *pNode; /* The new node that is begin inserted */ + void * pData; + int match; + + /* It is illegal to call sqliteBtreeInsert() if we are not in a transaction */ + assert( pCur->pBtree->eTransState != TRANS_NONE ); + + /* Take a copy of the input data now, in case we need it for the + * replace case */ + pData = sqliteMalloc(nData); + 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. + */ + sqliteBtreeMoveto( pCur, pKey, nKey, &match); + if( match ){ + BtRbNode *pNode = sqliteMalloc(sizeof(BtRbNode)); + pNode->nKey = nKey; + pNode->pKey = sqliteMalloc(nKey); + 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->pBtree->eTransState != TRANS_ROLLBACK ){ + BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) ); + pOp->eOp = ROLLBACK_DELETE; + pOp->iTab = pCur->iTree; + pOp->nKey = pNode->nKey; + pOp->pKey = sqliteMalloc( pOp->nKey ); + memcpy( pOp->pKey, pNode->pKey, pOp->nKey ); + btreeLogRollbackOp(pCur->pBtree, 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->pBtree->eTransState != TRANS_ROLLBACK ){ + BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) ); + pOp->iTab = pCur->iTree; + pOp->nKey = pCur->pNode->nKey; + pOp->pKey = sqliteMalloc( pOp->nKey ); + memcpy( pOp->pKey, pCur->pNode->pKey, pOp->nKey ); + pOp->nData = pCur->pNode->nData; + pOp->pData = pCur->pNode->pData; + pOp->eOp = ROLLBACK_INSERT; + btreeLogRollbackOp(pCur->pBtree, 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 sqliteBtreeMoveto(BtCursor* 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; + + 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 +** sqliteBtreeNext() to be a no-op. That way, you can always call +** sqliteBtreeNext() 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 +** sqliteBtreePrevious() will always leave the cursor pointing at the +** entry immediately before the one that was deleted. +*/ +static int sqliteBtreeDelete(BtCursor* pCur) +{ + BtRbNode *pZ; /* The one being deleted */ + BtRbNode *pChild; /* The child of the spliced out node */ + + /* It is illegal to call sqliteBtreeDelete() if we are not in a transaction */ + assert( pCur->pBtree->eTransState != TRANS_NONE ); + + 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->pBtree->eTransState != TRANS_ROLLBACK ){ + BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) ); + 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->pBtree, 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; + sqliteBtreeNext(pCur, &dummy); + assert( dummy == 0 ); + if( pCur->pBtree->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; + sqliteBtreeNext(pCur, &res); + pCur->eSkip = SKIP_NEXT; + if( res ){ + sqliteBtreeLast(pCur, &res); + sqliteBtreePrevious(pCur, &res); + pCur->eSkip = SKIP_PREV; + } + if( pCur->pBtree->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 Btree. + */ +static int sqliteBtreeClearTable(Btree* 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 = sqliteMalloc(sizeof(BtRollbackOp)); + 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 sqliteBtreeFirst(BtCursor* 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 sqliteBtreeLast(BtCursor* 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; +} + +static int sqliteBtreeNext(BtCursor* 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 sqliteBtreePrevious(BtCursor* 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 sqliteBtreeKeySize(BtCursor* pCur, int *pSize) +{ + if( pCur->pNode ){ + *pSize = pCur->pNode->nKey; + }else{ + *pSize = 0; + } + return SQLITE_OK; +} + +static int sqliteBtreeKey(BtCursor* pCur, int offset, int amt, char *zBuf) +{ + if( !pCur->pNode ) return 0; + if( !pCur->pNode->pKey || ((amt + offset) <= pCur->pNode->nKey) ){ + memcpy(zBuf, pCur->pNode->pKey+offset, amt); + return amt; + }else{ + memcpy(zBuf, pCur->pNode->pKey+offset ,pCur->pNode->nKey-offset); + return pCur->pNode->nKey-offset; + } + assert(0); +} + +static int sqliteBtreeDataSize(BtCursor* pCur, int *pSize) +{ + if( pCur->pNode ){ + *pSize = pCur->pNode->nData; + }else{ + *pSize = 0; + } + return SQLITE_OK; +} + +static int sqliteBtreeData(BtCursor *pCur, int offset, int amt, char *zBuf) +{ + if( !pCur->pNode ) return 0; + if( (amt + offset) <= pCur->pNode->nData ){ + memcpy(zBuf, pCur->pNode->pData+offset, amt); + return amt; + }else{ + memcpy(zBuf, pCur->pNode->pData+offset ,pCur->pNode->nData-offset); + return pCur->pNode->nData-offset; + } + assert(0); +} + +static int sqliteBtreeCloseCursor(BtCursor* pCur) +{ + sqliteFree(pCur); + return SQLITE_OK; +} + +static int sqliteBtreeGetMeta(Btree* tree, int* aMeta) +{ + memcpy( aMeta, tree->aMetaData, sizeof(int) * SQLITE_N_BTREE_META ); + return SQLITE_OK; +} + +static int sqliteBtreeUpdateMeta(Btree* tree, int* aMeta) +{ + memcpy( tree->aMetaData, aMeta, sizeof(int) * SQLITE_N_BTREE_META ); + return SQLITE_OK; +} + +/* + * Check that each table in the Btree 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 *sqliteBtreeIntegrityCheck(Btree* 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; +} + +/* + * Close the supplied Btree. Delete everything associated with it. + */ +static int sqliteBtreeClose(Btree* tree) +{ + HashElem *p; + for(p=sqliteHashFirst(&tree->tblHash); p; p=sqliteHashNext(p)){ + BtRbTree *pTree = sqliteHashData(p); + tree->eTransState = TRANS_ROLLBACK; + sqliteBtreeClearTable(tree, sqliteHashKeysize(p)); + sqliteFree(sqliteHashData(p)); + } + sqliteFree(tree); + return SQLITE_OK; +} + +static int sqliteBtreeSetCacheSize(Btree* tree, int sz) +{ + return SQLITE_OK; +} + +static int sqliteBtreeSetSafetyLevel(Btree *pBt, int level){ + return SQLITE_OK; +} + +static int sqliteBtreeBeginTrans(Btree* tree) +{ + if( tree->eTransState != TRANS_NONE ) + return SQLITE_ERROR; + + assert( tree->pTransRollback == 0 ); + tree->eTransState = TRANS_INTRANSACTION; + return SQLITE_OK; +} + +static int sqliteBtreeCommit(Btree* tree) +{ + /* Just delete pTransRollback and pCheckRollback */ + BtRollbackOp *pOp, *pTmp; + pOp = tree->pCheckRollback; + while( pOp ){ + pTmp = pOp->pNext; + sqliteFree(pOp->pData); + sqliteFree(pOp->pKey); + sqliteFree(pOp); + pOp = pTmp; + } + pOp = tree->pTransRollback; + while( pOp ){ + pTmp = pOp->pNext; + sqliteFree(pOp->pData); + sqliteFree(pOp->pKey); + sqliteFree(pOp); + pOp = pTmp; + } + tree->pTransRollback = 0; + tree->pCheckRollback = 0; + tree->pCheckRollbackTail = 0; + tree->eTransState = TRANS_NONE; + return SQLITE_OK; +} + +/* + * Execute and delete the supplied rollback-list on pBtree. + */ +static void execute_rollback_list(Btree *pBtree, BtRollbackOp *pList) +{ + BtRollbackOp *pTmp; + BtCursor cur; + cur.pBtree = pBtree; + + int res; + while( pList ){ + switch( pList->eOp ){ + case ROLLBACK_INSERT: + cur.pTree = sqliteHashFind( &pBtree->tblHash, 0, pList->iTab ); + assert(cur.pTree); + cur.iTree = pList->iTab; + cur.eSkip = SKIP_NONE; + sqliteBtreeInsert( &cur, pList->pKey, + pList->nKey, pList->pData, pList->nData ); + break; + case ROLLBACK_DELETE: + cur.pTree = sqliteHashFind( &pBtree->tblHash, 0, pList->iTab ); + assert(cur.pTree); + cur.iTree = pList->iTab; + cur.eSkip = SKIP_NONE; + sqliteBtreeMoveto(&cur, pList->pKey, pList->nKey, &res); + assert(res == 0); + sqliteBtreeDelete( &cur ); + break; + case ROLLBACK_CREATE: + btreeCreateTable(pBtree, pList->iTab); + break; + case ROLLBACK_DROP: + sqliteBtreeDropTable(pBtree, pList->iTab); + break; + default: + assert(0); + } + sqliteFree(pList->pKey); + sqliteFree(pList->pData); + pTmp = pList->pNext; + sqliteFree(pList); + pList = pTmp; + } +} + +static int sqliteBtreeRollback(Btree* 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 sqliteBtreeBeginCkpt(Btree* 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 sqliteBtreeCommitCkpt(Btree* 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 sqliteBtreeRollbackCkpt(Btree* 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; + return SQLITE_OK; +} + +#ifdef SQLITE_TEST +static int sqliteBtreePageDump(Btree* tree, int pgno, int rec) +{ + assert(!"Cannot call sqliteBtreePageDump"); + return SQLITE_OK; +} + +static int sqliteBtreeCursorDump(BtCursor* pCur, int* aRes) +{ + assert(!"Cannot call sqliteBtreeCursorDump"); + return SQLITE_OK; +} + +static struct Pager *sqliteBtreePager(Btree* tree) +{ + assert(!"Cannot call sqliteBtreePager"); + return SQLITE_OK; +} +#endif + +/* +** Return the full pathname of the underlying database file. +*/ +static const char *sqliteBtreeGetFilename(Btree *pBt){ + return ":memory:"; +} + +/* +** Change the name of the underlying database file. +*/ +static int sqliteBtreeChangeFilename(Btree *pBt, const char *zNew){ + return SQLITE_OK; +} + +static BtOps sqliteBtreeOps = { + sqliteBtreeClose, + sqliteBtreeSetCacheSize, + sqliteBtreeSetSafetyLevel, + sqliteBtreeBeginTrans, + sqliteBtreeCommit, + sqliteBtreeRollback, + sqliteBtreeBeginCkpt, + sqliteBtreeCommitCkpt, + sqliteBtreeRollbackCkpt, + sqliteBtreeCreateTable, + sqliteBtreeCreateIndex, + sqliteBtreeDropTable, + sqliteBtreeClearTable, + sqliteBtreeCursor, + sqliteBtreeGetMeta, + sqliteBtreeUpdateMeta, + sqliteBtreeIntegrityCheck, + sqliteBtreeGetFilename, + sqliteBtreeChangeFilename, + +#ifdef SQLITE_TEST + sqliteBtreePageDump, + sqliteBtreePager +#endif +}; + +static BtCursorOps sqliteBtreeCursorOps = { + sqliteBtreeMoveto, + sqliteBtreeDelete, + sqliteBtreeInsert, + sqliteBtreeFirst, + sqliteBtreeLast, + sqliteBtreeNext, + sqliteBtreePrevious, + sqliteBtreeKeySize, + sqliteBtreeKey, + sqliteBtreeKeyCompare, + sqliteBtreeDataSize, + sqliteBtreeData, + sqliteBtreeCloseCursor, +#ifdef SQLITE_TEST + sqliteBtreeCursorDump, +#endif + +};