]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Added btree_rb.c (CVS 907)
authorpaul <paul@noemail.net>
Tue, 15 Apr 2003 17:22:30 +0000 (17:22 +0000)
committerpaul <paul@noemail.net>
Tue, 15 Apr 2003 17:22:30 +0000 (17:22 +0000)
FossilOrigin-Name: 93eb6c52aca8de15a88247ec986c36245527ec7b

manifest
manifest.uuid
src/btree_rb.c [new file with mode: 0644]

index fbf2fde1ae972b80b2eb66f4cb23e3ffbf41a80d..57fbd3bd03fb2d897e048f5d51877b20c13ca571 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Do\snot\srecord\sthe\sinserted\srowid\son\swhen\sdoing\san\sINSERT\swithin\sa\strigger.\nTicket\s#290.\s(CVS\s906)
-D 2003-04-15T14:01:43
+C Added\s\sbtree_rb.c\s(CVS\s907)
+D 2003-04-15T17:22:30
 F Makefile.in df3a4db41a7450468b5fe934d9dd8f723b631249
 F Makefile.linux-gcc b86a99c493a5bfb402d1d9178dcdc4bd4b32f906
 F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd
@@ -23,6 +23,7 @@ F src/attach.c 8c98e2c0ca434b94deca1b8694c72bd0303a9a87
 F src/auth.c f37bfc9451b8c1fa52f34adff474560018892729
 F src/btree.c 9949031b6087e9d1b43b359b84c68a491086984f
 F src/btree.h 5cb871546bd6fa58396a6f033e2b29b388241e1b
+F src/btree_rb.c 85727e7ba6277a55385bf86ff5da6d1093dd8978
 F src/build.c daed1dacdb70e5d4def9df2e34a1cabeeb8467c9
 F src/copy.c ddd204d5dddac09d71a07f4ceded4c9926d5512b
 F src/delete.c 58d698779a6b7f819718ecd45b310a9de8537088
@@ -161,7 +162,7 @@ F www/speed.tcl cb4c10a722614aea76d2c51f32ee43400d5951be
 F www/sqlite.tcl ae3dcfb077e53833b59d4fcc94d8a12c50a44098
 F www/tclsqlite.tcl 1db15abeb446aad0caf0b95b8b9579720e4ea331
 F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218
