| |
| /* |
| ** This program attempts to test the correctness of some facets of the |
| ** LSM database library. Specifically, that the contents of the database |
| ** are maintained correctly during a series of inserts and deletes. |
| */ |
| |
| |
| #include "lsmtest_tdb.h" |
| #include "lsm.h" |
| |
| #include "lsmtest.h" |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <assert.h> |
| #ifndef _WIN32 |
| # include <unistd.h> |
| #endif |
| #include <stdio.h> |
| |
| |
| typedef struct SqlDb SqlDb; |
| |
| static int error_transaction_function(TestDb *p, int iLevel){ |
| unused_parameter(p); |
| unused_parameter(iLevel); |
| return -1; |
| } |
| |
| |
| /************************************************************************* |
| ** Begin wrapper for LevelDB. |
| */ |
| #ifdef HAVE_LEVELDB |
| |
| #include <leveldb/c.h> |
| |
| typedef struct LevelDb LevelDb; |
| struct LevelDb { |
| TestDb base; |
| leveldb_t *db; |
| leveldb_options_t *pOpt; |
| leveldb_writeoptions_t *pWriteOpt; |
| leveldb_readoptions_t *pReadOpt; |
| |
| char *pVal; |
| }; |
| |
| static int test_leveldb_close(TestDb *pTestDb){ |
| LevelDb *pDb = (LevelDb *)pTestDb; |
| |
| leveldb_close(pDb->db); |
| leveldb_writeoptions_destroy(pDb->pWriteOpt); |
| leveldb_readoptions_destroy(pDb->pReadOpt); |
| leveldb_options_destroy(pDb->pOpt); |
| free(pDb->pVal); |
| free(pDb); |
| |
| return 0; |
| } |
| |
| static int test_leveldb_write( |
| TestDb *pTestDb, |
| void *pKey, |
| int nKey, |
| void *pVal, |
| int nVal |
| ){ |
| LevelDb *pDb = (LevelDb *)pTestDb; |
| char *zErr = 0; |
| leveldb_put(pDb->db, pDb->pWriteOpt, pKey, nKey, pVal, nVal, &zErr); |
| return (zErr!=0); |
| } |
| |
| static int test_leveldb_delete(TestDb *pTestDb, void *pKey, int nKey){ |
| LevelDb *pDb = (LevelDb *)pTestDb; |
| char *zErr = 0; |
| leveldb_delete(pDb->db, pDb->pWriteOpt, pKey, nKey, &zErr); |
| return (zErr!=0); |
| } |
| |
| static int test_leveldb_fetch( |
| TestDb *pTestDb, |
| void *pKey, |
| int nKey, |
| void **ppVal, |
| int *pnVal |
| ){ |
| LevelDb *pDb = (LevelDb *)pTestDb; |
| char *zErr = 0; |
| size_t nVal = 0; |
| |
| if( pKey==0 ) return 0; |
| free(pDb->pVal); |
| pDb->pVal = leveldb_get(pDb->db, pDb->pReadOpt, pKey, nKey, &nVal, &zErr); |
| *ppVal = (void *)(pDb->pVal); |
| if( pDb->pVal==0 ){ |
| *pnVal = -1; |
| }else{ |
| *pnVal = (int)nVal; |
| } |
| |
| return (zErr!=0); |
| } |
| |
| static int test_leveldb_scan( |
| TestDb *pTestDb, |
| void *pCtx, |
| int bReverse, |
| void *pKey1, int nKey1, /* Start of search */ |
| void *pKey2, int nKey2, /* End of search */ |
| void (*xCallback)(void *, void *, int , void *, int) |
| ){ |
| LevelDb *pDb = (LevelDb *)pTestDb; |
| leveldb_iterator_t *iter; |
| |
| iter = leveldb_create_iterator(pDb->db, pDb->pReadOpt); |
| |
| if( bReverse==0 ){ |
| if( pKey1 ){ |
| leveldb_iter_seek(iter, pKey1, nKey1); |
| }else{ |
| leveldb_iter_seek_to_first(iter); |
| } |
| }else{ |
| if( pKey2 ){ |
| leveldb_iter_seek(iter, pKey2, nKey2); |
| |
| if( leveldb_iter_valid(iter)==0 ){ |
| leveldb_iter_seek_to_last(iter); |
| }else{ |
| const char *k; size_t n; |
| int res; |
| k = leveldb_iter_key(iter, &n); |
| res = memcmp(k, pKey2, MIN(n, nKey2)); |
| if( res==0 ) res = n - nKey2; |
| assert( res>=0 ); |
| if( res>0 ){ |
| leveldb_iter_prev(iter); |
| } |
| } |
| }else{ |
| leveldb_iter_seek_to_last(iter); |
| } |
| } |
| |
| |
| while( leveldb_iter_valid(iter) ){ |
| const char *k; size_t n; |
| const char *v; size_t n2; |
| int res; |
| |
| k = leveldb_iter_key(iter, &n); |
| if( bReverse==0 && pKey2 ){ |
| res = memcmp(k, pKey2, MIN(n, nKey2)); |
| if( res==0 ) res = n - nKey2; |
| if( res>0 ) break; |
| } |
| if( bReverse!=0 && pKey1 ){ |
| res = memcmp(k, pKey1, MIN(n, nKey1)); |
| if( res==0 ) res = n - nKey1; |
| if( res<0 ) break; |
| } |
| |
| v = leveldb_iter_value(iter, &n2); |
| |
| xCallback(pCtx, (void *)k, n, (void *)v, n2); |
| |
| if( bReverse==0 ){ |
| leveldb_iter_next(iter); |
| }else{ |
| leveldb_iter_prev(iter); |
| } |
| } |
| |
| leveldb_iter_destroy(iter); |
| return 0; |
| } |
| |
| static int test_leveldb_open( |
| const char *zSpec, |
| const char *zFilename, |
| int bClear, |
| TestDb **ppDb |
| ){ |
| static const DatabaseMethods LeveldbMethods = { |
| test_leveldb_close, |
| test_leveldb_write, |
| test_leveldb_delete, |
| 0, |
| test_leveldb_fetch, |
| test_leveldb_scan, |
| error_transaction_function, |
| error_transaction_function, |
| error_transaction_function |
| }; |
| |
| LevelDb *pLevelDb; |
| char *zErr = 0; |
| |
| if( bClear ){ |
| char *zCmd = sqlite3_mprintf("rm -rf %s\n", zFilename); |
| system(zCmd); |
| sqlite3_free(zCmd); |
| } |
| |
| pLevelDb = (LevelDb *)malloc(sizeof(LevelDb)); |
| memset(pLevelDb, 0, sizeof(LevelDb)); |
| |
| pLevelDb->pOpt = leveldb_options_create(); |
| leveldb_options_set_create_if_missing(pLevelDb->pOpt, 1); |
| pLevelDb->pWriteOpt = leveldb_writeoptions_create(); |
| pLevelDb->pReadOpt = leveldb_readoptions_create(); |
| |
| pLevelDb->db = leveldb_open(pLevelDb->pOpt, zFilename, &zErr); |
| |
| if( zErr ){ |
| test_leveldb_close((TestDb *)pLevelDb); |
| *ppDb = 0; |
| return 1; |
| } |
| |
| *ppDb = (TestDb *)pLevelDb; |
| pLevelDb->base.pMethods = &LeveldbMethods; |
| return 0; |
| } |
| #endif /* HAVE_LEVELDB */ |
| /* |
| ** End wrapper for LevelDB. |
| *************************************************************************/ |
| |
| #ifdef HAVE_KYOTOCABINET |
| static int kc_close(TestDb *pTestDb){ |
| return test_kc_close(pTestDb); |
| } |
| |
| static int kc_write( |
| TestDb *pTestDb, |
| void *pKey, |
| int nKey, |
| void *pVal, |
| int nVal |
| ){ |
| return test_kc_write(pTestDb, pKey, nKey, pVal, nVal); |
| } |
| |
| static int kc_delete(TestDb *pTestDb, void *pKey, int nKey){ |
| return test_kc_delete(pTestDb, pKey, nKey); |
| } |
| |
| static int kc_delete_range( |
| TestDb *pTestDb, |
| void *pKey1, int nKey1, |
| void *pKey2, int nKey2 |
| ){ |
| return test_kc_delete_range(pTestDb, pKey1, nKey1, pKey2, nKey2); |
| } |
| |
| static int kc_fetch( |
| TestDb *pTestDb, |
| void *pKey, |
| int nKey, |
| void **ppVal, |
| int *pnVal |
| ){ |
| if( pKey==0 ) return LSM_OK; |
| return test_kc_fetch(pTestDb, pKey, nKey, ppVal, pnVal); |
| } |
| |
| static int kc_scan( |
| TestDb *pTestDb, |
| void *pCtx, |
| int bReverse, |
| void *pFirst, int nFirst, |
| void *pLast, int nLast, |
| void (*xCallback)(void *, void *, int , void *, int) |
| ){ |
| return test_kc_scan( |
| pTestDb, pCtx, bReverse, pFirst, nFirst, pLast, nLast, xCallback |
| ); |
| } |
| |
| static int kc_open( |
| const char *zSpec, |
| const char *zFilename, |
| int bClear, |
| TestDb **ppDb |
| ){ |
| static const DatabaseMethods KcdbMethods = { |
| kc_close, |
| kc_write, |
| kc_delete, |
| kc_delete_range, |
| kc_fetch, |
| kc_scan, |
| error_transaction_function, |
| error_transaction_function, |
| error_transaction_function |
| }; |
| |
| int rc; |
| TestDb *pTestDb = 0; |
| |
| rc = test_kc_open(zFilename, bClear, &pTestDb); |
| if( rc!=0 ){ |
| *ppDb = 0; |
| return rc; |
| } |
| pTestDb->pMethods = &KcdbMethods; |
| *ppDb = pTestDb; |
| return 0; |
| } |
| #endif /* HAVE_KYOTOCABINET */ |
| /* |
| ** End wrapper for Kyoto cabinet. |
| *************************************************************************/ |
| |
| #ifdef HAVE_MDB |
| static int mdb_close(TestDb *pTestDb){ |
| return test_mdb_close(pTestDb); |
| } |
| |
| static int mdb_write( |
| TestDb *pTestDb, |
| void *pKey, |
| int nKey, |
| void *pVal, |
| int nVal |
| ){ |
| return test_mdb_write(pTestDb, pKey, nKey, pVal, nVal); |
| } |
| |
| static int mdb_delete(TestDb *pTestDb, void *pKey, int nKey){ |
| return test_mdb_delete(pTestDb, pKey, nKey); |
| } |
| |
| static int mdb_fetch( |
| TestDb *pTestDb, |
| void *pKey, |
| int nKey, |
| void **ppVal, |
| int *pnVal |
| ){ |
| if( pKey==0 ) return LSM_OK; |
| return test_mdb_fetch(pTestDb, pKey, nKey, ppVal, pnVal); |
| } |
| |
| static int mdb_scan( |
| TestDb *pTestDb, |
| void *pCtx, |
| int bReverse, |
| void *pFirst, int nFirst, |
| void *pLast, int nLast, |
| void (*xCallback)(void *, void *, int , void *, int) |
| ){ |
| return test_mdb_scan( |
| pTestDb, pCtx, bReverse, pFirst, nFirst, pLast, nLast, xCallback |
| ); |
| } |
| |
| static int mdb_open( |
| const char *zSpec, |
| const char *zFilename, |
| int bClear, |
| TestDb **ppDb |
| ){ |
| static const DatabaseMethods KcdbMethods = { |
| mdb_close, |
| mdb_write, |
| mdb_delete, |
| 0, |
| mdb_fetch, |
| mdb_scan, |
| error_transaction_function, |
| error_transaction_function, |
| error_transaction_function |
| }; |
| |
| int rc; |
| TestDb *pTestDb = 0; |
| |
| rc = test_mdb_open(zSpec, zFilename, bClear, &pTestDb); |
| if( rc!=0 ){ |
| *ppDb = 0; |
| return rc; |
| } |
| pTestDb->pMethods = &KcdbMethods; |
| *ppDb = pTestDb; |
| return 0; |
| } |
| #endif /* HAVE_MDB */ |
| |
| /************************************************************************* |
| ** Begin wrapper for SQLite. |
| */ |
| |
| /* |
| ** nOpenTrans: |
| ** The number of open nested transactions, in the same sense as used |
| ** by the tdb_begin/commit/rollback and SQLite 4 KV interfaces. If this |
| ** value is 0, there are no transactions open at all. If it is 1, then |
| ** there is a read transaction. If it is 2 or greater, then there are |
| ** (nOpenTrans-1) nested write transactions open. |
| */ |
| struct SqlDb { |
| TestDb base; |
| sqlite3 *db; |
| sqlite3_stmt *pInsert; |
| sqlite3_stmt *pDelete; |
| sqlite3_stmt *pDeleteRange; |
| sqlite3_stmt *pFetch; |
| sqlite3_stmt *apScan[8]; |
| |
| int nOpenTrans; |
| |
| /* Used by sql_fetch() to allocate space for results */ |
| int nAlloc; |
| u8 *aAlloc; |
| }; |
| |
| static int sql_close(TestDb *pTestDb){ |
| SqlDb *pDb = (SqlDb *)pTestDb; |
| sqlite3_finalize(pDb->pInsert); |
| sqlite3_finalize(pDb->pDelete); |
| sqlite3_finalize(pDb->pDeleteRange); |
| sqlite3_finalize(pDb->pFetch); |
| sqlite3_finalize(pDb->apScan[0]); |
| sqlite3_finalize(pDb->apScan[1]); |
| sqlite3_finalize(pDb->apScan[2]); |
| sqlite3_finalize(pDb->apScan[3]); |
| sqlite3_finalize(pDb->apScan[4]); |
| sqlite3_finalize(pDb->apScan[5]); |
| sqlite3_finalize(pDb->apScan[6]); |
| sqlite3_finalize(pDb->apScan[7]); |
| sqlite3_close(pDb->db); |
| free((char *)pDb->aAlloc); |
| free((char *)pDb); |
| return SQLITE_OK; |
| } |
| |
| static int sql_write( |
| TestDb *pTestDb, |
| void *pKey, |
| int nKey, |
| void *pVal, |
| int nVal |
| ){ |
| SqlDb *pDb = (SqlDb *)pTestDb; |
| sqlite3_bind_blob(pDb->pInsert, 1, pKey, nKey, SQLITE_STATIC); |
| sqlite3_bind_blob(pDb->pInsert, 2, pVal, nVal, SQLITE_STATIC); |
| sqlite3_step(pDb->pInsert); |
| return sqlite3_reset(pDb->pInsert); |
| } |
| |
| static int sql_delete(TestDb *pTestDb, void *pKey, int nKey){ |
| SqlDb *pDb = (SqlDb *)pTestDb; |
| sqlite3_bind_blob(pDb->pDelete, 1, pKey, nKey, SQLITE_STATIC); |
| sqlite3_step(pDb->pDelete); |
| return sqlite3_reset(pDb->pDelete); |
| } |
| |
| static int sql_delete_range( |
| TestDb *pTestDb, |
| void *pKey1, int nKey1, |
| void *pKey2, int nKey2 |
| ){ |
| SqlDb *pDb = (SqlDb *)pTestDb; |
| sqlite3_bind_blob(pDb->pDeleteRange, 1, pKey1, nKey1, SQLITE_STATIC); |
| sqlite3_bind_blob(pDb->pDeleteRange, 2, pKey2, nKey2, SQLITE_STATIC); |
| sqlite3_step(pDb->pDeleteRange); |
| return sqlite3_reset(pDb->pDeleteRange); |
| } |
| |
| static int sql_fetch( |
| TestDb *pTestDb, |
| void *pKey, |
| int nKey, |
| void **ppVal, |
| int *pnVal |
| ){ |
| SqlDb *pDb = (SqlDb *)pTestDb; |
| int rc; |
| |
| sqlite3_reset(pDb->pFetch); |
| if( pKey==0 ){ |
| assert( ppVal==0 ); |
| assert( pnVal==0 ); |
| return LSM_OK; |
| } |
| |
| sqlite3_bind_blob(pDb->pFetch, 1, pKey, nKey, SQLITE_STATIC); |
| rc = sqlite3_step(pDb->pFetch); |
| if( rc==SQLITE_ROW ){ |
| int nVal = sqlite3_column_bytes(pDb->pFetch, 0); |
| u8 *aVal = (void *)sqlite3_column_blob(pDb->pFetch, 0); |
| |
| if( nVal>pDb->nAlloc ){ |
| free(pDb->aAlloc); |
| pDb->aAlloc = (u8 *)malloc(nVal*2); |
| pDb->nAlloc = nVal*2; |
| } |
| memcpy(pDb->aAlloc, aVal, nVal); |
| *pnVal = nVal; |
| *ppVal = (void *)pDb->aAlloc; |
| }else{ |
| *pnVal = -1; |
| *ppVal = 0; |
| } |
| |
| rc = sqlite3_reset(pDb->pFetch); |
| return rc; |
| } |
| |
| static int sql_scan( |
| TestDb *pTestDb, |
| void *pCtx, |
| int bReverse, |
| void *pFirst, int nFirst, |
| void *pLast, int nLast, |
| void (*xCallback)(void *, void *, int , void *, int) |
| ){ |
| SqlDb *pDb = (SqlDb *)pTestDb; |
| sqlite3_stmt *pScan; |
| |
| assert( bReverse==1 || bReverse==0 ); |
| pScan = pDb->apScan[(pFirst==0) + (pLast==0)*2 + bReverse*4]; |
| |
| if( pFirst ) sqlite3_bind_blob(pScan, 1, pFirst, nFirst, SQLITE_STATIC); |
| if( pLast ) sqlite3_bind_blob(pScan, 2, pLast, nLast, SQLITE_STATIC); |
| |
| while( SQLITE_ROW==sqlite3_step(pScan) ){ |
| void *pKey; int nKey; |
| void *pVal; int nVal; |
| |
| nKey = sqlite3_column_bytes(pScan, 0); |
| pKey = (void *)sqlite3_column_blob(pScan, 0); |
| nVal = sqlite3_column_bytes(pScan, 1); |
| pVal = (void *)sqlite3_column_blob(pScan, 1); |
| |
| xCallback(pCtx, pKey, nKey, pVal, nVal); |
| } |
| return sqlite3_reset(pScan); |
| } |
| |
| static int sql_begin(TestDb *pTestDb, int iLevel){ |
| int i; |
| SqlDb *pDb = (SqlDb *)pTestDb; |
| |
| /* iLevel==0 is a no-op */ |
| if( iLevel==0 ) return 0; |
| |
| /* If there are no transactions at all open, open a read transaction. */ |
| if( pDb->nOpenTrans==0 ){ |
| int rc = sqlite3_exec(pDb->db, |
| "BEGIN; SELECT * FROM sqlite_master LIMIT 1;" , 0, 0, 0 |
| ); |
| if( rc!=0 ) return rc; |
| pDb->nOpenTrans = 1; |
| } |
| |
| /* Open any required write transactions */ |
| for(i=pDb->nOpenTrans; i<iLevel; i++){ |
| char *zSql = sqlite3_mprintf("SAVEPOINT x%d", i); |
| int rc = sqlite3_exec(pDb->db, zSql, 0, 0, 0); |
| sqlite3_free(zSql); |
| if( rc!=SQLITE_OK ) return rc; |
| } |
| |
| pDb->nOpenTrans = iLevel; |
| return 0; |
| } |
| |
| static int sql_commit(TestDb *pTestDb, int iLevel){ |
| SqlDb *pDb = (SqlDb *)pTestDb; |
| assert( iLevel>=0 ); |
| |
| /* Close the read transaction if requested. */ |
| if( pDb->nOpenTrans>=1 && iLevel==0 ){ |
| int rc = sqlite3_exec(pDb->db, "COMMIT", 0, 0, 0); |
| if( rc!=0 ) return rc; |
| pDb->nOpenTrans = 0; |
| } |
| |
| /* Close write transactions as required */ |
| if( pDb->nOpenTrans>iLevel ){ |
| char *zSql = sqlite3_mprintf("RELEASE x%d", iLevel); |
| int rc = sqlite3_exec(pDb->db, zSql, 0, 0, 0); |
| sqlite3_free(zSql); |
| if( rc!=0 ) return rc; |
| } |
| |
| pDb->nOpenTrans = iLevel; |
| return 0; |
| } |
| |
| static int sql_rollback(TestDb *pTestDb, int iLevel){ |
| SqlDb *pDb = (SqlDb *)pTestDb; |
| assert( iLevel>=0 ); |
| |
| if( pDb->nOpenTrans>=1 && iLevel==0 ){ |
| /* Close the read transaction if requested. */ |
| int rc = sqlite3_exec(pDb->db, "ROLLBACK", 0, 0, 0); |
| if( rc!=0 ) return rc; |
| }else if( pDb->nOpenTrans>1 && iLevel==1 ){ |
| /* Or, rollback and close the top-level write transaction */ |
| int rc = sqlite3_exec(pDb->db, "ROLLBACK TO x1; RELEASE x1;", 0, 0, 0); |
| if( rc!=0 ) return rc; |
| }else{ |
| /* Or, just roll back some nested transactions */ |
| char *zSql = sqlite3_mprintf("ROLLBACK TO x%d", iLevel-1); |
| int rc = sqlite3_exec(pDb->db, zSql, 0, 0, 0); |
| sqlite3_free(zSql); |
| if( rc!=0 ) return rc; |
| } |
| |
| pDb->nOpenTrans = iLevel; |
| return 0; |
| } |
| |
| static int sql_open( |
| const char *zSpec, |
| const char *zFilename, |
| int bClear, |
| TestDb **ppDb |
| ){ |
| static const DatabaseMethods SqlMethods = { |
| sql_close, |
| sql_write, |
| sql_delete, |
| sql_delete_range, |
| sql_fetch, |
| sql_scan, |
| sql_begin, |
| sql_commit, |
| sql_rollback |
| }; |
| const char *zCreate = "CREATE TABLE IF NOT EXISTS t1(k PRIMARY KEY, v)"; |
| const char *zInsert = "REPLACE INTO t1 VALUES(?, ?)"; |
| const char *zDelete = "DELETE FROM t1 WHERE k = ?"; |
| const char *zRange = "DELETE FROM t1 WHERE k>? AND k<?"; |
| const char *zFetch = "SELECT v FROM t1 WHERE k = ?"; |
| |
| const char *zScan0 = "SELECT * FROM t1 WHERE k BETWEEN ?1 AND ?2 ORDER BY k"; |
| const char *zScan1 = "SELECT * FROM t1 WHERE k <= ?2 ORDER BY k"; |
| const char *zScan2 = "SELECT * FROM t1 WHERE k >= ?1 ORDER BY k"; |
| const char *zScan3 = "SELECT * FROM t1 ORDER BY k"; |
| |
| const char *zScan4 = |
| "SELECT * FROM t1 WHERE k BETWEEN ?1 AND ?2 ORDER BY k DESC"; |
| const char *zScan5 = "SELECT * FROM t1 WHERE k <= ?2 ORDER BY k DESC"; |
| const char *zScan6 = "SELECT * FROM t1 WHERE k >= ?1 ORDER BY k DESC"; |
| const char *zScan7 = "SELECT * FROM t1 ORDER BY k DESC"; |
| |
| int rc; |
| SqlDb *pDb; |
| char *zPragma; |
| |
| if( bClear && zFilename && zFilename[0] ){ |
| unlink(zFilename); |
| } |
| |
| pDb = (SqlDb *)malloc(sizeof(SqlDb)); |
| memset(pDb, 0, sizeof(SqlDb)); |
| pDb->base.pMethods = &SqlMethods; |
| |
| if( 0!=(rc = sqlite3_open(zFilename, &pDb->db)) |
| || 0!=(rc = sqlite3_exec(pDb->db, zCreate, 0, 0, 0)) |
| || 0!=(rc = sqlite3_prepare_v2(pDb->db, zInsert, -1, &pDb->pInsert, 0)) |
| || 0!=(rc = sqlite3_prepare_v2(pDb->db, zDelete, -1, &pDb->pDelete, 0)) |
| || 0!=(rc = sqlite3_prepare_v2(pDb->db, zRange, -1, &pDb->pDeleteRange, 0)) |
| || 0!=(rc = sqlite3_prepare_v2(pDb->db, zFetch, -1, &pDb->pFetch, 0)) |
| || 0!=(rc = sqlite3_prepare_v2(pDb->db, zScan0, -1, &pDb->apScan[0], 0)) |
| || 0!=(rc = sqlite3_prepare_v2(pDb->db, zScan1, -1, &pDb->apScan[1], 0)) |
| || 0!=(rc = sqlite3_prepare_v2(pDb->db, zScan2, -1, &pDb->apScan[2], 0)) |
| || 0!=(rc = sqlite3_prepare_v2(pDb->db, zScan3, -1, &pDb->apScan[3], 0)) |
| || 0!=(rc = sqlite3_prepare_v2(pDb->db, zScan4, -1, &pDb->apScan[4], 0)) |
| || 0!=(rc = sqlite3_prepare_v2(pDb->db, zScan5, -1, &pDb->apScan[5], 0)) |
| || 0!=(rc = sqlite3_prepare_v2(pDb->db, zScan6, -1, &pDb->apScan[6], 0)) |
| || 0!=(rc = sqlite3_prepare_v2(pDb->db, zScan7, -1, &pDb->apScan[7], 0)) |
| ){ |
| *ppDb = 0; |
| sql_close((TestDb *)pDb); |
| return rc; |
| } |
| |
| zPragma = sqlite3_mprintf("PRAGMA page_size=%d", TESTDB_DEFAULT_PAGE_SIZE); |
| sqlite3_exec(pDb->db, zPragma, 0, 0, 0); |
| sqlite3_free(zPragma); |
| zPragma = sqlite3_mprintf("PRAGMA cache_size=%d", TESTDB_DEFAULT_CACHE_SIZE); |
| sqlite3_exec(pDb->db, zPragma, 0, 0, 0); |
| sqlite3_free(zPragma); |
| |
| /* sqlite3_exec(pDb->db, "PRAGMA locking_mode=EXCLUSIVE", 0, 0, 0); */ |
| sqlite3_exec(pDb->db, "PRAGMA synchronous=OFF", 0, 0, 0); |
| sqlite3_exec(pDb->db, "PRAGMA journal_mode=WAL", 0, 0, 0); |
| sqlite3_exec(pDb->db, "PRAGMA wal_autocheckpoint=4096", 0, 0, 0); |
| if( zSpec ){ |
| rc = sqlite3_exec(pDb->db, zSpec, 0, 0, 0); |
| if( rc!=SQLITE_OK ){ |
| sql_close((TestDb *)pDb); |
| return rc; |
| } |
| } |
| |
| *ppDb = (TestDb *)pDb; |
| return 0; |
| } |
| /* |
| ** End wrapper for SQLite. |
| *************************************************************************/ |
| |
| /************************************************************************* |
| ** Begin exported functions. |
| */ |
| static struct Lib { |
| const char *zName; |
| const char *zDefaultDb; |
| int (*xOpen)(const char *, const char *zFilename, int bClear, TestDb **ppDb); |
| } aLib[] = { |
| { "sqlite3", "testdb.sqlite", sql_open }, |
| { "lsm_small", "testdb.lsm_small", test_lsm_small_open }, |
| { "lsm_lomem", "testdb.lsm_lomem", test_lsm_lomem_open }, |
| { "lsm_lomem2", "testdb.lsm_lomem2", test_lsm_lomem2_open }, |
| #ifdef HAVE_ZLIB |
| { "lsm_zip", "testdb.lsm_zip", test_lsm_zip_open }, |
| #endif |
| { "lsm", "testdb.lsm", test_lsm_open }, |
| #ifdef LSM_MUTEX_PTHREADS |
| { "lsm_mt2", "testdb.lsm_mt2", test_lsm_mt2 }, |
| { "lsm_mt3", "testdb.lsm_mt3", test_lsm_mt3 }, |
| #endif |
| #ifdef HAVE_LEVELDB |
| { "leveldb", "testdb.leveldb", test_leveldb_open }, |
| #endif |
| #ifdef HAVE_KYOTOCABINET |
| { "kyotocabinet", "testdb.kc", kc_open }, |
| #endif |
| #ifdef HAVE_MDB |
| { "mdb", "./testdb.mdb", mdb_open } |
| #endif |
| }; |
| |
| const char *tdb_system_name(int i){ |
| if( i<0 || i>=ArraySize(aLib) ) return 0; |
| return aLib[i].zName; |
| } |
| |
| const char *tdb_default_db(const char *zSys){ |
| int i; |
| for(i=0; i<ArraySize(aLib); i++){ |
| if( strcmp(aLib[i].zName, zSys)==0 ) return aLib[i].zDefaultDb; |
| } |
| return 0; |
| } |
| |
| int tdb_open(const char *zLib, const char *zDb, int bClear, TestDb **ppDb){ |
| int i; |
| int rc = 1; |
| const char *zSpec = 0; |
| |
| int nLib = 0; |
| while( zLib[nLib] && zLib[nLib]!=' ' ){ |
| nLib++; |
| } |
| zSpec = &zLib[nLib]; |
| while( *zSpec==' ' ) zSpec++; |
| if( *zSpec=='\0' ) zSpec = 0; |
| |
| for(i=0; i<ArraySize(aLib); i++){ |
| if( (int)strlen(aLib[i].zName)==nLib |
| && 0==memcmp(zLib, aLib[i].zName, nLib) ){ |
| rc = aLib[i].xOpen(zSpec, (zDb ? zDb : aLib[i].zDefaultDb), bClear, ppDb); |
| if( rc==0 ){ |
| (*ppDb)->zLibrary = aLib[i].zName; |
| } |
| break; |
| } |
| } |
| |
| if( rc ){ |
| /* Failed to find the requested database library. Return an error. */ |
| *ppDb = 0; |
| } |
| return rc; |
| } |
| |
| int tdb_close(TestDb *pDb){ |
| if( pDb ){ |
| return pDb->pMethods->xClose(pDb); |
| } |
| return 0; |
| } |
| |
| int tdb_write(TestDb *pDb, void *pKey, int nKey, void *pVal, int nVal){ |
| return pDb->pMethods->xWrite(pDb, pKey, nKey, pVal, nVal); |
| } |
| |
| int tdb_delete(TestDb *pDb, void *pKey, int nKey){ |
| return pDb->pMethods->xDelete(pDb, pKey, nKey); |
| } |
| |
| int tdb_delete_range( |
| TestDb *pDb, void *pKey1, int nKey1, void *pKey2, int nKey2 |
| ){ |
| return pDb->pMethods->xDeleteRange(pDb, pKey1, nKey1, pKey2, nKey2); |
| } |
| |
| int tdb_fetch(TestDb *pDb, void *pKey, int nKey, void **ppVal, int *pnVal){ |
| return pDb->pMethods->xFetch(pDb, pKey, nKey, ppVal, pnVal); |
| } |
| |
| int tdb_scan( |
| TestDb *pDb, /* Database handle */ |
| void *pCtx, /* Context pointer to pass to xCallback */ |
| int bReverse, /* True to scan in reverse order */ |
| void *pKey1, int nKey1, /* Start of search */ |
| void *pKey2, int nKey2, /* End of search */ |
| void (*xCallback)(void *pCtx, void *pKey, int nKey, void *pVal, int nVal) |
| ){ |
| return pDb->pMethods->xScan( |
| pDb, pCtx, bReverse, pKey1, nKey1, pKey2, nKey2, xCallback |
| ); |
| } |
| |
| int tdb_begin(TestDb *pDb, int iLevel){ |
| return pDb->pMethods->xBegin(pDb, iLevel); |
| } |
| int tdb_commit(TestDb *pDb, int iLevel){ |
| return pDb->pMethods->xCommit(pDb, iLevel); |
| } |
| int tdb_rollback(TestDb *pDb, int iLevel){ |
| return pDb->pMethods->xRollback(pDb, iLevel); |
| } |
| |
| int tdb_transaction_support(TestDb *pDb){ |
| return (pDb->pMethods->xBegin != error_transaction_function); |
| } |
| |
| const char *tdb_library_name(TestDb *pDb){ |
| return pDb->zLibrary; |
| } |
| |
| /* |
| ** End exported functions. |
| *************************************************************************/ |