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 */
 |