-P 7902e4778ec86e25ad949ae7a6d55b63ac0e85f3
-R dd4a1ed15ab6eb4c2551ae58dad32820
-U drh
-Z 207b6a42d8bab1444378577acd311c3c
+P 96a717661a3b7108fe0cacb588d81fd8e91eb640
+R cbe58bcf88d6fb97e9a813f970d2df5a
+U paul
+Z df689c3d6a38a6ae7133718deb76cd51
index 32850c1cc0bda12069894f246bc2a669a1db5b01..e136ca02f0b871b66383d5e0c9c2ececdf44cc46 100644 (file)
@@ -1 +1 @@
-96a717661a3b7108fe0cacb588d81fd8e91eb640
\ No newline at end of file
+93eb6c52aca8de15a88247ec986c36245527ec7b
\ No newline at end of file
diff --git a/src/btree_rb.c b/src/btree_rb.c
new file mode 100644 (file)
index 0000000..f46b92d
--- /dev/null
@@ -0,0 +1,1393 @@
+/*
+** 2003 Feb 4
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**    May you do good and not evil.
+**    May you find forgiveness for yourself and forgive others.
+**    May you share freely, never taking more than you give.
+**
+*************************************************************************
+** $Id: btree_rb.c,v 1.1 2003/04/15 17:22:30 paul Exp $
+**
+** This file implements an in-core database using Red-Black balanced
+** binary trees.
+**
+** It was contributed to SQLite by anonymous on 2003-Feb-04 23:24:49 UTC.
+*/
+
+#define SQLITE_NO_BTREE_DEFS
+
+#include "btree.h"
+#include "sqliteInt.h"
+#include <assert.h>
+
+typedef struct BtRbTree BtRbTree;
+typedef struct BtRbNode BtRbNode;
+typedef struct BtRollbackOp BtRollbackOp;
+
+/* Forward declarations */
+static BtOps sqliteBtreeOps;
+static BtCursorOps sqliteBtreeCursorOps;
+
+/*
+ * During each transaction (or checkpoint), a linked-list of
+ * "rollback-operations" is accumulated. If the transaction is rolled back,
+ * then the list of operations must be executed (to restore the database to
+ * it's state before the transaction started). If the transaction is to be
+ * committed, just delete the list.
+ *
+ * Each operation is represented as follows, depending on the value of eOp:
+ *
+ * ROLLBACK_INSERT  ->  Need to insert (pKey, pData) into table iTab.
+ * ROLLBACK_DELETE  ->  Need to delete the record (pKey) into table iTab.
+ * ROLLBACK_CREATE  ->  Need to create table iTab.
+ * ROLLBACK_DROP    ->  Need to drop table iTab.
+ */
+struct BtRollbackOp {
+  u8 eOp;
+  int iTab;
+  int nKey; 
+  void *pKey;
+  int nData;
+  void *pData;
+  BtRollbackOp *pNext;
+};
+
+/*
+** Legal values for BtRollbackOp.eOp:
+*/
+#define ROLLBACK_INSERT 1 /* Insert a record */
+#define ROLLBACK_DELETE 2 /* Delete a record */
+#define ROLLBACK_CREATE 3 /* Create a table */
+#define ROLLBACK_DROP   4 /* Drop a table */
+
+struct Btree {
+  BtOps *pOps;   /* Function table */
+  int aMetaData[SQLITE_N_BTREE_META];
+
+  int next_idx;   /* next available table index */
+  Hash tblHash;   /* All created tables, by index */
+  u8 isAnonymous; /* True if this Btree is to be deleted when closed */
+  u8 eTransState; /* State of this Btree wrt transactions */
+
+  BtRollbackOp *pTransRollback; 
+  BtRollbackOp *pCheckRollback;
+  BtRollbackOp *pCheckRollbackTail;
+};
+
+/*
+** Legal values for Btree.eTransState.
+*/
+#define TRANS_NONE           0  /* No transaction is in progress */
+#define TRANS_INTRANSACTION  1  /* A transaction is in progress */
+#define TRANS_INCHECKPOINT   2  /* A checkpoint is in progress  */
+#define TRANS_ROLLBACK       3  /* We are currently rolling back a checkpoint or
+                                * transaction. */
+
+struct BtCursor {
+  BtCursorOps *pOps;       /* Function table */
+  Btree    *pBtree;
+  BtRbTree *pTree;
+  int       iTree;          /* Index of pTree in pBtree */
+  BtRbNode *pNode;
+  u8 eSkip;                 /* Determines if next step operation is a no-op */
+};
+
+/*
+** Legal values for BtCursor.eSkip.
+*/
+#define SKIP_NONE     0   /* Always step the cursor */
+#define SKIP_NEXT     1   /* The next sqliteBtreeNext() is a no-op */
+#define SKIP_PREV     2   /* The next sqliteBtreePrevious() is a no-op */
+#define SKIP_INVALID  3   /* Calls to Next() and Previous() are invalid */
+
+struct BtRbTree {
+  BtRbNode *pHead;   /* Head of the tree, or NULL */
+};
+
+struct BtRbNode {
+  int nKey;
+  void *pKey;
+  int nData;
+  void *pData;
+  u8 isBlack;        /* true for a black node, 0 for a red node */
+  BtRbNode *pParent; /* Nodes parent node, NULL for the tree head */
+  BtRbNode *pLeft;   /* Nodes left child, or NULL */
+  BtRbNode *pRight;  /* Nodes right child, or NULL */
+
+  int nBlackHeight;  /* Only used during the red-black integrity check */
+};
+
+/* Forward declarations */
+static int sqliteBtreeMoveto(BtCursor* pCur, const void *pKey, int nKey, int *pRes);
+static int sqliteBtreeClearTable(Btree* tree, int n);
+static int sqliteBtreeNext(BtCursor* pCur, int *pRes);
+static int sqliteBtreeLast(BtCursor* pCur, int *pRes);
+static int sqliteBtreePrevious(BtCursor* pCur, int *pRes);
+
+/*
+ * The key-compare function for the red-black trees. Returns as follows:
+ *
+ * (key1 < key2)             -1
+ * (key1 == key2)             0 
+ * (key1 > key2)              1
+ *
+ * Keys are compared using memcmp(). If one key is an exact prefix of the
+ * other, then the shorter key is less than the longer key.
+ */
+static int key_compare(void const*pKey1, int nKey1, void const*pKey2, int nKey2)
+{
+  int mcmp = memcmp(pKey1, pKey2, (nKey1 <= nKey2)?nKey1:nKey2);
+  if( mcmp == 0){
+    if( nKey1 == nKey2 ) return 0;
+    return ((nKey1 < nKey2)?-1:1);
+  }
+  return ((mcmp>0)?1:-1);
+}
+
+/*
+ * Perform the LEFT-rotate transformation on node X of tree pTree. This
+ * transform is part of the red-black balancing code.
+ *
+ *        |                   |
+ *        X                   Y
+ *       / \                 / \
+ *      a   Y               X   c
+ *         / \             / \
+ *        b   c           a   b
+ *
+ *      BEFORE              AFTER
+ */
+static void leftRotate(BtRbTree *pTree, BtRbNode *pX)
+{
+  BtRbNode *pY;
+  BtRbNode *pb;
+  pY = pX->pRight;
+  pb = pY->pLeft;
+
+  pY->pParent = pX->pParent;
+  if( pX->pParent ){
+    if( pX->pParent->pLeft == pX ) pX->pParent->pLeft = pY;
+    else pX->pParent->pRight = pY;
+  }
+  pY->pLeft = pX;
+  pX->pParent = pY;
+  pX->pRight = pb;
+  if( pb ) pb->pParent = pX;
+  if( pTree->pHead == pX ) pTree->pHead = pY;
+}
+
+/*
+ * Perform the RIGHT-rotate transformation on node X of tree pTree. This
+ * transform is part of the red-black balancing code.
+ *
+ *        |                   |
+ *        X                   Y
+ *       / \                 / \
+ *      Y   c               a   X
+ *     / \                     / \
+ *    a   b                   b   c
+ *
+ *      BEFORE              AFTER
+ */
+static void rightRotate(BtRbTree *pTree, BtRbNode *pX)
+{
+  BtRbNode *pY;
+  BtRbNode *pb;
+  pY = pX->pLeft;
+  pb = pY->pRight;
+
+  pY->pParent = pX->pParent;
+  if( pX->pParent ){
+    if( pX->pParent->pLeft == pX ) pX->pParent->pLeft = pY;
+    else pX->pParent->pRight = pY;
+  }
+  pY->pRight = pX;
+  pX->pParent = pY;
+  pX->pLeft = pb;
+  if( pb ) pb->pParent = pX;
+  if( pTree->pHead == pX ) pTree->pHead = pY;
+}
+
+/*
+ * A string-manipulation helper function for check_redblack_tree(). If (orig ==
+ * NULL) a copy of val is returned. If (orig != NULL) then a copy of the *
+ * concatenation of orig and val is returned. The original orig is deleted
+ * (using sqliteFree()).
+ */
+static char *append_val(char * orig, char const * val)
+{
+  if( !orig ){
+    return sqliteStrDup( val );
+  } else{
+    char * ret = 0;
+    sqliteSetString(&ret, orig, val, 0);
+    sqliteFree( orig );
+    return ret;
+  }
+  assert(0);
+}
+
+/*
+ * Append a string representation of the entire node to orig and return it.
+ * This is used to produce debugging information if check_redblack_tree() finds
+ * a problem with a red-black binary tree.
+ */
+static char *append_node(char * orig, BtRbNode *pNode, int indent)
+{
+  char buf[128];
+  int i;
+
+  for( i=0; i<indent; i++ ){
+      orig = append_val(orig, " ");
+  }
+
+  sprintf(buf, "%p", pNode);
+  orig = append_val(orig, buf);
+
+  if( pNode ){
+    indent += 3;
+    if( pNode->isBlack ){
+      orig = append_val(orig, " B \n");
+    }else{
+      orig = append_val(orig, " R \n");
+    }
+    orig = append_node( orig, pNode->pLeft, indent );
+    orig = append_node( orig, pNode->pRight, indent );
+  }else{
+    orig = append_val(orig, "\n");
+  }
+  return orig;
+}
+
+/*
+ * Print a representation of a node to stdout. This function is only included
+ * so you can call it from within a debugger if things get really bad.
+ */
+static void print_node(BtRbNode *pNode)
+{
+    char * str = append_node(0, pNode, 0);
+    printf(str);
+}
+
+/* 
+ * Check the following properties of the red-black tree:
+ * (1) - If a node is red, both of it's children are black
+ * (2) - Each path from a given node to a leaf (NULL) node passes thru the
+ *       same number of black nodes 
+ *
+ * If there is a problem, append a description (using append_val() ) to *msg.
+ */
+static void check_redblack_tree(BtRbTree * tree, char ** msg)
+{
+  BtRbNode *pNode;
+
+  /* 0 -> came from parent 
+   * 1 -> came from left
+   * 2 -> came from right */
+  int prev_step = 0;
+
+  pNode = tree->pHead;
+  while( pNode ){
+    switch( prev_step ){
+      case 0:
+       if( pNode->pLeft ){
+         pNode = pNode->pLeft;
+       }else{ 
+         prev_step = 1;
+       }
+       break;
+      case 1:
+       if( pNode->pRight ){
+         pNode = pNode->pRight;
+         prev_step = 0;
+       }else{
+         prev_step = 2;
+       }
+       break;
+      case 2:
+       /* Check red-black property (1) */
+       if( !pNode->isBlack &&
+           ( (pNode->pLeft && !pNode->pLeft->isBlack) ||
+             (pNode->pRight && !pNode->pRight->isBlack) )
+         ){
+         char buf[128];
+         sprintf(buf, "Red node with red child at %p\n", pNode);
+         *msg = append_val(*msg, buf);
+         *msg = append_node(*msg, tree->pHead, 0);
+         *msg = append_val(*msg, "\n");
+       }
+
+       /* Check red-black property (2) */
+       { 
+         int leftHeight = 0;
+         int rightHeight = 0;
+         if( pNode->pLeft ){
+           leftHeight += pNode->pLeft->nBlackHeight;
+           leftHeight += (pNode->pLeft->isBlack?1:0);
+         }
+         if( pNode->pRight ){
+           rightHeight += pNode->pRight->nBlackHeight;
+           rightHeight += (pNode->pRight->isBlack?1:0);
+         }
+         if( leftHeight != rightHeight ){
+           char buf[128];
+           sprintf(buf, "Different black-heights at %p\n", pNode);
+           *msg = append_val(*msg, buf);
+           *msg = append_node(*msg, tree->pHead, 0);
+         *msg = append_val(*msg, "\n");
+         }
+         pNode->nBlackHeight = leftHeight;
+       }
+
+       if( pNode->pParent ){
+         if( pNode == pNode->pParent->pLeft ) prev_step = 1;
+         else prev_step = 2;
+       }
+       pNode = pNode->pParent;
+       break;
+      default: assert(0);
+    }
+  }
+} 
+
+/*
+ * Node pX has just been inserted into pTree (by code in sqliteBtreeInsert()).
+ * It is possible that pX is a red node with a red parent, which is a violation
+ * of the red-black tree properties. This function performs rotations and 
+ * color changes to rebalance the tree
+ */
+static void do_insert_balancing(BtRbTree *pTree, BtRbNode *pX)
+{
+  /* In the first iteration of this loop, pX points to the red node just
+   * inserted in the tree. If the parent of pX exists (pX is not the root
+   * node) and is red, then the properties of the red-black tree are
+   * violated.
+   *
+   * At the start of any subsequent iterations, pX points to a red node
+   * with a red parent. In all other respects the tree is a legal red-black
+   * binary tree. */
+  while( pX != pTree->pHead && !pX->pParent->isBlack ){
+    BtRbNode *pUncle;
+    BtRbNode *pGrandparent;
+
+    /* Grandparent of pX must exist and must be black. */
+    pGrandparent = pX->pParent->pParent;
+    assert( pGrandparent );
+    assert( pGrandparent->isBlack );
+
+    /* Uncle of pX may or may not exist. */
+    if( pX->pParent == pGrandparent->pLeft ) 
+      pUncle = pGrandparent->pRight;
+    else 
+      pUncle = pGrandparent->pLeft;
+
+    /* If the uncle of pX exists and is red, we do the following:
+     *       |                 |
+     *      G(b)              G(r)
+     *      /  \              /  \        
+     *   U(r)   P(r)       U(b)  P(b)
+     *            \                \
+     *           X(r)              X(r)
+     *
+     *     BEFORE             AFTER
+     * pX is then set to G. If the parent of G is red, then the while loop
+     * will run again.  */
+    if( pUncle && !pUncle->isBlack ){
+      pGrandparent->isBlack = 0;
+      pUncle->isBlack = 1;
+      pX->pParent->isBlack = 1;
+      pX = pGrandparent;
+    }else{
+
+      if( pX->pParent == pGrandparent->pLeft ){
+       if( pX == pX->pParent->pRight ){
+         /* If pX is a right-child, do the following transform, essentially
+          * to change pX into a left-child: 
+          *       |                  | 
+          *      G(b)               G(b)
+          *      /  \               /  \        
+          *   P(r)   U(b)        X(r)  U(b)
+          *      \                /
+          *     X(r)            P(r) <-- new X
+          *
+          *     BEFORE             AFTER
+          */
+         pX = pX->pParent;
+         leftRotate(pTree, pX);
+       }
+
+       /* Do the following transform, which balances the tree :) 
+        *       |                  | 
+        *      G(b)               P(b)
+        *      /  \               /  \        
+        *   P(r)   U(b)        X(r)  G(r)
+        *    /                         \
+        *  X(r)                        U(b)
+        *
+        *     BEFORE             AFTER
+        */
+       assert( pGrandparent == pX->pParent->pParent );
+       pGrandparent->isBlack = 0;
+       pX->pParent->isBlack = 1;
+       rightRotate( pTree, pGrandparent );
+
+      }else{
+       /* This code is symetric to the illustrated case above. */
+       if( pX == pX->pParent->pLeft ){
+         pX = pX->pParent;
+         rightRotate(pTree, pX);
+       }
+       assert( pGrandparent == pX->pParent->pParent );
+       pGrandparent->isBlack = 0;
+       pX->pParent->isBlack = 1;
+       leftRotate( pTree, pGrandparent );
+      }
+    }
+  }
+  pTree->pHead->isBlack = 1;
+}
+
+/*
+ * A child of pParent, which in turn had child pX, has just been removed from 
+ * pTree (the figure below depicts the operation, Z is being removed). pParent
+ * or pX, or both may be NULL.  
+ *                |           |
+ *                P           P
+ *               / \         / \
+ *              Z           X
+ *             / \
+ *            X  nil
+ *
+ * This function is only called if Z was black. In this case the red-black tree
+ * properties have been violated, and pX has an "extra black". This function 
+ * performs rotations and color-changes to re-balance the tree.
+ */
+static void do_delete_balancing(BtRbTree *pTree, BtRbNode *pX, BtRbNode *pParent)
+{
+  BtRbNode *pSib; 
+
+  /* TODO: Comment this code! */
+  while( pX != pTree->pHead && (!pX || pX->isBlack) ){
+    if( pX == pParent->pLeft ){
+      pSib = pParent->pRight;
+      if( pSib && !(pSib->isBlack) ){
+       pSib->isBlack = 1;
+       pParent->isBlack = 0;
+       leftRotate(pTree, pParent);
+       pSib = pParent->pRight;
+      }
+      if( !pSib ){
+       pX = pParent;
+      }else if( 
+         (!pSib->pLeft  || pSib->pLeft->isBlack) &&
+         (!pSib->pRight || pSib->pRight->isBlack) ) {
+       pSib->isBlack = 0;
+       pX = pParent;
+      }else{
+       if( (!pSib->pRight || pSib->pRight->isBlack) ){
+         if( pSib->pLeft ) pSib->pLeft->isBlack = 1;
+         pSib->isBlack = 0;
+         rightRotate( pTree, pSib );
+         pSib = pParent->pRight;
+       }
+       pSib->isBlack = pParent->isBlack;
+       pParent->isBlack = 1;
+       if( pSib->pRight ) pSib->pRight->isBlack = 1;
+       leftRotate(pTree, pParent);
+       pX = pTree->pHead;
+      }
+    }else{
+      pSib = pParent->pLeft;
+      if( pSib && !(pSib->isBlack) ){
+       pSib->isBlack = 1;
+       pParent->isBlack = 0;
+       rightRotate(pTree, pParent);
+       pSib = pParent->pLeft;
+      }
+      if( !pSib ){
+       pX = pParent;
+      }else if( 
+          (!pSib->pLeft  || pSib->pLeft->isBlack) &&
+         (!pSib->pRight || pSib->pRight->isBlack) ){
+       pSib->isBlack = 0;
+       pX = pParent;
+      }else{
+       if( (!pSib->pLeft || pSib->pLeft->isBlack) ){
+         if( pSib->pRight ) pSib->pRight->isBlack = 1;
+         pSib->isBlack = 0;
+         leftRotate( pTree, pSib );
+         pSib = pParent->pLeft;
+       }
+       pSib->isBlack = pParent->isBlack;
+       pParent->isBlack = 1;
+       if( pSib->pLeft ) pSib->pLeft->isBlack = 1;
+       rightRotate(pTree, pParent);
+       pX = pTree->pHead;
+      }
+    }
+    pParent = pX->pParent;
+  }
+  if( pX ) pX->isBlack = 1;
+}
+
+/*
+ * Create table n in tree pBtree. Table n must not exist.
+ */
+static void btreeCreateTable(Btree* pBtree, int n)
+{
+  BtRbTree *pNewTbl = sqliteMalloc(sizeof(BtRbTree));
+  sqliteHashInsert(&pBtree->tblHash, 0, n, pNewTbl);
+}
+
+/*
+ * Log a single "rollback-op" for the given Btree. See comments for struct
+ * BtRollbackOp.
+ */
+static void btreeLogRollbackOp(Btree* pBtree, BtRollbackOp *pRollbackOp)
+{
+  assert( pBtree->eTransState == TRANS_INCHECKPOINT ||
+      pBtree->eTransState == TRANS_INTRANSACTION );
+  if( pBtree->eTransState == TRANS_INTRANSACTION ){
+    pRollbackOp->pNext = pBtree->pTransRollback;
+    pBtree->pTransRollback = pRollbackOp;
+  }
+  if( pBtree->eTransState == TRANS_INCHECKPOINT ){
+    if( !pBtree->pCheckRollback ){
+      pBtree->pCheckRollbackTail = pRollbackOp;
+    }
+    pRollbackOp->pNext = pBtree->pCheckRollback;
+    pBtree->pCheckRollback = pRollbackOp;
+  }
+}
+
+int sqliteRBtreeOpen(const char *zFilename, int mode, int nPg, Btree **ppBtree)
+{
+  int tnum;
+  *ppBtree = (Btree *)sqliteMalloc(sizeof(Btree));
+  sqliteHashInit(&(*ppBtree)->tblHash, SQLITE_HASH_INT, 0);
+
+  /* Create binary trees for tables 0, 1 and 2. SQLite assumes these
+   * tables always exist. At least I think so? */
+  btreeCreateTable(*ppBtree, 0);
+  btreeCreateTable(*ppBtree, 1);
+  btreeCreateTable(*ppBtree, 2);
+  (*ppBtree)->next_idx = 3;
+  (*ppBtree)->pOps = &sqliteBtreeOps;
+  /* Set file type to 4; this is so that "attach ':memory:' as ...."  does not
+  ** think that the database in uninitialised and refuse to attach
+  */
+  (*ppBtree)->aMetaData[2] = 4;
+  
+  return SQLITE_OK;
+}
+
+/*
+ * Create a new table in the supplied Btree. Set *n to the new table number.
+ * Return SQLITE_OK if the operation is a success.
+ */
+static int sqliteBtreeCreateTable(Btree* tree, int* n)
+{
+  BtRbTree *pNewTbl;
+  assert( tree->eTransState != TRANS_NONE );
+
+  *n = tree->next_idx++;
+  btreeCreateTable(tree, *n);
+
+  /* Set up the rollback structure (if we are not doing this as part of a
+   * rollback) */
+  if( tree->eTransState != TRANS_ROLLBACK ){
+    BtRollbackOp *pRollbackOp = sqliteMalloc(sizeof(BtRollbackOp));
+    pRollbackOp->eOp = ROLLBACK_DROP;
+    pRollbackOp->iTab = *n;
+    btreeLogRollbackOp(tree, pRollbackOp);
+  }
+
+  return SQLITE_OK;
+}
+
+/*
+ * This is currently an alias for sqliteBtreeCreateTable(). There is a note in
+ * btree.c suggesting that one day indices and tables may be optimized
+ * differently.
+ */
+static int sqliteBtreeCreateIndex(Btree* tree, int* n)
+{
+  return sqliteBtreeCreateTable(tree, n);
+}
+
+/*
+ * Delete table n from the supplied Btree. 
+ */
+static int sqliteBtreeDropTable(Btree* tree, int n)
+{
+  BtRbTree *pTree;
+  assert( tree->eTransState != TRANS_NONE );
+
+  sqliteBtreeClearTable(tree, n);
+  pTree = sqliteHashFind(&tree->tblHash, 0, n);
+  assert(pTree);
+  sqliteFree(pTree);
+  sqliteHashInsert(&tree->tblHash, 0, n, 0);
+
+  if( tree->eTransState != TRANS_ROLLBACK ){
+    BtRollbackOp *pRollbackOp = sqliteMalloc(sizeof(BtRollbackOp));
+    pRollbackOp->eOp = ROLLBACK_CREATE;
+    pRollbackOp->iTab = n;
+    btreeLogRollbackOp(tree, pRollbackOp);
+  }
+
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeKeyCompare(BtCursor* pCur, const void *pKey, int nKey,
+                                int nIgnore, int *pRes)
+{
+  assert(pCur);
+
+  if( !pCur->pNode ) {
+    *pRes = -1;
+  } else {
+    if( (pCur->pNode->nKey - nIgnore) < 0 ){
+      *pRes = -1;
+    }else{
+      *pRes = key_compare(pCur->pNode->pKey, pCur->pNode->nKey-nIgnore, 
+         pKey, nKey);
+    }
+  }
+  return SQLITE_OK;
+}
+
+/*
+ * Get a new cursor for table iTable of the supplied Btree. The wrFlag
+ * parameter is ignored, all cursors are capable of write-operations. 
+ *
+ * Note that BtCursor.eSkip and BtCursor.pNode both initialize to 0.
+ */
+static int sqliteBtreeCursor(Btree* tree, int iTable, int wrFlag, BtCursor **ppCur)
+{
+  assert(tree);
+  *ppCur = sqliteMalloc(sizeof(BtCursor));
+  (*ppCur)->pTree  = sqliteHashFind(&tree->tblHash, 0, iTable);
+  (*ppCur)->pBtree = tree;
+  (*ppCur)->iTree  = iTable;
+  (*ppCur)->pOps = &sqliteBtreeCursorOps;
+
+  assert( (*ppCur)->pTree );
+  return SQLITE_OK;
+}
+
+/*
+ * Insert a new record into the Btree.  The key is given by (pKey,nKey)
+ * and the data is given by (pData,nData).  The cursor is used only to
+ * define what database the record should be inserted into.  The cursor
+ * is left pointing at the new record.
+ *
+ * If the key exists already in the tree, just replace the data. 
+ */
+static int sqliteBtreeInsert(BtCursor* pCur, const void *pKey, int nKey,
+                            const void *pDataInput, int nData)
+{
+  BtRbNode *pNode; /* The new node that is begin inserted */
+  void * pData;
+  int match;
+
+  /* It is illegal to call sqliteBtreeInsert() if we are not in a transaction */
+  assert( pCur->pBtree->eTransState != TRANS_NONE );
+
+  /* Take a copy of the input data now, in case we need it for the 
+   * replace case */
+  pData = sqliteMalloc(nData);
+  memcpy(pData, pDataInput, nData);
+
+  /* Move the cursor to a node near the key to be inserted. If the key already
+   * exists in the table, then (match == 0). In this case we can just replace
+   * the data associated with the entry, we don't need to manipulate the tree.
+   * 
+   * If there is no exact match, then the cursor points at what would be either
+   * the predecessor (match == -1) or successor (match == 1) of the
+   * searched-for key, were it to be inserted. The new node becomes a child of
+   * this node.
+   * 
+   * The new node is initially red.
+   */
+  sqliteBtreeMoveto( pCur, pKey, nKey, &match);
+  if( match ){
+    BtRbNode *pNode = sqliteMalloc(sizeof(BtRbNode));
+    pNode->nKey = nKey;
+    pNode->pKey = sqliteMalloc(nKey);
+    memcpy(pNode->pKey, pKey, nKey);
+    pNode->nData = nData;
+    pNode->pData = pData; 
+    if( pCur->pNode ){
+      switch( match ){
+       case -1:
+         assert( !pCur->pNode->pRight );
+         pNode->pParent = pCur->pNode;
+         pCur->pNode->pRight = pNode;
+         break;
+       case 1:
+         assert( !pCur->pNode->pLeft );
+         pNode->pParent = pCur->pNode;
+         pCur->pNode->pLeft = pNode;
+         break;
+       default:
+         assert(0);
+      }
+    }else{
+      pCur->pTree->pHead = pNode;
+    }
+
+    /* Point the cursor at the node just inserted, as per SQLite requirements */
+    pCur->pNode = pNode;
+
+    /* A new node has just been inserted, so run the balancing code */
+    do_insert_balancing(pCur->pTree, pNode);
+
+    /* Set up a rollback-op in case we have to roll this operation back */
+    if( pCur->pBtree->eTransState != TRANS_ROLLBACK ){
+      BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) );
+      pOp->eOp = ROLLBACK_DELETE;
+      pOp->iTab = pCur->iTree;
+      pOp->nKey = pNode->nKey;
+      pOp->pKey = sqliteMalloc( pOp->nKey );
+      memcpy( pOp->pKey, pNode->pKey, pOp->nKey );
+      btreeLogRollbackOp(pCur->pBtree, pOp);
+    }
+
+  }else{ 
+    /* No need to insert a new node in the tree, as the key already exists.
+     * Just clobber the current nodes data. */
+
+    /* Set up a rollback-op in case we have to roll this operation back */
+    if( pCur->pBtree->eTransState != TRANS_ROLLBACK ){
+      BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) );
+      pOp->iTab = pCur->iTree;
+      pOp->nKey = pCur->pNode->nKey;
+      pOp->pKey = sqliteMalloc( pOp->nKey );
+      memcpy( pOp->pKey, pCur->pNode->pKey, pOp->nKey );
+      pOp->nData = pCur->pNode->nData;
+      pOp->pData = pCur->pNode->pData;
+      pOp->eOp = ROLLBACK_INSERT;
+      btreeLogRollbackOp(pCur->pBtree, pOp);
+    }else{
+      sqliteFree( pCur->pNode->pData );
+    }
+
+    /* Actually clobber the nodes data */
+    pCur->pNode->pData = pData;
+    pCur->pNode->nData = nData;
+  }
+
+  return SQLITE_OK;
+}
+
+/* Move the cursor so that it points to an entry near pKey.
+** Return a success code.
+**
+**     *pRes<0      The cursor is left pointing at an entry that
+**                  is smaller than pKey or if the table is empty
+**                  and the cursor is therefore left point to nothing.
+**
+**     *pRes==0     The cursor is left pointing at an entry that
+**                  exactly matches pKey.
+**
+**     *pRes>0      The cursor is left pointing at an entry that
+**                  is larger than pKey.
+*/
+static int sqliteBtreeMoveto(BtCursor* pCur, const void *pKey, int nKey, int *pRes)
+{
+  BtRbNode *pTmp = 0;
+
+  pCur->pNode = pCur->pTree->pHead;
+  *pRes = -1;
+  while( pCur->pNode && *pRes ) {
+    *pRes = key_compare(pCur->pNode->pKey, pCur->pNode->nKey, pKey, nKey);
+    pTmp = pCur->pNode;
+    switch( *pRes ){
+      case 1:    /* cursor > key */
+       pCur->pNode = pCur->pNode->pLeft;
+       break;
+      case -1:   /* cursor < key */
+       pCur->pNode = pCur->pNode->pRight;
+       break;
+    }
+  } 
+
+  /* If (pCur->pNode == NULL), then we have failed to find a match. Set
+   * pCur->pNode to pTmp, which is either NULL (if the tree is empty) or the
+   * last node traversed in the search. In either case the relation ship
+   * between pTmp and the searched for key is already stored in *pRes. pTmp is
+   * either the successor or predecessor of the key we tried to move to. */
+  if( !pCur->pNode ) pCur->pNode = pTmp;
+
+  return SQLITE_OK;
+}
+
+
+/*
+** Delete the entry that the cursor is pointing to.
+**
+** The cursor is left pointing at either the next or the previous
+** entry.  If the cursor is left pointing to the next entry, then 
+** the pCur->eSkip flag is set to SKIP_NEXT which forces the next call to 
+** sqliteBtreeNext() to be a no-op.  That way, you can always call
+** sqliteBtreeNext() after a delete and the cursor will be left
+** pointing to the first entry after the deleted entry.  Similarly,
+** pCur->eSkip is set to SKIP_PREV is the cursor is left pointing to
+** the entry prior to the deleted entry so that a subsequent call to
+** sqliteBtreePrevious() will always leave the cursor pointing at the
+** entry immediately before the one that was deleted.
+*/
+static int sqliteBtreeDelete(BtCursor* pCur)
+{
+  BtRbNode *pZ;      /* The one being deleted */
+  BtRbNode *pChild;  /* The child of the spliced out node */
+
+  /* It is illegal to call sqliteBtreeDelete() if we are not in a transaction */
+  assert( pCur->pBtree->eTransState != TRANS_NONE );
+
+  pZ = pCur->pNode;
+  if( !pZ ){
+    return SQLITE_OK;
+  }
+
+  /* If we are not currently doing a rollback, set up a rollback op for this 
+   * deletion */
+  if( pCur->pBtree->eTransState != TRANS_ROLLBACK ){
+    BtRollbackOp *pOp = sqliteMalloc( sizeof(BtRollbackOp) );
+    pOp->iTab = pCur->iTree;
+    pOp->nKey = pZ->nKey;
+    pOp->pKey = pZ->pKey;
+    pOp->nData = pZ->nData;
+    pOp->pData = pZ->pData;
+    pOp->eOp = ROLLBACK_INSERT;
+    btreeLogRollbackOp(pCur->pBtree, pOp);
+  }
+
+  /* First do a standard binary-tree delete (node pZ is to be deleted). How
+   * to do this depends on how many children pZ has:
+   *
+   * If pZ has no children or one child, then splice out pZ.  If pZ has two
+   * children, splice out the successor of pZ and replace the key and data of
+   * pZ with the key and data of the spliced out successor.  */
+  if( pZ->pLeft && pZ->pRight ){
+    BtRbNode *pTmp;
+    int dummy;
+    pCur->eSkip = SKIP_NONE;
+    sqliteBtreeNext(pCur, &dummy);
+    assert( dummy == 0 );
+    if( pCur->pBtree->eTransState == TRANS_ROLLBACK ){
+      sqliteFree(pZ->pKey);
+      sqliteFree(pZ->pData);
+    }
+    pZ->pData = pCur->pNode->pData;
+    pZ->nData = pCur->pNode->nData;
+    pZ->pKey = pCur->pNode->pKey;
+    pZ->nKey = pCur->pNode->nKey;
+    pTmp = pZ;
+    pZ = pCur->pNode;
+    pCur->pNode = pTmp;
+    pCur->eSkip = SKIP_NEXT;
+  }else{
+    int res;
+    pCur->eSkip = SKIP_NONE;
+    sqliteBtreeNext(pCur, &res);
+    pCur->eSkip = SKIP_NEXT;
+    if( res ){
+      sqliteBtreeLast(pCur, &res);
+      sqliteBtreePrevious(pCur, &res);
+      pCur->eSkip = SKIP_PREV;
+    }
+    if( pCur->pBtree->eTransState == TRANS_ROLLBACK ){
+       sqliteFree(pZ->pKey);
+       sqliteFree(pZ->pData);
+    }
+  }
+
+  /* pZ now points at the node to be spliced out. This block does the 
+   * splicing. */
+  {
+    BtRbNode **ppParentSlot = 0;
+    assert( !pZ->pLeft || !pZ->pRight ); /* pZ has at most one child */
+    pChild = ((pZ->pLeft)?pZ->pLeft:pZ->pRight);
+    if( pZ->pParent ){
+      assert( pZ == pZ->pParent->pLeft || pZ == pZ->pParent->pRight );
+      ppParentSlot = ((pZ == pZ->pParent->pLeft)
+         ?&pZ->pParent->pLeft:&pZ->pParent->pRight);
+      *ppParentSlot = pChild;
+    }else{
+      pCur->pTree->pHead = pChild;
+    }
+    if( pChild ) pChild->pParent = pZ->pParent;
+  }
+
+  /* pZ now points at the spliced out node. pChild is the only child of pZ, or
+   * NULL if pZ has no children. If pZ is black, and not the tree root, then we
+   * will have violated the "same number of black nodes in every path to a
+   * leaf" property of the red-black tree. The code in do_delete_balancing()
+   * repairs this. */
+  if( pZ->isBlack ){ 
+    do_delete_balancing(pCur->pTree, pChild, pZ->pParent);
+  }
+
+  sqliteFree(pZ);
+  return SQLITE_OK;
+}
+
+/*
+ * Empty table n of the Btree.
+ */
+static int sqliteBtreeClearTable(Btree* tree, int n)
+{
+  BtRbTree *pTree;
+  BtRbNode *pNode;
+
+  pTree = sqliteHashFind(&tree->tblHash, 0, n);
+  assert(pTree);
+
+  pNode = pTree->pHead;
+  while( pNode ){
+    if( pNode->pLeft ){
+      pNode = pNode->pLeft;
+    }
+    else if( pNode->pRight ){
+      pNode = pNode->pRight;
+    }
+    else {
+      BtRbNode *pTmp = pNode->pParent;
+      if( tree->eTransState == TRANS_ROLLBACK ){
+       sqliteFree( pNode->pKey );
+       sqliteFree( pNode->pData );
+      }else{
+       BtRollbackOp *pRollbackOp = sqliteMalloc(sizeof(BtRollbackOp));
+       pRollbackOp->eOp = ROLLBACK_INSERT;
+       pRollbackOp->iTab = n;
+       pRollbackOp->nKey = pNode->nKey;
+       pRollbackOp->pKey = pNode->pKey;
+       pRollbackOp->nData = pNode->nData;
+       pRollbackOp->pData = pNode->pData;
+       btreeLogRollbackOp(tree, pRollbackOp);
+      }
+      sqliteFree( pNode );
+      if( pTmp ){
+       if( pTmp->pLeft == pNode ) pTmp->pLeft = 0;
+       else if( pTmp->pRight == pNode ) pTmp->pRight = 0;
+      }
+      pNode = pTmp;
+    }
+  }
+
+  pTree->pHead = 0;
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeFirst(BtCursor* pCur, int *pRes)
+{
+  if( pCur->pTree->pHead ){
+    pCur->pNode = pCur->pTree->pHead;
+    while( pCur->pNode->pLeft ){
+      pCur->pNode = pCur->pNode->pLeft;
+    }
+  }
+  if( pCur->pNode ){
+    *pRes = 0;
+  }else{
+    *pRes = 1;
+  }
+  pCur->eSkip = SKIP_NONE;
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeLast(BtCursor* pCur, int *pRes)
+{
+  if( pCur->pTree->pHead ){
+    pCur->pNode = pCur->pTree->pHead;
+    while( pCur->pNode->pRight ){
+      pCur->pNode = pCur->pNode->pRight;
+    }
+  }
+  if( pCur->pNode ){
+    *pRes = 0;
+  }else{
+    *pRes = 1;
+  }
+  pCur->eSkip = SKIP_NONE;
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeNext(BtCursor* pCur, int *pRes)
+{
+  if( pCur->pNode && pCur->eSkip != SKIP_NEXT ){
+    if( pCur->pNode->pRight ){
+      pCur->pNode = pCur->pNode->pRight;
+      while( pCur->pNode->pLeft )
+       pCur->pNode = pCur->pNode->pLeft;
+    }else{
+      BtRbNode * pX = pCur->pNode;
+      pCur->pNode = pX->pParent;
+      while( pCur->pNode && (pCur->pNode->pRight == pX) ){
+       pX = pCur->pNode;
+       pCur->pNode = pX->pParent;
+      }
+    }
+  }
+  pCur->eSkip = SKIP_NONE;
+
+  if( !pCur->pNode ){
+    *pRes = 1;
+  }else{
+    *pRes = 0;
+  }
+
+  return SQLITE_OK;
+}
+
+static int sqliteBtreePrevious(BtCursor* pCur, int *pRes)
+{
+  if( pCur->pNode && pCur->eSkip != SKIP_PREV ){
+    if( pCur->pNode->pLeft ){
+      pCur->pNode = pCur->pNode->pLeft;
+      while( pCur->pNode->pRight )
+       pCur->pNode = pCur->pNode->pRight;
+    }else{
+      BtRbNode * pX = pCur->pNode;
+      pCur->pNode = pX->pParent;
+      while( pCur->pNode && (pCur->pNode->pLeft == pX) ){
+       pX = pCur->pNode;
+       pCur->pNode = pX->pParent;
+      }
+    }
+  }
+  pCur->eSkip = SKIP_NONE;
+
+  if( !pCur->pNode ){
+    *pRes = 1;
+  }else{
+    *pRes = 0;
+  }
+
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeKeySize(BtCursor* pCur, int *pSize)
+{
+  if( pCur->pNode ){
+    *pSize = pCur->pNode->nKey;
+  }else{
+    *pSize = 0;
+  }
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeKey(BtCursor* pCur, int offset, int amt, char *zBuf)
+{
+  if( !pCur->pNode ) return 0;
+  if( !pCur->pNode->pKey || ((amt + offset) <= pCur->pNode->nKey) ){
+    memcpy(zBuf, pCur->pNode->pKey+offset, amt);
+    return amt;
+  }else{
+    memcpy(zBuf, pCur->pNode->pKey+offset ,pCur->pNode->nKey-offset);
+    return pCur->pNode->nKey-offset;
+  }
+  assert(0);
+}
+
+static int sqliteBtreeDataSize(BtCursor* pCur, int *pSize)
+{
+  if( pCur->pNode ){
+    *pSize = pCur->pNode->nData;
+  }else{
+    *pSize = 0;
+  }
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeData(BtCursor *pCur, int offset, int amt, char *zBuf)
+{
+  if( !pCur->pNode ) return 0;
+  if( (amt + offset) <= pCur->pNode->nData ){
+    memcpy(zBuf, pCur->pNode->pData+offset, amt);
+    return amt;
+  }else{
+    memcpy(zBuf, pCur->pNode->pData+offset ,pCur->pNode->nData-offset);
+    return pCur->pNode->nData-offset;
+  }
+  assert(0);
+}
+
+static int sqliteBtreeCloseCursor(BtCursor* pCur)
+{
+  sqliteFree(pCur);
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeGetMeta(Btree* tree, int* aMeta)
+{
+  memcpy( aMeta, tree->aMetaData, sizeof(int) * SQLITE_N_BTREE_META );
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeUpdateMeta(Btree* tree, int* aMeta)
+{
+  memcpy( tree->aMetaData, aMeta, sizeof(int) * SQLITE_N_BTREE_META );
+  return SQLITE_OK;
+}
+
+/*
+ * Check that each table in the Btree meets the requirements for a red-black
+ * binary tree. If an error is found, return an explanation of the problem in 
+ * memory obtained from sqliteMalloc(). Parameters aRoot and nRoot are ignored. 
+ */
+static char *sqliteBtreeIntegrityCheck(Btree* tree, int* aRoot, int nRoot)
+{
+  char * msg = 0;
+  HashElem *p;
+
+  for(p=sqliteHashFirst(&tree->tblHash); p; p=sqliteHashNext(p)){
+    BtRbTree *pTree = sqliteHashData(p);
+    check_redblack_tree(pTree, &msg);
+  }
+
+  return msg;
+}
+
+/*
+ * Close the supplied Btree. Delete everything associated with it.
+ */
+static int sqliteBtreeClose(Btree* tree)
+{
+  HashElem *p;
+  for(p=sqliteHashFirst(&tree->tblHash); p; p=sqliteHashNext(p)){
+    BtRbTree *pTree = sqliteHashData(p);
+    tree->eTransState = TRANS_ROLLBACK;
+    sqliteBtreeClearTable(tree, sqliteHashKeysize(p));
+    sqliteFree(sqliteHashData(p));
+  }
+  sqliteFree(tree);
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeSetCacheSize(Btree* tree, int sz)
+{
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeSetSafetyLevel(Btree *pBt, int level){
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeBeginTrans(Btree* tree)
+{
+  if( tree->eTransState != TRANS_NONE ) 
+    return SQLITE_ERROR;
+
+  assert( tree->pTransRollback == 0 );
+  tree->eTransState = TRANS_INTRANSACTION;
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeCommit(Btree* tree)
+{
+  /* Just delete pTransRollback and pCheckRollback */
+  BtRollbackOp *pOp, *pTmp;
+  pOp = tree->pCheckRollback;
+  while( pOp ){
+    pTmp = pOp->pNext;
+    sqliteFree(pOp->pData);
+    sqliteFree(pOp->pKey);
+    sqliteFree(pOp);
+    pOp = pTmp;
+  }
+  pOp = tree->pTransRollback;
+  while( pOp ){
+    pTmp = pOp->pNext;
+    sqliteFree(pOp->pData);
+    sqliteFree(pOp->pKey);
+    sqliteFree(pOp);
+    pOp = pTmp;
+  }
+  tree->pTransRollback = 0;
+  tree->pCheckRollback = 0;
+  tree->pCheckRollbackTail = 0;
+  tree->eTransState = TRANS_NONE;
+  return SQLITE_OK;
+}
+
+/*
+ * Execute and delete the supplied rollback-list on pBtree.
+ */
+static void execute_rollback_list(Btree *pBtree, BtRollbackOp *pList)
+{
+  BtRollbackOp *pTmp;
+  BtCursor cur;
+  cur.pBtree = pBtree;
+
+  int res;
+  while( pList ){
+    switch( pList->eOp ){
+      case ROLLBACK_INSERT:
+       cur.pTree  = sqliteHashFind( &pBtree->tblHash, 0, pList->iTab );
+       assert(cur.pTree);
+       cur.iTree  = pList->iTab;
+       cur.eSkip  = SKIP_NONE;
+       sqliteBtreeInsert( &cur, pList->pKey,
+           pList->nKey, pList->pData, pList->nData );
+       break;
+      case ROLLBACK_DELETE:
+       cur.pTree  = sqliteHashFind( &pBtree->tblHash, 0, pList->iTab );
+       assert(cur.pTree);
+       cur.iTree  = pList->iTab;
+       cur.eSkip  = SKIP_NONE;
+       sqliteBtreeMoveto(&cur, pList->pKey, pList->nKey, &res);
+       assert(res == 0);
+       sqliteBtreeDelete( &cur );
+       break;
+      case ROLLBACK_CREATE:
+       btreeCreateTable(pBtree, pList->iTab);
+       break;
+      case ROLLBACK_DROP:
+       sqliteBtreeDropTable(pBtree, pList->iTab);
+       break;
+      default:
+       assert(0);
+    }
+    sqliteFree(pList->pKey);
+    sqliteFree(pList->pData);
+    pTmp = pList->pNext;
+    sqliteFree(pList);
+    pList = pTmp;
+  }
+}
+
+static int sqliteBtreeRollback(Btree* tree)
+{
+  tree->eTransState = TRANS_ROLLBACK;
+  execute_rollback_list(tree, tree->pCheckRollback);
+  execute_rollback_list(tree, tree->pTransRollback);
+  tree->pTransRollback = 0;
+  tree->pCheckRollback = 0;
+  tree->pCheckRollbackTail = 0;
+  tree->eTransState = TRANS_NONE;
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeBeginCkpt(Btree* tree)
+{
+  if( tree->eTransState != TRANS_INTRANSACTION ) 
+    return SQLITE_ERROR;
+
+  assert( tree->pCheckRollback == 0 );
+  assert( tree->pCheckRollbackTail == 0 );
+  tree->eTransState = TRANS_INCHECKPOINT;
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeCommitCkpt(Btree* tree)
+{
+  if( tree->eTransState == TRANS_INCHECKPOINT ){ 
+    if( tree->pCheckRollback ){
+      tree->pCheckRollbackTail->pNext = tree->pTransRollback;
+      tree->pTransRollback = tree->pCheckRollback;
+      tree->pCheckRollback = 0;
+      tree->pCheckRollbackTail = 0;
+    }
+    tree->eTransState = TRANS_INTRANSACTION;
+  }
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeRollbackCkpt(Btree* tree)
+{
+  if( tree->eTransState != TRANS_INCHECKPOINT ) return SQLITE_OK;
+  tree->eTransState = TRANS_ROLLBACK;
+  execute_rollback_list(tree, tree->pCheckRollback);
+  tree->pCheckRollback = 0;
+  tree->pCheckRollbackTail = 0;
+  tree->eTransState = TRANS_INTRANSACTION;
+  return SQLITE_OK;
+  return SQLITE_OK;
+}
+
+#ifdef SQLITE_TEST
+static int sqliteBtreePageDump(Btree* tree, int pgno, int rec)
+{
+  assert(!"Cannot call sqliteBtreePageDump");
+  return SQLITE_OK;
+}
+
+static int sqliteBtreeCursorDump(BtCursor* pCur, int* aRes)
+{
+  assert(!"Cannot call sqliteBtreeCursorDump");
+  return SQLITE_OK;
+}
+
+static struct Pager *sqliteBtreePager(Btree* tree)
+{
+  assert(!"Cannot call sqliteBtreePager");
+  return SQLITE_OK;
+}
+#endif
+
+/*
+** Return the full pathname of the underlying database file.
+*/
+static const char *sqliteBtreeGetFilename(Btree *pBt){
+  return ":memory:";
+}
+
+/*
+** Change the name of the underlying database file.
+*/
+static int sqliteBtreeChangeFilename(Btree *pBt, const char *zNew){
+  return SQLITE_OK;
+}
+
+static BtOps sqliteBtreeOps = {
+    sqliteBtreeClose,
+    sqliteBtreeSetCacheSize,
+    sqliteBtreeSetSafetyLevel,
+    sqliteBtreeBeginTrans,
+    sqliteBtreeCommit,
+    sqliteBtreeRollback,
+    sqliteBtreeBeginCkpt,
+    sqliteBtreeCommitCkpt,
+    sqliteBtreeRollbackCkpt,
+    sqliteBtreeCreateTable,
+    sqliteBtreeCreateIndex,
+    sqliteBtreeDropTable,
+    sqliteBtreeClearTable,
+    sqliteBtreeCursor,
+    sqliteBtreeGetMeta,
+    sqliteBtreeUpdateMeta,
+    sqliteBtreeIntegrityCheck,
+    sqliteBtreeGetFilename,
+    sqliteBtreeChangeFilename,
+
+#ifdef SQLITE_TEST
+    sqliteBtreePageDump,
+    sqliteBtreePager
+#endif
+};
+
+static BtCursorOps sqliteBtreeCursorOps = {
+    sqliteBtreeMoveto,
+    sqliteBtreeDelete,
+    sqliteBtreeInsert,
+    sqliteBtreeFirst,
+    sqliteBtreeLast,
+    sqliteBtreeNext,
+    sqliteBtreePrevious,
+    sqliteBtreeKeySize,
+    sqliteBtreeKey,
+    sqliteBtreeKeyCompare,
+    sqliteBtreeDataSize,
+    sqliteBtreeData,
+    sqliteBtreeCloseCursor,
+#ifdef SQLITE_TEST
+    sqliteBtreeCursorDump,
+#endif
+
+};