From efc251da9250a9022afb4d0d596a9732309b89c0 Mon Sep 17 00:00:00 2001 From: drh Date: Sun, 1 Jul 2001 22:12:01 +0000 Subject: [PATCH] More BTree tests (CVS 233) FossilOrigin-Name: 55c89bfdd35f1ad494618a451f9a1ed08502ae07 --- Makefile.in | 3 +- manifest | 20 +-- manifest.uuid | 2 +- src/btree.c | 8 +- src/md5.c | 352 +++++++++++++++++++++++++++++++++++++++++++ src/tclsqlite.c | 4 +- src/test3.c | 32 +++- test/btree2.test | 385 +++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 792 insertions(+), 14 deletions(-) create mode 100644 src/md5.c create mode 100644 test/btree2.test diff --git a/Makefile.in b/Makefile.in index bcccd3d211..fa845cc949 100644 --- a/Makefile.in +++ b/Makefile.in @@ -84,7 +84,8 @@ SRC = \ TESTSRC = \ $(TOP)/src/test1.c \ $(TOP)/src/test2.c \ - $(TOP)/src/test3.c + $(TOP)/src/test3.c \ + $(TOP)/src/md5.c # This is the default Makefile target. The objects listed here # are what get build when you type just "make" with no arguments. diff --git a/manifest b/manifest index e823fb6d44..182bc50182 100644 --- a/manifest +++ b/manifest @@ -1,7 +1,7 @@ -C Implemented\sthe\ssqliteBtreeSanityCheck()\stest\sfunction.\s(CVS\s232) -D 2001-06-30T21:53:53 +C More\sBTree\stests\s(CVS\s233) +D 2001-07-01T22:12:01 F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4 -F Makefile.in 63bc9a6a39b7160ce8d2392ae74eb4ca4ca84c6e +F Makefile.in df14e0f23d6946304d4681c24799d1ece965bf74 F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958 F VERSION 71874cb7e2a53c2bd22bb6affa7d223dd94a7a13 F configure d2051345f49f7e48604423da26e086a745c86a47 x @@ -12,7 +12,7 @@ F notes/notes1.txt b7c0812b704a022e88c621146ae50955c923d464 F notes/notes2.txt 7e3fafd5e25906c1fe1e95f13b089aa398ca403e F notes/notes3.txt 985bf688b59f1f52bfe6e4b1f896efdeffac1432 F src/TODO 38a68a489e56e9fd4a96263e0ff9404a47368ad4 -F src/btree.c 26f04c3fc1bb5bc9ba598c373cba09acaf82d6ce +F src/btree.c 7e39906a52592d3683552235c2a7d3782cc9e6f9 F src/btree.h 987d80658ae67f0e4d8b849539c113d4f9a7e835 F src/build.c 4f6a2d551c56342cd4a0420654835be3ad179651 F src/dbbe.c b18259f99d87240cbe751021cf14dd3aa83a48af @@ -31,6 +31,7 @@ F src/ex/sizes.tcl f54bad4a2ac567624be59131a6ee42d71b41a3d7 F src/expr.c c4c24c3af1eba094a816522eb0e085bed518ee16 F src/insert.c aa528e20a787af85432a61daaea6df394bd251d7 F src/main.c 0a13c7a2beb8ce36aee43daf8c95989b200727a7 +F src/md5.c 52f677bfc590e09f71d07d7e327bd59da738d07c F src/pager.c 3e864a3e6cdec6f000a343f793360b42714028d8 F src/pager.h d85259a2fd59e39f976abfb2bf6703c6f810e993 F src/parse.y 8fc096948994a7ffbf61ba13129cc589f794a9cb @@ -42,10 +43,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 af29a45cb4c2244a6fd032568a22d26516472b2c +F src/tclsqlite.c 7acb8887c44622214edb0dedeaab2593a3f86c62 F src/test1.c abb3cb427e735ae87e6533f5b3b7164b7da91bc4 F src/test2.c 0183625225a860397b4fd3041aefb48f77e4630a -F src/test3.c 6b5a099476ab96e7bca8bb6c48bc28700157a314 +F src/test3.c ad8ff3513c3deb2d3909eca0f94527017b6d2fe6 F src/tokenize.c 0118b57702cb6550769316e8443b06760b067acf F src/update.c 0cf789656a936d4356668393267692fa4b03ffc6 F src/util.c 1b396ac34e30dd6222d82e996c17b161bbc906bc @@ -54,6 +55,7 @@ F src/vdbe.h dc1205da434c6a9da03b5d6b089270bbc8e6d437 F src/where.c 0c542fc44bd85152dfb8507862cfe2e60c629e9f F test/all.test 21d55a97e39e7ec5776751dc9dd8b1b51ef4a048 F test/btree.test 2463425e01ef94ec123fdbfb0dcae33f5303d5b1 +F test/btree2.test 480e39c80109280cdfdbc305b77919c5eae69b2e F test/copy.test b77a1214bd7756f2849d5c4fa6e715c0ff0c34eb F test/dbbe.test a022fe2d983848f786e17ef1fc6809cfd37fb02c F test/delete.test 50b9b1f06c843d591741dba7869433a105360dbf @@ -108,7 +110,7 @@ F www/opcode.tcl cb3a1abf8b7b9be9f3a228d097d6bf8b742c2b6f F www/sqlite.tcl cb0d23d8f061a80543928755ec7775da6e4f362f F www/tclsqlite.tcl 06f81c401f79a04f2c5ebfb97e7c176225c0aef2 F www/vdbe.tcl 0c8aaa529dd216ccbf7daaabd80985e413d5f9ad -P 2c9127943cd5a541613924d2df773c4e8df4c1a6 -R 28adcf006d91ae47e28b3894b3bf7acf +P 42486880ed31318ef36a8831b959e9115b4fbac6 +R 453a76da442402a2188baa6e8a5a2ed6 U drh -Z a0a81713c9302268fb4ff7274344753c +Z 41c451abad8201b164f89425654bbd84 diff --git a/manifest.uuid b/manifest.uuid index 4062ecd017..d71cf0d6b6 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -42486880ed31318ef36a8831b959e9115b4fbac6 \ No newline at end of file +55c89bfdd35f1ad494618a451f9a1ed08502ae07 \ No newline at end of file diff --git a/src/btree.c b/src/btree.c index 232fa914fe..ada01d9747 100644 --- a/src/btree.c +++ b/src/btree.c @@ -21,7 +21,7 @@ ** http://www.hwaci.com/drh/ ** ************************************************************************* -** $Id: btree.c,v 1.18 2001/06/30 21:53:53 drh Exp $ +** $Id: btree.c,v 1.19 2001/07/01 22:12:01 drh Exp $ ** ** This file implements a external (disk-based) database using BTrees. ** For a detailed discussion of BTrees, refer to @@ -1666,6 +1666,8 @@ static int balance(Btree *pBt, MemPage *pPage, BtCursor *pCur){ reparentChildPages(pBt->pPager, pPage); freePage(pBt, pChild, pgnoChild); sqlitepager_unref(pChild); + }else{ + relinkCellList(pPage); } return SQLITE_OK; } @@ -2489,6 +2491,9 @@ char *sqliteBtreeSanityCheck(Btree *pBt, int *aRoot, int nRoot){ SanityCheck sCheck; nRef = *sqlitepager_stats(pBt->pPager); + if( lockBtree(pBt)!=SQLITE_OK ){ + return sqliteStrDup("Unable to acquire a read lock on the database"); + } sCheck.pBt = pBt; sCheck.pPager = pBt->pPager; sCheck.nPage = sqlitepager_pagecount(sCheck.pPager); @@ -2519,6 +2524,7 @@ char *sqliteBtreeSanityCheck(Btree *pBt, int *aRoot, int nRoot){ /* Make sure this analysis did not leave any unref() pages */ + unlockBtree(pBt); if( nRef != *sqlitepager_stats(pBt->pPager) ){ char zBuf[100]; sprintf(zBuf, diff --git a/src/md5.c b/src/md5.c new file mode 100644 index 0000000000..c6c70628a1 --- /dev/null +++ b/src/md5.c @@ -0,0 +1,352 @@ +/* +** SQLite uses this code for testing only. It is not a part of +** the SQLite library. This file implements two new TCL commands +** "md5" and "md5file" that compute md5 checksums on arbitrary text +** and on complete files. These commands are used by the "testfixture" +** program to help verify the correct operation of the SQLite library. +** +** The original use of these TCL commands was to test the ROLLBACK +** feature of SQLite. First compute the MD5-checksum of the database. +** Then make some changes but rollback the changes rather than commit +** them. Compute a second MD5-checksum of the file and verify that the +** two checksums are the same. Such is the original use of this code. +** New uses may have been added since this comment was written. +*/ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ +#include +#include + +/* + * If compiled on a machine that doesn't have a 32-bit integer, + * you just set "uint32" to the appropriate datatype for an + * unsigned 32-bit integer. For example: + * + * cc -Duint32='unsigned long' md5.c + * + */ +#ifndef uint32 +# define uint32 unsigned int +#endif + +struct Context { + uint32 buf[4]; + uint32 bits[2]; + unsigned char in[64]; +}; +typedef char MD5Context[88]; + +/* + * Note: this code is harmless on little-endian machines. + */ +static void byteReverse (unsigned char *buf, unsigned longs){ + uint32 t; + do { + t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 | + ((unsigned)buf[1]<<8 | buf[0]); + *(uint32 *)buf = t; + buf += 4; + } while (--longs); +} +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void MD5Transform(uint32 buf[4], const uint32 in[16]){ + register uint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +static void MD5Init(MD5Context *pCtx){ + struct Context *ctx = (struct Context *)pCtx; + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +static +void MD5Update(MD5Context *pCtx, const unsigned char *buf, unsigned int len){ + struct Context *ctx = (struct Context *)pCtx; + uint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32)len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if ( t ) { + unsigned char *p = (unsigned char *)ctx->in + t; + + t = 64-t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += t; + len -= t; + } + + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +static void MD5Final(unsigned char digest[16], MD5Context *pCtx){ + struct Context *ctx = (struct Context *)pCtx; + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count-8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32 *)ctx->in)[ 14 ] = ctx->bits[0]; + ((uint32 *)ctx->in)[ 15 ] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32 *)ctx->in); + byteReverse((unsigned char *)ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +/* +** Convert a digest into base-16. digest should be declared as +** "unsigned char digest[16]" in the calling function. The MD5 +** digest is stored in the first 16 bytes. zBuf should +** be "char zBuf[33]". +*/ +static void DigestToBase16(unsigned char *digest, char *zBuf){ + static char const zEncode[] = "0123456789abcdef"; + int i, j; + + for(j=i=0; i<16; i++){ + int a = digest[i]; + zBuf[j++] = zEncode[(a>>4)&0xf]; + zBuf[j++] = zEncode[a & 0xf]; + } + zBuf[j] = 0; +} + +/* +** A TCL command for md5. The argument is the text to be hashed. The +** Result is the hash in base64. +*/ +static int md5_cmd(ClientData cd, Tcl_Interp *interp, int argc, char **argv){ + MD5Context ctx; + unsigned char digest[16]; + + if( argc!=2 ){ + Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0], + " TEXT\"", 0); + return TCL_ERROR; + } + MD5Init(&ctx); + MD5Update(&ctx, (unsigned char*)argv[1], (unsigned)strlen(argv[1])); + MD5Final(digest, &ctx); + DigestToBase16(digest, interp->result); + return TCL_OK; +} + +/* +** A TCL command to take the md5 hash of a file. The argument is the +** name of the file. +*/ +static int md5file_cmd(ClientData cd, Tcl_Interp*interp, int argc, char **argv){ + FILE *in; + MD5Context ctx; + unsigned char digest[16]; + char zBuf[10240]; + + if( argc!=2 ){ + Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0], + " FILENAME\"", 0); + return TCL_ERROR; + } + in = fopen(argv[1],"rb"); + if( in==0 ){ + Tcl_AppendResult(interp,"unable to open file \"", argv[1], + "\" for reading", 0); + return TCL_ERROR; + } + MD5Init(&ctx); + for(;;){ + int n; + n = fread(zBuf, 1, sizeof(zBuf), in); + if( n<=0 ) break; + MD5Update(&ctx, (unsigned char*)zBuf, (unsigned)n); + } + fclose(in); + MD5Final(digest, &ctx); + DigestToBase16(digest, interp->result); + return TCL_OK; +} + +/* +** Register the two TCL commands above with the TCL interpreter. +*/ +int Md5_Init(Tcl_Interp *interp){ + Tcl_CreateCommand(interp, "md5", md5_cmd, 0, 0); + Tcl_CreateCommand(interp, "md5file", md5file_cmd, 0, 0); + return TCL_OK; +} diff --git a/src/tclsqlite.c b/src/tclsqlite.c index d364cb7b35..a663f46ff3 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -23,7 +23,7 @@ ************************************************************************* ** A TCL Interface to SQLite ** -** $Id: tclsqlite.c,v 1.19 2001/06/22 19:15:01 drh Exp $ +** $Id: tclsqlite.c,v 1.20 2001/07/01 22:12:02 drh Exp $ */ #ifndef NO_TCL /* Omit this whole file if TCL is unavailable */ @@ -512,9 +512,11 @@ int TCLSH_MAIN(int argc, char **argv){ 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 if( argc>=2 ){ diff --git a/src/test3.c b/src/test3.c index 13f2ab709e..0c135a74ca 100644 --- a/src/test3.c +++ b/src/test3.c @@ -25,7 +25,7 @@ ** is not included in the SQLite library. It is used for automated ** testing of the SQLite library. ** -** $Id: test3.c,v 1.5 2001/06/30 21:53:53 drh Exp $ +** $Id: test3.c,v 1.6 2001/07/01 22:12:02 drh Exp $ */ #include "sqliteInt.h" #include "pager.h" @@ -256,6 +256,35 @@ static int btree_drop_table( 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 ** @@ -762,6 +791,7 @@ int Sqlitetest3_Init(Tcl_Interp *interp){ 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); diff --git a/test/btree2.test b/test/btree2.test new file mode 100644 index 0000000000..0e569fb6fb --- /dev/null +++ b/test/btree2.test @@ -0,0 +1,385 @@ +# 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.1 2001/07/01 22:12:02 drh Exp $ + + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +if {$dbprefix!="memory:" && [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 { + 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 {} { + 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] + scan $key %d k + if {$k!=$i} { + set key [btree_key $::c4] + scan $key %d k + if {$k!=$i} { + return "Key $i is missing from both foreground and backgroun" + } + 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. +# +proc random_changes {n I K D} { + 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)}] + set insert [expr {rand()<=$I}] + set longkey [expr {rand()<=$K}] + set longdata [expr {rand()<=$D}] + if {$longkey} { + set x [expr {rand()}] + set keylen [expr {int($x*$x*$x*$x*3000)}] + } 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)}] + } else { + set datalen $L + } + set data [make_payload $k $L $datalen] + set basekey [format $format $k] + if {$insert} { + btree_move_to $::c4 $basekey + if {[scan [btree_key $::c4] %d kx]<1} {set kx -1} + if {$kx==$k} { + btree_delete $::c4 + } + btree_insert $::c3 $key $data + } else { + btree_move_to $::c3 $basekey + if {[scan [btree_key $::c4] %d kx]<1} {set kx -1} + if {$kx==$k} { + btree_delete $::c3 + } + 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 + } + } + return [btree_sanity_check $::b 2 3 4 5 6] +} + +# Repeat this test sequence on database of various sizes +# +set testno 2 +foreach {N L} { + 10 2 +} { + 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] + check_invariants + } {} + + # For each database size, run various changes tests. + # + set num2 1 + foreach {n I K D} { + 0.5 0.5 0.5 0.5 + } { + set testid btree2-$testno.8.$num2 + do_test $testid.1 { + 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 + lindex [btree_pager_stats $::b] 1 + } {6} + set hash [md5file test2.bt] + do_test $testid.2 [subst -nocommands { + random_changes $n $I $K $D + check_invariants + }] {} + do_test $testid.3 { + 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 $testid.4 [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] + random_changes $n $I $K $D + check_invariants + }] {} + do_test $testid.5 { + btree_commit $::b + check_invariants + } {} + set hash [md5file test2.bt] + do_test $testid.6 { + btree_close_cursor $::c2 + btree_close_cursor $::c3 + btree_close_cursor $::c4 + btree_close_cursor $::c5 + btree_close_cursor $::c6 + btree_close $::b + set ::b [btree_open test2.bt] + check_invariants + } {} + incr num2 + } + 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