3 ** This program attempts to test the correctness of some facets of the
4 ** LSM database library. Specifically, that the contents of the database
5 ** are maintained correctly during a series of inserts and deletes.
9 #include "lsmtest_tdb.h"
23 typedef struct SqlDb SqlDb
;
25 static int error_transaction_function(TestDb
*p
, int iLevel
){
27 unused_parameter(iLevel
);
32 /*************************************************************************
33 ** Begin wrapper for LevelDB.
37 #include <leveldb/c.h>
39 typedef struct LevelDb LevelDb
;
43 leveldb_options_t
*pOpt
;
44 leveldb_writeoptions_t
*pWriteOpt
;
45 leveldb_readoptions_t
*pReadOpt
;
50 static int test_leveldb_close(TestDb
*pTestDb
){
51 LevelDb
*pDb
= (LevelDb
*)pTestDb
;
53 leveldb_close(pDb
->db
);
54 leveldb_writeoptions_destroy(pDb
->pWriteOpt
);
55 leveldb_readoptions_destroy(pDb
->pReadOpt
);
56 leveldb_options_destroy(pDb
->pOpt
);
63 static int test_leveldb_write(
70 LevelDb
*pDb
= (LevelDb
*)pTestDb
;
72 leveldb_put(pDb
->db
, pDb
->pWriteOpt
, pKey
, nKey
, pVal
, nVal
, &zErr
);
76 static int test_leveldb_delete(TestDb
*pTestDb
, void *pKey
, int nKey
){
77 LevelDb
*pDb
= (LevelDb
*)pTestDb
;
79 leveldb_delete(pDb
->db
, pDb
->pWriteOpt
, pKey
, nKey
, &zErr
);
83 static int test_leveldb_fetch(
90 LevelDb
*pDb
= (LevelDb
*)pTestDb
;
94 if( pKey
==0 ) return 0;
96 pDb
->pVal
= leveldb_get(pDb
->db
, pDb
->pReadOpt
, pKey
, nKey
, &nVal
, &zErr
);
97 *ppVal
= (void *)(pDb
->pVal
);
107 static int test_leveldb_scan(
111 void *pKey1
, int nKey1
, /* Start of search */
112 void *pKey2
, int nKey2
, /* End of search */
113 void (*xCallback
)(void *, void *, int , void *, int)
115 LevelDb
*pDb
= (LevelDb
*)pTestDb
;
116 leveldb_iterator_t
*iter
;
118 iter
= leveldb_create_iterator(pDb
->db
, pDb
->pReadOpt
);
122 leveldb_iter_seek(iter
, pKey1
, nKey1
);
124 leveldb_iter_seek_to_first(iter
);
128 leveldb_iter_seek(iter
, pKey2
, nKey2
);
130 if( leveldb_iter_valid(iter
)==0 ){
131 leveldb_iter_seek_to_last(iter
);
133 const char *k
; size_t n
;
135 k
= leveldb_iter_key(iter
, &n
);
136 res
= memcmp(k
, pKey2
, MIN(n
, nKey2
));
137 if( res
==0 ) res
= n
- nKey2
;
140 leveldb_iter_prev(iter
);
144 leveldb_iter_seek_to_last(iter
);
149 while( leveldb_iter_valid(iter
) ){
150 const char *k
; size_t n
;
151 const char *v
; size_t n2
;
154 k
= leveldb_iter_key(iter
, &n
);
155 if( bReverse
==0 && pKey2
){
156 res
= memcmp(k
, pKey2
, MIN(n
, nKey2
));
157 if( res
==0 ) res
= n
- nKey2
;
160 if( bReverse
!=0 && pKey1
){
161 res
= memcmp(k
, pKey1
, MIN(n
, nKey1
));
162 if( res
==0 ) res
= n
- nKey1
;
166 v
= leveldb_iter_value(iter
, &n2
);
168 xCallback(pCtx
, (void *)k
, n
, (void *)v
, n2
);
171 leveldb_iter_next(iter
);
173 leveldb_iter_prev(iter
);
177 leveldb_iter_destroy(iter
);
181 static int test_leveldb_open(
183 const char *zFilename
,
187 static const DatabaseMethods LeveldbMethods
= {
194 error_transaction_function
,
195 error_transaction_function
,
196 error_transaction_function
203 char *zCmd
= sqlite3_mprintf("rm -rf %s\n", zFilename
);
208 pLevelDb
= (LevelDb
*)malloc(sizeof(LevelDb
));
209 memset(pLevelDb
, 0, sizeof(LevelDb
));
211 pLevelDb
->pOpt
= leveldb_options_create();
212 leveldb_options_set_create_if_missing(pLevelDb
->pOpt
, 1);
213 pLevelDb
->pWriteOpt
= leveldb_writeoptions_create();
214 pLevelDb
->pReadOpt
= leveldb_readoptions_create();
216 pLevelDb
->db
= leveldb_open(pLevelDb
->pOpt
, zFilename
, &zErr
);
219 test_leveldb_close((TestDb
*)pLevelDb
);
224 *ppDb
= (TestDb
*)pLevelDb
;
225 pLevelDb
->base
.pMethods
= &LeveldbMethods
;
228 #endif /* HAVE_LEVELDB */
230 ** End wrapper for LevelDB.
231 *************************************************************************/
233 #ifdef HAVE_KYOTOCABINET
234 static int kc_close(TestDb
*pTestDb
){
235 return test_kc_close(pTestDb
);
245 return test_kc_write(pTestDb
, pKey
, nKey
, pVal
, nVal
);
248 static int kc_delete(TestDb
*pTestDb
, void *pKey
, int nKey
){
249 return test_kc_delete(pTestDb
, pKey
, nKey
);
252 static int kc_delete_range(
254 void *pKey1
, int nKey1
,
255 void *pKey2
, int nKey2
257 return test_kc_delete_range(pTestDb
, pKey1
, nKey1
, pKey2
, nKey2
);
267 if( pKey
==0 ) return LSM_OK
;
268 return test_kc_fetch(pTestDb
, pKey
, nKey
, ppVal
, pnVal
);
275 void *pFirst
, int nFirst
,
276 void *pLast
, int nLast
,
277 void (*xCallback
)(void *, void *, int , void *, int)
280 pTestDb
, pCtx
, bReverse
, pFirst
, nFirst
, pLast
, nLast
, xCallback
286 const char *zFilename
,
290 static const DatabaseMethods KcdbMethods
= {
297 error_transaction_function
,
298 error_transaction_function
,
299 error_transaction_function
305 rc
= test_kc_open(zFilename
, bClear
, &pTestDb
);
310 pTestDb
->pMethods
= &KcdbMethods
;
314 #endif /* HAVE_KYOTOCABINET */
316 ** End wrapper for Kyoto cabinet.
317 *************************************************************************/
320 static int mdb_close(TestDb
*pTestDb
){
321 return test_mdb_close(pTestDb
);
324 static int mdb_write(
331 return test_mdb_write(pTestDb
, pKey
, nKey
, pVal
, nVal
);
334 static int mdb_delete(TestDb
*pTestDb
, void *pKey
, int nKey
){
335 return test_mdb_delete(pTestDb
, pKey
, nKey
);
338 static int mdb_fetch(
345 if( pKey
==0 ) return LSM_OK
;
346 return test_mdb_fetch(pTestDb
, pKey
, nKey
, ppVal
, pnVal
);
353 void *pFirst
, int nFirst
,
354 void *pLast
, int nLast
,
355 void (*xCallback
)(void *, void *, int , void *, int)
357 return test_mdb_scan(
358 pTestDb
, pCtx
, bReverse
, pFirst
, nFirst
, pLast
, nLast
, xCallback
364 const char *zFilename
,
368 static const DatabaseMethods KcdbMethods
= {
375 error_transaction_function
,
376 error_transaction_function
,
377 error_transaction_function
383 rc
= test_mdb_open(zSpec
, zFilename
, bClear
, &pTestDb
);
388 pTestDb
->pMethods
= &KcdbMethods
;
392 #endif /* HAVE_MDB */
394 /*************************************************************************
395 ** Begin wrapper for SQLite.
400 ** The number of open nested transactions, in the same sense as used
401 ** by the tdb_begin/commit/rollback and SQLite 4 KV interfaces. If this
402 ** value is 0, there are no transactions open at all. If it is 1, then
403 ** there is a read transaction. If it is 2 or greater, then there are
404 ** (nOpenTrans-1) nested write transactions open.
409 sqlite3_stmt
*pInsert
;
410 sqlite3_stmt
*pDelete
;
411 sqlite3_stmt
*pDeleteRange
;
412 sqlite3_stmt
*pFetch
;
413 sqlite3_stmt
*apScan
[8];
417 /* Used by sql_fetch() to allocate space for results */
422 static int sql_close(TestDb
*pTestDb
){
423 SqlDb
*pDb
= (SqlDb
*)pTestDb
;
424 sqlite3_finalize(pDb
->pInsert
);
425 sqlite3_finalize(pDb
->pDelete
);
426 sqlite3_finalize(pDb
->pDeleteRange
);
427 sqlite3_finalize(pDb
->pFetch
);
428 sqlite3_finalize(pDb
->apScan
[0]);
429 sqlite3_finalize(pDb
->apScan
[1]);
430 sqlite3_finalize(pDb
->apScan
[2]);
431 sqlite3_finalize(pDb
->apScan
[3]);
432 sqlite3_finalize(pDb
->apScan
[4]);
433 sqlite3_finalize(pDb
->apScan
[5]);
434 sqlite3_finalize(pDb
->apScan
[6]);
435 sqlite3_finalize(pDb
->apScan
[7]);
436 sqlite3_close(pDb
->db
);
437 free((char *)pDb
->aAlloc
);
442 static int sql_write(
449 SqlDb
*pDb
= (SqlDb
*)pTestDb
;
450 sqlite3_bind_blob(pDb
->pInsert
, 1, pKey
, nKey
, SQLITE_STATIC
);
451 sqlite3_bind_blob(pDb
->pInsert
, 2, pVal
, nVal
, SQLITE_STATIC
);
452 sqlite3_step(pDb
->pInsert
);
453 return sqlite3_reset(pDb
->pInsert
);
456 static int sql_delete(TestDb
*pTestDb
, void *pKey
, int nKey
){
457 SqlDb
*pDb
= (SqlDb
*)pTestDb
;
458 sqlite3_bind_blob(pDb
->pDelete
, 1, pKey
, nKey
, SQLITE_STATIC
);
459 sqlite3_step(pDb
->pDelete
);
460 return sqlite3_reset(pDb
->pDelete
);
463 static int sql_delete_range(
465 void *pKey1
, int nKey1
,
466 void *pKey2
, int nKey2
468 SqlDb
*pDb
= (SqlDb
*)pTestDb
;
469 sqlite3_bind_blob(pDb
->pDeleteRange
, 1, pKey1
, nKey1
, SQLITE_STATIC
);
470 sqlite3_bind_blob(pDb
->pDeleteRange
, 2, pKey2
, nKey2
, SQLITE_STATIC
);
471 sqlite3_step(pDb
->pDeleteRange
);
472 return sqlite3_reset(pDb
->pDeleteRange
);
475 static int sql_fetch(
482 SqlDb
*pDb
= (SqlDb
*)pTestDb
;
485 sqlite3_reset(pDb
->pFetch
);
492 sqlite3_bind_blob(pDb
->pFetch
, 1, pKey
, nKey
, SQLITE_STATIC
);
493 rc
= sqlite3_step(pDb
->pFetch
);
494 if( rc
==SQLITE_ROW
){
495 int nVal
= sqlite3_column_bytes(pDb
->pFetch
, 0);
496 u8
*aVal
= (void *)sqlite3_column_blob(pDb
->pFetch
, 0);
498 if( nVal
>pDb
->nAlloc
){
500 pDb
->aAlloc
= (u8
*)malloc(nVal
*2);
501 pDb
->nAlloc
= nVal
*2;
503 memcpy(pDb
->aAlloc
, aVal
, nVal
);
505 *ppVal
= (void *)pDb
->aAlloc
;
511 rc
= sqlite3_reset(pDb
->pFetch
);
519 void *pFirst
, int nFirst
,
520 void *pLast
, int nLast
,
521 void (*xCallback
)(void *, void *, int , void *, int)
523 SqlDb
*pDb
= (SqlDb
*)pTestDb
;
526 assert( bReverse
==1 || bReverse
==0 );
527 pScan
= pDb
->apScan
[(pFirst
==0) + (pLast
==0)*2 + bReverse
*4];
529 if( pFirst
) sqlite3_bind_blob(pScan
, 1, pFirst
, nFirst
, SQLITE_STATIC
);
530 if( pLast
) sqlite3_bind_blob(pScan
, 2, pLast
, nLast
, SQLITE_STATIC
);
532 while( SQLITE_ROW
==sqlite3_step(pScan
) ){
533 void *pKey
; int nKey
;
534 void *pVal
; int nVal
;
536 nKey
= sqlite3_column_bytes(pScan
, 0);
537 pKey
= (void *)sqlite3_column_blob(pScan
, 0);
538 nVal
= sqlite3_column_bytes(pScan
, 1);
539 pVal
= (void *)sqlite3_column_blob(pScan
, 1);
541 xCallback(pCtx
, pKey
, nKey
, pVal
, nVal
);
543 return sqlite3_reset(pScan
);
546 static int sql_begin(TestDb
*pTestDb
, int iLevel
){
548 SqlDb
*pDb
= (SqlDb
*)pTestDb
;
550 /* iLevel==0 is a no-op */
551 if( iLevel
==0 ) return 0;
553 /* If there are no transactions at all open, open a read transaction. */
554 if( pDb
->nOpenTrans
==0 ){
555 int rc
= sqlite3_exec(pDb
->db
,
556 "BEGIN; SELECT * FROM sqlite_master LIMIT 1;" , 0, 0, 0
558 if( rc
!=0 ) return rc
;
562 /* Open any required write transactions */
563 for(i
=pDb
->nOpenTrans
; i
<iLevel
; i
++){
564 char *zSql
= sqlite3_mprintf("SAVEPOINT x%d", i
);
565 int rc
= sqlite3_exec(pDb
->db
, zSql
, 0, 0, 0);
567 if( rc
!=SQLITE_OK
) return rc
;
570 pDb
->nOpenTrans
= iLevel
;
574 static int sql_commit(TestDb
*pTestDb
, int iLevel
){
575 SqlDb
*pDb
= (SqlDb
*)pTestDb
;
578 /* Close the read transaction if requested. */
579 if( pDb
->nOpenTrans
>=1 && iLevel
==0 ){
580 int rc
= sqlite3_exec(pDb
->db
, "COMMIT", 0, 0, 0);
581 if( rc
!=0 ) return rc
;
585 /* Close write transactions as required */
586 if( pDb
->nOpenTrans
>iLevel
){
587 char *zSql
= sqlite3_mprintf("RELEASE x%d", iLevel
);
588 int rc
= sqlite3_exec(pDb
->db
, zSql
, 0, 0, 0);
590 if( rc
!=0 ) return rc
;
593 pDb
->nOpenTrans
= iLevel
;
597 static int sql_rollback(TestDb
*pTestDb
, int iLevel
){
598 SqlDb
*pDb
= (SqlDb
*)pTestDb
;
601 if( pDb
->nOpenTrans
>=1 && iLevel
==0 ){
602 /* Close the read transaction if requested. */
603 int rc
= sqlite3_exec(pDb
->db
, "ROLLBACK", 0, 0, 0);
604 if( rc
!=0 ) return rc
;
605 }else if( pDb
->nOpenTrans
>1 && iLevel
==1 ){
606 /* Or, rollback and close the top-level write transaction */
607 int rc
= sqlite3_exec(pDb
->db
, "ROLLBACK TO x1; RELEASE x1;", 0, 0, 0);
608 if( rc
!=0 ) return rc
;
610 /* Or, just roll back some nested transactions */
611 char *zSql
= sqlite3_mprintf("ROLLBACK TO x%d", iLevel
-1);
612 int rc
= sqlite3_exec(pDb
->db
, zSql
, 0, 0, 0);
614 if( rc
!=0 ) return rc
;
617 pDb
->nOpenTrans
= iLevel
;
623 const char *zFilename
,
627 static const DatabaseMethods SqlMethods
= {
638 const char *zCreate
= "CREATE TABLE IF NOT EXISTS t1(k PRIMARY KEY, v)";
639 const char *zInsert
= "REPLACE INTO t1 VALUES(?, ?)";
640 const char *zDelete
= "DELETE FROM t1 WHERE k = ?";
641 const char *zRange
= "DELETE FROM t1 WHERE k>? AND k<?";
642 const char *zFetch
= "SELECT v FROM t1 WHERE k = ?";
644 const char *zScan0
= "SELECT * FROM t1 WHERE k BETWEEN ?1 AND ?2 ORDER BY k";
645 const char *zScan1
= "SELECT * FROM t1 WHERE k <= ?2 ORDER BY k";
646 const char *zScan2
= "SELECT * FROM t1 WHERE k >= ?1 ORDER BY k";
647 const char *zScan3
= "SELECT * FROM t1 ORDER BY k";
650 "SELECT * FROM t1 WHERE k BETWEEN ?1 AND ?2 ORDER BY k DESC";
651 const char *zScan5
= "SELECT * FROM t1 WHERE k <= ?2 ORDER BY k DESC";
652 const char *zScan6
= "SELECT * FROM t1 WHERE k >= ?1 ORDER BY k DESC";
653 const char *zScan7
= "SELECT * FROM t1 ORDER BY k DESC";
659 if( bClear
&& zFilename
&& zFilename
[0] ){
663 pDb
= (SqlDb
*)malloc(sizeof(SqlDb
));
664 memset(pDb
, 0, sizeof(SqlDb
));
665 pDb
->base
.pMethods
= &SqlMethods
;
667 if( 0!=(rc
= sqlite3_open(zFilename
, &pDb
->db
))
668 || 0!=(rc
= sqlite3_exec(pDb
->db
, zCreate
, 0, 0, 0))
669 || 0!=(rc
= sqlite3_prepare_v2(pDb
->db
, zInsert
, -1, &pDb
->pInsert
, 0))
670 || 0!=(rc
= sqlite3_prepare_v2(pDb
->db
, zDelete
, -1, &pDb
->pDelete
, 0))
671 || 0!=(rc
= sqlite3_prepare_v2(pDb
->db
, zRange
, -1, &pDb
->pDeleteRange
, 0))
672 || 0!=(rc
= sqlite3_prepare_v2(pDb
->db
, zFetch
, -1, &pDb
->pFetch
, 0))
673 || 0!=(rc
= sqlite3_prepare_v2(pDb
->db
, zScan0
, -1, &pDb
->apScan
[0], 0))
674 || 0!=(rc
= sqlite3_prepare_v2(pDb
->db
, zScan1
, -1, &pDb
->apScan
[1], 0))
675 || 0!=(rc
= sqlite3_prepare_v2(pDb
->db
, zScan2
, -1, &pDb
->apScan
[2], 0))
676 || 0!=(rc
= sqlite3_prepare_v2(pDb
->db
, zScan3
, -1, &pDb
->apScan
[3], 0))
677 || 0!=(rc
= sqlite3_prepare_v2(pDb
->db
, zScan4
, -1, &pDb
->apScan
[4], 0))
678 || 0!=(rc
= sqlite3_prepare_v2(pDb
->db
, zScan5
, -1, &pDb
->apScan
[5], 0))
679 || 0!=(rc
= sqlite3_prepare_v2(pDb
->db
, zScan6
, -1, &pDb
->apScan
[6], 0))
680 || 0!=(rc
= sqlite3_prepare_v2(pDb
->db
, zScan7
, -1, &pDb
->apScan
[7], 0))
683 sql_close((TestDb
*)pDb
);
687 zPragma
= sqlite3_mprintf("PRAGMA page_size=%d", TESTDB_DEFAULT_PAGE_SIZE
);
688 sqlite3_exec(pDb
->db
, zPragma
, 0, 0, 0);
689 sqlite3_free(zPragma
);
690 zPragma
= sqlite3_mprintf("PRAGMA cache_size=%d", TESTDB_DEFAULT_CACHE_SIZE
);
691 sqlite3_exec(pDb
->db
, zPragma
, 0, 0, 0);
692 sqlite3_free(zPragma
);
694 /* sqlite3_exec(pDb->db, "PRAGMA locking_mode=EXCLUSIVE", 0, 0, 0); */
695 sqlite3_exec(pDb
->db
, "PRAGMA synchronous=OFF", 0, 0, 0);
696 sqlite3_exec(pDb
->db
, "PRAGMA journal_mode=WAL", 0, 0, 0);
697 sqlite3_exec(pDb
->db
, "PRAGMA wal_autocheckpoint=4096", 0, 0, 0);
699 rc
= sqlite3_exec(pDb
->db
, zSpec
, 0, 0, 0);
701 sql_close((TestDb
*)pDb
);
706 *ppDb
= (TestDb
*)pDb
;
710 ** End wrapper for SQLite.
711 *************************************************************************/
713 /*************************************************************************
714 ** Begin exported functions.
718 const char *zDefaultDb
;
719 int (*xOpen
)(const char *, const char *zFilename
, int bClear
, TestDb
**ppDb
);
721 { "sqlite3", "testdb.sqlite", sql_open
},
722 { "lsm_small", "testdb.lsm_small", test_lsm_small_open
},
723 { "lsm_lomem", "testdb.lsm_lomem", test_lsm_lomem_open
},
724 { "lsm_lomem2", "testdb.lsm_lomem2", test_lsm_lomem2_open
},
726 { "lsm_zip", "testdb.lsm_zip", test_lsm_zip_open
},
728 { "lsm", "testdb.lsm", test_lsm_open
},
729 #ifdef LSM_MUTEX_PTHREADS
730 { "lsm_mt2", "testdb.lsm_mt2", test_lsm_mt2
},
731 { "lsm_mt3", "testdb.lsm_mt3", test_lsm_mt3
},
734 { "leveldb", "testdb.leveldb", test_leveldb_open
},
736 #ifdef HAVE_KYOTOCABINET
737 { "kyotocabinet", "testdb.kc", kc_open
},
740 { "mdb", "./testdb.mdb", mdb_open
}
744 const char *tdb_system_name(int i
){
745 if( i
<0 || i
>=ArraySize(aLib
) ) return 0;
746 return aLib
[i
].zName
;
749 const char *tdb_default_db(const char *zSys
){
751 for(i
=0; i
<ArraySize(aLib
); i
++){
752 if( strcmp(aLib
[i
].zName
, zSys
)==0 ) return aLib
[i
].zDefaultDb
;
757 int tdb_open(const char *zLib
, const char *zDb
, int bClear
, TestDb
**ppDb
){
760 const char *zSpec
= 0;
763 while( zLib
[nLib
] && zLib
[nLib
]!=' ' ){
767 while( *zSpec
==' ' ) zSpec
++;
768 if( *zSpec
=='\0' ) zSpec
= 0;
770 for(i
=0; i
<ArraySize(aLib
); i
++){
771 if( (int)strlen(aLib
[i
].zName
)==nLib
772 && 0==memcmp(zLib
, aLib
[i
].zName
, nLib
) ){
773 rc
= aLib
[i
].xOpen(zSpec
, (zDb
? zDb
: aLib
[i
].zDefaultDb
), bClear
, ppDb
);
775 (*ppDb
)->zLibrary
= aLib
[i
].zName
;
782 /* Failed to find the requested database library. Return an error. */
788 int tdb_close(TestDb
*pDb
){
790 return pDb
->pMethods
->xClose(pDb
);
795 int tdb_write(TestDb
*pDb
, void *pKey
, int nKey
, void *pVal
, int nVal
){
796 return pDb
->pMethods
->xWrite(pDb
, pKey
, nKey
, pVal
, nVal
);
799 int tdb_delete(TestDb
*pDb
, void *pKey
, int nKey
){
800 return pDb
->pMethods
->xDelete(pDb
, pKey
, nKey
);
803 int tdb_delete_range(
804 TestDb
*pDb
, void *pKey1
, int nKey1
, void *pKey2
, int nKey2
806 return pDb
->pMethods
->xDeleteRange(pDb
, pKey1
, nKey1
, pKey2
, nKey2
);
809 int tdb_fetch(TestDb
*pDb
, void *pKey
, int nKey
, void **ppVal
, int *pnVal
){
810 return pDb
->pMethods
->xFetch(pDb
, pKey
, nKey
, ppVal
, pnVal
);
814 TestDb
*pDb
, /* Database handle */
815 void *pCtx
, /* Context pointer to pass to xCallback */
816 int bReverse
, /* True to scan in reverse order */
817 void *pKey1
, int nKey1
, /* Start of search */
818 void *pKey2
, int nKey2
, /* End of search */
819 void (*xCallback
)(void *pCtx
, void *pKey
, int nKey
, void *pVal
, int nVal
)
821 return pDb
->pMethods
->xScan(
822 pDb
, pCtx
, bReverse
, pKey1
, nKey1
, pKey2
, nKey2
, xCallback
826 int tdb_begin(TestDb
*pDb
, int iLevel
){
827 return pDb
->pMethods
->xBegin(pDb
, iLevel
);
829 int tdb_commit(TestDb
*pDb
, int iLevel
){
830 return pDb
->pMethods
->xCommit(pDb
, iLevel
);
832 int tdb_rollback(TestDb
*pDb
, int iLevel
){
833 return pDb
->pMethods
->xRollback(pDb
, iLevel
);
836 int tdb_transaction_support(TestDb
*pDb
){
837 return (pDb
->pMethods
->xBegin
!= error_transaction_function
);
840 const char *tdb_library_name(TestDb
*pDb
){
841 return pDb
->zLibrary
;
845 ** End exported functions.
846 *************************************************************************/