1 /* 2 ** 2001 September 15 3 ** 4 ** The author disclaims copyright to this source code. In place of 5 ** a legal notice, here is a blessing: 6 ** 7 ** May you do good and not evil. 8 ** May you find forgiveness for yourself and forgive others. 9 ** May you share freely, never taking more than you give. 10 ** 11 ************************************************************************* 12 ** Code for testing the btree.c module in SQLite. This code 13 ** is not included in the SQLite library. It is used for automated 14 ** testing of the SQLite library. 15 */ 16 #include "sqliteInt.h" 17 #include "btreeInt.h" 18 #include "tcl.h" 19 #include <stdlib.h> 20 #include <string.h> 21 22 /* 23 ** Interpret an SQLite error number 24 */ 25 static char *errorName(int rc){ 26 char *zName; 27 switch( rc ){ 28 case SQLITE_OK: zName = "SQLITE_OK"; break; 29 case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; 30 case SQLITE_PERM: zName = "SQLITE_PERM"; break; 31 case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; 32 case SQLITE_BUSY: zName = "SQLITE_BUSY"; break; 33 case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break; 34 case SQLITE_READONLY: zName = "SQLITE_READONLY"; break; 35 case SQLITE_INTERRUPT: zName = "SQLITE_INTERRUPT"; break; 36 case SQLITE_IOERR: zName = "SQLITE_IOERR"; break; 37 case SQLITE_CORRUPT: zName = "SQLITE_CORRUPT"; break; 38 case SQLITE_FULL: zName = "SQLITE_FULL"; break; 39 case SQLITE_CANTOPEN: zName = "SQLITE_CANTOPEN"; break; 40 case SQLITE_PROTOCOL: zName = "SQLITE_PROTOCOL"; break; 41 case SQLITE_EMPTY: zName = "SQLITE_EMPTY"; break; 42 case SQLITE_LOCKED: zName = "SQLITE_LOCKED"; break; 43 default: zName = "SQLITE_Unknown"; break; 44 } 45 return zName; 46 } 47 48 /* 49 ** A bogus sqlite3 connection structure for use in the btree 50 ** tests. 51 */ 52 static sqlite3 sDb; 53 static int nRefSqlite3 = 0; 54 55 /* 56 ** Usage: btree_open FILENAME NCACHE FLAGS 57 ** 58 ** Open a new database 59 */ 60 static int btree_open( 61 void *NotUsed, 62 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 63 int argc, /* Number of arguments */ 64 const char **argv /* Text of each argument */ 65 ){ 66 Btree *pBt; 67 int rc, nCache, flags; 68 char zBuf[100]; 69 if( argc!=4 ){ 70 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 71 " FILENAME NCACHE FLAGS\"", 0); 72 return TCL_ERROR; 73 } 74 if( Tcl_GetInt(interp, argv[2], &nCache) ) return TCL_ERROR; 75 if( Tcl_GetInt(interp, argv[3], &flags) ) return TCL_ERROR; 76 nRefSqlite3++; 77 if( nRefSqlite3==1 ){ 78 sDb.pVfs = sqlite3_vfs_find(0); 79 sDb.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE); 80 sqlite3_mutex_enter(sDb.mutex); 81 } 82 rc = sqlite3BtreeOpen(argv[1], &sDb, &pBt, flags, 83 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB); 84 if( rc!=SQLITE_OK ){ 85 Tcl_AppendResult(interp, errorName(rc), 0); 86 return TCL_ERROR; 87 } 88 sqlite3BtreeSetCacheSize(pBt, nCache); 89 sqlite3_snprintf(sizeof(zBuf), zBuf,"%p", pBt); 90 Tcl_AppendResult(interp, zBuf, 0); 91 return TCL_OK; 92 } 93 94 /* 95 ** Usage: btree_close ID 96 ** 97 ** Close the given database. 98 */ 99 static int btree_close( 100 void *NotUsed, 101 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 102 int argc, /* Number of arguments */ 103 const char **argv /* Text of each argument */ 104 ){ 105 Btree *pBt; 106 int rc; 107 if( argc!=2 ){ 108 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 109 " ID\"", 0); 110 return TCL_ERROR; 111 } 112 pBt = sqlite3TestTextToPtr(argv[1]); 113 rc = sqlite3BtreeClose(pBt); 114 if( rc!=SQLITE_OK ){ 115 Tcl_AppendResult(interp, errorName(rc), 0); 116 return TCL_ERROR; 117 } 118 nRefSqlite3--; 119 if( nRefSqlite3==0 ){ 120 sqlite3_mutex_leave(sDb.mutex); 121 sqlite3_mutex_free(sDb.mutex); 122 sDb.mutex = 0; 123 sDb.pVfs = 0; 124 } 125 return TCL_OK; 126 } 127 128 129 /* 130 ** Usage: btree_begin_transaction ID 131 ** 132 ** Start a new transaction 133 */ 134 static int btree_begin_transaction( 135 void *NotUsed, 136 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 137 int argc, /* Number of arguments */ 138 const char **argv /* Text of each argument */ 139 ){ 140 Btree *pBt; 141 int rc; 142 if( argc!=2 ){ 143 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 144 " ID\"", 0); 145 return TCL_ERROR; 146 } 147 pBt = sqlite3TestTextToPtr(argv[1]); 148 sqlite3BtreeEnter(pBt); 149 rc = sqlite3BtreeBeginTrans(pBt, 1); 150 sqlite3BtreeLeave(pBt); 151 if( rc!=SQLITE_OK ){ 152 Tcl_AppendResult(interp, errorName(rc), 0); 153 return TCL_ERROR; 154 } 155 return TCL_OK; 156 } 157 158 /* 159 ** Usage: btree_pager_stats ID 160 ** 161 ** Returns pager statistics 162 */ 163 static int btree_pager_stats( 164 void *NotUsed, 165 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 166 int argc, /* Number of arguments */ 167 const char **argv /* Text of each argument */ 168 ){ 169 Btree *pBt; 170 int i; 171 int *a; 172 173 if( argc!=2 ){ 174 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 175 " ID\"", 0); 176 return TCL_ERROR; 177 } 178 pBt = sqlite3TestTextToPtr(argv[1]); 179 180 /* Normally in this file, with a b-tree handle opened using the 181 ** [btree_open] command it is safe to call sqlite3BtreeEnter() directly. 182 ** But this function is sometimes called with a btree handle obtained 183 ** from an open SQLite connection (using [btree_from_db]). In this case 184 ** we need to obtain the mutex for the controlling SQLite handle before 185 ** it is safe to call sqlite3BtreeEnter(). 186 */ 187 sqlite3_mutex_enter(pBt->db->mutex); 188 189 sqlite3BtreeEnter(pBt); 190 a = sqlite3PagerStats(sqlite3BtreePager(pBt)); 191 for(i=0; i<11; i++){ 192 static char *zName[] = { 193 "ref", "page", "max", "size", "state", "err", 194 "hit", "miss", "ovfl", "read", "write" 195 }; 196 char zBuf[100]; 197 Tcl_AppendElement(interp, zName[i]); 198 sqlite3_snprintf(sizeof(zBuf), zBuf,"%d",a[i]); 199 Tcl_AppendElement(interp, zBuf); 200 } 201 sqlite3BtreeLeave(pBt); 202 203 /* Release the mutex on the SQLite handle that controls this b-tree */ 204 sqlite3_mutex_leave(pBt->db->mutex); 205 return TCL_OK; 206 } 207 208 /* 209 ** Usage: btree_cursor ID TABLENUM WRITEABLE 210 ** 211 ** Create a new cursor. Return the ID for the cursor. 212 */ 213 static int btree_cursor( 214 void *NotUsed, 215 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 216 int argc, /* Number of arguments */ 217 const char **argv /* Text of each argument */ 218 ){ 219 Btree *pBt; 220 int iTable; 221 BtCursor *pCur; 222 int rc; 223 int wrFlag; 224 char zBuf[30]; 225 226 if( argc!=4 ){ 227 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 228 " ID TABLENUM WRITEABLE\"", 0); 229 return TCL_ERROR; 230 } 231 pBt = sqlite3TestTextToPtr(argv[1]); 232 if( Tcl_GetInt(interp, argv[2], &iTable) ) return TCL_ERROR; 233 if( Tcl_GetBoolean(interp, argv[3], &wrFlag) ) return TCL_ERROR; 234 pCur = (BtCursor *)ckalloc(sqlite3BtreeCursorSize()); 235 memset(pCur, 0, sqlite3BtreeCursorSize()); 236 sqlite3BtreeEnter(pBt); 237 rc = sqlite3BtreeLockTable(pBt, iTable, wrFlag); 238 if( rc==SQLITE_OK ){ 239 rc = sqlite3BtreeCursor(pBt, iTable, wrFlag, 0, pCur); 240 } 241 sqlite3BtreeLeave(pBt); 242 if( rc ){ 243 ckfree((char *)pCur); 244 Tcl_AppendResult(interp, errorName(rc), 0); 245 return TCL_ERROR; 246 } 247 sqlite3_snprintf(sizeof(zBuf), zBuf,"%p", pCur); 248 Tcl_AppendResult(interp, zBuf, 0); 249 return SQLITE_OK; 250 } 251 252 /* 253 ** Usage: btree_close_cursor ID 254 ** 255 ** Close a cursor opened using btree_cursor. 256 */ 257 static int btree_close_cursor( 258 void *NotUsed, 259 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 260 int argc, /* Number of arguments */ 261 const char **argv /* Text of each argument */ 262 ){ 263 BtCursor *pCur; 264 Btree *pBt; 265 int rc; 266 267 if( argc!=2 ){ 268 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 269 " ID\"", 0); 270 return TCL_ERROR; 271 } 272 pCur = sqlite3TestTextToPtr(argv[1]); 273 pBt = pCur->pBtree; 274 sqlite3BtreeEnter(pBt); 275 rc = sqlite3BtreeCloseCursor(pCur); 276 sqlite3BtreeLeave(pBt); 277 ckfree((char *)pCur); 278 if( rc ){ 279 Tcl_AppendResult(interp, errorName(rc), 0); 280 return TCL_ERROR; 281 } 282 return SQLITE_OK; 283 } 284 285 /* 286 ** Usage: btree_next ID 287 ** 288 ** Move the cursor to the next entry in the table. Return 0 on success 289 ** or 1 if the cursor was already on the last entry in the table or if 290 ** the table is empty. 291 */ 292 static int btree_next( 293 void *NotUsed, 294 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 295 int argc, /* Number of arguments */ 296 const char **argv /* Text of each argument */ 297 ){ 298 BtCursor *pCur; 299 int rc; 300 int res = 0; 301 char zBuf[100]; 302 303 if( argc!=2 ){ 304 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 305 " ID\"", 0); 306 return TCL_ERROR; 307 } 308 pCur = sqlite3TestTextToPtr(argv[1]); 309 sqlite3BtreeEnter(pCur->pBtree); 310 rc = sqlite3BtreeNext(pCur, &res); 311 sqlite3BtreeLeave(pCur->pBtree); 312 if( rc ){ 313 Tcl_AppendResult(interp, errorName(rc), 0); 314 return TCL_ERROR; 315 } 316 sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",res); 317 Tcl_AppendResult(interp, zBuf, 0); 318 return SQLITE_OK; 319 } 320 321 /* 322 ** Usage: btree_first ID 323 ** 324 ** Move the cursor to the first entry in the table. Return 0 if the 325 ** cursor was left point to something and 1 if the table is empty. 326 */ 327 static int btree_first( 328 void *NotUsed, 329 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 330 int argc, /* Number of arguments */ 331 const char **argv /* Text of each argument */ 332 ){ 333 BtCursor *pCur; 334 int rc; 335 int res = 0; 336 char zBuf[100]; 337 338 if( argc!=2 ){ 339 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 340 " ID\"", 0); 341 return TCL_ERROR; 342 } 343 pCur = sqlite3TestTextToPtr(argv[1]); 344 sqlite3BtreeEnter(pCur->pBtree); 345 rc = sqlite3BtreeFirst(pCur, &res); 346 sqlite3BtreeLeave(pCur->pBtree); 347 if( rc ){ 348 Tcl_AppendResult(interp, errorName(rc), 0); 349 return TCL_ERROR; 350 } 351 sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",res); 352 Tcl_AppendResult(interp, zBuf, 0); 353 return SQLITE_OK; 354 } 355 356 /* 357 ** Usage: btree_eof ID 358 ** 359 ** Return TRUE if the given cursor is not pointing at a valid entry. 360 ** Return FALSE if the cursor does point to a valid entry. 361 */ 362 static int btree_eof( 363 void *NotUsed, 364 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 365 int argc, /* Number of arguments */ 366 const char **argv /* Text of each argument */ 367 ){ 368 BtCursor *pCur; 369 int rc; 370 char zBuf[50]; 371 372 if( argc!=2 ){ 373 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 374 " ID\"", 0); 375 return TCL_ERROR; 376 } 377 pCur = sqlite3TestTextToPtr(argv[1]); 378 sqlite3BtreeEnter(pCur->pBtree); 379 rc = sqlite3BtreeEof(pCur); 380 sqlite3BtreeLeave(pCur->pBtree); 381 sqlite3_snprintf(sizeof(zBuf),zBuf, "%d", rc); 382 Tcl_AppendResult(interp, zBuf, 0); 383 return SQLITE_OK; 384 } 385 386 /* 387 ** Usage: btree_payload_size ID 388 ** 389 ** Return the number of bytes of payload 390 */ 391 static int btree_payload_size( 392 void *NotUsed, 393 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 394 int argc, /* Number of arguments */ 395 const char **argv /* Text of each argument */ 396 ){ 397 BtCursor *pCur; 398 int n2; 399 u64 n1; 400 char zBuf[50]; 401 402 if( argc!=2 ){ 403 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 404 " ID\"", 0); 405 return TCL_ERROR; 406 } 407 pCur = sqlite3TestTextToPtr(argv[1]); 408 sqlite3BtreeEnter(pCur->pBtree); 409 410 /* The cursor may be in "require-seek" state. If this is the case, the 411 ** call to BtreeDataSize() will fix it. */ 412 sqlite3BtreeDataSize(pCur, (u32*)&n2); 413 if( pCur->apPage[pCur->iPage]->intKey ){ 414 n1 = 0; 415 }else{ 416 sqlite3BtreeKeySize(pCur, (i64*)&n1); 417 } 418 sqlite3BtreeLeave(pCur->pBtree); 419 sqlite3_snprintf(sizeof(zBuf),zBuf, "%d", (int)(n1+n2)); 420 Tcl_AppendResult(interp, zBuf, 0); 421 return SQLITE_OK; 422 } 423 424 /* 425 ** usage: varint_test START MULTIPLIER COUNT INCREMENT 426 ** 427 ** This command tests the putVarint() and getVarint() 428 ** routines, both for accuracy and for speed. 429 ** 430 ** An integer is written using putVarint() and read back with 431 ** getVarint() and varified to be unchanged. This repeats COUNT 432 ** times. The first integer is START*MULTIPLIER. Each iteration 433 ** increases the integer by INCREMENT. 434 ** 435 ** This command returns nothing if it works. It returns an error message 436 ** if something goes wrong. 437 */ 438 static int btree_varint_test( 439 void *NotUsed, 440 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 441 int argc, /* Number of arguments */ 442 const char **argv /* Text of each argument */ 443 ){ 444 u32 start, mult, count, incr; 445 u64 in, out; 446 int n1, n2, i, j; 447 unsigned char zBuf[100]; 448 if( argc!=5 ){ 449 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 450 " START MULTIPLIER COUNT INCREMENT\"", 0); 451 return TCL_ERROR; 452 } 453 if( Tcl_GetInt(interp, argv[1], (int*)&start) ) return TCL_ERROR; 454 if( Tcl_GetInt(interp, argv[2], (int*)&mult) ) return TCL_ERROR; 455 if( Tcl_GetInt(interp, argv[3], (int*)&count) ) return TCL_ERROR; 456 if( Tcl_GetInt(interp, argv[4], (int*)&incr) ) return TCL_ERROR; 457 in = start; 458 in *= mult; 459 for(i=0; i<count; i++){ 460 char zErr[200]; 461 n1 = putVarint(zBuf, in); 462 if( n1>9 || n1<1 ){ 463 sprintf(zErr, "putVarint returned %d - should be between 1 and 9", n1); 464 Tcl_AppendResult(interp, zErr, 0); 465 return TCL_ERROR; 466 } 467 n2 = getVarint(zBuf, &out); 468 if( n1!=n2 ){ 469 sprintf(zErr, "putVarint returned %d and getVarint returned %d", n1, n2); 470 Tcl_AppendResult(interp, zErr, 0); 471 return TCL_ERROR; 472 } 473 if( in!=out ){ 474 sprintf(zErr, "Wrote 0x%016llx and got back 0x%016llx", in, out); 475 Tcl_AppendResult(interp, zErr, 0); 476 return TCL_ERROR; 477 } 478 if( (in & 0xffffffff)==in ){ 479 u32 out32; 480 n2 = getVarint32(zBuf, out32); 481 out = out32; 482 if( n1!=n2 ){ 483 sprintf(zErr, "putVarint returned %d and GetVarint32 returned %d", 484 n1, n2); 485 Tcl_AppendResult(interp, zErr, 0); 486 return TCL_ERROR; 487 } 488 if( in!=out ){ 489 sprintf(zErr, "Wrote 0x%016llx and got back 0x%016llx from GetVarint32", 490 in, out); 491 Tcl_AppendResult(interp, zErr, 0); 492 return TCL_ERROR; 493 } 494 } 495 496 /* In order to get realistic timings, run getVarint 19 more times. 497 ** This is because getVarint is called about 20 times more often 498 ** than putVarint. 499 */ 500 for(j=0; j<19; j++){ 501 getVarint(zBuf, &out); 502 } 503 in += incr; 504 } 505 return TCL_OK; 506 } 507 508 /* 509 ** usage: btree_from_db DB-HANDLE 510 ** 511 ** This command returns the btree handle for the main database associated 512 ** with the database-handle passed as the argument. Example usage: 513 ** 514 ** sqlite3 db test.db 515 ** set bt [btree_from_db db] 516 */ 517 static int btree_from_db( 518 void *NotUsed, 519 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 520 int argc, /* Number of arguments */ 521 const char **argv /* Text of each argument */ 522 ){ 523 char zBuf[100]; 524 Tcl_CmdInfo info; 525 sqlite3 *db; 526 Btree *pBt; 527 int iDb = 0; 528 529 if( argc!=2 && argc!=3 ){ 530 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 531 " DB-HANDLE ?N?\"", 0); 532 return TCL_ERROR; 533 } 534 535 if( 1!=Tcl_GetCommandInfo(interp, argv[1], &info) ){ 536 Tcl_AppendResult(interp, "No such db-handle: \"", argv[1], "\"", 0); 537 return TCL_ERROR; 538 } 539 if( argc==3 ){ 540 iDb = atoi(argv[2]); 541 } 542 543 db = *((sqlite3 **)info.objClientData); 544 assert( db ); 545 546 pBt = db->aDb[iDb].pBt; 547 sqlite3_snprintf(sizeof(zBuf), zBuf, "%p", pBt); 548 Tcl_SetResult(interp, zBuf, TCL_VOLATILE); 549 return TCL_OK; 550 } 551 552 /* 553 ** Usage: btree_ismemdb ID 554 ** 555 ** Return true if the B-Tree is in-memory. 556 */ 557 static int btree_ismemdb( 558 void *NotUsed, 559 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 560 int argc, /* Number of arguments */ 561 const char **argv /* Text of each argument */ 562 ){ 563 Btree *pBt; 564 int res; 565 566 if( argc!=2 ){ 567 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 568 " ID\"", 0); 569 return TCL_ERROR; 570 } 571 pBt = sqlite3TestTextToPtr(argv[1]); 572 sqlite3_mutex_enter(pBt->db->mutex); 573 sqlite3BtreeEnter(pBt); 574 res = sqlite3PagerIsMemdb(sqlite3BtreePager(pBt)); 575 sqlite3BtreeLeave(pBt); 576 sqlite3_mutex_leave(pBt->db->mutex); 577 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(res)); 578 return SQLITE_OK; 579 } 580 581 /* 582 ** usage: btree_set_cache_size ID NCACHE 583 ** 584 ** Set the size of the cache used by btree $ID. 585 */ 586 static int btree_set_cache_size( 587 void *NotUsed, 588 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 589 int argc, /* Number of arguments */ 590 const char **argv /* Text of each argument */ 591 ){ 592 int nCache; 593 Btree *pBt; 594 595 if( argc!=3 ){ 596 Tcl_AppendResult( 597 interp, "wrong # args: should be \"", argv[0], " BT NCACHE\"", 0); 598 return TCL_ERROR; 599 } 600 pBt = sqlite3TestTextToPtr(argv[1]); 601 if( Tcl_GetInt(interp, argv[2], &nCache) ) return TCL_ERROR; 602 603 sqlite3_mutex_enter(pBt->db->mutex); 604 sqlite3BtreeEnter(pBt); 605 sqlite3BtreeSetCacheSize(pBt, nCache); 606 sqlite3BtreeLeave(pBt); 607 sqlite3_mutex_leave(pBt->db->mutex); 608 return TCL_OK; 609 } 610 611 612 613 /* 614 ** Register commands with the TCL interpreter. 615 */ 616 int Sqlitetest3_Init(Tcl_Interp *interp){ 617 static struct { 618 char *zName; 619 Tcl_CmdProc *xProc; 620 } aCmd[] = { 621 { "btree_open", (Tcl_CmdProc*)btree_open }, 622 { "btree_close", (Tcl_CmdProc*)btree_close }, 623 { "btree_begin_transaction", (Tcl_CmdProc*)btree_begin_transaction }, 624 { "btree_pager_stats", (Tcl_CmdProc*)btree_pager_stats }, 625 { "btree_cursor", (Tcl_CmdProc*)btree_cursor }, 626 { "btree_close_cursor", (Tcl_CmdProc*)btree_close_cursor }, 627 { "btree_next", (Tcl_CmdProc*)btree_next }, 628 { "btree_eof", (Tcl_CmdProc*)btree_eof }, 629 { "btree_payload_size", (Tcl_CmdProc*)btree_payload_size }, 630 { "btree_first", (Tcl_CmdProc*)btree_first }, 631 { "btree_varint_test", (Tcl_CmdProc*)btree_varint_test }, 632 { "btree_from_db", (Tcl_CmdProc*)btree_from_db }, 633 { "btree_ismemdb", (Tcl_CmdProc*)btree_ismemdb }, 634 { "btree_set_cache_size", (Tcl_CmdProc*)btree_set_cache_size } 635 }; 636 int i; 637 638 for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){ 639 Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0); 640 } 641 642 return TCL_OK; 643 } 644