ed9d03ee76
git-svn-id: svn://10.65.10.50/trunk@11835 c028cbd2-c16b-5b4b-a496-9718f37d4682
1489 lines
42 KiB
C
Executable File
1489 lines
42 KiB
C
Executable File
/*
|
|
** 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.1.1 2004-03-11 22:22:22 alex 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(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 */
|