From 5c4d9703d936ee6220ff59c6c42a8f280fa10744 Mon Sep 17 00:00:00 2001 From: drh Date: Mon, 20 Aug 2001 00:33:58 +0000 Subject: [PATCH] Restore btree to the main line. (CVS 237) FossilOrigin-Name: 2e6aff980287825b59d2ebb7005bb08dd601ff1c --- Makefile.in | 16 +- manifest | 25 +- manifest.uuid | 2 +- src/btree.c | 23 +- src/btree.h | 7 +- src/dbbemem.c | 8 +- src/tclsqlite.c | 6 +- src/test2.c | 416 +++++++++++++++++++ src/test3.c | 844 +++++++++++++++++++++++++++++++++++++ test/btree.test | 1030 ++++++++++++++++++++++++++++++++++++++++++++++ test/btree2.test | 449 ++++++++++++++++++++ test/pager.test | 261 ++++++++++++ 12 files changed, 3057 insertions(+), 30 deletions(-) create mode 100644 src/test2.c create mode 100644 src/test3.c create mode 100644 test/btree.test create mode 100644 test/btree2.test create mode 100644 test/pager.test diff --git a/Makefile.in b/Makefile.in index f3af9281f1..20ffb5e313 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 diff --git a/manifest b/manifest index 0fe001ba43..f9f331bf8b 100644 --- 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 diff --git a/manifest.uuid b/manifest.uuid index 181a107136..0981288553 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c15f6ffc4d41f30a06d750c8015226713ae0126b \ No newline at end of file +2e6aff980287825b59d2ebb7005bb08dd601ff1c \ No newline at end of file diff --git a/src/btree.c b/src/btree.c index 46d2332a1e..98410be63b 100644 --- a/src/btree.c +++ b/src/btree.c @@ -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; diff --git a/src/btree.h b/src/btree.h index 6c54b67084..2e98f4213f 100644 --- a/src/btree.h +++ b/src/btree.h @@ -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); diff --git a/src/dbbemem.c b/src/dbbemem.c index 54f18b2283..95d7319605 100644 --- a/src/dbbemem.c +++ b/src/dbbemem.c @@ -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 @@ -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 diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 95ae3c5c8b..ade3ddb61c 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -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 index 0000000000..70e06eb978 --- /dev/null +++ b/src/test2.c @@ -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 +#include + +/* +** 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 index 0000000000..289c45e52c --- /dev/null +++ b/src/test3.c @@ -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 +#include + +/* +** 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; i0} +} {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 index 0000000000..7329313bf0 --- /dev/null +++ b/test/btree2.test @@ -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 index 0000000000..c01f9f48b8 --- /dev/null +++ b/test/pager.test @@ -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