Files
sqlite/src/btree_rb.c
T
paul 048bd5937b Added btree_rb.c (CVS 907)
FossilOrigin-Name: 93eb6c52aca8de15a88247ec986c36245527ec7b
2003-04-15 17:22:30 +00:00

1394 lines
37 KiB
C

/*
** 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 <assert.h>
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; 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.
*/
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
};