]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Restore btree to the main line. (CVS 237)
authordrh <drh@noemail.net>
Mon, 20 Aug 2001 00:33:58 +0000 (00:33 +0000)
committerdrh <drh@noemail.net>
Mon, 20 Aug 2001 00:33:58 +0000 (00:33 +0000)
FossilOrigin-Name: 2e6aff980287825b59d2ebb7005bb08dd601ff1c

12 files changed:
Makefile.in
manifest
manifest.uuid
src/btree.c
src/btree.h
src/dbbemem.c
src/tclsqlite.c
src/test2.c [new file with mode: 0644]
src/test3.c [new file with mode: 0644]
test/btree.test [new file with mode: 0644]
test/btree2.test [new file with mode: 0644]
test/pager.test [new file with mode: 0644]

index f3af9281f167d97a230495b1c054eb6eab2f93b3..20ffb5e313d73d43c691df78dd2b7e60fb18aeb2 100644 (file)
@@ -47,13 +47,14 @@ LIBREADLINE = @TARGET_READLINE_LIBS@
 
 # Object files for the SQLite library.
 #
-LIBOBJ = build.o dbbe.o dbbegdbm.o dbbemem.o delete.o expr.o insert.o \
-         main.o parse.o printf.o random.o select.o table.o \
+LIBOBJ = btree.o build.o dbbe.o dbbegdbm.o dbbemem.o delete.o expr.o insert.o \
+         main.o pager.o parse.o printf.o random.o select.o table.o \
          tokenize.o update.o util.o vdbe.o where.o tclsqlite.o
 
 # All of the source code files.
 #
 SRC = \
+  $(TOP)/src/btree.c \
   $(TOP)/src/build.c \
   $(TOP)/src/dbbe.c \
   $(TOP)/src/dbbe.h \
@@ -63,6 +64,7 @@ SRC = \
   $(TOP)/src/expr.c \
   $(TOP)/src/insert.c \
   $(TOP)/src/main.c \
+  $(TOP)/src/pager.c \
   $(TOP)/src/parse.y \
   $(TOP)/src/printf.c \
   $(TOP)/src/random.c \
@@ -82,7 +84,11 @@ SRC = \
 # Source code to the test files.
 #
 TESTSRC = \
+  $(TOP)/src/btree.c \
+  $(TOP)/src/pager.c \
   $(TOP)/src/test1.c \
+  $(TOP)/src/test2.c \
+  $(TOP)/src/test3.c \
   $(TOP)/src/md5.c
 
 # This is the default Makefile target.  The objects listed here
@@ -120,6 +126,9 @@ HDR = \
    $(TOP)/src/vdbe.h  \
    parse.h
 
+btree.o:       $(TOP)/src/btree.c $(HDR)
+       $(TCC) $(GDBM_FLAGS) -c $(TOP)/src/btree.c
+
 build.o:       $(TOP)/src/build.c $(HDR)
        $(TCC) $(GDBM_FLAGS) -c $(TOP)/src/build.c
 
@@ -135,6 +144,9 @@ dbbemem.o:  $(TOP)/src/dbbemem.c $(HDR)
 main.o:        $(TOP)/src/main.c $(HDR)
        $(TCC) $(GDBM_FLAGS) -c $(TOP)/src/main.c
 
+pager.o:       $(TOP)/src/pager.c $(HDR)
+       $(TCC) $(GDBM_FLAGS) -c $(TOP)/src/pager.c
+
 parse.o:       parse.c $(HDR)
        $(TCC) $(GDBM_FLAGS) -c parse.c
 
index 0fe001ba4374c4eaa9c9e6db460b823e8dcfb3d0..f9f331bf8b9da8c66d9144d009df11de432d9cdc 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,7 +1,7 @@
-C Add\sindex\saccess\smethods\sto\sthe\sDBBE\sin\spreparation\sfor\sadding\sa\snew\nDBBE\sfor\sthe\sbtree.c\smodule.\s(CVS\s236)
-D 2001-08-19T18:19:46
+C Restore\sbtree\sto\sthe\smain\sline.\s(CVS\s237)
+D 2001-08-20T00:33:58
 F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4
-F Makefile.in a4595a83d56549b527dace5415729d20995f717b
+F Makefile.in 9eea999e1d95531de4dd0a96a6ecf6ba0027b05b
 F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958
 F VERSION 00453ed53ff28fe8e701e1609e81f1b9df12adab
 F configure d2051345f49f7e48604423da26e086a745c86a47 x
@@ -13,13 +13,13 @@ F notes/notes2.txt 7e3fafd5e25906c1fe1e95f13b089aa398ca403e
 F notes/notes2b.txt 1c17a5b7f6b44a75cd3eb98ed2c24db1eefb06c3
 F notes/notes3.txt 71e47be517e3d2578b3b9343a45b772d43b7ba16
 F src/TODO 38a68a489e56e9fd4a96263e0ff9404a47368ad4
-F src/btree.c d6bbe3152ce3eb47ffd0c797897bf75c5ca784fc
-F src/btree.h 5fb5799bcb39900386ce6cae61fa33e357851ffe
+F src/btree.c 049c6a8d6c308b1945bd2f32746f3df187c7f18b
+F src/btree.h 6617284287be90afe41bcd5047e44f109ecd1b48
 F src/build.c 4f6a2d551c56342cd4a0420654835be3ad179651
 F src/dbbe.c b18259f99d87240cbe751021cf14dd3aa83a48af
 F src/dbbe.h bbb53eafcd1e3186597f6ee4a17ef2501f1b0628
 F src/dbbegdbm.c cbb6ebc79a7100324f07b67d4e867faca9f9efa9
-F src/dbbemem.c f4534a524e956b1db9af070b896040fc1b9cb80e
+F src/dbbemem.c 910ad3bb82fc065a95a762b34593b3386b4833d5
 F src/delete.c 40ddb169ee98013d976b2dadd140d98f7876f54f
 F src/ex/README b745b00acce2d892f60c40111dacdfc48e0c1c7a
 F src/ex/db.c f1419ae6c93e40b5ac6e39fe7efd95d868e6f9d7
@@ -44,8 +44,10 @@ F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e
 F src/sqlite.h.in 3e5906f72608f0fd4394dfbb1d7e8d35b8353677
 F src/sqliteInt.h 47845c60e2e196b5409d774936a56700b1611f00
 F src/table.c adcaf074f6c1075e86359174e68701fa2acfc4d6
-F src/tclsqlite.c 386f502060325d9cb05d418bf0f1cf6a4b57c873
+F src/tclsqlite.c d328970848c028e13e61e173bef79adcc379568a
 F src/test1.c abb3cb427e735ae87e6533f5b3b7164b7da91bc4
+F src/test2.c b3177e061fabd20d48e4b1b4bca610a0d2b28670
+F src/test3.c 147b42ec368a10e9f267e7466d30c46e76d7f278
 F src/tokenize.c 0118b57702cb6550769316e8443b06760b067acf
 F src/update.c 0cf789656a936d4356668393267692fa4b03ffc6
 F src/util.c 1b396ac34e30dd6222d82e996c17b161bbc906bc
@@ -53,6 +55,8 @@ F src/vdbe.c b019394ebe0de12917a93ec06d787d8d909726cc
 F src/vdbe.h 5331b9a3963d13af01a9cf749f57ac46727bdbe6
 F src/where.c a49083e59358bac83c80cf0d19626d09bab629bd
 F test/all.test 21d55a97e39e7ec5776751dc9dd8b1b51ef4a048
+F test/btree.test 5e1eeb03cda22161eec827dc5224ce6c500eaaf9
+F test/btree2.test a66add2093843d0e5617fed6924002667832f279
 F test/copy.test b77a1214bd7756f2849d5c4fa6e715c0ff0c34eb
 F test/dbbe.test a022fe2d983848f786e17ef1fc6809cfd37fb02c
 F test/delete.test 50b9b1f06c843d591741dba7869433a105360dbf
@@ -65,6 +69,7 @@ F test/insert2.test 732405e30331635af8d159fccabe835eea5cd0c6
 F test/lock.test bca7d53de73138b1f670a2fbdb1f481ff7eaa45a
 F test/main.test da635f9e078cd21ddf074e727381a715064489ff
 F test/malloc.test 3daa97f6a9577d8f4c6e468b274333af19ce5861
+F test/pager.test 775a86d5b4877f3b49cb0f8ef0a1621cc6683e3b
 F test/printf.test 4c71871e1a75a2dacb673945fc13ddb30168798f
 F test/quote.test 40a3164af8456933a81312803fa8cdb21b205c12
 F test/rowid.test b01e6dec09780c93f55db6cfe7ad097323954f23
@@ -107,7 +112,7 @@ F www/opcode.tcl cb3a1abf8b7b9be9f3a228d097d6bf8b742c2b6f
 F www/sqlite.tcl cb0d23d8f061a80543928755ec7775da6e4f362f
 F www/tclsqlite.tcl 06f81c401f79a04f2c5ebfb97e7c176225c0aef2
 F www/vdbe.tcl 0c8aaa529dd216ccbf7daaabd80985e413d5f9ad
-P cfc86dc48afb6b7e052e418db1c596b665d5cc66
-R 79a41eb08d305d22b658e8f00e7f0110
+P c15f6ffc4d41f30a06d750c8015226713ae0126b
+R 7b718c617d1cbd654e712509bb5deb19
 U drh
-Z 385160b9b18c8b4f775dcbb82eee9159
+Z a087795b1966c95abd2653a136093358
index 181a1071360f1a7e12ad5fa930181e56bf40eba4..0981288553b3af2f7a31271142a72c1e19ca8bf5 100644 (file)
@@ -1 +1 @@
-c15f6ffc4d41f30a06d750c8015226713ae0126b
\ No newline at end of file
+2e6aff980287825b59d2ebb7005bb08dd601ff1c
\ No newline at end of file
index 46d2332a1ec8051afae2dd9f73498cde368c54df..98410be63b5213c32614d6103e74065830d92818 100644 (file)
@@ -21,7 +21,7 @@
 **   http://www.hwaci.com/drh/
 **
 *************************************************************************
-** $Id: btree.c,v 1.20 2001/07/02 17:51:46 drh Exp $
+** $Id: btree.c,v 1.21 2001/08/20 00:33:58 drh Exp $
 **
 ** This file implements a external (disk-based) database using BTrees.
 ** For a detailed discussion of BTrees, refer to
@@ -1026,7 +1026,12 @@ int sqliteBtreeData(BtCursor *pCur, int offset, int amt, char *zBuf){
 ** pages, then some other value might be returned to indicate the
 ** reason for the error.
 */
-static int compareKey(BtCursor *pCur, char *pKey, int nKeyOrig, int *pResult){
+static int compareKey(
+  BtCursor *pCur,      /* Points to the entry against which we are comparing */
+  const char *pKey,    /* The comparison key */
+  int nKeyOrig,        /* Number of bytes in the comparison key */
+  int *pResult         /* Write the comparison results here */
+){
   Pgno nextPage;
   int nKey = nKeyOrig;
   int n, c, rc;
@@ -1178,7 +1183,7 @@ static int moveToLeftmost(BtCursor *pCur){
 **     *pRes>0      The cursor is left pointing at an entry that
 **                  is larger than pKey.
 */
-int sqliteBtreeMoveto(BtCursor *pCur, void *pKey, int nKey, int *pRes){
+int sqliteBtreeMoveto(BtCursor *pCur, const void *pKey, int nKey, int *pRes){
   int rc;
   pCur->bSkipNext = 0;
   rc = moveToRoot(pCur);
@@ -1382,15 +1387,15 @@ static int clearCell(Btree *pBt, Cell *pCell){
 static int fillInCell(
   Btree *pBt,              /* The whole Btree.  Needed to allocate pages */
   Cell *pCell,             /* Populate this Cell structure */
-  void *pKey, int nKey,    /* The key */
-  void *pData,int nData    /* The data */
+  const void *pKey, int nKey,    /* The key */
+  const void *pData,int nData    /* The data */
 ){
   OverflowPage *pOvfl, *pPrior;
   Pgno *pNext;
   int spaceLeft;
   int n, rc;
   int nPayload;
-  char *pPayload;
+  const char *pPayload;
   char *pSpace;
 
   pCell->h.leftChild = 0;
@@ -1965,9 +1970,9 @@ balance_cleanup:
 ** is left pointing at the new record.
 */
 int sqliteBtreeInsert(
-  BtCursor *pCur,            /* Insert data into the table of this cursor */
-  void *pKey,  int nKey,     /* The key of the new record */
-  void *pData, int nData     /* The data of the new record */
+  BtCursor *pCur,                /* Insert data into the table of this cursor */
+  const void *pKey,  int nKey,   /* The key of the new record */
+  const void *pData, int nData   /* The data of the new record */
 ){
   Cell newCell;
   int rc;
index 6c54b6708462d9aaf9e0131ac3a7a376eb188c5b..2e98f4213fc2d4b67d1012ddd288444ce151c45d 100644 (file)
@@ -24,7 +24,7 @@
 ** This header file defines the interface that the sqlite B-Tree file
 ** subsystem.
 **
-** @(#) $Id: btree.h,v 1.9 2001/07/02 17:51:46 drh Exp $
+** @(#) $Id: btree.h,v 1.10 2001/08/20 00:33:58 drh Exp $
 */
 
 typedef struct Btree Btree;
@@ -42,9 +42,10 @@ int sqliteBtreeDropTable(Btree*, int);
 int sqliteBtreeClearTable(Btree*, int);
 
 int sqliteBtreeCursor(Btree*, int iTable, BtCursor **ppCur);
-int sqliteBtreeMoveto(BtCursor*, void *pKey, int nKey, int *pRes);
+int sqliteBtreeMoveto(BtCursor*, const void *pKey, int nKey, int *pRes);
 int sqliteBtreeDelete(BtCursor*);
-int sqliteBtreeInsert(BtCursor*, void *pKey, int nKey, void *pData, int nData);
+int sqliteBtreeInsert(BtCursor*, const void *pKey, int nKey,
+                                 const void *pData, int nData);
 int sqliteBtreeNext(BtCursor*, int *pRes);
 int sqliteBtreeKeySize(BtCursor*, int *pSize);
 int sqliteBtreeKey(BtCursor*, int offset, int amt, char *zBuf);
index 54f18b2283a92df828eb860531b239baed088765..95d7319605ecf2b75baf81753bca64ae36b1337c 100644 (file)
@@ -30,7 +30,7 @@
 ** Nothing is ever written to disk using this backend.  All information
 ** is forgotten when the program exits.
 **
-** $Id: dbbemem.c,v 1.16 2001/08/19 18:19:46 drh Exp $
+** $Id: dbbemem.c,v 1.17 2001/08/20 00:33:58 drh Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -867,9 +867,9 @@ static struct DbbeMethods memoryMethods = {
 };
 
 /*
-** This routine opens a new database.  For the GDBM driver
-** implemented here, the database name is the name of the directory
-** containing all the files of the database.
+** This routine opens a new database.  For the MEMORY driver
+** implemented here, the database name is ignored.  Every MEMORY database
+** is unique and is erased when the database is closed.
 **
 ** If successful, a pointer to the Dbbe structure is returned.
 ** If there are errors, an appropriate error message is left
index 95ae3c5c8bc90a29fe79c31a346f70e5d5f0771c..ade3ddb61c3551fd5aef226bc341f8df9860cdb7 100644 (file)
@@ -23,7 +23,7 @@
 *************************************************************************
 ** A TCL Interface to SQLite
 **
-** $Id: tclsqlite.c,v 1.21 2001/07/23 14:33:04 drh Exp $
+** $Id: tclsqlite.c,v 1.22 2001/08/20 00:33:58 drh Exp $
 */
 #ifndef NO_TCL     /* Omit this whole file if TCL is unavailable */
 
@@ -510,8 +510,12 @@ int TCLSH_MAIN(int argc, char **argv){
 #ifdef SQLITE_TEST
   {
     extern int Sqlitetest1_Init(Tcl_Interp*);
+    extern int Sqlitetest2_Init(Tcl_Interp*);
+    extern int Sqlitetest3_Init(Tcl_Interp*);
     extern int Md5_Init(Tcl_Interp*);
     Sqlitetest1_Init(interp);
+    Sqlitetest2_Init(interp);
+    Sqlitetest3_Init(interp);
     Md5_Init(interp);
   }
 #endif
diff --git a/src/test2.c b/src/test2.c
new file mode 100644 (file)
index 0000000..70e06eb
--- /dev/null
@@ -0,0 +1,416 @@
+/*
+** Copyright (c) 2001 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the GNU General Public
+** License as published by the Free Software Foundation; either
+** version 2 of the License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+** General Public License for more details.
+** 
+** You should have received a copy of the GNU General Public
+** License along with this library; if not, write to the
+** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+** Boston, MA  02111-1307, USA.
+**
+** Author contact information:
+**   drh@hwaci.com
+**   http://www.hwaci.com/drh/
+**
+*************************************************************************
+** Code for testing the pager.c module in SQLite.  This code
+** is not included in the SQLite library.  It is used for automated
+** testing of the SQLite library.
+**
+** $Id: test2.c,v 1.4 2001/08/20 00:33:58 drh Exp $
+*/
+#include "sqliteInt.h"
+#include "pager.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+
+/*
+** Interpret an SQLite error number
+*/
+static char *errorName(int rc){
+  char *zName;
+  switch( rc ){
+    case SQLITE_OK:         zName = "SQLITE_OK";          break;
+    case SQLITE_ERROR:      zName = "SQLITE_ERROR";       break;
+    case SQLITE_INTERNAL:   zName = "SQLITE_INTERNAL";    break;
+    case SQLITE_PERM:       zName = "SQLITE_PERM";        break;
+    case SQLITE_ABORT:      zName = "SQLITE_ABORT";       break;
+    case SQLITE_BUSY:       zName = "SQLITE_BUSY";        break;
+    case SQLITE_NOMEM:      zName = "SQLITE_NOMEM";       break;
+    case SQLITE_READONLY:   zName = "SQLITE_READONLY";    break;
+    case SQLITE_INTERRUPT:  zName = "SQLITE_INTERRUPT";   break;
+    case SQLITE_IOERR:      zName = "SQLITE_IOERR";       break;
+    case SQLITE_CORRUPT:    zName = "SQLITE_CORRUPT";     break;
+    case SQLITE_NOTFOUND:   zName = "SQLITE_NOTFOUND";    break;
+    case SQLITE_FULL:       zName = "SQLITE_FULL";        break;
+    case SQLITE_CANTOPEN:   zName = "SQLITE_CANTOPEN";    break;
+    case SQLITE_PROTOCOL:   zName = "SQLITE_PROTOCOL";    break;
+    default:                zName = "SQLITE_Unknown";     break;
+  }
+  return zName;
+}
+
+/*
+** Usage:   pager_open FILENAME N-PAGE
+**
+** Open a new pager
+*/
+static int pager_open(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  int nPage;
+  int rc;
+  char zBuf[100];
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FILENAME N-PAGE\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[2], &nPage) ) return TCL_ERROR;
+  rc = sqlitepager_open(&pPager, argv[1], nPage, 0);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  sprintf(zBuf,"0x%x",(int)pPager);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_close ID
+**
+** Close the given pager.
+*/
+static int pager_close(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
+  rc = sqlitepager_close(pPager);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_rollback ID
+**
+** Rollback changes
+*/
+static int pager_rollback(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
+  rc = sqlitepager_rollback(pPager);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_commit ID
+**
+** Commit all changes
+*/
+static int pager_commit(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
+  rc = sqlitepager_commit(pPager);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_stats ID
+**
+** Return pager statistics.
+*/
+static int pager_stats(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  int i, *a;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
+  a = sqlitepager_stats(pPager);
+  for(i=0; i<9; i++){
+    static char *zName[] = {
+      "ref", "page", "max", "size", "state", "err",
+      "hit", "miss", "ovfl",
+    };
+    char zBuf[100];
+    Tcl_AppendElement(interp, zName[i]);
+    sprintf(zBuf,"%d",a[i]);
+    Tcl_AppendElement(interp, zBuf);
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   pager_pagecount ID
+**
+** Return the size of the database file.
+*/
+static int pager_pagecount(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  char zBuf[100];
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
+  sprintf(zBuf,"%d",sqlitepager_pagecount(pPager));
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_get ID PGNO
+**
+** Return a pointer to a page from the database.
+*/
+static int page_get(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  char zBuf[100];
+  void *pPage;
+  int pgno;
+  int rc;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID PGNO\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
+  if( Tcl_GetInt(interp, argv[2], &pgno) ) return TCL_ERROR;
+  rc = sqlitepager_get(pPager, pgno, &pPage);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  sprintf(zBuf,"0x%x",(int)pPage);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_lookup ID PGNO
+**
+** Return a pointer to a page if the page is already in cache.
+** If not in cache, return an empty string.
+*/
+static int page_lookup(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Pager *pPager;
+  char zBuf[100];
+  void *pPage;
+  int pgno;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID PGNO\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
+  if( Tcl_GetInt(interp, argv[2], &pgno) ) return TCL_ERROR;
+  pPage = sqlitepager_lookup(pPager, pgno);
+  if( pPage ){
+    sprintf(zBuf,"0x%x",(int)pPage);
+    Tcl_AppendResult(interp, zBuf, 0);
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_unref PAGE
+**
+** Drop a pointer to a page.
+*/
+static int page_unref(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  void *pPage;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " PAGE\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPage) ) return TCL_ERROR;
+  rc = sqlitepager_unref(pPage);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_read PAGE
+**
+** Return the content of a page
+*/
+static int page_read(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  char zBuf[100];
+  void *pPage;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " PAGE\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPage) ) return TCL_ERROR;
+  memcpy(zBuf, pPage, sizeof(zBuf));
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_number PAGE
+**
+** Return the page number for a page.
+*/
+static int page_number(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  char zBuf[100];
+  void *pPage;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " PAGE\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPage) ) return TCL_ERROR;
+  sprintf(zBuf, "%d", sqlitepager_pagenumber(pPage));
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   page_write PAGE DATA
+**
+** Write something into a page.
+*/
+static int page_write(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  void *pPage;
+  int rc;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " PAGE DATA\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pPage) ) return TCL_ERROR;
+  rc = sqlitepager_write(pPage);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  strncpy((char*)pPage, argv[2], SQLITE_PAGE_SIZE-1);
+  ((char*)pPage)[SQLITE_PAGE_SIZE-1] = 0;
+  return TCL_OK;
+}
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest2_Init(Tcl_Interp *interp){
+  Tcl_CreateCommand(interp, "pager_open", pager_open, 0, 0);
+  Tcl_CreateCommand(interp, "pager_close", pager_close, 0, 0);
+  Tcl_CreateCommand(interp, "pager_commit", pager_commit, 0, 0);
+  Tcl_CreateCommand(interp, "pager_rollback", pager_rollback, 0, 0);
+  Tcl_CreateCommand(interp, "pager_stats", pager_stats, 0, 0);
+  Tcl_CreateCommand(interp, "pager_pagecount", pager_pagecount, 0, 0);
+  Tcl_CreateCommand(interp, "page_get", page_get, 0, 0);
+  Tcl_CreateCommand(interp, "page_lookup", page_lookup, 0, 0);
+  Tcl_CreateCommand(interp, "page_unref", page_unref, 0, 0);
+  Tcl_CreateCommand(interp, "page_read", page_read, 0, 0);
+  Tcl_CreateCommand(interp, "page_write", page_write, 0, 0);
+  Tcl_CreateCommand(interp, "page_number", page_number, 0, 0);
+  return TCL_OK;
+}
diff --git a/src/test3.c b/src/test3.c
new file mode 100644 (file)
index 0000000..289c45e
--- /dev/null
@@ -0,0 +1,844 @@
+/*
+** Copyright (c) 2001 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the GNU General Public
+** License as published by the Free Software Foundation; either
+** version 2 of the License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+** General Public License for more details.
+** 
+** You should have received a copy of the GNU General Public
+** License along with this library; if not, write to the
+** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+** Boston, MA  02111-1307, USA.
+**
+** Author contact information:
+**   drh@hwaci.com
+**   http://www.hwaci.com/drh/
+**
+*************************************************************************
+** Code for testing the btree.c module in SQLite.  This code
+** is not included in the SQLite library.  It is used for automated
+** testing of the SQLite library.
+**
+** $Id: test3.c,v 1.9 2001/08/20 00:33:58 drh Exp $
+*/
+#include "sqliteInt.h"
+#include "pager.h"
+#include "btree.h"
+#include "tcl.h"
+#include <stdlib.h>
+#include <string.h>
+
+/*
+** Interpret an SQLite error number
+*/
+static char *errorName(int rc){
+  char *zName;
+  switch( rc ){
+    case SQLITE_OK:         zName = "SQLITE_OK";          break;
+    case SQLITE_ERROR:      zName = "SQLITE_ERROR";       break;
+    case SQLITE_INTERNAL:   zName = "SQLITE_INTERNAL";    break;
+    case SQLITE_PERM:       zName = "SQLITE_PERM";        break;
+    case SQLITE_ABORT:      zName = "SQLITE_ABORT";       break;
+    case SQLITE_BUSY:       zName = "SQLITE_BUSY";        break;
+    case SQLITE_NOMEM:      zName = "SQLITE_NOMEM";       break;
+    case SQLITE_READONLY:   zName = "SQLITE_READONLY";    break;
+    case SQLITE_INTERRUPT:  zName = "SQLITE_INTERRUPT";   break;
+    case SQLITE_IOERR:      zName = "SQLITE_IOERR";       break;
+    case SQLITE_CORRUPT:    zName = "SQLITE_CORRUPT";     break;
+    case SQLITE_NOTFOUND:   zName = "SQLITE_NOTFOUND";    break;
+    case SQLITE_FULL:       zName = "SQLITE_FULL";        break;
+    case SQLITE_CANTOPEN:   zName = "SQLITE_CANTOPEN";    break;
+    case SQLITE_PROTOCOL:   zName = "SQLITE_PROTOCOL";    break;
+    default:                zName = "SQLITE_Unknown";     break;
+  }
+  return zName;
+}
+
+/*
+** Usage:   btree_open FILENAME
+**
+** Open a new database
+*/
+static int btree_open(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  int rc;
+  char zBuf[100];
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " FILENAME\"", 0);
+    return TCL_ERROR;
+  }
+  rc = sqliteBtreeOpen(argv[1], 0666, 10, &pBt);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  sprintf(zBuf,"0x%x",(int)pBt);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_close ID
+**
+** Close the given database.
+*/
+static int btree_close(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  rc = sqliteBtreeClose(pBt);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_begin_transaction ID
+**
+** Start a new transaction
+*/
+static int btree_begin_transaction(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  rc = sqliteBtreeBeginTrans(pBt);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_rollback ID
+**
+** Rollback changes
+*/
+static int btree_rollback(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  rc = sqliteBtreeRollback(pBt);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_commit ID
+**
+** Commit all changes
+*/
+static int btree_commit(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  int rc;
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  rc = sqliteBtreeCommit(pBt);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_create_table ID
+**
+** Create a new table in the database
+*/
+static int btree_create_table(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  int rc, iTable;
+  char zBuf[30];
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  rc = sqliteBtreeCreateTable(pBt, &iTable);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  sprintf(zBuf, "%d", iTable);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_drop_table ID TABLENUM
+**
+** Delete an entire table from the database
+*/
+static int btree_drop_table(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  int iTable;
+  int rc;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID TABLENUM\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  if( Tcl_GetInt(interp, argv[2], &iTable) ) return TCL_ERROR;
+  rc = sqliteBtreeDropTable(pBt, iTable);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_clear_table ID TABLENUM
+**
+** Remove all entries from the given table but keep the table around.
+*/
+static int btree_clear_table(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  int iTable;
+  int rc;
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID TABLENUM\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  if( Tcl_GetInt(interp, argv[2], &iTable) ) return TCL_ERROR;
+  rc = sqliteBtreeClearTable(pBt, iTable);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_get_meta ID
+**
+** Return meta data
+*/
+static int btree_get_meta(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  int rc;
+  int i;
+  int aMeta[SQLITE_N_BTREE_META];
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  rc = sqliteBtreeGetMeta(pBt, aMeta);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  for(i=0; i<SQLITE_N_BTREE_META; i++){
+    char zBuf[30];
+    sprintf(zBuf,"%d",aMeta[i]);
+    Tcl_AppendElement(interp, zBuf);
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_update_meta ID METADATA...
+**
+** Return meta data
+*/
+static int btree_update_meta(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  int rc;
+  int i;
+  int aMeta[SQLITE_N_BTREE_META];
+
+  if( argc!=2+SQLITE_N_BTREE_META ){
+    char zBuf[30];
+    sprintf(zBuf,"%d",SQLITE_N_BTREE_META);
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID METADATA...\" (METADATA is ", zBuf, " integers)", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  for(i=0; i<SQLITE_N_BTREE_META; i++){
+    if( Tcl_GetInt(interp, argv[i+2], &aMeta[i]) ) return TCL_ERROR;
+  }
+  rc = sqliteBtreeUpdateMeta(pBt, aMeta);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_page_dump ID PAGENUM
+**
+** Print a disassembly of a page on standard output
+*/
+static int btree_page_dump(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  int iPage;
+  int rc;
+
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  if( Tcl_GetInt(interp, argv[2], &iPage) ) return TCL_ERROR;
+  rc = sqliteBtreePageDump(pBt, iPage, 0);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_tree_dump ID PAGENUM
+**
+** Print a disassembly of a page and all its child pages on standard output
+*/
+static int btree_tree_dump(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  int iPage;
+  int rc;
+
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  if( Tcl_GetInt(interp, argv[2], &iPage) ) return TCL_ERROR;
+  rc = sqliteBtreePageDump(pBt, iPage, 1);
+  if( rc!=SQLITE_OK ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_pager_stats ID
+**
+** Returns pager statistics
+*/
+static int btree_pager_stats(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  int i;
+  int *a;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  a = sqlitepager_stats(sqliteBtreePager(pBt));
+  for(i=0; i<9; i++){
+    static char *zName[] = {
+      "ref", "page", "max", "size", "state", "err",
+      "hit", "miss", "ovfl",
+    };
+    char zBuf[100];
+    Tcl_AppendElement(interp, zName[i]);
+    sprintf(zBuf,"%d",a[i]);
+    Tcl_AppendElement(interp, zBuf);
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_pager_ref_dump ID
+**
+** Print out all outstanding pages.
+*/
+static int btree_pager_ref_dump(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  sqlitepager_refdump(sqliteBtreePager(pBt));
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_sanity_check ID ROOT ...
+**
+** Look through every page of the given BTree file to verify correct
+** formatting and linkage.  Return a line of text for each problem found.
+** Return an empty string if everything worked.
+*/
+static int btree_sanity_check(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  char *zResult;
+  int nRoot;
+  int *aRoot;
+  int i;
+
+  if( argc<3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID ROOT ...\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  nRoot = argc-2;
+  aRoot = malloc( sizeof(int)*(argc-2) );
+  for(i=0; i<argc-2; i++){
+    if( Tcl_GetInt(interp, argv[i+2], &aRoot[i]) ) return TCL_ERROR;
+  }
+  zResult = sqliteBtreeSanityCheck(pBt, aRoot, nRoot);
+  if( zResult ){
+    Tcl_AppendResult(interp, zResult, 0);
+    free(zResult); 
+  }
+  return TCL_OK;
+}
+
+/*
+** Usage:   btree_cursor ID TABLENUM
+**
+** Create a new cursor.  Return the ID for the cursor.
+*/
+static int btree_cursor(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  Btree *pBt;
+  int iTable;
+  BtCursor *pCur;
+  int rc;
+  char zBuf[30];
+
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID TABLENUM\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  if( Tcl_GetInt(interp, argv[2], &iTable) ) return TCL_ERROR;
+  rc = sqliteBtreeCursor(pBt, iTable, &pCur);
+  if( rc ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  sprintf(zBuf,"0x%x", (int)pCur);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return SQLITE_OK;
+}
+
+/*
+** Usage:   btree_close_cursor ID
+**
+** Close a cursor opened using btree_cursor.
+*/
+static int btree_close_cursor(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  BtCursor *pCur;
+  int rc;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pCur) ) return TCL_ERROR;
+  rc = sqliteBtreeCloseCursor(pCur);
+  if( rc ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return SQLITE_OK;
+}
+
+/*
+** Usage:   btree_move_to ID KEY
+**
+** Move the cursor to the entry with the given key.
+*/
+static int btree_move_to(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  BtCursor *pCur;
+  int rc;
+  int res;
+  char zBuf[20];
+
+  if( argc!=3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID KEY\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pCur) ) return TCL_ERROR;
+  rc = sqliteBtreeMoveto(pCur, argv[2], strlen(argv[2]), &res);  
+  if( rc ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  sprintf(zBuf,"%d",res);
+  Tcl_AppendResult(interp, zBuf, 0);
+  return SQLITE_OK;
+}
+
+/*
+** Usage:   btree_delete ID
+**
+** Delete the entry that the cursor is pointing to
+*/
+static int btree_delete(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  BtCursor *pCur;
+  int rc;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pCur) ) return TCL_ERROR;
+  rc = sqliteBtreeDelete(pCur);
+  if( rc ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return SQLITE_OK;
+}
+
+/*
+** Usage:   btree_insert ID KEY DATA
+**
+** Create a new entry with the given key and data.  If an entry already
+** exists with the same key the old entry is overwritten.
+*/
+static int btree_insert(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  BtCursor *pCur;
+  int rc;
+
+  if( argc!=4 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID KEY DATA\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pCur) ) return TCL_ERROR;
+  rc = sqliteBtreeInsert(pCur, argv[2], strlen(argv[2]),
+                         argv[3], strlen(argv[3]));
+  if( rc ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return SQLITE_OK;
+}
+
+/*
+** Usage:   btree_next ID
+**
+** Move the cursor to the next entry in the table.
+*/
+static int btree_next(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  BtCursor *pCur;
+  int rc;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pCur) ) return TCL_ERROR;
+  rc = sqliteBtreeNext(pCur, 0);
+  if( rc ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  return SQLITE_OK;
+}
+
+/*
+** Usage:   btree_key ID
+**
+** Return the key for the entry at which the cursor is pointing.
+*/
+static int btree_key(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  BtCursor *pCur;
+  int rc;
+  int n;
+  char *zBuf;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pCur) ) return TCL_ERROR;
+  sqliteBtreeKeySize(pCur, &n);
+  zBuf = malloc( n+1 );
+  rc = sqliteBtreeKey(pCur, 0, n, zBuf);
+  if( rc ){
+    free(zBuf);
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  zBuf[n] = 0;
+  Tcl_AppendResult(interp, zBuf, 0);
+  free(zBuf);
+  return SQLITE_OK;
+}
+
+/*
+** Usage:   btree_data ID
+**
+** Return the data for the entry at which the cursor is pointing.
+*/
+static int btree_data(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  BtCursor *pCur;
+  int rc;
+  int n;
+  char *zBuf;
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pCur) ) return TCL_ERROR;
+  sqliteBtreeDataSize(pCur, &n);
+  zBuf = malloc( n+1 );
+  rc = sqliteBtreeData(pCur, 0, n, zBuf);
+  if( rc ){
+    free(zBuf);
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  zBuf[n] = 0;
+  Tcl_AppendResult(interp, zBuf, 0);
+  free(zBuf);
+  return SQLITE_OK;
+}
+
+/*
+** Usage:   btree_cursor_dump ID
+**
+** Return eight integers containing information about the entry the
+** cursor is pointing to:
+**
+**   aResult[0] =  The page number
+**   aResult[1] =  The entry number
+**   aResult[2] =  Total number of entries on this page
+**   aResult[3] =  Size of this entry
+**   aResult[4] =  Number of free bytes on this page
+**   aResult[5] =  Number of free blocks on the page
+**   aResult[6] =  Page number of the left child of this entry
+**   aResult[7] =  Page number of the right child for the whole page
+*/
+static int btree_cursor_dump(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  char **argv            /* Text of each argument */
+){
+  BtCursor *pCur;
+  int rc;
+  int i, j;
+  int aResult[8];
+  char zBuf[400];
+
+  if( argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pCur) ) return TCL_ERROR;
+  rc = sqliteBtreeCursorDump(pCur, aResult);
+  if( rc ){
+    Tcl_AppendResult(interp, errorName(rc), 0);
+    return TCL_ERROR;
+  }
+  j = 0;
+  for(i=0; i<sizeof(aResult)/sizeof(aResult[0]); i++){
+    sprintf(&zBuf[j]," %d", aResult[i]);
+    j += strlen(&zBuf[j]);
+  }
+  Tcl_AppendResult(interp, &zBuf[1], 0);
+  return SQLITE_OK;
+}
+
+/*
+** Register commands with the TCL interpreter.
+*/
+int Sqlitetest3_Init(Tcl_Interp *interp){
+  Tcl_CreateCommand(interp, "btree_open", btree_open, 0, 0);
+  Tcl_CreateCommand(interp, "btree_close", btree_close, 0, 0);
+  Tcl_CreateCommand(interp, "btree_begin_transaction",
+      btree_begin_transaction, 0, 0);
+  Tcl_CreateCommand(interp, "btree_commit", btree_commit, 0, 0);
+  Tcl_CreateCommand(interp, "btree_rollback", btree_rollback, 0, 0);
+  Tcl_CreateCommand(interp, "btree_create_table", btree_create_table, 0, 0);
+  Tcl_CreateCommand(interp, "btree_drop_table", btree_drop_table, 0, 0);
+  Tcl_CreateCommand(interp, "btree_clear_table", btree_clear_table, 0, 0);
+  Tcl_CreateCommand(interp, "btree_get_meta", btree_get_meta, 0, 0);
+  Tcl_CreateCommand(interp, "btree_update_meta", btree_update_meta, 0, 0);
+  Tcl_CreateCommand(interp, "btree_page_dump", btree_page_dump, 0, 0);
+  Tcl_CreateCommand(interp, "btree_tree_dump", btree_tree_dump, 0, 0);
+  Tcl_CreateCommand(interp, "btree_pager_stats", btree_pager_stats, 0, 0);
+  Tcl_CreateCommand(interp, "btree_pager_ref_dump", btree_pager_ref_dump, 0, 0);
+  Tcl_CreateCommand(interp, "btree_cursor", btree_cursor, 0, 0);
+  Tcl_CreateCommand(interp, "btree_close_cursor", btree_close_cursor, 0, 0);
+  Tcl_CreateCommand(interp, "btree_move_to", btree_move_to, 0, 0);
+  Tcl_CreateCommand(interp, "btree_delete", btree_delete, 0, 0);
+  Tcl_CreateCommand(interp, "btree_insert", btree_insert, 0, 0);
+  Tcl_CreateCommand(interp, "btree_next", btree_next, 0, 0);
+  Tcl_CreateCommand(interp, "btree_key", btree_key, 0, 0);
+  Tcl_CreateCommand(interp, "btree_data", btree_data, 0, 0);
+  Tcl_CreateCommand(interp, "btree_cursor_dump", btree_cursor_dump, 0, 0);
+  Tcl_CreateCommand(interp, "btree_sanity_check", btree_sanity_check, 0, 0);
+  Tcl_LinkVar(interp, "pager_refinfo_enable", (char*)&pager_refinfo_enable,
+     TCL_LINK_INT);
+  return TCL_OK;
+}
diff --git a/test/btree.test b/test/btree.test
new file mode 100644 (file)
index 0000000..9cc8cb1
--- /dev/null
@@ -0,0 +1,1030 @@
+# Copyright (c) 1999, 2000 D. Richard Hipp
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA  02111-1307, USA.
+#
+# Author contact information:
+#   drh@hwaci.com
+#   http://www.hwaci.com/drh/
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this script is btree database backend
+#
+# $Id: btree.test,v 1.8 2001/08/20 00:33:58 drh Exp $
+
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+if {[info commands btree_open]!=""} {
+
+# Basic functionality.  Open and close a database.
+#
+do_test btree-1.1 {
+  file delete -force test1.bt
+  file delete -force test1.bt-journal
+  set rc [catch {btree_open test1.bt} ::b1]
+} {0}
+
+# The second element of the list returned by btree_pager_stats is the
+# number of pages currently checked out.  We'll be checking this value
+# frequently during this test script, to make sure the btree library
+# is properly releasing the pages it checks out, and thus avoiding
+# page leaks.
+#
+do_test btree-1.1.1 {
+  lindex [btree_pager_stats $::b1] 1
+} {0}
+do_test btree-1.2 {
+  set rc [catch {btree_open test1.bt} ::b2]
+} {0}
+do_test btree-1.3 {
+  set rc [catch {btree_close $::b2} msg]
+  lappend rc $msg
+} {0 {}}
+
+# Do an insert and verify that the database file grows in size.
+#
+do_test btree-1.4 {
+  set rc [catch {btree_begin_transaction $::b1} msg]
+  lappend rc $msg
+} {0 {}}
+do_test btree-1.4.1 {
+  lindex [btree_pager_stats $::b1] 1
+} {1}
+do_test btree-1.5 {
+  set rc [catch {btree_cursor $::b1 2} ::c1]
+  if {$rc} {lappend rc $::c1}
+  set rc
+} {0}
+do_test btree-1.6 {
+  set rc [catch {btree_insert $::c1 one 1.00} msg]
+  lappend rc $msg
+} {0 {}}
+do_test btree-1.7 {
+  btree_key $::c1
+} {one}
+do_test btree-1.8 {
+  btree_data $::c1
+} {1.00}
+do_test btree-1.9 {
+  set rc [catch {btree_close_cursor $::c1} msg]
+  lappend rc $msg
+} {0 {}}
+do_test btree-1.10 {
+  set rc [catch {btree_commit $::b1} msg]
+  lappend rc $msg
+} {0 {}}
+do_test btree-1.11 {
+  file size test1.bt
+} {2048}
+do_test btree-1.12 {
+  lindex [btree_pager_stats $::b1] 1
+} {0}
+
+# Reopen the database and attempt to read the record that we wrote.
+#
+do_test btree-2.1 {
+  set rc [catch {btree_cursor $::b1 2} ::c1]
+  if {$rc} {lappend rc $::c1}
+  set rc
+} {0}
+do_test btree-2.2 {
+  btree_move_to $::c1 abc
+} {1}
+do_test btree-2.3 {
+  btree_move_to $::c1 xyz
+} {-1}
+do_test btree-2.4 {
+  btree_move_to $::c1 one
+} {0}
+do_test btree-2.5 {
+  btree_key $::c1
+} {one}
+do_test btree-2.6 {
+  btree_data $::c1
+} {1.00}
+do_test btree-2.7 {
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+
+# Do some additional inserts
+#
+do_test btree-3.1 {
+  btree_begin_transaction $::b1
+  btree_insert $::c1 two 2.00
+  btree_key $::c1
+} {two}
+do_test btree-3.1.1 {
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+do_test btree-3.2 {
+  btree_insert $::c1 three 3.00
+  btree_key $::c1
+} {three}
+do_test btree-3.4 {
+  btree_insert $::c1 four 4.00
+  btree_key $::c1
+} {four}
+do_test btree-3.5 {
+  btree_insert $::c1 five 5.00
+  btree_key $::c1
+} {five}
+do_test btree-3.6 {
+  btree_insert $::c1 six 6.00
+  btree_key $::c1
+} {six}
+#btree_page_dump $::b1 2
+do_test btree-3.7 {
+  set rc [btree_move_to $::c1 {}]
+  expr {$rc>0}
+} {1}
+do_test btree-3.8 {
+  btree_key $::c1
+} {five}
+do_test btree-3.9 {
+  btree_data $::c1
+} {5.00}
+do_test btree-3.10 {
+  btree_next $::c1
+  btree_key $::c1
+} {four}
+do_test btree-3.11 {
+  btree_data $::c1
+} {4.00}
+do_test btree-3.12 {
+  btree_next $::c1
+  btree_key $::c1
+} {one}
+do_test btree-3.13 {
+  btree_data $::c1
+} {1.00}
+do_test btree-3.14 {
+  btree_next $::c1
+  btree_key $::c1
+} {six}
+do_test btree-3.15 {
+  btree_data $::c1
+} {6.00}
+do_test btree-3.16 {
+  btree_next $::c1
+  btree_key $::c1
+} {three}
+do_test btree-3.17 {
+  btree_data $::c1
+} {3.00}
+do_test btree-3.18 {
+  btree_next $::c1
+  btree_key $::c1
+} {two}
+do_test btree-3.19 {
+  btree_data $::c1
+} {2.00}
+do_test btree-3.20 {
+  btree_next $::c1
+  btree_key $::c1
+} {}
+do_test btree-3.21 {
+  btree_data $::c1
+} {}
+
+# Commit the changes, reopen and reread the data
+#
+do_test btree-3.22 {
+  set rc [catch {btree_close_cursor $::c1} msg]
+  lappend rc $msg
+} {0 {}}
+do_test btree-3.22.1 {
+  lindex [btree_pager_stats $::b1] 1
+} {1}
+do_test btree-3.23 {
+  set rc [catch {btree_commit $::b1} msg]
+  lappend rc $msg
+} {0 {}}
+do_test btree-3.23.1 {
+  lindex [btree_pager_stats $::b1] 1
+} {0}
+do_test btree-3.24 {
+  file size test1.bt
+} {2048}
+do_test btree-3.25 {
+  set rc [catch {btree_cursor $::b1 2} ::c1]
+  if {$rc} {lappend rc $::c1}
+  set rc
+} {0}
+do_test btree-3.25.1 {
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+do_test btree-3.26 {
+  set rc [btree_move_to $::c1 {}]
+  expr {$rc>0}
+} {1}
+do_test btree-3.27 {
+  btree_key $::c1
+} {five}
+do_test btree-3.28 {
+  btree_data $::c1
+} {5.00}
+do_test btree-3.29 {
+  btree_next $::c1
+  btree_key $::c1
+} {four}
+do_test btree-3.30 {
+  btree_data $::c1
+} {4.00}
+do_test btree-3.31 {
+  btree_next $::c1
+  btree_key $::c1
+} {one}
+do_test btree-3.32 {
+  btree_data $::c1
+} {1.00}
+do_test btree-3.33 {
+  btree_next $::c1
+  btree_key $::c1
+} {six}
+do_test btree-3.34 {
+  btree_data $::c1
+} {6.00}
+do_test btree-3.35 {
+  btree_next $::c1
+  btree_key $::c1
+} {three}
+do_test btree-3.36 {
+  btree_data $::c1
+} {3.00}
+do_test btree-3.37 {
+  btree_next $::c1
+  btree_key $::c1
+} {two}
+do_test btree-3.38 {
+  btree_data $::c1
+} {2.00}
+do_test btree-3.39 {
+  btree_next $::c1
+  btree_key $::c1
+} {}
+do_test btree-3.40 {
+  btree_data $::c1
+} {}
+do_test btree-3.41 {
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+
+
+# Now try a delete
+#
+do_test btree-4.1 {
+  btree_begin_transaction $::b1
+  btree_move_to $::c1 one
+  btree_key $::c1
+} {one}
+do_test btree-4.1.1 {
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+do_test btree-4.2 {
+  btree_delete $::c1
+} {}
+do_test btree-4.3 {
+  btree_key $::c1
+} {six}
+do_test btree-4.4 {
+  btree_next $::c1
+  btree_key $::c1
+} {six}
+do_test btree-4.5 {
+  btree_next $::c1
+  btree_key $::c1
+} {three}
+do_test btree-4.4 {
+  btree_move_to $::c1 {}
+  set r {}
+  while 1 {
+    set key [btree_key $::c1]
+    if {$key==""} break
+    lappend r $key
+    lappend r [btree_data $::c1]
+    btree_next $::c1
+  }
+  set r   
+} {five 5.00 four 4.00 six 6.00 three 3.00 two 2.00}
+
+# Commit and make sure the delete is still there.
+#
+do_test btree-4.5 {
+  btree_commit $::b1
+  btree_move_to $::c1 {}
+  set r {}
+  while 1 {
+    set key [btree_key $::c1]
+    if {$key==""} break
+    lappend r $key
+    lappend r [btree_data $::c1]
+    btree_next $::c1
+  }
+  set r   
+} {five 5.00 four 4.00 six 6.00 three 3.00 two 2.00}
+
+# Completely close the database and reopen it.  Then check
+# the data again.
+#
+do_test btree-4.6 {
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+do_test btree-4.7 {
+  btree_close_cursor $::c1
+  lindex [btree_pager_stats $::b1] 1
+} {0}
+do_test btree-4.8 {
+  btree_close $::b1
+  set ::b1 [btree_open test1.bt]
+  set ::c1 [btree_cursor $::b1 2]
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+do_test btree-4.9 {
+  set r {}
+  while 1 {
+    set key [btree_key $::c1]
+    if {$key==""} break
+    lappend r $key
+    lappend r [btree_data $::c1]
+    btree_next $::c1
+  }
+  set r   
+} {five 5.00 four 4.00 six 6.00 three 3.00 two 2.00}
+
+# Try to read and write meta data
+#
+do_test btree-5.1 {
+  btree_get_meta $::b1
+} {0 0 0 0}
+do_test btree-5.2 {
+  set rc [catch {btree_update_meta $::b1 1 2 3 4} msg]
+  lappend rc $msg
+} {1 SQLITE_ERROR}
+do_test btree-5.3 {
+  btree_begin_transaction $::b1
+  set rc [catch {btree_update_meta $::b1 1 2 3 4} msg]
+  lappend rc $msg
+} {0 {}}
+do_test btree-5.4 {
+  btree_get_meta $::b1
+} {0 2 3 4}
+do_test btree-5.5 {
+  btree_close_cursor $::c1
+  btree_rollback $::b1
+  btree_get_meta $::b1
+} {0 0 0 0}
+do_test btree-5.6 {
+  btree_begin_transaction $::b1
+  btree_update_meta $::b1 999 10 20 30
+  btree_commit $::b1
+  btree_get_meta $::b1
+} {0 10 20 30}
+
+proc select_all {cursor} {
+  set r {}
+  btree_move_to $cursor {}
+  while 1 {
+    set key [btree_key $cursor]
+    if {$key==""} break
+    lappend r $key
+    lappend r [btree_data $cursor]
+    btree_next $cursor
+  }
+  return $r
+}
+proc select_keys {cursor} {
+  set r {}
+  btree_move_to $cursor {}
+  while 1 {
+    set key [btree_key $cursor]
+    if {$key==""} break
+    lappend r $key
+    btree_next $cursor
+  }
+  return $r
+}
+
+# Try to create a new table in the database file
+#
+do_test btree-6.1 {
+  set rc [catch {btree_create_table $::b1} msg]
+  lappend rc $msg
+} {1 SQLITE_ERROR}
+do_test btree-6.2 {
+  btree_begin_transaction $::b1
+  set ::t2 [btree_create_table $::b1]
+} {3}
+do_test btree-6.2.1 {
+  lindex [btree_pager_stats $::b1] 1
+} {1}
+do_test btree-6.2.2 {
+  set ::c2 [btree_cursor $::b1 $::t2]
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+do_test btree-6.2.3 {
+  btree_insert $::c2 ten 10
+  btree_key $::c2
+} {ten}
+do_test btree-6.3 {
+  btree_commit $::b1
+  set ::c1 [btree_cursor $::b1 2]
+  lindex [btree_pager_stats $::b1] 1
+} {3}
+do_test btree-6.3.1 {
+  select_all $::c1
+} {five 5.00 four 4.00 six 6.00 three 3.00 two 2.00}
+#btree_page_dump $::b1 3
+do_test btree-6.4 {
+  select_all $::c2
+} {ten 10}
+
+# Drop the new table, then create it again anew.
+#
+do_test btree-6.5 {
+  btree_begin_transaction $::b1
+} {}
+do_test btree-6.6 {
+  btree_close_cursor $::c2
+} {}
+do_test btree-6.6.1 {
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+do_test btree-6.7 {
+  btree_drop_table $::b1 $::t2
+} {}
+do_test btree-6.7.1 {
+  lindex [btree_get_meta $::b1] 0
+} {1}
+do_test btree-6.8 {
+  set ::t2 [btree_create_table $::b1]
+} {3}
+do_test btree-6.8.1 {
+  lindex [btree_get_meta $::b1] 0
+} {0}
+do_test btree-6.9 {
+  set ::c2 [btree_cursor $::b1 $::t2]
+  lindex [btree_pager_stats $::b1] 1
+} {3}
+
+do_test btree-6.9.1 {
+  btree_move_to $::c2 {}
+  btree_key $::c2
+} {}
+
+# If we drop table 2 it just clears the table.  Table 2 always exists.
+#
+do_test btree-6.10 {
+  btree_close_cursor $::c1
+  btree_drop_table $::b1 2
+  set ::c1 [btree_cursor $::b1 2]
+  btree_move_to $::c1 {}
+  btree_key $::c1
+} {}
+do_test btree-6.11 {
+  btree_commit $::b1
+  select_all $::c1
+} {}
+do_test btree-6.12 {
+  select_all $::c2
+} {}
+do_test btree-6.13 {
+  btree_close_cursor $::c2
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+
+# Check to see that pages defragment properly.  To do this test we will
+# 
+#   1.  Fill the first page table 2 with data.
+#   2.  Delete every other entry of table 2. 
+#   3.  Insert a single entry that requires more contiguous
+#       space than is available.
+#
+do_test btree-7.1 {
+  btree_begin_transaction $::b1
+} {}
+catch {unset key}
+catch {unset data}
+do_test btree-7.2 {
+  for {set i 0} {$i<36} {incr i} {
+    set key [format %03d $i]
+    set data "*** $key ***"
+    btree_insert $::c1 $key $data
+  }
+  lrange [btree_cursor_dump $::c1] 4 5
+} {8 1}
+do_test btree-7.3 {
+  btree_move_to $::c1 000
+  while {[btree_key $::c1]!=""} {
+    btree_delete $::c1
+    btree_next $::c1
+    btree_next $::c1
+  }
+  lrange [btree_cursor_dump $::c1] 4 5
+} {512 19}
+#btree_page_dump $::b1 2
+do_test btree-7.4 {
+  btree_insert $::c1 018 {*** 018 ***+++}
+  btree_key $::c1
+} {018}
+do_test btree-7.5 {
+  lrange [btree_cursor_dump $::c1] 4 5
+} {480 1}
+#btree_page_dump $::b1 2
+
+# Delete an entry to make a hole of a known size, then immediately recreate
+# that entry.  This tests the path into allocateSpace where the hole exactly
+# matches the size of the desired space.
+#
+do_test btree-7.6 {
+  btree_move_to $::c1 007
+  btree_delete $::c1
+  btree_move_to $::c1 011
+  btree_delete $::c1
+} {}
+do_test btree-7.7 {
+  lindex [btree_cursor_dump $::c1] 5
+} {3}
+#btree_page_dump $::b1 2
+do_test btree-7.8 {
+  btree_insert $::c1 007 {*** 007 ***}
+  lindex [btree_cursor_dump $::c1] 5
+} {2}
+#btree_page_dump $::b1 2
+
+# Make sure the freeSpace() routine properly coaleses adjacent memory blocks
+#
+do_test btree-7.9 {
+  btree_move_to $::c1 013
+  btree_delete $::c1
+  lrange [btree_cursor_dump $::c1] 4 5
+} {536 2}
+do_test btree-7.10 {
+  btree_move_to $::c1 009
+  btree_delete $::c1
+  lrange [btree_cursor_dump $::c1] 4 5
+} {564 2}
+do_test btree-7.11 {
+  btree_move_to $::c1 018
+  btree_delete $::c1
+  lrange [btree_cursor_dump $::c1] 4 5
+} {596 2}
+do_test btree-7.13 {
+  btree_move_to $::c1 033
+  btree_delete $::c1
+  lrange [btree_cursor_dump $::c1] 4 5
+} {624 3}
+do_test btree-7.14 {
+  btree_move_to $::c1 035
+  btree_delete $::c1
+  lrange [btree_cursor_dump $::c1] 4 5
+} {652 2}
+#btree_page_dump $::b1 2
+do_test btree-7.15 {
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+
+# Check to see that data on overflow pages work correctly.
+#
+do_test btree-8.1 {
+  set data "*** This is a very long key "
+  while {[string length $data]<256} {append data $data}
+  set ::data $data
+  btree_insert $::c1 020 $data
+} {}
+#btree_page_dump $::b1 2
+do_test btree-8.1.1 {
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+#btree_pager_ref_dump $::b1
+do_test btree-8.2 {
+  string length [btree_data $::c1]
+} [string length $::data]
+do_test btree-8.3 {
+  btree_data $::c1
+} $::data
+do_test btree-8.4 {
+  btree_delete $::c1
+} {}
+do_test btree-8.4.1 {
+  lindex [btree_get_meta $::b1] 0
+} [expr {int(([string length $::data]-238+1019)/1020)}]
+do_test btree-8.5 {
+  set data "*** This is an even longer key"
+  while {[string length $data]<2000} {append data $data}
+  set ::data $data
+  btree_insert $::c1 020 $data
+} {}
+do_test btree-8.6 {
+  string length [btree_data $::c1]
+} [string length $::data]
+do_test btree-8.7 {
+  btree_data $::c1
+} $::data
+do_test btree-8.8 {
+  btree_commit $::b1
+  btree_data $::c1
+} $::data
+do_test btree-8.9 {
+  btree_close_cursor $::c1
+  btree_close $::b1
+  set ::b1 [btree_open test1.bt]
+  set ::c1 [btree_cursor $::b1 2]
+  btree_move_to $::c1 020
+  btree_data $::c1
+} $::data
+do_test btree-8.10 {
+  btree_begin_transaction $::b1
+  btree_delete $::c1
+} {}
+do_test btree-8.11 {
+  lindex [btree_get_meta $::b1] 0
+} [expr {int(([string length $::data]-238+1019)/1020)}]
+
+# Now check out keys on overflow pages.
+#
+do_test btree-8.12 {
+  set ::keyprefix "This is a long prefix to a key "
+  while {[string length $::keyprefix]<256} {append ::keyprefix $::keyprefix}
+  btree_close_cursor $::c1
+  btree_drop_table $::b1 2
+  lindex [btree_get_meta $::b1] 0
+} {4}
+do_test btree-8.12.1 {
+  set ::c1 [btree_cursor $::b1 2]
+  btree_insert $::c1 ${::keyprefix}1 1
+  btree_data $::c1
+} {1}
+do_test btree-8.13 {
+  btree_key $::c1
+} ${keyprefix}1
+do_test btree-8.14 {
+  btree_insert $::c1 ${::keyprefix}2 2
+  btree_insert $::c1 ${::keyprefix}3 3
+  btree_key $::c1
+} ${keyprefix}3
+do_test btree-8.15 {
+  btree_move_to $::c1 ${::keyprefix}2
+  btree_data $::c1
+} {2}
+do_test btree-8.16 {
+  btree_move_to $::c1 ${::keyprefix}1
+  btree_data $::c1
+} {1}
+do_test btree-8.17 {
+  btree_move_to $::c1 ${::keyprefix}3
+  btree_data $::c1
+} {3}
+do_test btree-8.18 {
+  lindex [btree_get_meta $::b1] 0
+} {1}
+do_test btree-8.19 {
+  btree_move_to $::c1 ${::keyprefix}2
+  btree_key $::c1
+} ${::keyprefix}2
+#btree_page_dump $::b1 2
+do_test btree-8.20 {
+  btree_delete $::c1
+  btree_next $::c1
+  btree_key $::c1
+} ${::keyprefix}3
+#btree_page_dump $::b1 2
+do_test btree-8.21 {
+  lindex [btree_get_meta $::b1] 0
+} {2}
+do_test btree-8.22 {
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+do_test btree-8.23 {
+  btree_close_cursor $::c1
+  btree_drop_table $::b1 2
+  set ::c1 [btree_cursor $::b1 2]
+  lindex [btree_get_meta $::b1] 0
+} {4}
+do_test btree-8.24 {
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+#btree_pager_ref_dump $::b1
+
+# Check page splitting logic
+#
+do_test btree-9.1 {
+  for {set i 1} {$i<=19} {incr i} {
+    set key [format %03d $i]
+    set data "*** $key *** $key *** $key *** $key ***"
+    btree_insert $::c1 $key $data
+  }
+} {}
+#btree_tree_dump $::b1 2
+#btree_pager_ref_dump $::b1
+#set pager_refinfo_enable 1
+do_test btree-9.2 {
+  btree_insert $::c1 020 {*** 020 *** 020 *** 020 *** 020 ***}
+  select_keys $::c1
+} {001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020}
+#btree_page_dump $::b1 5
+#btree_page_dump $::b1 2
+#btree_page_dump $::b1 7
+#btree_pager_ref_dump $::b1
+#set pager_refinfo_enable 0
+
+# The previous "select_keys" command left the cursor pointing at the root
+# page.  So there should only be two pages checked out.  2 (the root) and
+# page 1.
+do_test btree-9.2.1 {
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+for {set i 1} {$i<=20} {incr i} {
+  do_test btree-9.3.$i.1 [subst {
+    btree_move_to $::c1 [format %03d $i]
+    btree_key $::c1
+  }] [format %03d $i]
+  do_test btree-9.3.$i.2 [subst {
+    btree_move_to $::c1 [format %03d $i]
+    string range \[btree_data $::c1\] 0 10
+  }] "*** [format %03d $i] ***"
+}
+do_test btree-9.4.1 {
+  lindex [btree_pager_stats $::b1] 1
+} {3}
+
+# Check the page joining logic.
+#
+#btree_page_dump $::b1 2
+#btree_pager_ref_dump $::b1
+do_test btree-9.4.2 {
+  btree_move_to $::c1 005
+  btree_delete $::c1
+} {}
+#btree_page_dump $::b1 2
+for {set i 1} {$i<=19} {incr i} {
+  if {$i==5} continue
+  do_test btree-9.5.$i.1 [subst {
+    btree_move_to $::c1 [format %03d $i]
+    btree_key $::c1
+  }] [format %03d $i]
+  do_test btree-9.5.$i.2 [subst {
+    btree_move_to $::c1 [format %03d $i]
+    string range \[btree_data $::c1\] 0 10
+  }] "*** [format %03d $i] ***"
+}
+#btree_pager_ref_dump $::b1
+do_test btree-9.6 {
+  btree_close_cursor $::c1
+  lindex [btree_pager_stats $::b1] 1
+} {1}
+do_test btree-9.7 {
+  btree_rollback $::b1
+  lindex [btree_pager_stats $::b1] 1
+} {0}
+
+# Create a tree of depth two.  That is, there is a single divider entry
+# on the root pages and two leaf pages.  Then delete the divider entry
+# see what happens.
+#
+do_test btree-10.1 {
+  btree_begin_transaction $::b1
+  btree_drop_table $::b1 2
+  lindex [btree_pager_stats $::b1] 1
+} {1}
+do_test btree-10.2 {
+  set ::c1 [btree_cursor $::b1 2]
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+do_test btree-10.3 {
+  for {set i 1} {$i<=20} {incr i} {
+    set key [format %03d $i]
+    set data "*** $key *** $key *** $key *** $key ***"
+    btree_insert $::c1 $key $data
+  }
+  select_keys $::c1
+} {001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020}
+#btree_page_dump $::b1 7
+#btree_page_dump $::b1 2
+#btree_page_dump $::b1 6
+do_test btree-10.4 {
+  btree_move_to $::c1 011
+  btree_delete $::c1
+  select_keys $::c1
+} {001 002 003 004 005 006 007 008 009 010 012 013 014 015 016 017 018 019 020}
+#btree_tree_dump $::b1 2
+#btree_pager_ref_dump $::b1
+for {set i 1} {$i<=20} {incr i} {
+  do_test btree-10.5.$i {
+    btree_move_to $::c1 [format %03d $i]
+    lindex [btree_pager_stats $::b1] 1
+  } {2}
+  #btree_pager_ref_dump $::b1
+  #btree_tree_dump $::b1 2
+}
+
+# Create a tree with lots more pages
+#
+catch {unset ::data}
+catch {unset ::key}
+for {set i 21} {$i<=1000} {incr i} {
+  do_test btree-11.1.$i.1 {
+    set key [format %03d $i]
+    set ::data "*** $key *** $key *** $key *** $key ***"
+    btree_insert $::c1 $key $data
+    btree_key $::c1
+  } [format %03d $i]
+  do_test btree-11.1.$i.2 {
+    btree_data $::c1
+  } $::data
+  set ::key [format %03d [expr {$i/2}]]
+  if {$::key=="011"} {set ::key 010}
+  do_test btree-11.1.$i.3 {
+    btree_move_to $::c1 $::key
+    btree_key $::c1
+  } $::key
+}
+catch {unset ::data}
+catch {unset ::key}
+
+# Make sure our reference count is still correct.
+#
+do_test btree-11.2 {
+  btree_close_cursor $::c1
+  lindex [btree_pager_stats $::b1] 1
+} {1}
+do_test btree-11.3 {
+  set ::c1 [btree_cursor $::b1 2]
+  lindex [btree_pager_stats $::b1] 1
+} {2}
+#btree_page_dump $::b1 2
+
+# Delete the dividers on the root page
+#
+do_test btree-11.4 {
+  btree_move_to $::c1 257
+  btree_delete $::c1
+  btree_next $::c1
+  btree_key $::c1
+} {258}
+do_test btree-11.4.1 {
+  btree_move_to $::c1 256
+  btree_key $::c1
+} {256}
+do_test btree-11.4.2 {
+  btree_move_to $::c1 258
+  btree_key $::c1
+} {258}
+do_test btree-11.4.3 {
+  btree_move_to $::c1 259
+  btree_key $::c1
+} {259}
+do_test btree-11.4.4 {
+  btree_move_to $::c1 257
+  set n [btree_key $::c1]
+  expr {$n==256||$n==258}
+} {1}
+do_test btree-11.5 {
+  btree_move_to $::c1 513
+  btree_delete $::c1
+  btree_next $::c1
+  btree_key $::c1
+} {514}
+do_test btree-11.5.1 {
+  btree_move_to $::c1 512
+  btree_key $::c1
+} {512}
+do_test btree-11.5.2 {
+  btree_move_to $::c1 514
+  btree_key $::c1
+} {514}
+do_test btree-11.5.3 {
+  btree_move_to $::c1 515
+  btree_key $::c1
+} {515}
+do_test btree-11.5.4 {
+  btree_move_to $::c1 513
+  set n [btree_key $::c1]
+  expr {$n==512||$n==514}
+} {1}
+do_test btree-11.6 {
+  btree_move_to $::c1 769
+  btree_delete $::c1
+  btree_next $::c1
+  btree_key $::c1
+} {770}
+do_test btree-11.6.1 {
+  btree_move_to $::c1 768
+  btree_key $::c1
+} {768}
+do_test btree-11.6.2 {
+  btree_move_to $::c1 771
+  btree_key $::c1
+} {771}
+do_test btree-11.6.3 {
+  btree_move_to $::c1 770
+  btree_key $::c1
+} {770}
+do_test btree-11.6.4 {
+  btree_move_to $::c1 769
+  set n [btree_key $::c1]
+  expr {$n==768||$n==770}
+} {1}
+#btree_page_dump $::b1 2
+#btree_page_dump $::b1 25
+
+# Change the data on an intermediate node such that the node becomes overfull
+# and has to split.  We happen to know that intermediate nodes exist on
+# 337, 401 and 465 by the btree_page_dumps above
+#
+catch {unset ::data}
+set ::data {This is going to be a very long data segment}
+append ::data $::data
+append ::data $::data
+do_test btree-12.1 {
+  btree_insert $::c1 337 $::data
+  btree_data $::c1
+} $::data
+do_test btree-12.2 {
+  btree_insert $::c1 401 $::data
+  btree_data $::c1
+} $::data
+do_test btree-12.3 {
+  btree_insert $::c1 465 $::data
+  btree_data $::c1
+} $::data
+do_test btree-12.4 {
+  btree_move_to $::c1 337
+  btree_key $::c1
+} {337}
+do_test btree-12.5 {
+  btree_data $::c1
+} $::data
+do_test btree-12.6 {
+  btree_next $::c1
+  btree_key $::c1
+} {338}
+do_test btree-12.7 {
+  btree_move_to $::c1 464
+  btree_key $::c1
+} {464}
+do_test btree-12.8 {
+  btree_next $::c1
+  btree_data $::c1
+} $::data
+do_test btree-12.9 {
+  btree_next $::c1
+  btree_key $::c1
+} {466}
+do_test btree-12.10 {
+  btree_move_to $::c1 400
+  btree_key $::c1
+} {400}
+do_test btree-12.11 {
+  btree_next $::c1
+  btree_data $::c1
+} $::data
+do_test btree-12.12 {
+  btree_next $::c1
+  btree_key $::c1
+} {402}
+do_test btree-13.1 {
+  btree_sanity_check $::b1 2 3
+} {}
+
+# To Do:
+#
+#   1.  Do some deletes from the 3-layer tree
+#   2.  Commit and reopen the database
+#   3.  Read every 15th entry and make sure it works
+#   4.  Implement btree_sanity and put it throughout this script
+#
+
+do_test btree-10.98 {
+  btree_close_cursor $::c1
+  lindex [btree_pager_stats $::b1] 1
+} {1}
+do_test btree-10.99 {
+  btree_rollback $::b1
+  lindex [btree_pager_stats $::b1] 1
+} {0}
+btree_pager_ref_dump $::b1
+
+do_test btree-99.1 {
+  btree_close $::b1
+} {}
+catch {unset data}
+catch {unset key}
+
+} ;# end if( not mem: and has pager_open command );
+
+finish_test
diff --git a/test/btree2.test b/test/btree2.test
new file mode 100644 (file)
index 0000000..7329313
--- /dev/null
@@ -0,0 +1,449 @@
+# Copyright (c) 1999, 2000 D. Richard Hipp
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA  02111-1307, USA.
+#
+# Author contact information:
+#   drh@hwaci.com
+#   http://www.hwaci.com/drh/
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this script is btree database backend
+#
+# $Id: btree2.test,v 1.4 2001/08/20 00:33:58 drh Exp $
+
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+if {[info commands btree_open]!=""} {
+
+# Create a new database file containing no entries.  The database should
+# contain 5 tables:
+#
+#     2   The descriptor table
+#     3   The foreground table
+#     4   The background table
+#     5   The long key table
+#     6   The long data table
+#
+# An explanation for what all these tables are used for is provided below.
+#
+do_test btree2-1.1 {
+  expr srand(1)
+  file delete -force test2.bt
+  file delete -force test2.bt-journal
+  set ::b [btree_open test2.bt]
+  btree_begin_transaction $::b
+  btree_create_table $::b
+} {3}
+do_test btree2-1.2 {
+  btree_create_table $::b
+} {4}
+do_test btree2-1.3 {
+  btree_create_table $::b
+} {5}
+do_test btree2-1.4 {
+  btree_create_table $::b
+} {6}
+do_test btree2-1.5 {
+  set ::c2 [btree_cursor $::b 2]
+  btree_insert $::c2 {one} {1}
+  btree_delete $::c2
+  btree_close_cursor $::c2
+  btree_commit $::b
+  btree_sanity_check $::b 2 3 4 5 6
+} {}
+
+# This test module works by making lots of pseudo-random changes to a
+# database while simultaneously maintaining an invariant on that database.
+# Periodically, the script does a sanity check on the database and verifies
+# that the invariant is satisfied.
+#
+# The invariant is as follows:
+#
+#   1.  The descriptor table always contains 2 enters.  An entry keyed by
+#       "N" is the number of elements in the foreground and background tables
+#       combined.  The entry keyed by "L" is the number of digits in the keys
+#       for foreground and background tables.
+#
+#   2.  The union of the foreground an background tables consists of N entries
+#       where each entry an L-digit key.  (Actually, some keys can be longer 
+#       than L characters, but they always start with L digits.)  The keys
+#       cover all integers between 1 and N.  Whenever an entry is added to
+#       the foreground it is removed form the background and vice versa.
+#
+#   3.  Some entries in the foreground and background tables have keys that
+#       begin with an L-digit number but are followed by additional characters.
+#       For each such entry there is a corresponding entry in the long key
+#       table.  The long key table entry has a key which is just the L-digit
+#       number and data which is the length of the key in the foreground and
+#       background tables.
+#
+#   4.  The data for both foreground and background entries is usually a
+#       short string.  But some entries have long data strings.  For each
+#       such entries there is an entry in the long data type.  The key to
+#       long data table is an L-digit number.  (The extension on long keys
+#       is omitted.)  The data is the number of charaters in the data of the
+#       foreground or background entry.
+#
+# The following function builds a database that satisfies all of the above
+# invariants.
+#
+proc build_db {N L} {
+  for {set i 2} {$i<=6} {incr i} {
+    catch {btree_close_cursor [set ::c$i]}
+    btree_clear_table $::b $i
+    set ::c$i [btree_cursor $::b $i]
+  }
+  btree_insert $::c2 N $N
+  btree_insert $::c2 L $L
+  set format %0${L}d
+  for {set i 1} {$i<=$N} {incr i} { 
+    set key [format $format $i]
+    set data $key
+    btree_insert $::c3 $key $data
+  }
+}
+
+# Given a base key number and a length, construct the full text of the key
+# or data.
+#
+proc make_payload {keynum L len} {
+  set key [format %0${L}d $keynum]
+  set r $key
+  set i 1
+  while {[string length $r]<$len} {
+    append r " ($i) $key"
+    incr i
+  }
+  return [string range $r 0 [expr {$len-1}]]
+}
+
+# Verify the invariants on the database.  Return an empty string on 
+# success or an error message if something is amiss.
+#
+proc check_invariants {} {
+  set ck [btree_sanity_check $::b 2 3 4 5 6]
+  if {$ck!=""} {
+    puts "\n*** SANITY:\n$ck"
+    exit
+    return $ck
+  }
+  btree_move_to $::c3 {}
+  btree_move_to $::c4 {}
+  btree_move_to $::c2 N
+  set N [btree_data $::c2]
+  btree_move_to $::c2 L
+  set L [btree_data $::c2]
+  set LM1 [expr {$L-1}]
+  for {set i 1} {$i<=$N} {incr i} {
+    set key [btree_key $::c3]
+    if {[scan $key %d k]<1} {set k 0}
+    if {$k!=$i} {
+      set key [btree_key $::c4]
+      if {[scan $key %d k]<1} {set k 0}
+      if {$k!=$i} {
+        # puts "MISSING $i"
+        # puts {Page 3:}; btree_page_dump $::b 3
+        # puts {Page 4:}; btree_page_dump $::b 4
+        # exit
+        return "Key $i is missing from both foreground and background"
+      }
+      set data [btree_data $::c4]
+      btree_next $::c4
+    } else {
+      set data [btree_data $::c3]
+      btree_next $::c3
+    }
+    set skey [string range $key 0 $LM1]
+    if {[btree_move_to $::c5 $skey]==0} {
+      set keylen [btree_data $::c5]
+    } else {
+      set keylen $L
+    }
+    if {[string length $key]!=$keylen} {
+      return "Key $i is the wrong size.\
+              Is \"$key\" but should be \"[make_payload $k $L $keylen]\""
+    }
+    if {[make_payload $k $L $keylen]!=$key} {
+      return "Key $i has an invalid extension"
+    }
+    if {[btree_move_to $::c6 $skey]==0} {
+      set datalen [btree_data $::c6]
+    } else {
+      set datalen $L
+    }
+    if {[string length $data]!=$datalen} {
+      return "Data for $i is the wrong size.\
+              Is [string length $data] but should be $datalen"
+    }
+    if {[make_payload $k $L $datalen]!=$data} {
+      return "Entry $i has an incorrect data"
+    }
+  }
+}
+
+# Make random changes to the database such that each change preserves
+# the invariants.  The number of changes is $n*N where N is the parameter
+# from the descriptor table.  Each changes begins with a random key.
+# the entry with that key is put in the foreground table with probability
+# $I and it is put in background with probability (1.0-$I).  It gets
+# a long key with probability $K and long data with probability $D.  
+# 
+set chngcnt 0
+proc random_changes {n I K D} {
+  btree_move_to $::c2 N
+  set N [btree_data $::c2]
+  btree_move_to $::c2 L
+  set L [btree_data $::c2]
+  set LM1 [expr {$L-1}]
+  set total [expr {int($N*$n)}]
+  set format %0${L}d
+  for {set i 0} {$i<$total} {incr i} {
+    set k [expr {int(rand()*$N)+1}]
+    set insert [expr {rand()<=$I}]
+    set longkey [expr {rand()<=$K}]
+    set longdata [expr {rand()<=$D}]
+    # incr ::chngcnt
+    # if {$::chngcnt==251} {btree_tree_dump $::b 3} 
+    # puts "CHANGE $::chngcnt: $k $insert $longkey $longdata"
+    if {$longkey} {
+      set x [expr {rand()}]
+      set keylen [expr {int($x*$x*$x*$x*3000)+10}]
+    } else {
+      set keylen $L
+    }
+    set key [make_payload $k $L $keylen]
+    if {$longdata} {
+      set x [expr {rand()}]
+      set datalen [expr {int($x*$x*$x*$x*3000)+10}]
+    } else {
+      set datalen $L
+    }
+    set data [make_payload $k $L $datalen]
+    set basekey [format $format $k]
+    if {[set c [btree_move_to $::c3 $basekey]]==0} {
+      btree_delete $::c3
+    } else {
+      if {$c<0} {btree_next $::c3}
+      if {[string match $basekey* [btree_key $::c3]]} {
+        btree_delete $::c3
+      }
+    }
+    if {[set c [btree_move_to $::c4 $basekey]]==0} {
+      btree_delete $::c4
+    } else {
+      if {$c<0} {btree_next $::c4}
+      if {[string match $basekey* [btree_key $::c4]]} {
+        btree_delete $::c4
+      }
+    }
+    if {[scan [btree_key $::c4] %d kx]<1} {set kx -1}
+    if {$kx==$k} {
+      btree_delete $::c4
+    }
+    if {$insert} {
+      btree_insert $::c3 $key $data
+    } else {
+      btree_insert $::c4 $key $data
+    }
+    if {$longkey} {
+      btree_insert $::c5 $basekey $keylen
+    } elseif {[btree_move_to $::c5 $basekey]==0} {
+      btree_delete $::c5
+    }
+    if {$longdata} {
+      btree_insert $::c6 $basekey $datalen
+    } elseif {[btree_move_to $::c6 $basekey]==0} {
+      btree_delete $::c6
+    }
+    # set ck [btree_sanity_check $::b 2 3 4 5 6]
+    # if {$ck!=""} {
+    #   puts "\nSANITY CHECK FAILED!\n$ck"
+    #   exit
+    # }
+    # puts "PAGE 3:"; btree_page_dump $::b 3
+    # puts "PAGE 4:"; btree_page_dump $::b 4
+  }
+}
+
+# Repeat this test sequence on database of various sizes
+#
+set testno 2
+foreach {N L} {
+  10 2
+  50 2
+  200 3
+} {
+#  2000 5
+  puts "**** N=$N L=$L ****"
+  set hash [md5file test2.bt]
+  do_test btree2-$testno.1 [subst -nocommands {
+    set ::c2 [btree_cursor $::b 2]
+    set ::c3 [btree_cursor $::b 3]
+    set ::c4 [btree_cursor $::b 4]
+    set ::c5 [btree_cursor $::b 5]
+    set ::c6 [btree_cursor $::b 6]
+    btree_begin_transaction $::b
+    build_db $N $L
+    check_invariants
+  }] {}
+  do_test btree2-$testno.2 {
+    btree_close_cursor $::c2
+    btree_close_cursor $::c3
+    btree_close_cursor $::c4
+    btree_close_cursor $::c5
+    btree_close_cursor $::c6
+    btree_rollback $::b
+    md5file test2.bt
+  } $hash
+  do_test btree2-$testno.3 [subst -nocommands {
+    btree_begin_transaction $::b
+    set ::c2 [btree_cursor $::b 2]
+    set ::c3 [btree_cursor $::b 3]
+    set ::c4 [btree_cursor $::b 4]
+    set ::c5 [btree_cursor $::b 5]
+    set ::c6 [btree_cursor $::b 6]
+    build_db $N $L
+    check_invariants
+  }] {}
+  do_test btree2-$testno.4 {
+    btree_commit $::b
+    check_invariants
+  } {}
+  do_test btree2-$testno.5  {
+    lindex [btree_pager_stats $::b] 1
+  } {6}
+  do_test btree2-$testno.6  {
+    btree_close_cursor $::c2
+    btree_close_cursor $::c3
+    btree_close_cursor $::c4
+    btree_close_cursor $::c5
+    btree_close_cursor $::c6
+    lindex [btree_pager_stats $::b] 1
+  } {0}
+  do_test btree2-$testno.7 {
+    btree_close $::b
+    set ::b [btree_open test2.bt]
+    set ::c2 [btree_cursor $::b 2]
+    set ::c3 [btree_cursor $::b 3]
+    set ::c4 [btree_cursor $::b 4]
+    set ::c5 [btree_cursor $::b 5]
+    set ::c6 [btree_cursor $::b 6]
+    check_invariants
+  } {}
+
+  # For each database size, run various changes tests.
+  #
+  set num2 1
+  foreach {n I K D} {
+    0.5 0.5 0.1 0.1
+    1.0 0.2 0.1 0.1
+    1.0 0.8 0.1 0.1
+    2.0 0.0 0.1 0.1
+    2.0 1.0 0.1 0.1
+    2.0 0.0 0.0 0.0
+    2.0 1.0 0.0 0.0
+  } {
+    set testid btree2-$testno.8.$num2
+    set cnt 6
+    for {set i 2} {$i<=6} {incr i} {
+      if {[lindex [btree_cursor_dump [set ::c$i]] 0]!=$i} {incr cnt}
+    }
+    do_test $testid.1 {
+      btree_begin_transaction $::b
+      lindex [btree_pager_stats $::b] 1
+    } $cnt
+    set hash [md5file test2.bt]
+    # exec cp test2.bt test2.bt.bu1
+    do_test $testid.2 [subst {
+      random_changes $n $I $K $D
+    }] {}
+    do_test $testid.3 {
+      check_invariants
+    } {}
+    do_test $testid.4 {
+      btree_close_cursor $::c2
+      btree_close_cursor $::c3
+      btree_close_cursor $::c4
+      btree_close_cursor $::c5
+      btree_close_cursor $::c6
+      btree_rollback $::b
+      md5file test2.bt
+    } $hash
+    # exec cp test2.bt test2.bt.bu2
+    btree_begin_transaction $::b
+    set ::c2 [btree_cursor $::b 2]
+    set ::c3 [btree_cursor $::b 3]
+    set ::c4 [btree_cursor $::b 4]
+    set ::c5 [btree_cursor $::b 5]
+    set ::c6 [btree_cursor $::b 6]
+    do_test $testid.5 [subst {
+      random_changes $n $I $K $D
+    }] {}
+    do_test $testid.6 {
+      check_invariants
+    } {}
+    do_test $testid.7 {
+      btree_commit $::b
+      check_invariants
+    } {}
+    set hash [md5file test2.bt]
+    do_test $testid.8 {
+      btree_close_cursor $::c2
+      btree_close_cursor $::c3
+      btree_close_cursor $::c4
+      btree_close_cursor $::c5
+      btree_close_cursor $::c6
+      lindex [btree_pager_stats $::b] 1
+    } {0}
+    do_test $testid.9 {
+      btree_close $::b
+      set ::b [btree_open test2.bt]
+      set ::c2 [btree_cursor $::b 2]
+      set ::c3 [btree_cursor $::b 3]
+      set ::c4 [btree_cursor $::b 4]
+      set ::c5 [btree_cursor $::b 5]
+      set ::c6 [btree_cursor $::b 6]
+      check_invariants
+    } {}
+    incr num2
+  }
+  btree_close_cursor $::c2
+  btree_close_cursor $::c3
+  btree_close_cursor $::c4
+  btree_close_cursor $::c5
+  btree_close_cursor $::c6
+  incr testno
+}  
+
+# Testing is complete.  Shut everything down.
+#
+do_test btree-999.1 {
+  lindex [btree_pager_stats $::b] 1
+} {0}
+do_test btree-999.2 {
+  btree_close $::b
+} {}
+do_test btree-999.3 {
+  file delete -force test2.bt
+  file exists test2.bt-journal
+} {0}
+
+} ;# end if( not mem: and has pager_open command );
+
+finish_test
diff --git a/test/pager.test b/test/pager.test
new file mode 100644 (file)
index 0000000..c01f9f4
--- /dev/null
@@ -0,0 +1,261 @@
+# Copyright (c) 1999, 2000 D. Richard Hipp
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA  02111-1307, USA.
+#
+# Author contact information:
+#   drh@hwaci.com
+#   http://www.hwaci.com/drh/
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this script is page cache subsystem.
+#
+# $Id: pager.test,v 1.8 2001/08/20 00:33:58 drh Exp $
+
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+if {$dbprefix!="memory:" && [info commands pager_open]!=""} {
+
+# Basic sanity check.  Open and close a pager.
+#
+do_test pager-1.0 {
+  catch {file delete -force ptf1.db}
+  catch {file delete -force ptf1.db-journal}
+  set v [catch {
+    set ::p1 [pager_open ptf1.db 10]
+  } msg]
+} {0}
+do_test pager-1.1 {
+  pager_stats $::p1
+} {ref 0 page 0 max 10 size -1 state 0 err 0 hit 0 miss 0 ovfl 0}
+do_test pager-1.2 {
+  pager_pagecount $::p1
+} {0}
+do_test pager-1.3 {
+  pager_stats $::p1
+} {ref 0 page 0 max 10 size -1 state 0 err 0 hit 0 miss 0 ovfl 0}
+do_test pager-1.4 {
+  pager_close $::p1
+} {}
+
+# Try to write a few pages.
+#
+do_test pager-2.1 {
+  set v [catch {
+    set ::p1 [pager_open ptf1.db 10]
+  } msg]
+} {0}
+do_test pager-2.2 {
+  set v [catch {
+    set ::g1 [page_get $::p1 0]
+  } msg]
+  lappend v $msg
+} {1 SQLITE_ERROR}
+do_test pager-2.3.1 {
+  set ::gx [page_lookup $::p1 1]
+} {}
+do_test pager-2.3.2 {
+  pager_stats $::p1
+} {ref 0 page 0 max 10 size -1 state 0 err 0 hit 0 miss 0 ovfl 0}
+do_test pager-2.3.3 {
+  set v [catch {
+    set ::g1 [page_get $::p1 1]
+  } msg]
+  if {$v} {lappend v $msg}
+  set v
+} {0}
+do_test pager-2.3.3 {
+  pager_stats $::p1
+} {ref 1 page 1 max 10 size 0 state 1 err 0 hit 0 miss 1 ovfl 0}
+do_test pager-2.3.4 {
+  set ::gx [page_lookup $::p1 1]
+  expr {$::gx!=""}
+} {1}
+do_test pager-2.3.5 {
+  pager_stats $::p1
+} {ref 1 page 1 max 10 size 0 state 1 err 0 hit 0 miss 1 ovfl 0}
+do_test pager-2.3.6 {
+  expr $::g1==$::gx
+} {1}
+do_test pager-2.3.7 {
+  page_unref $::gx
+  pager_stats $::p1
+} {ref 1 page 1 max 10 size 0 state 1 err 0 hit 0 miss 1 ovfl 0}
+do_test pager-2.4 {
+  pager_stats $::p1
+} {ref 1 page 1 max 10 size 0 state 1 err 0 hit 0 miss 1 ovfl 0}
+do_test pager-2.5 {
+  pager_pagecount $::p1
+} {0}
+do_test pager-2.6 {
+  pager_stats $::p1
+} {ref 1 page 1 max 10 size 0 state 1 err 0 hit 0 miss 1 ovfl 0}
+do_test pager-2.7 {
+  page_number $::g1
+} {1}
+do_test pager-2.8 {
+  page_read $::g1
+} {}
+do_test pager-2.9 {
+  page_unref $::g1
+} {}
+do_test pager-2.10 {
+  pager_stats $::p1
+} {ref 0 page 0 max 10 size -1 state 0 err 0 hit 0 miss 1 ovfl 0}
+do_test pager-2.11 {
+  set ::g1 [page_get $::p1 1]
+  expr {$::g1!=0}
+} {1}
+do_test pager-2.12 {
+  page_number $::g1
+} {1}
+do_test pager-2.13 {
+  pager_stats $::p1
+} {ref 1 page 1 max 10 size 0 state 1 err 0 hit 0 miss 2 ovfl 0}
+do_test pager-2.14 {
+  set v [catch {
+    page_write $::g1 "Page-One"
+  } msg]
+  lappend v $msg
+} {0 {}}
+do_test pager-2.15 {
+  pager_stats $::p1
+} {ref 1 page 1 max 10 size 1 state 2 err 0 hit 0 miss 2 ovfl 0}
+do_test pager-2.16 {
+  page_read $::g1
+} {Page-One}
+do_test pager-2.17 {
+  set v [catch {
+    pager_commit $::p1
+  } msg]
+  lappend v $msg
+} {0 {}}
+do_test pager-2.20 {
+  pager_stats $::p1
+} {ref 1 page 1 max 10 size -1 state 1 err 0 hit 0 miss 2 ovfl 0}
+do_test pager-2.19 {
+  pager_pagecount $::p1
+} {1}
+do_test pager-2.21 {
+  pager_stats $::p1
+} {ref 1 page 1 max 10 size 1 state 1 err 0 hit 0 miss 2 ovfl 0}
+do_test pager-2.22 {
+  page_unref $::g1
+} {}
+do_test pager-2.23 {
+  pager_stats $::p1
+} {ref 0 page 0 max 10 size -1 state 0 err 0 hit 0 miss 2 ovfl 0}
+do_test pager-2.24 {
+  set v [catch {
+    page_get $::p1 1
+  } ::g1]
+  if {$v} {lappend v $::g1}
+  set v
+} {0}
+do_test pager-2.25 {
+  page_read $::g1
+} {Page-One}
+do_test pager-2.26 {
+  set v [catch {
+    page_write $::g1 {page-one}
+  } msg]
+  lappend v $msg
+} {0 {}}
+do_test pager-2.27 {
+  page_read $::g1
+} {page-one}
+do_test pager-2.28 {
+  set v [catch {
+    pager_rollback $::p1
+  } msg]
+  lappend v $msg
+} {0 {}}
+do_test pager-2.29 {
+  page_read $::g1
+} {Page-One}
+do_test pager-2.99 {
+  pager_close $::p1
+} {}
+
+do_test pager-3.1 {
+  set v [catch {
+    set ::p1 [pager_open ptf1.db 15]
+  } msg]
+  if {$v} {lappend v $msg}
+  set v
+} {0}
+do_test pager-3.2 {
+  pager_pagecount $::p1
+} {1}
+do_test pager-3.3 {
+  set v [catch {
+    set ::g(1) [page_get $::p1 1]
+  } msg]
+  if {$v} {lappend v $msg}
+  set v
+} {0}
+do_test pager-3.4 {
+  page_read $::g(1)
+} {Page-One}
+do_test pager-3.5 {
+  for {set i 2} {$i<=20} {incr i} {
+    set gx [page_get $::p1 $i]
+    page_write $gx "Page-$i"
+    page_unref $gx
+  }
+  pager_commit $::p1
+} {}
+for {set i 2} {$i<=20} {incr i} {
+  do_test pager-3.6.[expr {$i-1}] [subst {
+    set gx \[page_get $::p1 $i\]
+    set v \[page_read \$gx\]
+    page_unref \$gx
+    set v
+  }] "Page-$i"
+}
+for {set i 1} {$i<=20} {incr i} {
+  regsub -all CNT {
+    set ::g1 [page_get $::p1 CNT]
+    set ::g2 [page_get $::p1 CNT]
+    set ::vx [page_read $::g2]
+    expr {$::g1==$::g2}
+  } $i body;
+  do_test pager-3.7.$i.1 $body {1}
+  regsub -all CNT {
+    page_unref $::g2
+    set vy [page_read $::g1]
+    expr {$vy==$::vx}
+  } $i body;
+  do_test pager-3.7.$i.2 $body {1}
+  regsub -all CNT {
+    page_unref $::g1
+    set gx [page_get $::p1 CNT]
+    set vy [page_read $gx]
+    page_unref $gx
+    expr {$vy==$::vx}
+  } $i body;
+  do_test pager-3.7.$i.3 $body {1}
+}
+do_test pager-3.99 {
+  pager_close $::p1
+} {}
+
+
+} ;# end if( not mem: and has pager_open command );
+
+finish_test