Add test code for LSM to the ext/lsm1/lsm-test directory.
FossilOrigin-Name: bb7436e84a315baf05f00e6cab396017e3f287ea404d32e0cc4f389fa1194dec
This commit is contained in:
parent
16b7dcc717
commit
38d6985509
@ -1,10 +1,12 @@
|
||||
#!/usr/bin/make
|
||||
#
|
||||
# This is a temporary makefile for use during experimental development.
|
||||
# Replace with something more portable, if the experiments actually work out.
|
||||
# This Makefile is designed for use with main.mk in the root directory of
|
||||
# this project. After including main.mk, the users makefile should contain:
|
||||
#
|
||||
# LSMDIR=$(TOP)/ext/lsm1/
|
||||
# include $(LSMDIR)/Makefile
|
||||
#
|
||||
# The most useful targets are [lsmtest] and [lsm.so].
|
||||
#
|
||||
CC = gcc
|
||||
CFLAGS =-g -fPIC -Wall -I. -I../..
|
||||
|
||||
LSMOBJ = \
|
||||
lsm_ckpt.o \
|
||||
@ -18,17 +20,35 @@ LSMOBJ = \
|
||||
lsm_str.o \
|
||||
lsm_tree.o \
|
||||
lsm_unix.o \
|
||||
lsm_win32.o \
|
||||
lsm_varint.o \
|
||||
lsm_vtab.o
|
||||
|
||||
LSMHDR = \
|
||||
lsm.h \
|
||||
lsmInt.h
|
||||
$(LSMDIR)/lsm.h \
|
||||
$(LSMDIR)/lsmInt.h
|
||||
|
||||
all: lsm.so
|
||||
LSMTESTSRC = $(LSMDIR)/lsm-test/lsmtest1.c $(LSMDIR)/lsm-test/lsmtest2.c \
|
||||
$(LSMDIR)/lsm-test/lsmtest3.c $(LSMDIR)/lsm-test/lsmtest4.c \
|
||||
$(LSMDIR)/lsm-test/lsmtest5.c $(LSMDIR)/lsm-test/lsmtest6.c \
|
||||
$(LSMDIR)/lsm-test/lsmtest7.c $(LSMDIR)/lsm-test/lsmtest8.c \
|
||||
$(LSMDIR)/lsm-test/lsmtest9.c \
|
||||
$(LSMDIR)/lsm-test/lsmtest_datasource.c \
|
||||
$(LSMDIR)/lsm-test/lsmtest_func.c $(LSMDIR)/lsm-test/lsmtest_io.c \
|
||||
$(LSMDIR)/lsm-test/lsmtest_main.c $(LSMDIR)/lsm-test/lsmtest_mem.c \
|
||||
$(LSMDIR)/lsm-test/lsmtest_tdb.c $(LSMDIR)/lsm-test/lsmtest_tdb3.c \
|
||||
$(LSMDIR)/lsm-test/lsmtest_util.c
|
||||
|
||||
|
||||
# all: lsm.so
|
||||
|
||||
lsm.so: $(LSMOBJ)
|
||||
$(CC) $(CFLAGS) -shared -o lsm.so $(LSMOBJ)
|
||||
$(TCCX) $(CFLAGS) -shared -o lsm.so $(LSMOBJ)
|
||||
|
||||
%.o: $(LSMDIR)/%.c $(LSMHDR)
|
||||
$(TCCX) -I$(LSMDIR) $(CFLAGS) -c $<
|
||||
|
||||
lsmtest$(EXE): $(LSMOBJ) $(LSMTESTSRC) $(LSMTESTHDR)
|
||||
# $(TCPPX) -c $(TOP)/lsm-test/lsmtest_tdb2.cc
|
||||
$(TCCX) -I$(LSMDIR) $(LSMTESTSRC) $(LSMOBJ) -o lsmtest$(EXE) $(THREADLIB) -lsqlite3
|
||||
|
||||
%.o: %.c $(LSMHDR)
|
||||
$(CC) $(CFLAGS) -c $<
|
||||
|
40
ext/lsm1/lsm-test/README
Normal file
40
ext/lsm1/lsm-test/README
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
|
||||
Organization of test case files:
|
||||
|
||||
lsmtest1.c: Data tests. Tests that perform many inserts and deletes on a
|
||||
database file, then verify that the contents of the database can
|
||||
be queried.
|
||||
|
||||
lsmtest2.c: Crash tests. Tests that attempt to verify that the database
|
||||
recovers correctly following an application or system crash.
|
||||
|
||||
lsmtest3.c: Rollback tests. Tests that focus on the explicit rollback of
|
||||
transactions and sub-transactions.
|
||||
|
||||
lsmtest4.c: Multi-client tests.
|
||||
|
||||
lsmtest5.c: Multi-client tests with a different thread for each client.
|
||||
|
||||
lsmtest6.c: OOM injection tests.
|
||||
|
||||
lsmtest7.c: API tests.
|
||||
|
||||
lsmtest8.c: Writer crash tests. Tests in this file attempt to verify that
|
||||
the system recovers and other clients proceed unaffected if
|
||||
a process fails in the middle of a write transaction.
|
||||
|
||||
The difference from lsmtest2.c is that this file tests
|
||||
live-recovery (recovery from a failure that occurs while other
|
||||
clients are still running) whereas lsmtest2.c tests recovery
|
||||
from a system or power failure.
|
||||
|
||||
lsmtest9.c: More data tests. These focus on testing that calling
|
||||
lsm_work(nMerge=1) to compact the database does not corrupt it.
|
||||
In other words, that databases containing block-redirects
|
||||
can be read and written.
|
||||
|
||||
|
||||
|
||||
|
||||
|
263
ext/lsm1/lsm-test/lsmtest.h
Normal file
263
ext/lsm1/lsm-test/lsmtest.h
Normal file
@ -0,0 +1,263 @@
|
||||
|
||||
#ifndef __WRAPPER_INT_H_
|
||||
#define __WRAPPER_INT_H_
|
||||
|
||||
#include "lsmtest_tdb.h"
|
||||
#include "sqlite3.h"
|
||||
#include "lsm.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef _LSM_INT_H
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned char u8;
|
||||
typedef long long int i64;
|
||||
typedef unsigned long long int u64;
|
||||
#endif
|
||||
|
||||
|
||||
#define ArraySize(x) ((int)(sizeof(x) / sizeof((x)[0])))
|
||||
|
||||
#define MIN(x,y) ((x)<(y) ? (x) : (y))
|
||||
#define MAX(x,y) ((x)>(y) ? (x) : (y))
|
||||
|
||||
#define unused_parameter(x) (void)(x)
|
||||
|
||||
#define TESTDB_DEFAULT_PAGE_SIZE 4096
|
||||
#define TESTDB_DEFAULT_CACHE_SIZE 2048
|
||||
|
||||
/*
|
||||
** Ideally, these should be in wrapper.c. But they are here instead so that
|
||||
** they can be used by the C++ database wrappers in wrapper2.cc.
|
||||
*/
|
||||
typedef struct DatabaseMethods DatabaseMethods;
|
||||
struct TestDb {
|
||||
DatabaseMethods const *pMethods; /* Database methods */
|
||||
const char *zLibrary; /* Library name for tdb_open() */
|
||||
};
|
||||
struct DatabaseMethods {
|
||||
int (*xClose)(TestDb *);
|
||||
int (*xWrite)(TestDb *, void *, int , void *, int);
|
||||
int (*xDelete)(TestDb *, void *, int);
|
||||
int (*xDeleteRange)(TestDb *, void *, int, void *, int);
|
||||
int (*xFetch)(TestDb *, void *, int, void **, int *);
|
||||
int (*xScan)(TestDb *, void *, int, void *, int, void *, int,
|
||||
void (*)(void *, void *, int , void *, int)
|
||||
);
|
||||
int (*xBegin)(TestDb *, int);
|
||||
int (*xCommit)(TestDb *, int);
|
||||
int (*xRollback)(TestDb *, int);
|
||||
};
|
||||
|
||||
/*
|
||||
** Functions in wrapper2.cc (a C++ source file). wrapper2.cc contains the
|
||||
** wrapper for Kyoto Cabinet. Kyoto cabinet has a C API, but
|
||||
** the primary interface is the C++ API.
|
||||
*/
|
||||
int test_kc_open(const char*, const char *zFilename, int bClear, TestDb **ppDb);
|
||||
int test_kc_close(TestDb *);
|
||||
int test_kc_write(TestDb *, void *, int , void *, int);
|
||||
int test_kc_delete(TestDb *, void *, int);
|
||||
int test_kc_delete_range(TestDb *, void *, int, void *, int);
|
||||
int test_kc_fetch(TestDb *, void *, int, void **, int *);
|
||||
int test_kc_scan(TestDb *, void *, int, void *, int, void *, int,
|
||||
void (*)(void *, void *, int , void *, int)
|
||||
);
|
||||
|
||||
int test_mdb_open(const char*, const char *zFile, int bClear, TestDb **ppDb);
|
||||
int test_mdb_close(TestDb *);
|
||||
int test_mdb_write(TestDb *, void *, int , void *, int);
|
||||
int test_mdb_delete(TestDb *, void *, int);
|
||||
int test_mdb_fetch(TestDb *, void *, int, void **, int *);
|
||||
int test_mdb_scan(TestDb *, void *, int, void *, int, void *, int,
|
||||
void (*)(void *, void *, int , void *, int)
|
||||
);
|
||||
|
||||
/*
|
||||
** Functions in wrapper3.c. This file contains the tdb wrapper for lsm.
|
||||
** The wrapper for lsm is a bit more involved than the others, as it
|
||||
** includes code for a couple of different lsm configurations, and for
|
||||
** various types of fault injection and robustness testing.
|
||||
*/
|
||||
int test_lsm_open(const char*, const char *zFile, int bClear, TestDb **ppDb);
|
||||
int test_lsm_lomem_open(const char*, const char*, int bClear, TestDb **ppDb);
|
||||
int test_lsm_zip_open(const char*, const char*, int bClear, TestDb **ppDb);
|
||||
int test_lsm_small_open(const char*, const char*, int bClear, TestDb **ppDb);
|
||||
int test_lsm_mt2(const char*, const char *zFile, int bClear, TestDb **ppDb);
|
||||
int test_lsm_mt3(const char*, const char *zFile, int bClear, TestDb **ppDb);
|
||||
|
||||
int tdb_lsm_configure(lsm_db *, const char *);
|
||||
|
||||
/* Functions in lsmtest_tdb4.c */
|
||||
int test_bt_open(const char*, const char *zFile, int bClear, TestDb **ppDb);
|
||||
int test_fbt_open(const char*, const char *zFile, int bClear, TestDb **ppDb);
|
||||
int test_fbts_open(const char*, const char *zFile, int bClear, TestDb **ppDb);
|
||||
|
||||
|
||||
/* Functions in testutil.c. */
|
||||
int testPrngInit(void);
|
||||
u32 testPrngValue(u32 iVal);
|
||||
void testPrngArray(u32 iVal, u32 *aOut, int nOut);
|
||||
void testPrngString(u32 iVal, char *aOut, int nOut);
|
||||
|
||||
void testErrorInit(int argc, char **);
|
||||
void testPrintError(const char *zFormat, ...);
|
||||
void testPrintUsage(const char *zArgs);
|
||||
void testPrintFUsage(const char *zFormat, ...);
|
||||
void testTimeInit(void);
|
||||
int testTimeGet(void);
|
||||
|
||||
/* Functions in testmem.c. */
|
||||
void testMallocInstall(lsm_env *pEnv);
|
||||
void testMallocUninstall(lsm_env *pEnv);
|
||||
void testMallocCheck(lsm_env *pEnv, int *, int *, FILE *);
|
||||
void testMallocOom(lsm_env *pEnv, int, int, void(*)(void*), void *);
|
||||
void testMallocOomEnable(lsm_env *pEnv, int);
|
||||
|
||||
/* lsmtest.c */
|
||||
TestDb *testOpen(const char *zSystem, int, int *pRc);
|
||||
void testReopen(TestDb **ppDb, int *pRc);
|
||||
void testClose(TestDb **ppDb);
|
||||
|
||||
void testFetch(TestDb *, void *, int, void *, int, int *);
|
||||
void testWrite(TestDb *, void *, int, void *, int, int *);
|
||||
void testDelete(TestDb *, void *, int, int *);
|
||||
void testDeleteRange(TestDb *, void *, int, void *, int, int *);
|
||||
void testWriteStr(TestDb *, const char *, const char *zVal, int *pRc);
|
||||
void testFetchStr(TestDb *, const char *, const char *, int *pRc);
|
||||
|
||||
void testBegin(TestDb *pDb, int iTrans, int *pRc);
|
||||
void testCommit(TestDb *pDb, int iTrans, int *pRc);
|
||||
|
||||
void test_failed(void);
|
||||
|
||||
char *testMallocPrintf(const char *zFormat, ...);
|
||||
char *testMallocVPrintf(const char *zFormat, va_list ap);
|
||||
int testGlobMatch(const char *zPattern, const char *zStr);
|
||||
|
||||
void testScanCompare(TestDb *, TestDb *, int, void *, int, void *, int, int *);
|
||||
void testFetchCompare(TestDb *, TestDb *, void *, int, int *);
|
||||
|
||||
void *testMalloc(int);
|
||||
void *testMallocCopy(void *pCopy, int nByte);
|
||||
void *testRealloc(void *, int);
|
||||
void testFree(void *);
|
||||
|
||||
/* lsmtest_bt.c */
|
||||
int do_bt(int nArg, char **azArg);
|
||||
|
||||
/* testio.c */
|
||||
int testVfsConfigureDb(TestDb *pDb);
|
||||
|
||||
/* testfunc.c */
|
||||
int do_show(int nArg, char **azArg);
|
||||
int do_work(int nArg, char **azArg);
|
||||
|
||||
/* testio.c */
|
||||
int do_io(int nArg, char **azArg);
|
||||
|
||||
/* lsmtest2.c */
|
||||
void do_crash_test(const char *zPattern, int *pRc);
|
||||
int do_rollback_test(int nArg, char **azArg);
|
||||
|
||||
/* test3.c */
|
||||
void test_rollback(const char *zSystem, const char *zPattern, int *pRc);
|
||||
|
||||
/* test4.c */
|
||||
void test_mc(const char *zSystem, const char *zPattern, int *pRc);
|
||||
|
||||
/* test5.c */
|
||||
void test_mt(const char *zSystem, const char *zPattern, int *pRc);
|
||||
|
||||
/* lsmtest6.c */
|
||||
void test_oom(const char *zPattern, int *pRc);
|
||||
void testDeleteLsmdb(const char *zFile);
|
||||
|
||||
void testSaveDb(const char *zFile, const char *zAuxExt);
|
||||
void testRestoreDb(const char *zFile, const char *zAuxExt);
|
||||
void testCopyLsmdb(const char *zFrom, const char *zTo);
|
||||
|
||||
/* lsmtest7.c */
|
||||
void test_api(const char *zPattern, int *pRc);
|
||||
|
||||
/* lsmtest8.c */
|
||||
void do_writer_crash_test(const char *zPattern, int *pRc);
|
||||
|
||||
/*************************************************************************
|
||||
** Interface to functionality in test_datasource.c.
|
||||
*/
|
||||
typedef struct Datasource Datasource;
|
||||
typedef struct DatasourceDefn DatasourceDefn;
|
||||
|
||||
struct DatasourceDefn {
|
||||
int eType; /* A TEST_DATASOURCE_* value */
|
||||
int nMinKey; /* Minimum key size */
|
||||
int nMaxKey; /* Maximum key size */
|
||||
int nMinVal; /* Minimum value size */
|
||||
int nMaxVal; /* Maximum value size */
|
||||
};
|
||||
|
||||
#define TEST_DATASOURCE_RANDOM 1
|
||||
#define TEST_DATASOURCE_SEQUENCE 2
|
||||
|
||||
char *testDatasourceName(const DatasourceDefn *);
|
||||
Datasource *testDatasourceNew(const DatasourceDefn *);
|
||||
void testDatasourceFree(Datasource *);
|
||||
void testDatasourceEntry(Datasource *, int, void **, int *, void **, int *);
|
||||
/* End of test_datasource.c interface.
|
||||
*************************************************************************/
|
||||
|
||||
void testWriteDatasource(TestDb *, Datasource *, int, int *);
|
||||
void testWriteDatasourceRange(TestDb *, Datasource *, int, int, int *);
|
||||
void testDeleteDatasource(TestDb *, Datasource *, int, int *);
|
||||
void testDeleteDatasourceRange(TestDb *, Datasource *, int, int, int *);
|
||||
|
||||
|
||||
/* test1.c */
|
||||
void test_data_1(const char *, const char *, int *pRc);
|
||||
void test_data_2(const char *, const char *, int *pRc);
|
||||
void test_data_3(const char *, const char *, int *pRc);
|
||||
void testDbContents(TestDb *, Datasource *, int, int, int, int, int, int *);
|
||||
void testCaseProgress(int, int, int, int *);
|
||||
int testCaseNDot(void);
|
||||
|
||||
void testCompareDb(Datasource *, int, int, TestDb *, TestDb *, int *);
|
||||
int testControlDb(TestDb **ppDb);
|
||||
|
||||
typedef struct CksumDb CksumDb;
|
||||
CksumDb *testCksumArrayNew(Datasource *, int, int, int);
|
||||
char *testCksumArrayGet(CksumDb *, int);
|
||||
void testCksumArrayFree(CksumDb *);
|
||||
void testCaseStart(int *pRc, char *zFmt, ...);
|
||||
void testCaseFinish(int rc);
|
||||
void testCaseSkip(void);
|
||||
int testCaseBegin(int *, const char *, const char *, ...);
|
||||
|
||||
#define TEST_CKSUM_BYTES 29
|
||||
int testCksumDatabase(TestDb *pDb, char *zOut);
|
||||
int testCountDatabase(TestDb *pDb);
|
||||
void testCompareInt(int, int, int *);
|
||||
void testCompareStr(const char *z1, const char *z2, int *pRc);
|
||||
|
||||
/* lsmtest9.c */
|
||||
void test_data_4(const char *, const char *, int *pRc);
|
||||
|
||||
|
||||
/*
|
||||
** Similar to the Tcl_GetIndexFromObjStruct() Tcl library function.
|
||||
*/
|
||||
#define testArgSelect(w,x,y,z) testArgSelectX(w,x,sizeof(w[0]),y,z)
|
||||
int testArgSelectX(void *, const char *, int, const char *, int *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* End of the 'extern "C"' block */
|
||||
#endif
|
||||
|
||||
#endif
|
621
ext/lsm1/lsm-test/lsmtest1.c
Normal file
621
ext/lsm1/lsm-test/lsmtest1.c
Normal file
@ -0,0 +1,621 @@
|
||||
|
||||
#include "lsmtest.h"
|
||||
|
||||
#define DATA_SEQUENTIAL TEST_DATASOURCE_SEQUENCE
|
||||
#define DATA_RANDOM TEST_DATASOURCE_RANDOM
|
||||
|
||||
typedef struct Datatest1 Datatest1;
|
||||
typedef struct Datatest2 Datatest2;
|
||||
|
||||
/*
|
||||
** An instance of the following structure contains parameters used to
|
||||
** customize the test function in this file. Test procedure:
|
||||
**
|
||||
** 1. Create a data-source based on the "datasource definition" vars.
|
||||
**
|
||||
** 2. Insert nRow key value pairs into the database.
|
||||
**
|
||||
** 3. Delete all keys from the database. Deletes are done in the same
|
||||
** order as the inserts.
|
||||
**
|
||||
** During steps 2 and 3 above, after each Datatest1.nVerify inserts or
|
||||
** deletes, the following:
|
||||
**
|
||||
** a. Run Datasource.nTest key lookups and check the results are as expected.
|
||||
**
|
||||
** b. If Datasource.bTestScan is true, run a handful (8) of range
|
||||
** queries (scanning forwards and backwards). Check that the results
|
||||
** are as expected.
|
||||
**
|
||||
** c. Close and reopen the database. Then run (a) and (b) again.
|
||||
*/
|
||||
struct Datatest1 {
|
||||
/* Datasource definition */
|
||||
DatasourceDefn defn;
|
||||
|
||||
/* Test procedure parameters */
|
||||
int nRow; /* Number of rows to insert then delete */
|
||||
int nVerify; /* How often to verify the db contents */
|
||||
int nTest; /* Number of keys to test (0==all) */
|
||||
int bTestScan; /* True to do scan tests */
|
||||
};
|
||||
|
||||
/*
|
||||
** An instance of the following data structure is used to describe the
|
||||
** second type of test case in this file. The chief difference between
|
||||
** these tests and those described by Datatest1 is that these tests also
|
||||
** experiment with range-delete operations. Tests proceed as follows:
|
||||
**
|
||||
** 1. Open the datasource described by Datatest2.defn.
|
||||
**
|
||||
** 2. Open a connection on an empty database.
|
||||
**
|
||||
** 3. Do this Datatest2.nIter times:
|
||||
**
|
||||
** a) Insert Datatest2.nWrite key-value pairs from the datasource.
|
||||
**
|
||||
** b) Select two pseudo-random keys and use them as the start
|
||||
** and end points of a range-delete operation.
|
||||
**
|
||||
** c) Verify that the contents of the database are as expected (see
|
||||
** below for details).
|
||||
**
|
||||
** d) Close and then reopen the database handle.
|
||||
**
|
||||
** e) Verify that the contents of the database are still as expected.
|
||||
**
|
||||
** The inserts and range deletes are run twice - once on the database being
|
||||
** tested and once using a control system (sqlite3, kc etc. - something that
|
||||
** works). In order to verify that the contents of the db being tested are
|
||||
** correct, the test runs a bunch of scans and lookups on both the test and
|
||||
** control databases. If the results are the same, the test passes.
|
||||
*/
|
||||
struct Datatest2 {
|
||||
DatasourceDefn defn;
|
||||
int nRange;
|
||||
int nWrite; /* Number of writes per iteration */
|
||||
int nIter; /* Total number of iterations to run */
|
||||
};
|
||||
|
||||
/*
|
||||
** Generate a unique name for the test case pTest with database system
|
||||
** zSystem.
|
||||
*/
|
||||
static char *getName(const char *zSystem, Datatest1 *pTest){
|
||||
char *zRet;
|
||||
char *zData;
|
||||
zData = testDatasourceName(&pTest->defn);
|
||||
zRet = testMallocPrintf("data.%s.%s.%d.%d",
|
||||
zSystem, zData, pTest->nRow, pTest->nVerify
|
||||
);
|
||||
testFree(zData);
|
||||
return zRet;
|
||||
}
|
||||
|
||||
int testControlDb(TestDb **ppDb){
|
||||
#ifdef HAVE_KYOTOCABINET
|
||||
return tdb_open("kyotocabinet", "tmp.db", 1, ppDb);
|
||||
#else
|
||||
return tdb_open("sqlite3", ":memory:", 1, ppDb);
|
||||
#endif
|
||||
}
|
||||
|
||||
void testDatasourceFetch(
|
||||
TestDb *pDb, /* Database handle */
|
||||
Datasource *pData,
|
||||
int iKey,
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
void *pKey; int nKey; /* Database key to query for */
|
||||
void *pVal; int nVal; /* Expected result of query */
|
||||
|
||||
testDatasourceEntry(pData, iKey, &pKey, &nKey, &pVal, &nVal);
|
||||
testFetch(pDb, pKey, nKey, pVal, nVal, pRc);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is called to test that the contents of database pDb
|
||||
** are as expected. In this case, expected is defined as containing
|
||||
** key-value pairs iFirst through iLast, inclusive, from data source
|
||||
** pData. In other words, a loop like the following could be used to
|
||||
** construct a database with identical contents from scratch.
|
||||
**
|
||||
** for(i=iFirst; i<=iLast; i++){
|
||||
** testDatasourceEntry(pData, i, &pKey, &nKey, &pVal, &nVal);
|
||||
** // insert (pKey, nKey) -> (pVal, nVal) into database
|
||||
** }
|
||||
**
|
||||
** The key domain consists of keys 0 to (nRow-1), inclusive, from
|
||||
** data source pData. For both scan and lookup tests, keys are selected
|
||||
** pseudo-randomly from within this set.
|
||||
**
|
||||
** This function runs nLookupTest lookup tests and nScanTest scan tests.
|
||||
**
|
||||
** A lookup test consists of selecting a key from the domain and querying
|
||||
** pDb for it. The test fails if the presence of the key and, if present,
|
||||
** the associated value do not match the expectations defined above.
|
||||
**
|
||||
** A scan test involves selecting a key from the domain and running
|
||||
** the following queries:
|
||||
**
|
||||
** 1. Scan all keys equal to or greater than the key, in ascending order.
|
||||
** 2. Scan all keys equal to or smaller than the key, in descending order.
|
||||
**
|
||||
** Additionally, if nLookupTest is greater than zero, the following are
|
||||
** run once:
|
||||
**
|
||||
** 1. Scan all keys in the db, in ascending order.
|
||||
** 2. Scan all keys in the db, in descending order.
|
||||
**
|
||||
** As you would assume, the test fails if the returned values do not match
|
||||
** expectations.
|
||||
*/
|
||||
void testDbContents(
|
||||
TestDb *pDb, /* Database handle being tested */
|
||||
Datasource *pData, /* pDb contains data from here */
|
||||
int nRow, /* Size of key domain */
|
||||
int iFirst, /* Index of first key from pData in pDb */
|
||||
int iLast, /* Index of last key from pData in pDb */
|
||||
int nLookupTest, /* Number of lookup tests to run */
|
||||
int nScanTest, /* Number of scan tests to run */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
int j;
|
||||
int rc = *pRc;
|
||||
|
||||
if( rc==0 && nScanTest ){
|
||||
TestDb *pDb2 = 0;
|
||||
|
||||
/* Open a control db (i.e. one that we assume works) */
|
||||
rc = testControlDb(&pDb2);
|
||||
|
||||
for(j=iFirst; rc==0 && j<=iLast; j++){
|
||||
void *pKey; int nKey; /* Database key to insert */
|
||||
void *pVal; int nVal; /* Database value to insert */
|
||||
testDatasourceEntry(pData, j, &pKey, &nKey, &pVal, &nVal);
|
||||
rc = tdb_write(pDb2, pKey, nKey, pVal, nVal);
|
||||
}
|
||||
|
||||
if( rc==0 ){
|
||||
int iKey1;
|
||||
int iKey2;
|
||||
void *pKey1; int nKey1; /* Start key */
|
||||
void *pKey2; int nKey2; /* Final key */
|
||||
|
||||
iKey1 = testPrngValue((iFirst<<8) + (iLast<<16)) % nRow;
|
||||
iKey2 = testPrngValue((iLast<<8) + (iFirst<<16)) % nRow;
|
||||
testDatasourceEntry(pData, iKey1, &pKey2, &nKey1, 0, 0);
|
||||
pKey1 = testMalloc(nKey1+1);
|
||||
memcpy(pKey1, pKey2, nKey1+1);
|
||||
testDatasourceEntry(pData, iKey2, &pKey2, &nKey2, 0, 0);
|
||||
|
||||
testScanCompare(pDb2, pDb, 0, 0, 0, 0, 0, &rc);
|
||||
testScanCompare(pDb2, pDb, 0, 0, 0, pKey2, nKey2, &rc);
|
||||
testScanCompare(pDb2, pDb, 0, pKey1, nKey1, 0, 0, &rc);
|
||||
testScanCompare(pDb2, pDb, 0, pKey1, nKey1, pKey2, nKey2, &rc);
|
||||
testScanCompare(pDb2, pDb, 1, 0, 0, 0, 0, &rc);
|
||||
testScanCompare(pDb2, pDb, 1, 0, 0, pKey2, nKey2, &rc);
|
||||
testScanCompare(pDb2, pDb, 1, pKey1, nKey1, 0, 0, &rc);
|
||||
testScanCompare(pDb2, pDb, 1, pKey1, nKey1, pKey2, nKey2, &rc);
|
||||
testFree(pKey1);
|
||||
}
|
||||
tdb_close(pDb2);
|
||||
}
|
||||
|
||||
/* Test some lookups. */
|
||||
for(j=0; rc==0 && j<nLookupTest; j++){
|
||||
int iKey; /* Datasource key to test */
|
||||
void *pKey; int nKey; /* Database key to query for */
|
||||
void *pVal; int nVal; /* Expected result of query */
|
||||
|
||||
if( nLookupTest>=nRow ){
|
||||
iKey = j;
|
||||
}else{
|
||||
iKey = testPrngValue(j + (iFirst<<8) + (iLast<<16)) % nRow;
|
||||
}
|
||||
|
||||
testDatasourceEntry(pData, iKey, &pKey, &nKey, &pVal, &nVal);
|
||||
if( iFirst>iKey || iKey>iLast ){
|
||||
pVal = 0;
|
||||
nVal = -1;
|
||||
}
|
||||
|
||||
testFetch(pDb, pKey, nKey, pVal, nVal, &rc);
|
||||
}
|
||||
|
||||
*pRc = rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function should be called during long running test cases to output
|
||||
** the progress dots (...) to stdout.
|
||||
*/
|
||||
void testCaseProgress(int i, int n, int nDot, int *piDot){
|
||||
int iDot = *piDot;
|
||||
while( iDot < ( ((nDot*2+1) * i) / (n*2) ) ){
|
||||
printf(".");
|
||||
fflush(stdout);
|
||||
iDot++;
|
||||
}
|
||||
*piDot = iDot;
|
||||
}
|
||||
|
||||
int testCaseNDot(void){ return 20; }
|
||||
|
||||
#if 0
|
||||
static void printScanCb(
|
||||
void *pCtx, void *pKey, int nKey, void *pVal, int nVal
|
||||
){
|
||||
printf("%s\n", (char *)pKey);
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void doDataTest1(
|
||||
const char *zSystem, /* Database system to test */
|
||||
Datatest1 *p, /* Structure containing test parameters */
|
||||
int *pRc /* OUT: Error code */
|
||||
){
|
||||
int i;
|
||||
int iDot;
|
||||
int rc = LSM_OK;
|
||||
Datasource *pData;
|
||||
TestDb *pDb;
|
||||
|
||||
/* Start the test case, open a database and allocate the datasource. */
|
||||
pDb = testOpen(zSystem, 1, &rc);
|
||||
pData = testDatasourceNew(&p->defn);
|
||||
|
||||
i = 0;
|
||||
iDot = 0;
|
||||
while( rc==LSM_OK && i<p->nRow ){
|
||||
|
||||
/* Insert some data */
|
||||
testWriteDatasourceRange(pDb, pData, i, p->nVerify, &rc);
|
||||
i += p->nVerify;
|
||||
|
||||
/* Check that the db content is correct. */
|
||||
testDbContents(pDb, pData, p->nRow, 0, i-1, p->nTest, p->bTestScan, &rc);
|
||||
|
||||
/* Close and reopen the database. */
|
||||
testReopen(&pDb, &rc);
|
||||
|
||||
/* Check that the db content is still correct. */
|
||||
testDbContents(pDb, pData, p->nRow, 0, i-1, p->nTest, p->bTestScan, &rc);
|
||||
|
||||
/* Update the progress dots... */
|
||||
testCaseProgress(i, p->nRow, testCaseNDot()/2, &iDot);
|
||||
}
|
||||
|
||||
i = 0;
|
||||
iDot = 0;
|
||||
while( rc==LSM_OK && i<p->nRow ){
|
||||
|
||||
/* Delete some entries */
|
||||
testDeleteDatasourceRange(pDb, pData, i, p->nVerify, &rc);
|
||||
i += p->nVerify;
|
||||
|
||||
/* Check that the db content is correct. */
|
||||
testDbContents(pDb, pData, p->nRow, i, p->nRow-1,p->nTest,p->bTestScan,&rc);
|
||||
|
||||
/* Close and reopen the database. */
|
||||
testReopen(&pDb, &rc);
|
||||
|
||||
/* Check that the db content is still correct. */
|
||||
testDbContents(pDb, pData, p->nRow, i, p->nRow-1,p->nTest,p->bTestScan,&rc);
|
||||
|
||||
/* Update the progress dots... */
|
||||
testCaseProgress(i, p->nRow, testCaseNDot()/2, &iDot);
|
||||
}
|
||||
|
||||
/* Free the datasource, close the database and finish the test case. */
|
||||
testDatasourceFree(pData);
|
||||
tdb_close(pDb);
|
||||
testCaseFinish(rc);
|
||||
*pRc = rc;
|
||||
}
|
||||
|
||||
|
||||
void test_data_1(
|
||||
const char *zSystem, /* Database system name */
|
||||
const char *zPattern, /* Run test cases that match this pattern */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
Datatest1 aTest[] = {
|
||||
{ {DATA_RANDOM, 500,600, 1000,2000}, 1000, 100, 10, 0},
|
||||
{ {DATA_RANDOM, 20,25, 100,200}, 1000, 250, 1000, 1},
|
||||
{ {DATA_RANDOM, 8,10, 100,200}, 1000, 250, 1000, 1},
|
||||
{ {DATA_RANDOM, 8,10, 10,20}, 1000, 250, 1000, 1},
|
||||
{ {DATA_RANDOM, 8,10, 1000,2000}, 1000, 250, 1000, 1},
|
||||
{ {DATA_RANDOM, 8,100, 10000,20000}, 100, 25, 100, 1},
|
||||
{ {DATA_RANDOM, 80,100, 10,20}, 1000, 250, 1000, 1},
|
||||
{ {DATA_RANDOM, 5000,6000, 10,20}, 100, 25, 100, 1},
|
||||
{ {DATA_SEQUENTIAL, 5,10, 10,20}, 1000, 250, 1000, 1},
|
||||
{ {DATA_SEQUENTIAL, 5,10, 100,200}, 1000, 250, 1000, 1},
|
||||
{ {DATA_SEQUENTIAL, 5,10, 1000,2000}, 1000, 250, 1000, 1},
|
||||
{ {DATA_SEQUENTIAL, 5,100, 10000,20000}, 100, 25, 100, 1},
|
||||
{ {DATA_RANDOM, 10,10, 100,100}, 100000, 1000, 100, 0},
|
||||
{ {DATA_SEQUENTIAL, 10,10, 100,100}, 100000, 1000, 100, 0},
|
||||
};
|
||||
|
||||
int i;
|
||||
|
||||
for(i=0; *pRc==LSM_OK && i<ArraySize(aTest); i++){
|
||||
char *zName = getName(zSystem, &aTest[i]);
|
||||
if( testCaseBegin(pRc, zPattern, "%s", zName) ){
|
||||
doDataTest1(zSystem, &aTest[i], pRc);
|
||||
}
|
||||
testFree(zName);
|
||||
}
|
||||
}
|
||||
|
||||
void testCompareDb(
|
||||
Datasource *pData,
|
||||
int nData,
|
||||
int iSeed,
|
||||
TestDb *pControl,
|
||||
TestDb *pDb,
|
||||
int *pRc
|
||||
){
|
||||
int i;
|
||||
|
||||
static int nCall = 0;
|
||||
nCall++;
|
||||
|
||||
testScanCompare(pControl, pDb, 0, 0, 0, 0, 0, pRc);
|
||||
testScanCompare(pControl, pDb, 1, 0, 0, 0, 0, pRc);
|
||||
|
||||
if( *pRc==0 ){
|
||||
int iKey1;
|
||||
int iKey2;
|
||||
void *pKey1; int nKey1; /* Start key */
|
||||
void *pKey2; int nKey2; /* Final key */
|
||||
|
||||
iKey1 = testPrngValue(iSeed) % nData;
|
||||
iKey2 = testPrngValue(iSeed+1) % nData;
|
||||
testDatasourceEntry(pData, iKey1, &pKey2, &nKey1, 0, 0);
|
||||
pKey1 = testMalloc(nKey1+1);
|
||||
memcpy(pKey1, pKey2, nKey1+1);
|
||||
testDatasourceEntry(pData, iKey2, &pKey2, &nKey2, 0, 0);
|
||||
|
||||
testScanCompare(pControl, pDb, 0, 0, 0, pKey2, nKey2, pRc);
|
||||
testScanCompare(pControl, pDb, 0, pKey1, nKey1, 0, 0, pRc);
|
||||
testScanCompare(pControl, pDb, 0, pKey1, nKey1, pKey2, nKey2, pRc);
|
||||
testScanCompare(pControl, pDb, 1, 0, 0, pKey2, nKey2, pRc);
|
||||
testScanCompare(pControl, pDb, 1, pKey1, nKey1, 0, 0, pRc);
|
||||
testScanCompare(pControl, pDb, 1, pKey1, nKey1, pKey2, nKey2, pRc);
|
||||
testFree(pKey1);
|
||||
}
|
||||
|
||||
for(i=0; i<nData && *pRc==0; i++){
|
||||
void *pKey; int nKey;
|
||||
testDatasourceEntry(pData, i, &pKey, &nKey, 0, 0);
|
||||
testFetchCompare(pControl, pDb, pKey, nKey, pRc);
|
||||
}
|
||||
}
|
||||
|
||||
static void doDataTest2(
|
||||
const char *zSystem, /* Database system to test */
|
||||
Datatest2 *p, /* Structure containing test parameters */
|
||||
int *pRc /* OUT: Error code */
|
||||
){
|
||||
TestDb *pDb;
|
||||
TestDb *pControl;
|
||||
Datasource *pData;
|
||||
int i;
|
||||
int rc = LSM_OK;
|
||||
int iDot = 0;
|
||||
|
||||
/* Start the test case, open a database and allocate the datasource. */
|
||||
pDb = testOpen(zSystem, 1, &rc);
|
||||
pData = testDatasourceNew(&p->defn);
|
||||
rc = testControlDb(&pControl);
|
||||
|
||||
if( tdb_lsm(pDb) ){
|
||||
int nBuf = 32 * 1024 * 1024;
|
||||
lsm_config(tdb_lsm(pDb), LSM_CONFIG_AUTOFLUSH, &nBuf);
|
||||
}
|
||||
|
||||
for(i=0; rc==0 && i<p->nIter; i++){
|
||||
void *pKey1; int nKey1;
|
||||
void *pKey2; int nKey2;
|
||||
int ii;
|
||||
int nRange = MIN(p->nIter*p->nWrite, p->nRange);
|
||||
|
||||
for(ii=0; rc==0 && ii<p->nWrite; ii++){
|
||||
int iKey = (i*p->nWrite + ii) % p->nRange;
|
||||
testWriteDatasource(pControl, pData, iKey, &rc);
|
||||
testWriteDatasource(pDb, pData, iKey, &rc);
|
||||
}
|
||||
|
||||
testDatasourceEntry(pData, i+1000000, &pKey1, &nKey1, 0, 0);
|
||||
pKey1 = testMallocCopy(pKey1, nKey1);
|
||||
testDatasourceEntry(pData, i+2000000, &pKey2, &nKey2, 0, 0);
|
||||
|
||||
testDeleteRange(pDb, pKey1, nKey1, pKey2, nKey2, &rc);
|
||||
testDeleteRange(pControl, pKey1, nKey1, pKey2, nKey2, &rc);
|
||||
testFree(pKey1);
|
||||
|
||||
testCompareDb(pData, nRange, i, pControl, pDb, &rc);
|
||||
testReopen(&pDb, &rc);
|
||||
testCompareDb(pData, nRange, i, pControl, pDb, &rc);
|
||||
|
||||
/* Update the progress dots... */
|
||||
testCaseProgress(i, p->nIter, testCaseNDot(), &iDot);
|
||||
}
|
||||
|
||||
testClose(&pDb);
|
||||
testClose(&pControl);
|
||||
testDatasourceFree(pData);
|
||||
testCaseFinish(rc);
|
||||
*pRc = rc;
|
||||
}
|
||||
|
||||
static char *getName2(const char *zSystem, Datatest2 *pTest){
|
||||
char *zRet;
|
||||
char *zData;
|
||||
zData = testDatasourceName(&pTest->defn);
|
||||
zRet = testMallocPrintf("data2.%s.%s.%d.%d.%d",
|
||||
zSystem, zData, pTest->nRange, pTest->nWrite, pTest->nIter
|
||||
);
|
||||
testFree(zData);
|
||||
return zRet;
|
||||
}
|
||||
|
||||
void test_data_2(
|
||||
const char *zSystem, /* Database system name */
|
||||
const char *zPattern, /* Run test cases that match this pattern */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
Datatest2 aTest[] = {
|
||||
/* defn, nRange, nWrite, nIter */
|
||||
{ {DATA_RANDOM, 20,25, 100,200}, 10000, 10, 50 },
|
||||
{ {DATA_RANDOM, 20,25, 100,200}, 10000, 200, 50 },
|
||||
{ {DATA_RANDOM, 20,25, 100,200}, 100, 10, 1000 },
|
||||
{ {DATA_RANDOM, 20,25, 100,200}, 100, 200, 50 },
|
||||
};
|
||||
|
||||
int i;
|
||||
|
||||
for(i=0; *pRc==LSM_OK && i<ArraySize(aTest); i++){
|
||||
char *zName = getName2(zSystem, &aTest[i]);
|
||||
if( testCaseBegin(pRc, zPattern, "%s", zName) ){
|
||||
doDataTest2(zSystem, &aTest[i], pRc);
|
||||
}
|
||||
testFree(zName);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
** Test case data3.*
|
||||
*/
|
||||
|
||||
typedef struct Datatest3 Datatest3;
|
||||
struct Datatest3 {
|
||||
int nRange; /* Keys are between 1 and this value, incl. */
|
||||
int nIter; /* Number of iterations */
|
||||
int nWrite; /* Number of writes per iteration */
|
||||
int nDelete; /* Number of deletes per iteration */
|
||||
|
||||
int nValMin; /* Minimum value size for writes */
|
||||
int nValMax; /* Maximum value size for writes */
|
||||
};
|
||||
|
||||
void testPutU32(u8 *aBuf, u32 iVal){
|
||||
aBuf[0] = (iVal >> 24) & 0xFF;
|
||||
aBuf[1] = (iVal >> 16) & 0xFF;
|
||||
aBuf[2] = (iVal >> 8) & 0xFF;
|
||||
aBuf[3] = (iVal >> 0) & 0xFF;
|
||||
}
|
||||
|
||||
void dt3PutKey(u8 *aBuf, int iKey){
|
||||
assert( iKey<100000 && iKey>=0 );
|
||||
sprintf((char *)aBuf, "%.5d", iKey);
|
||||
}
|
||||
|
||||
static void doDataTest3(
|
||||
const char *zSystem, /* Database system to test */
|
||||
Datatest3 *p, /* Structure containing test parameters */
|
||||
int *pRc /* OUT: Error code */
|
||||
){
|
||||
int iDot = 0;
|
||||
int rc = *pRc;
|
||||
TestDb *pDb;
|
||||
u8 *abPresent; /* Array of boolean */
|
||||
char *aVal; /* Buffer to hold values */
|
||||
int i;
|
||||
u32 iSeq = 10; /* prng counter */
|
||||
|
||||
abPresent = (u8 *)testMalloc(p->nRange+1);
|
||||
aVal = (char *)testMalloc(p->nValMax+1);
|
||||
pDb = testOpen(zSystem, 1, &rc);
|
||||
|
||||
for(i=0; i<p->nIter && rc==0; i++){
|
||||
int ii;
|
||||
|
||||
testCaseProgress(i, p->nIter, testCaseNDot(), &iDot);
|
||||
|
||||
/* Perform nWrite inserts */
|
||||
for(ii=0; ii<p->nWrite; ii++){
|
||||
u8 aKey[6];
|
||||
u32 iKey;
|
||||
int nVal;
|
||||
|
||||
iKey = (testPrngValue(iSeq++) % p->nRange) + 1;
|
||||
nVal = (testPrngValue(iSeq++) % (p->nValMax - p->nValMin)) + p->nValMin;
|
||||
testPrngString(testPrngValue(iSeq++), aVal, nVal);
|
||||
dt3PutKey(aKey, iKey);
|
||||
|
||||
testWrite(pDb, aKey, sizeof(aKey)-1, aVal, nVal, &rc);
|
||||
abPresent[iKey] = 1;
|
||||
}
|
||||
|
||||
/* Perform nDelete deletes */
|
||||
for(ii=0; ii<p->nDelete; ii++){
|
||||
u8 aKey1[6];
|
||||
u8 aKey2[6];
|
||||
u32 iKey;
|
||||
|
||||
iKey = (testPrngValue(iSeq++) % p->nRange) + 1;
|
||||
dt3PutKey(aKey1, iKey-1);
|
||||
dt3PutKey(aKey2, iKey+1);
|
||||
|
||||
testDeleteRange(pDb, aKey1, sizeof(aKey1)-1, aKey2, sizeof(aKey2)-1, &rc);
|
||||
abPresent[iKey] = 0;
|
||||
}
|
||||
|
||||
testReopen(&pDb, &rc);
|
||||
|
||||
for(ii=1; rc==0 && ii<=p->nRange; ii++){
|
||||
int nDbVal;
|
||||
void *pDbVal;
|
||||
u8 aKey[6];
|
||||
int dbrc;
|
||||
|
||||
dt3PutKey(aKey, ii);
|
||||
dbrc = tdb_fetch(pDb, aKey, sizeof(aKey)-1, &pDbVal, &nDbVal);
|
||||
testCompareInt(0, dbrc, &rc);
|
||||
|
||||
if( abPresent[ii] ){
|
||||
testCompareInt(1, (nDbVal>0), &rc);
|
||||
}else{
|
||||
testCompareInt(1, (nDbVal<0), &rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testClose(&pDb);
|
||||
testCaseFinish(rc);
|
||||
*pRc = rc;
|
||||
}
|
||||
|
||||
static char *getName3(const char *zSystem, Datatest3 *p){
|
||||
return testMallocPrintf("data3.%s.%d.%d.%d.%d.(%d..%d)",
|
||||
zSystem, p->nRange, p->nIter, p->nWrite, p->nDelete,
|
||||
p->nValMin, p->nValMax
|
||||
);
|
||||
}
|
||||
|
||||
void test_data_3(
|
||||
const char *zSystem, /* Database system name */
|
||||
const char *zPattern, /* Run test cases that match this pattern */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
Datatest3 aTest[] = {
|
||||
/* nRange, nIter, nWrite, nDelete, nValMin, nValMax */
|
||||
{ 100, 1000, 5, 5, 50, 100 },
|
||||
{ 100, 1000, 2, 2, 5, 10 },
|
||||
};
|
||||
|
||||
int i;
|
||||
|
||||
for(i=0; *pRc==LSM_OK && i<ArraySize(aTest); i++){
|
||||
char *zName = getName3(zSystem, &aTest[i]);
|
||||
if( testCaseBegin(pRc, zPattern, "%s", zName) ){
|
||||
doDataTest3(zSystem, &aTest[i], pRc);
|
||||
}
|
||||
testFree(zName);
|
||||
}
|
||||
}
|
||||
|
||||
|
485
ext/lsm1/lsm-test/lsmtest2.c
Normal file
485
ext/lsm1/lsm-test/lsmtest2.c
Normal file
@ -0,0 +1,485 @@
|
||||
|
||||
/*
|
||||
** This file contains tests related to recovery following application
|
||||
** and system crashes (power failures) while writing to the database.
|
||||
*/
|
||||
|
||||
#include "lsmtest.h"
|
||||
|
||||
/*
|
||||
** Structure used by testCksumDatabase() to accumulate checksum values in.
|
||||
*/
|
||||
typedef struct Cksum Cksum;
|
||||
struct Cksum {
|
||||
int nRow;
|
||||
int cksum1;
|
||||
int cksum2;
|
||||
};
|
||||
|
||||
/*
|
||||
** tdb_scan() callback used by testCksumDatabase()
|
||||
*/
|
||||
static void scanCksumDb(
|
||||
void *pCtx,
|
||||
void *pKey, int nKey,
|
||||
void *pVal, int nVal
|
||||
){
|
||||
Cksum *p = (Cksum *)pCtx;
|
||||
int i;
|
||||
|
||||
p->nRow++;
|
||||
for(i=0; i<nKey; i++){
|
||||
p->cksum1 += ((u8 *)pKey)[i];
|
||||
p->cksum2 += p->cksum1;
|
||||
}
|
||||
for(i=0; i<nVal; i++){
|
||||
p->cksum1 += ((u8 *)pVal)[i];
|
||||
p->cksum2 += p->cksum1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** tdb_scan() callback used by testCountDatabase()
|
||||
*/
|
||||
static void scanCountDb(
|
||||
void *pCtx,
|
||||
void *pKey, int nKey,
|
||||
void *pVal, int nVal
|
||||
){
|
||||
Cksum *p = (Cksum *)pCtx;
|
||||
p->nRow++;
|
||||
|
||||
unused_parameter(pKey);
|
||||
unused_parameter(nKey);
|
||||
unused_parameter(pVal);
|
||||
unused_parameter(nVal);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Iterate through the entire contents of database pDb. Write a checksum
|
||||
** string based on the db contents into buffer zOut before returning. A
|
||||
** checksum string is at most 29 (TEST_CKSUM_BYTES) bytes in size:
|
||||
**
|
||||
** * 32-bit integer (10 bytes)
|
||||
** * 1 space (1 byte)
|
||||
** * 32-bit hex (8 bytes)
|
||||
** * 1 space (1 byte)
|
||||
** * 32-bit hex (8 bytes)
|
||||
** * nul-terminator (1 byte)
|
||||
**
|
||||
** The number of entries in the database is returned.
|
||||
*/
|
||||
int testCksumDatabase(
|
||||
TestDb *pDb, /* Database handle */
|
||||
char *zOut /* Buffer to write checksum to */
|
||||
){
|
||||
Cksum cksum;
|
||||
memset(&cksum, 0, sizeof(Cksum));
|
||||
tdb_scan(pDb, (void *)&cksum, 0, 0, 0, 0, 0, scanCksumDb);
|
||||
sprintf(zOut, "%d %x %x",
|
||||
cksum.nRow, (u32)cksum.cksum1, (u32)cksum.cksum2
|
||||
);
|
||||
assert( strlen(zOut)<TEST_CKSUM_BYTES );
|
||||
return cksum.nRow;
|
||||
}
|
||||
|
||||
int testCountDatabase(TestDb *pDb){
|
||||
Cksum cksum;
|
||||
memset(&cksum, 0, sizeof(Cksum));
|
||||
tdb_scan(pDb, (void *)&cksum, 0, 0, 0, 0, 0, scanCountDb);
|
||||
return cksum.nRow;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op if *pRc is not 0 when it is called.
|
||||
**
|
||||
** Otherwise, the two nul-terminated strings z1 and z1 are compared. If
|
||||
** they are the same, the function returns without doing anything. Otherwise,
|
||||
** an error message is printed, *pRc is set to 1 and the test_failed()
|
||||
** function called.
|
||||
*/
|
||||
void testCompareStr(const char *z1, const char *z2, int *pRc){
|
||||
if( *pRc==0 ){
|
||||
if( strcmp(z1, z2) ){
|
||||
testPrintError("testCompareStr: \"%s\" != \"%s\"\n", z1, z2);
|
||||
*pRc = 1;
|
||||
test_failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op if *pRc is not 0 when it is called.
|
||||
**
|
||||
** Otherwise, the two integers i1 and i2 are compared. If they are equal,
|
||||
** the function returns without doing anything. Otherwise, an error message
|
||||
** is printed, *pRc is set to 1 and the test_failed() function called.
|
||||
*/
|
||||
void testCompareInt(int i1, int i2, int *pRc){
|
||||
if( *pRc==0 && i1!=i2 ){
|
||||
testPrintError("testCompareInt: %d != %d\n", i1, i2);
|
||||
*pRc = 1;
|
||||
test_failed();
|
||||
}
|
||||
}
|
||||
|
||||
void testCaseStart(int *pRc, char *zFmt, ...){
|
||||
va_list ap;
|
||||
va_start(ap, zFmt);
|
||||
vprintf(zFmt, ap);
|
||||
printf(" ...");
|
||||
va_end(ap);
|
||||
*pRc = 0;
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op if *pRc is non-zero when it is called. Zero
|
||||
** is returned in this case.
|
||||
**
|
||||
** Otherwise, the zFmt (a printf style format string) and following arguments
|
||||
** are used to create a test case name. If zPattern is NULL or a glob pattern
|
||||
** that matches the test case name, 1 is returned and the test case started.
|
||||
** Otherwise, zero is returned and the test case does not start.
|
||||
*/
|
||||
int testCaseBegin(int *pRc, const char *zPattern, const char *zFmt, ...){
|
||||
int res = 0;
|
||||
if( *pRc==0 ){
|
||||
char *zTest;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, zFmt);
|
||||
zTest = testMallocVPrintf(zFmt, ap);
|
||||
va_end(ap);
|
||||
if( zPattern==0 || testGlobMatch(zPattern, zTest) ){
|
||||
printf("%-50s ...", zTest);
|
||||
res = 1;
|
||||
}
|
||||
testFree(zTest);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void testCaseFinish(int rc){
|
||||
if( rc==0 ){
|
||||
printf("Ok\n");
|
||||
}else{
|
||||
printf("FAILED\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void testCaseSkip(){
|
||||
printf("Skipped\n");
|
||||
}
|
||||
|
||||
void testSetupSavedLsmdb(
|
||||
const char *zCfg,
|
||||
const char *zFile,
|
||||
Datasource *pData,
|
||||
int nRow,
|
||||
int *pRc
|
||||
){
|
||||
if( *pRc==0 ){
|
||||
int rc;
|
||||
TestDb *pDb;
|
||||
rc = tdb_lsm_open(zCfg, zFile, 1, &pDb);
|
||||
if( rc==0 ){
|
||||
testWriteDatasourceRange(pDb, pData, 0, nRow, &rc);
|
||||
testClose(&pDb);
|
||||
if( rc==0 ) testSaveDb(zFile, "log");
|
||||
}
|
||||
*pRc = rc;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is a no-op if *pRc is non-zero when it is called.
|
||||
**
|
||||
** Open the LSM database identified by zFile and compute its checksum
|
||||
** (a string, as returned by testCksumDatabase()). If the checksum is
|
||||
** identical to zExpect1 or, if it is not NULL, zExpect2, the test passes.
|
||||
** Otherwise, print an error message and set *pRc to 1.
|
||||
*/
|
||||
static void testCompareCksumLsmdb(
|
||||
const char *zFile, /* Path to LSM database */
|
||||
int bCompress, /* True if db is compressed */
|
||||
const char *zExpect1, /* Expected checksum 1 */
|
||||
const char *zExpect2, /* Expected checksum 2 (or NULL) */
|
||||
int *pRc /* IN/OUT: Test case error code */
|
||||
){
|
||||
if( *pRc==0 ){
|
||||
char zCksum[TEST_CKSUM_BYTES];
|
||||
TestDb *pDb;
|
||||
|
||||
*pRc = tdb_lsm_open((bCompress?"compression=1 mmap=0":""), zFile, 0, &pDb);
|
||||
testCksumDatabase(pDb, zCksum);
|
||||
testClose(&pDb);
|
||||
|
||||
if( *pRc==0 ){
|
||||
int r1 = 0;
|
||||
int r2 = -1;
|
||||
|
||||
r1 = strcmp(zCksum, zExpect1);
|
||||
if( zExpect2 ) r2 = strcmp(zCksum, zExpect2);
|
||||
if( r1 && r2 ){
|
||||
if( zExpect2 ){
|
||||
testPrintError("testCompareCksumLsmdb: \"%s\" != (\"%s\" OR \"%s\")",
|
||||
zCksum, zExpect1, zExpect2
|
||||
);
|
||||
}else{
|
||||
testPrintError("testCompareCksumLsmdb: \"%s\" != \"%s\"",
|
||||
zCksum, zExpect1
|
||||
);
|
||||
}
|
||||
*pRc = 1;
|
||||
test_failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void testCompareCksumBtdb(
|
||||
const char *zFile, /* Path to LSM database */
|
||||
const char *zExpect1, /* Expected checksum 1 */
|
||||
const char *zExpect2, /* Expected checksum 2 (or NULL) */
|
||||
int *pRc /* IN/OUT: Test case error code */
|
||||
){
|
||||
if( *pRc==0 ){
|
||||
char zCksum[TEST_CKSUM_BYTES];
|
||||
TestDb *pDb;
|
||||
|
||||
*pRc = tdb_open("bt", zFile, 0, &pDb);
|
||||
testCksumDatabase(pDb, zCksum);
|
||||
testClose(&pDb);
|
||||
|
||||
if( *pRc==0 ){
|
||||
int r1 = 0;
|
||||
int r2 = -1;
|
||||
|
||||
r1 = strcmp(zCksum, zExpect1);
|
||||
if( zExpect2 ) r2 = strcmp(zCksum, zExpect2);
|
||||
if( r1 && r2 ){
|
||||
if( zExpect2 ){
|
||||
testPrintError("testCompareCksumLsmdb: \"%s\" != (\"%s\" OR \"%s\")",
|
||||
zCksum, zExpect1, zExpect2
|
||||
);
|
||||
}else{
|
||||
testPrintError("testCompareCksumLsmdb: \"%s\" != \"%s\"",
|
||||
zCksum, zExpect1
|
||||
);
|
||||
}
|
||||
*pRc = 1;
|
||||
test_failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Above this point are reusable test routines. Not clear that they
|
||||
** should really be in this file.
|
||||
*************************************************************************/
|
||||
|
||||
/*
|
||||
** This test verifies that if a system crash occurs while doing merge work
|
||||
** on the db, no data is lost.
|
||||
*/
|
||||
static void crash_test1(int bCompress, int *pRc){
|
||||
const char *DBNAME = "testdb.lsm";
|
||||
const DatasourceDefn defn = {TEST_DATASOURCE_RANDOM, 12, 16, 200, 200};
|
||||
|
||||
const int nRow = 5000; /* Database size */
|
||||
const int nIter = 200; /* Number of test iterations */
|
||||
const int nWork = 20; /* Maximum lsm_work() calls per iteration */
|
||||
const int nPage = 15; /* Pages per lsm_work call */
|
||||
|
||||
int i;
|
||||
int iDot = 0;
|
||||
Datasource *pData;
|
||||
CksumDb *pCksumDb;
|
||||
TestDb *pDb;
|
||||
char *zCfg;
|
||||
|
||||
const char *azConfig[2] = {
|
||||
"page_size=1024 block_size=65536 autoflush=16384 safety=2 mmap=0",
|
||||
"page_size=1024 block_size=65536 autoflush=16384 safety=2 "
|
||||
" compression=1 mmap=0"
|
||||
};
|
||||
assert( bCompress==0 || bCompress==1 );
|
||||
|
||||
/* Allocate datasource. And calculate the expected checksums. */
|
||||
pData = testDatasourceNew(&defn);
|
||||
pCksumDb = testCksumArrayNew(pData, nRow, nRow, 1);
|
||||
|
||||
/* Setup and save the initial database. */
|
||||
|
||||
zCfg = testMallocPrintf("%s automerge=7", azConfig[bCompress]);
|
||||
testSetupSavedLsmdb(zCfg, DBNAME, pData, 5000, pRc);
|
||||
testFree(zCfg);
|
||||
|
||||
for(i=0; i<nIter && *pRc==0; i++){
|
||||
int iWork;
|
||||
int testrc = 0;
|
||||
|
||||
testCaseProgress(i, nIter, testCaseNDot(), &iDot);
|
||||
|
||||
/* Restore and open the database. */
|
||||
testRestoreDb(DBNAME, "log");
|
||||
testrc = tdb_lsm_open(azConfig[bCompress], DBNAME, 0, &pDb);
|
||||
assert( testrc==0 );
|
||||
|
||||
/* Call lsm_work() on the db */
|
||||
tdb_lsm_prepare_sync_crash(pDb, 1 + (i%(nWork*2)));
|
||||
for(iWork=0; testrc==0 && iWork<nWork; iWork++){
|
||||
int nWrite = 0;
|
||||
lsm_db *db = tdb_lsm(pDb);
|
||||
testrc = lsm_work(db, 0, nPage, &nWrite);
|
||||
assert( testrc!=0 || nWrite>0 );
|
||||
if( testrc==0 ) testrc = lsm_checkpoint(db, 0);
|
||||
}
|
||||
tdb_close(pDb);
|
||||
|
||||
/* Check that the database content is still correct */
|
||||
testCompareCksumLsmdb(DBNAME,
|
||||
bCompress, testCksumArrayGet(pCksumDb, nRow), 0, pRc);
|
||||
}
|
||||
|
||||
testCksumArrayFree(pCksumDb);
|
||||
testDatasourceFree(pData);
|
||||
}
|
||||
|
||||
/*
|
||||
** This test verifies that if a system crash occurs while committing a
|
||||
** transaction to the log file, no earlier transactions are lost or damaged.
|
||||
*/
|
||||
static void crash_test2(int bCompress, int *pRc){
|
||||
const char *DBNAME = "testdb.lsm";
|
||||
const DatasourceDefn defn = {TEST_DATASOURCE_RANDOM, 12, 16, 1000, 1000};
|
||||
|
||||
const int nIter = 200;
|
||||
const int nInsert = 20;
|
||||
|
||||
int i;
|
||||
int iDot = 0;
|
||||
Datasource *pData;
|
||||
CksumDb *pCksumDb;
|
||||
TestDb *pDb;
|
||||
|
||||
/* Allocate datasource. And calculate the expected checksums. */
|
||||
pData = testDatasourceNew(&defn);
|
||||
pCksumDb = testCksumArrayNew(pData, 100, 100+nInsert, 1);
|
||||
|
||||
/* Setup and save the initial database. */
|
||||
testSetupSavedLsmdb("", DBNAME, pData, 100, pRc);
|
||||
|
||||
for(i=0; i<nIter && *pRc==0; i++){
|
||||
int iIns;
|
||||
int testrc = 0;
|
||||
|
||||
testCaseProgress(i, nIter, testCaseNDot(), &iDot);
|
||||
|
||||
/* Restore and open the database. */
|
||||
testRestoreDb(DBNAME, "log");
|
||||
testrc = tdb_lsm_open("safety=2", DBNAME, 0, &pDb);
|
||||
assert( testrc==0 );
|
||||
|
||||
/* Insert nInsert records into the database. Crash midway through. */
|
||||
tdb_lsm_prepare_sync_crash(pDb, 1 + (i%(nInsert+2)));
|
||||
for(iIns=0; iIns<nInsert; iIns++){
|
||||
void *pKey; int nKey;
|
||||
void *pVal; int nVal;
|
||||
|
||||
testDatasourceEntry(pData, 100+iIns, &pKey, &nKey, &pVal, &nVal);
|
||||
testrc = tdb_write(pDb, pKey, nKey, pVal, nVal);
|
||||
if( testrc ) break;
|
||||
}
|
||||
tdb_close(pDb);
|
||||
|
||||
/* Check that no data was lost when the system crashed. */
|
||||
testCompareCksumLsmdb(DBNAME, bCompress,
|
||||
testCksumArrayGet(pCksumDb, 100 + iIns),
|
||||
testCksumArrayGet(pCksumDb, 100 + iIns + 1),
|
||||
pRc
|
||||
);
|
||||
}
|
||||
|
||||
testDatasourceFree(pData);
|
||||
testCksumArrayFree(pCksumDb);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** This test verifies that if a system crash occurs when checkpointing
|
||||
** the database, data is not lost (assuming that any writes not synced
|
||||
** to the db have been synced into the log file).
|
||||
*/
|
||||
static void crash_test3(int bCompress, int *pRc){
|
||||
const char *DBNAME = "testdb.lsm";
|
||||
const int nIter = 100;
|
||||
const DatasourceDefn defn = {TEST_DATASOURCE_RANDOM, 12, 16, 1000, 1000};
|
||||
|
||||
int i;
|
||||
int iDot = 0;
|
||||
Datasource *pData;
|
||||
CksumDb *pCksumDb;
|
||||
TestDb *pDb;
|
||||
|
||||
/* Allocate datasource. And calculate the expected checksums. */
|
||||
pData = testDatasourceNew(&defn);
|
||||
pCksumDb = testCksumArrayNew(pData, 110, 150, 10);
|
||||
|
||||
/* Setup and save the initial database. */
|
||||
testSetupSavedLsmdb("", DBNAME, pData, 100, pRc);
|
||||
|
||||
for(i=0; i<nIter && *pRc==0; i++){
|
||||
int iOpen;
|
||||
testCaseProgress(i, nIter, testCaseNDot(), &iDot);
|
||||
testRestoreDb(DBNAME, "log");
|
||||
|
||||
for(iOpen=0; iOpen<5; iOpen++){
|
||||
/* Open the database. Insert 10 more records. */
|
||||
pDb = testOpen("lsm", 0, pRc);
|
||||
testWriteDatasourceRange(pDb, pData, 100+iOpen*10, 10, pRc);
|
||||
|
||||
/* Schedule a crash simulation then close the db. */
|
||||
tdb_lsm_prepare_sync_crash(pDb, 1 + (i%2));
|
||||
tdb_close(pDb);
|
||||
|
||||
/* Open the database and check that the crash did not cause any
|
||||
** data loss. */
|
||||
testCompareCksumLsmdb(DBNAME, bCompress,
|
||||
testCksumArrayGet(pCksumDb, 110 + iOpen*10), 0,
|
||||
pRc
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
testDatasourceFree(pData);
|
||||
testCksumArrayFree(pCksumDb);
|
||||
}
|
||||
|
||||
void do_crash_test(const char *zPattern, int *pRc){
|
||||
struct Test {
|
||||
const char *zTest;
|
||||
void (*x)(int, int *);
|
||||
int bCompress;
|
||||
} aTest [] = {
|
||||
{ "crash.lsm.1", crash_test1, 0 },
|
||||
{ "crash.lsm_zip.1", crash_test1, 1 },
|
||||
{ "crash.lsm.2", crash_test2, 0 },
|
||||
{ "crash.lsm.3", crash_test3, 0 },
|
||||
};
|
||||
int i;
|
||||
|
||||
for(i=0; *pRc==LSM_OK && i<ArraySize(aTest); i++){
|
||||
struct Test *p = &aTest[i];
|
||||
if( testCaseBegin(pRc, zPattern, "%s", p->zTest) ){
|
||||
p->x(p->bCompress, pRc);
|
||||
testCaseFinish(*pRc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
238
ext/lsm1/lsm-test/lsmtest3.c
Normal file
238
ext/lsm1/lsm-test/lsmtest3.c
Normal file
@ -0,0 +1,238 @@
|
||||
|
||||
|
||||
/*
|
||||
** This file contains tests related to the explicit rollback of database
|
||||
** transactions and sub-transactions.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
** Repeat 2000 times (until the db contains 100,000 entries):
|
||||
**
|
||||
** 1. Open a transaction and insert 500 rows, opening a nested
|
||||
** sub-transaction each 100 rows.
|
||||
**
|
||||
** 2. Roll back to each sub-transaction savepoint. Check the database
|
||||
** checksum looks Ok.
|
||||
**
|
||||
** 3. Every second iteration, roll back the main transaction. Check the
|
||||
** db checksum is correct. Every other iteration, commit the main
|
||||
** transaction (increasing the size of the db by 100 rows).
|
||||
*/
|
||||
|
||||
|
||||
#include "lsmtest.h"
|
||||
|
||||
struct CksumDb {
|
||||
int nFirst;
|
||||
int nLast;
|
||||
int nStep;
|
||||
char **azCksum;
|
||||
};
|
||||
|
||||
CksumDb *testCksumArrayNew(
|
||||
Datasource *pData,
|
||||
int nFirst,
|
||||
int nLast,
|
||||
int nStep
|
||||
){
|
||||
TestDb *pDb;
|
||||
CksumDb *pRet;
|
||||
int i;
|
||||
int nEntry;
|
||||
int rc = 0;
|
||||
|
||||
assert( nLast>=nFirst && ((nLast-nFirst)%nStep)==0 );
|
||||
|
||||
pRet = malloc(sizeof(CksumDb));
|
||||
memset(pRet, 0, sizeof(CksumDb));
|
||||
pRet->nFirst = nFirst;
|
||||
pRet->nLast = nLast;
|
||||
pRet->nStep = nStep;
|
||||
nEntry = 1 + ((nLast - nFirst) / nStep);
|
||||
|
||||
/* Allocate space so that azCksum is an array of nEntry pointers to
|
||||
** buffers each TEST_CKSUM_BYTES in size. */
|
||||
pRet->azCksum = (char **)malloc(nEntry * (sizeof(char *) + TEST_CKSUM_BYTES));
|
||||
for(i=0; i<nEntry; i++){
|
||||
char *pStart = (char *)(&pRet->azCksum[nEntry]);
|
||||
pRet->azCksum[i] = &pStart[i * TEST_CKSUM_BYTES];
|
||||
}
|
||||
|
||||
tdb_open("lsm", "tempdb.lsm", 1, &pDb);
|
||||
testWriteDatasourceRange(pDb, pData, 0, nFirst, &rc);
|
||||
for(i=0; i<nEntry; i++){
|
||||
testCksumDatabase(pDb, pRet->azCksum[i]);
|
||||
if( i==nEntry ) break;
|
||||
testWriteDatasourceRange(pDb, pData, nFirst+i*nStep, nStep, &rc);
|
||||
}
|
||||
|
||||
tdb_close(pDb);
|
||||
|
||||
return pRet;
|
||||
}
|
||||
|
||||
char *testCksumArrayGet(CksumDb *p, int nRow){
|
||||
int i;
|
||||
assert( nRow>=p->nFirst );
|
||||
assert( nRow<=p->nLast );
|
||||
assert( ((nRow-p->nFirst) % p->nStep)==0 );
|
||||
|
||||
i = (nRow - p->nFirst) / p->nStep;
|
||||
return p->azCksum[i];
|
||||
}
|
||||
|
||||
void testCksumArrayFree(CksumDb *p){
|
||||
free(p->azCksum);
|
||||
memset(p, 0x55, sizeof(*p));
|
||||
free(p);
|
||||
}
|
||||
|
||||
/* End of CksumDb code.
|
||||
**************************************************************************/
|
||||
|
||||
/*
|
||||
** Test utility function. Write key-value pair $i from datasource pData
|
||||
** into database pDb.
|
||||
*/
|
||||
void testWriteDatasource(TestDb *pDb, Datasource *pData, int i, int *pRc){
|
||||
void *pKey; int nKey;
|
||||
void *pVal; int nVal;
|
||||
testDatasourceEntry(pData, i, &pKey, &nKey, &pVal, &nVal);
|
||||
testWrite(pDb, pKey, nKey, pVal, nVal, pRc);
|
||||
}
|
||||
|
||||
/*
|
||||
** Test utility function. Delete datasource pData key $i from database pDb.
|
||||
*/
|
||||
void testDeleteDatasource(TestDb *pDb, Datasource *pData, int i, int *pRc){
|
||||
void *pKey; int nKey;
|
||||
testDatasourceEntry(pData, i, &pKey, &nKey, 0, 0);
|
||||
testDelete(pDb, pKey, nKey, pRc);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function inserts nWrite key/value pairs into database pDb - the
|
||||
** nWrite key value pairs starting at iFirst from data source pData.
|
||||
*/
|
||||
void testWriteDatasourceRange(
|
||||
TestDb *pDb, /* Database to write to */
|
||||
Datasource *pData, /* Data source to read values from */
|
||||
int iFirst, /* Index of first key/value pair */
|
||||
int nWrite, /* Number of key/value pairs to write */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
int i;
|
||||
for(i=0; i<nWrite; i++){
|
||||
testWriteDatasource(pDb, pData, iFirst+i, pRc);
|
||||
}
|
||||
}
|
||||
|
||||
void testDeleteDatasourceRange(
|
||||
TestDb *pDb, /* Database to write to */
|
||||
Datasource *pData, /* Data source to read keys from */
|
||||
int iFirst, /* Index of first key */
|
||||
int nWrite, /* Number of keys to delete */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
int i;
|
||||
for(i=0; i<nWrite; i++){
|
||||
testDeleteDatasource(pDb, pData, iFirst+i, pRc);
|
||||
}
|
||||
}
|
||||
|
||||
static char *getName(const char *zSystem){
|
||||
char *zRet;
|
||||
zRet = testMallocPrintf("rollback.%s", zSystem);
|
||||
return zRet;
|
||||
}
|
||||
|
||||
static int rollback_test_1(
|
||||
const char *zSystem,
|
||||
Datasource *pData
|
||||
){
|
||||
const int nRepeat = 100;
|
||||
|
||||
TestDb *pDb;
|
||||
int rc;
|
||||
int i;
|
||||
CksumDb *pCksum;
|
||||
char *zName;
|
||||
|
||||
zName = getName(zSystem);
|
||||
testCaseStart(&rc, zName);
|
||||
testFree(zName);
|
||||
|
||||
pCksum = testCksumArrayNew(pData, 0, nRepeat*100, 100);
|
||||
pDb = 0;
|
||||
rc = tdb_open(zSystem, 0, 1, &pDb);
|
||||
if( pDb && tdb_transaction_support(pDb)==0 ){
|
||||
testCaseSkip();
|
||||
goto skip_rollback_test;
|
||||
}
|
||||
|
||||
for(i=0; i<nRepeat && rc==0; i++){
|
||||
char zCksum[TEST_CKSUM_BYTES];
|
||||
int nCurrent = (((i+1)/2) * 100);
|
||||
int nDbRow;
|
||||
int iTrans;
|
||||
|
||||
/* Check that the database is the expected size. */
|
||||
nDbRow = testCountDatabase(pDb);
|
||||
testCompareInt(nCurrent, nDbRow, &rc);
|
||||
|
||||
for(iTrans=2; iTrans<=6 && rc==0; iTrans++){
|
||||
tdb_begin(pDb, iTrans);
|
||||
testWriteDatasourceRange(pDb, pData, nCurrent, 100, &rc);
|
||||
nCurrent += 100;
|
||||
}
|
||||
|
||||
testCksumDatabase(pDb, zCksum);
|
||||
testCompareStr(zCksum, testCksumArrayGet(pCksum, nCurrent), &rc);
|
||||
|
||||
for(iTrans=6; iTrans>2 && rc==0; iTrans--){
|
||||
tdb_rollback(pDb, iTrans);
|
||||
nCurrent -= 100;
|
||||
testCksumDatabase(pDb, zCksum);
|
||||
testCompareStr(zCksum, testCksumArrayGet(pCksum, nCurrent), &rc);
|
||||
}
|
||||
|
||||
if( i%2 ){
|
||||
tdb_rollback(pDb, 0);
|
||||
nCurrent -= 100;
|
||||
testCksumDatabase(pDb, zCksum);
|
||||
testCompareStr(zCksum, testCksumArrayGet(pCksum, nCurrent), &rc);
|
||||
}else{
|
||||
tdb_commit(pDb, 0);
|
||||
}
|
||||
}
|
||||
testCaseFinish(rc);
|
||||
|
||||
skip_rollback_test:
|
||||
tdb_close(pDb);
|
||||
testCksumArrayFree(pCksum);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void test_rollback(
|
||||
const char *zSystem,
|
||||
const char *zPattern,
|
||||
int *pRc
|
||||
){
|
||||
if( *pRc==0 ){
|
||||
int bRun = 1;
|
||||
|
||||
if( zPattern ){
|
||||
char *zName = getName(zSystem);
|
||||
bRun = testGlobMatch(zPattern, zName);
|
||||
testFree(zName);
|
||||
}
|
||||
|
||||
if( bRun ){
|
||||
DatasourceDefn defn = { TEST_DATASOURCE_RANDOM, 10, 15, 50, 100 };
|
||||
Datasource *pData = testDatasourceNew(&defn);
|
||||
*pRc = rollback_test_1(zSystem, pData);
|
||||
testDatasourceFree(pData);
|
||||
}
|
||||
}
|
||||
}
|
127
ext/lsm1/lsm-test/lsmtest4.c
Normal file
127
ext/lsm1/lsm-test/lsmtest4.c
Normal file
@ -0,0 +1,127 @@
|
||||
|
||||
/*
|
||||
** This file contains test cases involving multiple database clients.
|
||||
*/
|
||||
|
||||
#include "lsmtest.h"
|
||||
|
||||
/*
|
||||
** The following code implements test cases "mc1.*".
|
||||
**
|
||||
** This test case uses one writer and $nReader readers. All connections
|
||||
** are driven by a single thread. All connections are opened at the start
|
||||
** of the test and remain open until the test is finished.
|
||||
**
|
||||
** The test consists of $nStep steps. Each step the following is performed:
|
||||
**
|
||||
** 1. The writer inserts $nWriteStep records into the db.
|
||||
**
|
||||
** 2. The writer checks that the contents of the db are as expected.
|
||||
**
|
||||
** 3. Each reader that currently has an open read transaction also checks
|
||||
** that the contents of the db are as expected (according to the snapshot
|
||||
** the read transaction is reading - see below).
|
||||
**
|
||||
** After step 1, reader 1 opens a read transaction. After step 2, reader
|
||||
** 2 opens a read transaction, and so on. At step ($nReader+1), reader 1
|
||||
** closes the current read transaction and opens a new one. And so on.
|
||||
** The result is that at step N (for N > $nReader), there exists a reader
|
||||
** with an open read transaction reading the snapshot committed following
|
||||
** steps (N-$nReader-1) to N.
|
||||
*/
|
||||
typedef struct Mctest Mctest;
|
||||
struct Mctest {
|
||||
DatasourceDefn defn; /* Datasource to use */
|
||||
int nStep; /* Total number of steps in test */
|
||||
int nWriteStep; /* Number of rows to insert each step */
|
||||
int nReader; /* Number of read connections */
|
||||
};
|
||||
static void do_mc_test(
|
||||
const char *zSystem, /* Database system to test */
|
||||
Mctest *pTest,
|
||||
int *pRc /* IN/OUT: return code */
|
||||
){
|
||||
const int nDomain = pTest->nStep * pTest->nWriteStep;
|
||||
Datasource *pData; /* Source of data */
|
||||
TestDb *pDb; /* First database connection (writer) */
|
||||
int iReader; /* Used to iterate through aReader */
|
||||
int iStep; /* Current step in test */
|
||||
int iDot = 0; /* Current step in test */
|
||||
|
||||
/* Array of reader connections */
|
||||
struct Reader {
|
||||
TestDb *pDb; /* Connection handle */
|
||||
int iLast; /* Current snapshot contains keys 0..iLast */
|
||||
} *aReader;
|
||||
|
||||
/* Create a data source */
|
||||
pData = testDatasourceNew(&pTest->defn);
|
||||
|
||||
/* Open the writer connection */
|
||||
pDb = testOpen(zSystem, 1, pRc);
|
||||
|
||||
/* Allocate aReader */
|
||||
aReader = (struct Reader *)testMalloc(sizeof(aReader[0]) * pTest->nReader);
|
||||
for(iReader=0; iReader<pTest->nReader; iReader++){
|
||||
aReader[iReader].pDb = testOpen(zSystem, 0, pRc);
|
||||
}
|
||||
|
||||
for(iStep=0; iStep<pTest->nStep; iStep++){
|
||||
int iLast;
|
||||
int iBegin; /* Start read trans using aReader[iBegin] */
|
||||
|
||||
/* Insert nWriteStep more records into the database */
|
||||
int iFirst = iStep*pTest->nWriteStep;
|
||||
testWriteDatasourceRange(pDb, pData, iFirst, pTest->nWriteStep, pRc);
|
||||
|
||||
/* Check that the db is Ok according to the writer */
|
||||
iLast = (iStep+1) * pTest->nWriteStep - 1;
|
||||
testDbContents(pDb, pData, nDomain, 0, iLast, iLast, 1, pRc);
|
||||
|
||||
/* Have reader (iStep % nReader) open a read transaction here. */
|
||||
iBegin = (iStep % pTest->nReader);
|
||||
if( iBegin<iStep ) tdb_commit(aReader[iBegin].pDb, 0);
|
||||
tdb_begin(aReader[iBegin].pDb, 1);
|
||||
aReader[iBegin].iLast = iLast;
|
||||
|
||||
/* Check that the db is Ok for each open reader */
|
||||
for(iReader=0; iReader<pTest->nReader && aReader[iReader].iLast; iReader++){
|
||||
iLast = aReader[iReader].iLast;
|
||||
testDbContents(
|
||||
aReader[iReader].pDb, pData, nDomain, 0, iLast, iLast, 1, pRc
|
||||
);
|
||||
}
|
||||
|
||||
/* Report progress */
|
||||
testCaseProgress(iStep, pTest->nStep, testCaseNDot(), &iDot);
|
||||
}
|
||||
|
||||
/* Close all readers */
|
||||
for(iReader=0; iReader<pTest->nReader; iReader++){
|
||||
testClose(&aReader[iReader].pDb);
|
||||
}
|
||||
testFree(aReader);
|
||||
|
||||
/* Close the writer-connection and free the datasource */
|
||||
testClose(&pDb);
|
||||
testDatasourceFree(pData);
|
||||
}
|
||||
|
||||
|
||||
void test_mc(
|
||||
const char *zSystem, /* Database system name */
|
||||
const char *zPattern, /* Run test cases that match this pattern */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
int i;
|
||||
Mctest aTest[] = {
|
||||
{ { TEST_DATASOURCE_RANDOM, 10,10, 100,100 }, 100, 10, 5 },
|
||||
};
|
||||
|
||||
for(i=0; i<ArraySize(aTest); i++){
|
||||
if( testCaseBegin(pRc, zPattern, "mc1.%s.%d", zSystem, i) ){
|
||||
do_mc_test(zSystem, &aTest[i], pRc);
|
||||
testCaseFinish(*pRc);
|
||||
}
|
||||
}
|
||||
}
|
633
ext/lsm1/lsm-test/lsmtest5.c
Normal file
633
ext/lsm1/lsm-test/lsmtest5.c
Normal file
@ -0,0 +1,633 @@
|
||||
|
||||
/*
|
||||
** This file is broken into three semi-autonomous parts:
|
||||
**
|
||||
** 1. The database functions.
|
||||
** 2. The thread wrappers.
|
||||
** 3. The implementation of the mt1.* tests.
|
||||
*/
|
||||
|
||||
/*************************************************************************
|
||||
** DATABASE CONTENTS:
|
||||
**
|
||||
** The database contains up to N key/value pairs, where N is some large
|
||||
** number (say 10,000,000). Keys are integer values between 0 and (N-1).
|
||||
** The value associated with each key is a pseudo-random blob of data.
|
||||
**
|
||||
** Key/value pair keys are encoded as the two bytes "k." followed by a
|
||||
** 10-digit decimal number. i.e. key 45 -> "k.0000000045".
|
||||
**
|
||||
** As well as the key/value pairs, the database also contains checksum
|
||||
** entries. The checksums form a hierarchy - for every F key/value
|
||||
** entries there is one level 1 checksum. And for each F level 1 checksums
|
||||
** there is one level 2 checksum. And so on.
|
||||
**
|
||||
** Checksum keys are encoded as the two byte "c." followed by the
|
||||
** checksum level, followed by a 10 digit decimal number containing
|
||||
** the value of the first key that contributes to the checksum value.
|
||||
** For example, assuming F==10, the level 1 checksum that spans keys
|
||||
** 10 to 19 is "c.1.0000000010".
|
||||
**
|
||||
** Clients may perform one of two operations on the database: a read
|
||||
** or a write.
|
||||
**
|
||||
** READ OPERATIONS:
|
||||
**
|
||||
** A read operation scans a range of F key/value pairs. It computes
|
||||
** the expected checksum and then compares the computed value to the
|
||||
** actual value stored in the level 1 checksum entry. It then scans
|
||||
** the group of F level 1 checksums, and compares the computed checksum
|
||||
** to the associated level 2 checksum value, and so on until the
|
||||
** highest level checksum value has been verified.
|
||||
**
|
||||
** If a checksum ever fails to match the expected value, the test
|
||||
** has failed.
|
||||
**
|
||||
** WRITE OPERATIONS:
|
||||
**
|
||||
** A write operation involves writing (possibly clobbering) a single
|
||||
** key/value pair. The associated level 1 checksum is then recalculated
|
||||
** updated. Then the level 2 checksum, and so on until the highest
|
||||
** level checksum has been modified.
|
||||
**
|
||||
** All updates occur inside a single transaction.
|
||||
**
|
||||
** INTERFACE:
|
||||
**
|
||||
** The interface used by test cases to read and write the db consists
|
||||
** of type DbParameters and the following functions:
|
||||
**
|
||||
** dbReadOperation()
|
||||
** dbWriteOperation()
|
||||
*/
|
||||
|
||||
#include "lsmtest.h"
|
||||
|
||||
typedef struct DbParameters DbParameters;
|
||||
struct DbParameters {
|
||||
int nFanout; /* Checksum fanout (F) */
|
||||
int nKey; /* Size of key space (N) */
|
||||
};
|
||||
|
||||
#define DB_KEY_BYTES (2+5+10+1)
|
||||
|
||||
/*
|
||||
** Argument aBuf[] must point to a buffer at least DB_KEY_BYTES in size.
|
||||
** This function populates the buffer with a nul-terminated key string
|
||||
** corresponding to key iKey.
|
||||
*/
|
||||
static void dbFormatKey(
|
||||
DbParameters *pParam,
|
||||
int iLevel,
|
||||
int iKey, /* Key value */
|
||||
char *aBuf /* Write key string here */
|
||||
){
|
||||
if( iLevel==0 ){
|
||||
snprintf(aBuf, DB_KEY_BYTES, "k.%.10d", iKey);
|
||||
}else{
|
||||
int f = 1;
|
||||
int i;
|
||||
for(i=0; i<iLevel; i++) f = f * pParam->nFanout;
|
||||
snprintf(aBuf, DB_KEY_BYTES, "c.%d.%.10d", iLevel, f*(iKey/f));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Argument aBuf[] must point to a buffer at least DB_KEY_BYTES in size.
|
||||
** This function populates the buffer with the string representation of
|
||||
** checksum value iVal.
|
||||
*/
|
||||
static void dbFormatCksumValue(u32 iVal, char *aBuf){
|
||||
snprintf(aBuf, DB_KEY_BYTES, "%.10u", iVal);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the highest level of checksum in the database described
|
||||
** by *pParam.
|
||||
*/
|
||||
static int dbMaxLevel(DbParameters *pParam){
|
||||
int iMax;
|
||||
int n = 1;
|
||||
for(iMax=0; n<pParam->nKey; iMax++){
|
||||
n = n * pParam->nFanout;
|
||||
}
|
||||
return iMax;
|
||||
}
|
||||
|
||||
static void dbCksum(
|
||||
void *pCtx, /* IN/OUT: Pointer to u32 containing cksum */
|
||||
void *pKey, int nKey, /* Database key. Unused. */
|
||||
void *pVal, int nVal /* Database value. Checksum this. */
|
||||
){
|
||||
u8 *aVal = (u8 *)pVal;
|
||||
u32 *pCksum = (u32 *)pCtx;
|
||||
u32 cksum = *pCksum;
|
||||
int i;
|
||||
|
||||
unused_parameter(pKey);
|
||||
unused_parameter(nKey);
|
||||
|
||||
for(i=0; i<nVal; i++){
|
||||
cksum += (cksum<<3) + (int)aVal[i];
|
||||
}
|
||||
|
||||
*pCksum = cksum;
|
||||
}
|
||||
|
||||
/*
|
||||
** Compute the value of the checksum stored on level iLevel that contains
|
||||
** data from key iKey by scanning the pParam->nFanout entries at level
|
||||
** iLevel-1.
|
||||
*/
|
||||
static u32 dbComputeCksum(
|
||||
DbParameters *pParam, /* Database parameters */
|
||||
TestDb *pDb, /* Database connection handle */
|
||||
int iLevel, /* Level of checksum to compute */
|
||||
int iKey, /* Compute checksum for this key */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
u32 cksum = 0;
|
||||
if( *pRc==0 ){
|
||||
int nFirst;
|
||||
int nLast;
|
||||
int iFirst = 0;
|
||||
int iLast = 0;
|
||||
int i;
|
||||
int f = 1;
|
||||
char zFirst[DB_KEY_BYTES];
|
||||
char zLast[DB_KEY_BYTES];
|
||||
|
||||
assert( iLevel>=1 );
|
||||
for(i=0; i<iLevel; i++) f = f * pParam->nFanout;
|
||||
|
||||
iFirst = f*(iKey/f);
|
||||
iLast = iFirst + f - 1;
|
||||
dbFormatKey(pParam, iLevel-1, iFirst, zFirst);
|
||||
dbFormatKey(pParam, iLevel-1, iLast, zLast);
|
||||
nFirst = strlen(zFirst);
|
||||
nLast = strlen(zLast);
|
||||
|
||||
*pRc = tdb_scan(pDb, (u32*)&cksum, 0, zFirst, nFirst, zLast, nLast,dbCksum);
|
||||
}
|
||||
|
||||
return cksum;
|
||||
}
|
||||
|
||||
static void dbReadOperation(
|
||||
DbParameters *pParam, /* Database parameters */
|
||||
TestDb *pDb, /* Database connection handle */
|
||||
void (*xDelay)(void *),
|
||||
void *pDelayCtx,
|
||||
int iKey, /* Key to read */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
const int iMax = dbMaxLevel(pParam);
|
||||
int i;
|
||||
|
||||
if( tdb_transaction_support(pDb) ) testBegin(pDb, 1, pRc);
|
||||
for(i=1; *pRc==0 && i<=iMax; i++){
|
||||
char zCksum[DB_KEY_BYTES];
|
||||
char zKey[DB_KEY_BYTES];
|
||||
u32 iCksum = 0;
|
||||
|
||||
iCksum = dbComputeCksum(pParam, pDb, i, iKey, pRc);
|
||||
if( iCksum ){
|
||||
if( xDelay && i==1 ) xDelay(pDelayCtx);
|
||||
dbFormatCksumValue(iCksum, zCksum);
|
||||
dbFormatKey(pParam, i, iKey, zKey);
|
||||
testFetchStr(pDb, zKey, zCksum, pRc);
|
||||
}
|
||||
}
|
||||
if( tdb_transaction_support(pDb) ) testCommit(pDb, 0, pRc);
|
||||
}
|
||||
|
||||
static int dbWriteOperation(
|
||||
DbParameters *pParam, /* Database parameters */
|
||||
TestDb *pDb, /* Database connection handle */
|
||||
int iKey, /* Key to write to */
|
||||
const char *zValue, /* Nul-terminated value to write */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
const int iMax = dbMaxLevel(pParam);
|
||||
char zKey[DB_KEY_BYTES];
|
||||
int i;
|
||||
int rc;
|
||||
|
||||
assert( iKey>=0 && iKey<pParam->nKey );
|
||||
dbFormatKey(pParam, 0, iKey, zKey);
|
||||
|
||||
/* Open a write transaction. This may fail - SQLITE4_BUSY */
|
||||
if( *pRc==0 && tdb_transaction_support(pDb) ){
|
||||
rc = tdb_begin(pDb, 2);
|
||||
if( rc==5 ) return 0;
|
||||
*pRc = rc;
|
||||
}
|
||||
|
||||
testWriteStr(pDb, zKey, zValue, pRc);
|
||||
for(i=1; i<=iMax; i++){
|
||||
char zCksum[DB_KEY_BYTES];
|
||||
u32 iCksum = 0;
|
||||
|
||||
iCksum = dbComputeCksum(pParam, pDb, i, iKey, pRc);
|
||||
dbFormatCksumValue(iCksum, zCksum);
|
||||
dbFormatKey(pParam, i, iKey, zKey);
|
||||
testWriteStr(pDb, zKey, zCksum, pRc);
|
||||
}
|
||||
if( tdb_transaction_support(pDb) ) testCommit(pDb, 0, pRc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
** The following block contains testXXX() functions that implement a
|
||||
** wrapper around the systems native multi-thread support. There are no
|
||||
** synchronization primitives - just functions to launch and join
|
||||
** threads. Wrapper functions are:
|
||||
**
|
||||
** testThreadSupport()
|
||||
**
|
||||
** testThreadInit()
|
||||
** testThreadShutdown()
|
||||
** testThreadLaunch()
|
||||
** testThreadWait()
|
||||
**
|
||||
** testThreadSetHalt()
|
||||
** testThreadGetHalt()
|
||||
** testThreadSetResult()
|
||||
** testThreadGetResult()
|
||||
**
|
||||
** testThreadEnterMutex()
|
||||
** testThreadLeaveMutex()
|
||||
*/
|
||||
typedef struct ThreadSet ThreadSet;
|
||||
#ifdef LSM_MUTEX_PTHREADS
|
||||
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct Thread Thread;
|
||||
struct Thread {
|
||||
int rc;
|
||||
char *zMsg;
|
||||
pthread_t id;
|
||||
void (*xMain)(ThreadSet *, int, void *);
|
||||
void *pCtx;
|
||||
ThreadSet *pThreadSet;
|
||||
};
|
||||
|
||||
struct ThreadSet {
|
||||
int bHalt; /* Halt flag */
|
||||
int nThread; /* Number of threads */
|
||||
Thread *aThread; /* Array of Thread structures */
|
||||
pthread_mutex_t mutex; /* Mutex used for cheating */
|
||||
};
|
||||
|
||||
/*
|
||||
** Return true if this build supports threads, or false otherwise. If
|
||||
** this function returns false, no other testThreadXXX() functions should
|
||||
** be called.
|
||||
*/
|
||||
static int testThreadSupport(){ return 1; }
|
||||
|
||||
/*
|
||||
** Allocate and return a thread-set handle with enough space allocated
|
||||
** to handle up to nMax threads. Each call to this function should be
|
||||
** matched by a call to testThreadShutdown() to delete the object.
|
||||
*/
|
||||
static ThreadSet *testThreadInit(int nMax){
|
||||
int nByte; /* Total space to allocate */
|
||||
ThreadSet *p; /* Return value */
|
||||
|
||||
nByte = sizeof(ThreadSet) + sizeof(struct Thread) * nMax;
|
||||
p = (ThreadSet *)testMalloc(nByte);
|
||||
p->nThread = nMax;
|
||||
p->aThread = (Thread *)&p[1];
|
||||
pthread_mutex_init(&p->mutex, 0);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
** Delete a thread-set object and release all resources held by it.
|
||||
*/
|
||||
static void testThreadShutdown(ThreadSet *p){
|
||||
int i;
|
||||
for(i=0; i<p->nThread; i++){
|
||||
testFree(p->aThread[i].zMsg);
|
||||
}
|
||||
pthread_mutex_destroy(&p->mutex);
|
||||
testFree(p);
|
||||
}
|
||||
|
||||
static void *ttMain(void *pArg){
|
||||
Thread *pThread = (Thread *)pArg;
|
||||
int iThread;
|
||||
iThread = (pThread - pThread->pThreadSet->aThread);
|
||||
pThread->xMain(pThread->pThreadSet, iThread, pThread->pCtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Launch a new thread.
|
||||
*/
|
||||
static int testThreadLaunch(
|
||||
ThreadSet *p,
|
||||
int iThread,
|
||||
void (*xMain)(ThreadSet *, int, void *),
|
||||
void *pCtx
|
||||
){
|
||||
int rc;
|
||||
Thread *pThread;
|
||||
|
||||
assert( iThread>=0 && iThread<p->nThread );
|
||||
|
||||
pThread = &p->aThread[iThread];
|
||||
assert( pThread->pThreadSet==0 );
|
||||
pThread->xMain = xMain;
|
||||
pThread->pCtx = pCtx;
|
||||
pThread->pThreadSet = p;
|
||||
rc = pthread_create(&pThread->id, 0, ttMain, (void *)pThread);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Set the thread-set "halt" flag.
|
||||
*/
|
||||
static void testThreadSetHalt(ThreadSet *pThreadSet){
|
||||
pThreadSet->bHalt = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the current value of the thread-set "halt" flag.
|
||||
*/
|
||||
static int testThreadGetHalt(ThreadSet *pThreadSet){
|
||||
return pThreadSet->bHalt;
|
||||
}
|
||||
|
||||
static void testThreadSleep(ThreadSet *pThreadSet, int nMs){
|
||||
int nRem = nMs;
|
||||
while( nRem>0 && testThreadGetHalt(pThreadSet)==0 ){
|
||||
usleep(50000);
|
||||
nRem -= 50;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Wait for all threads launched to finish before returning. If nMs
|
||||
** is greater than zero, set the "halt" flag to tell all threads
|
||||
** to halt after waiting nMs milliseconds.
|
||||
*/
|
||||
static void testThreadWait(ThreadSet *pThreadSet, int nMs){
|
||||
int i;
|
||||
|
||||
testThreadSleep(pThreadSet, nMs);
|
||||
testThreadSetHalt(pThreadSet);
|
||||
for(i=0; i<pThreadSet->nThread; i++){
|
||||
Thread *pThread = &pThreadSet->aThread[i];
|
||||
if( pThread->xMain ){
|
||||
pthread_join(pThread->id, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Set the result for thread iThread.
|
||||
*/
|
||||
static void testThreadSetResult(
|
||||
ThreadSet *pThreadSet, /* Thread-set handle */
|
||||
int iThread, /* Set result for this thread */
|
||||
int rc, /* Result error code */
|
||||
char *zFmt, /* Result string format */
|
||||
... /* Result string formatting args... */
|
||||
){
|
||||
va_list ap;
|
||||
|
||||
testFree(pThreadSet->aThread[iThread].zMsg);
|
||||
pThreadSet->aThread[iThread].rc = rc;
|
||||
pThreadSet->aThread[iThread].zMsg = 0;
|
||||
if( zFmt ){
|
||||
va_start(ap, zFmt);
|
||||
pThreadSet->aThread[iThread].zMsg = testMallocVPrintf(zFmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Retrieve the result for thread iThread.
|
||||
*/
|
||||
static int testThreadGetResult(
|
||||
ThreadSet *pThreadSet, /* Thread-set handle */
|
||||
int iThread, /* Get result for this thread */
|
||||
const char **pzRes /* OUT: Pointer to result string */
|
||||
){
|
||||
if( pzRes ) *pzRes = pThreadSet->aThread[iThread].zMsg;
|
||||
return pThreadSet->aThread[iThread].rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Enter and leave the test case mutex.
|
||||
*/
|
||||
#if 0
|
||||
static void testThreadEnterMutex(ThreadSet *p){
|
||||
pthread_mutex_lock(&p->mutex);
|
||||
}
|
||||
static void testThreadLeaveMutex(ThreadSet *p){
|
||||
pthread_mutex_unlock(&p->mutex);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(LSM_MUTEX_PTHREADS)
|
||||
static int testThreadSupport(){ return 0; }
|
||||
|
||||
#define testThreadInit(a) 0
|
||||
#define testThreadShutdown(a)
|
||||
#define testThreadLaunch(a,b,c,d) 0
|
||||
#define testThreadWait(a,b)
|
||||
#define testThreadSetHalt(a)
|
||||
#define testThreadGetHalt(a) 0
|
||||
#define testThreadGetResult(a,b,c) 0
|
||||
#define testThreadSleep(a,b) 0
|
||||
|
||||
static void testThreadSetResult(ThreadSet *a, int b, int c, char *d, ...){
|
||||
unused_parameter(a);
|
||||
unused_parameter(b);
|
||||
unused_parameter(c);
|
||||
unused_parameter(d);
|
||||
}
|
||||
#endif
|
||||
/* End of threads wrapper.
|
||||
*************************************************************************/
|
||||
|
||||
/*************************************************************************
|
||||
** Below this point is the third part of this file - the implementation
|
||||
** of the mt1.* tests.
|
||||
*/
|
||||
typedef struct Mt1Test Mt1Test;
|
||||
struct Mt1Test {
|
||||
DbParameters param; /* Description of database to read/write */
|
||||
int nReadwrite; /* Number of read/write threads */
|
||||
int nFastReader; /* Number of fast reader threads */
|
||||
int nSlowReader; /* Number of slow reader threads */
|
||||
int nMs; /* How long to run for */
|
||||
const char *zSystem; /* Database system to test */
|
||||
};
|
||||
|
||||
typedef struct Mt1DelayCtx Mt1DelayCtx;
|
||||
struct Mt1DelayCtx {
|
||||
ThreadSet *pSet; /* Threadset to sleep within */
|
||||
int nMs; /* Sleep in ms */
|
||||
};
|
||||
|
||||
static void xMt1Delay(void *pCtx){
|
||||
Mt1DelayCtx *p = (Mt1DelayCtx *)pCtx;
|
||||
testThreadSleep(p->pSet, p->nMs);
|
||||
}
|
||||
|
||||
#define MT1_THREAD_RDWR 0
|
||||
#define MT1_THREAD_SLOW 1
|
||||
#define MT1_THREAD_FAST 2
|
||||
|
||||
static void xMt1Work(lsm_db *pDb, void *pCtx){
|
||||
#if 0
|
||||
char *z = 0;
|
||||
lsm_info(pDb, LSM_INFO_DB_STRUCTURE, &z);
|
||||
printf("%s\n", z);
|
||||
fflush(stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
** This is the main() proc for all threads in test case "mt1".
|
||||
*/
|
||||
static void mt1Main(ThreadSet *pThreadSet, int iThread, void *pCtx){
|
||||
Mt1Test *p = (Mt1Test *)pCtx; /* Test parameters */
|
||||
Mt1DelayCtx delay;
|
||||
int nRead = 0; /* Number of calls to dbReadOperation() */
|
||||
int nWrite = 0; /* Number of completed database writes */
|
||||
int rc = 0; /* Error code */
|
||||
int iPrng; /* Prng argument variable */
|
||||
TestDb *pDb; /* Database handle */
|
||||
int eType;
|
||||
|
||||
delay.pSet = pThreadSet;
|
||||
delay.nMs = 0;
|
||||
if( iThread<p->nReadwrite ){
|
||||
eType = MT1_THREAD_RDWR;
|
||||
}else if( iThread<(p->nReadwrite+p->nFastReader) ){
|
||||
eType = MT1_THREAD_FAST;
|
||||
}else{
|
||||
eType = MT1_THREAD_SLOW;
|
||||
delay.nMs = (p->nMs / 20);
|
||||
}
|
||||
|
||||
/* Open a new database connection. Initialize the pseudo-random number
|
||||
** argument based on the thread number. */
|
||||
iPrng = testPrngValue(iThread);
|
||||
pDb = testOpen(p->zSystem, 0, &rc);
|
||||
|
||||
if( rc==0 ){
|
||||
tdb_lsm_config_work_hook(pDb, xMt1Work, 0);
|
||||
}
|
||||
|
||||
/* Loop until either an error occurs or some other thread sets the
|
||||
** halt flag. */
|
||||
while( rc==0 && testThreadGetHalt(pThreadSet)==0 ){
|
||||
int iKey;
|
||||
|
||||
/* Perform a read operation on an arbitrarily selected key. */
|
||||
iKey = (testPrngValue(iPrng++) % p->param.nKey);
|
||||
dbReadOperation(&p->param, pDb, xMt1Delay, (void *)&delay, iKey, &rc);
|
||||
if( rc ) continue;
|
||||
nRead++;
|
||||
|
||||
/* Attempt to write an arbitrary key value pair (and update the associated
|
||||
** checksum entries). dbWriteOperation() returns 1 if the write is
|
||||
** successful, or 0 if it failed with an LSM_BUSY error. */
|
||||
if( eType==MT1_THREAD_RDWR ){
|
||||
char aValue[50];
|
||||
char aRnd[25];
|
||||
|
||||
iKey = (testPrngValue(iPrng++) % p->param.nKey);
|
||||
testPrngString(iPrng, aRnd, sizeof(aRnd));
|
||||
iPrng += sizeof(aRnd);
|
||||
snprintf(aValue, sizeof(aValue), "%d.%s", iThread, aRnd);
|
||||
nWrite += dbWriteOperation(&p->param, pDb, iKey, aValue, &rc);
|
||||
}
|
||||
}
|
||||
testClose(&pDb);
|
||||
|
||||
/* If an error has occured, set the thread error code and the threadset
|
||||
** halt flag to tell the other test threads to halt. Otherwise, set the
|
||||
** thread error code to 0 and post a message with the number of read
|
||||
** and write operations completed. */
|
||||
if( rc ){
|
||||
testThreadSetResult(pThreadSet, iThread, rc, 0);
|
||||
testThreadSetHalt(pThreadSet);
|
||||
}else{
|
||||
testThreadSetResult(pThreadSet, iThread, 0, "r/w: %d/%d", nRead, nWrite);
|
||||
}
|
||||
}
|
||||
|
||||
static void do_test_mt1(
|
||||
const char *zSystem, /* Database system name */
|
||||
const char *zPattern, /* Run test cases that match this pattern */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
Mt1Test aTest[] = {
|
||||
/* param, nReadwrite, nFastReader, nSlowReader, nMs, zSystem */
|
||||
{ {10, 1000}, 4, 0, 0, 10000, 0 },
|
||||
{ {10, 1000}, 4, 4, 2, 100000, 0 },
|
||||
{ {10, 100000}, 4, 0, 0, 10000, 0 },
|
||||
{ {10, 100000}, 4, 4, 2, 100000, 0 },
|
||||
};
|
||||
int i;
|
||||
|
||||
for(i=0; *pRc==0 && i<ArraySize(aTest); i++){
|
||||
Mt1Test *p = &aTest[i];
|
||||
int bRun = testCaseBegin(pRc, zPattern,
|
||||
"mt1.%s.db=%d,%d.ms=%d.rdwr=%d.fast=%d.slow=%d",
|
||||
zSystem, p->param.nFanout, p->param.nKey,
|
||||
p->nMs, p->nReadwrite, p->nFastReader, p->nSlowReader
|
||||
);
|
||||
if( bRun ){
|
||||
TestDb *pDb;
|
||||
ThreadSet *pSet;
|
||||
int iThread;
|
||||
int nThread;
|
||||
|
||||
p->zSystem = zSystem;
|
||||
pDb = testOpen(zSystem, 1, pRc);
|
||||
|
||||
nThread = p->nReadwrite + p->nFastReader + p->nSlowReader;
|
||||
pSet = testThreadInit(nThread);
|
||||
for(iThread=0; *pRc==0 && iThread<nThread; iThread++){
|
||||
testThreadLaunch(pSet, iThread, mt1Main, (void *)p);
|
||||
}
|
||||
|
||||
testThreadWait(pSet, p->nMs);
|
||||
for(iThread=0; *pRc==0 && iThread<nThread; iThread++){
|
||||
*pRc = testThreadGetResult(pSet, iThread, 0);
|
||||
}
|
||||
testCaseFinish(*pRc);
|
||||
|
||||
for(iThread=0; *pRc==0 && iThread<nThread; iThread++){
|
||||
const char *zMsg = 0;
|
||||
*pRc = testThreadGetResult(pSet, iThread, &zMsg);
|
||||
printf(" Info: thread %d (%d): %s\n", iThread, *pRc, zMsg);
|
||||
}
|
||||
|
||||
testThreadShutdown(pSet);
|
||||
testClose(&pDb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_mt(
|
||||
const char *zSystem, /* Database system name */
|
||||
const char *zPattern, /* Run test cases that match this pattern */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
if( testThreadSupport()==0 ) return;
|
||||
do_test_mt1(zSystem, zPattern, pRc);
|
||||
}
|
660
ext/lsm1/lsm-test/lsmtest6.c
Normal file
660
ext/lsm1/lsm-test/lsmtest6.c
Normal file
@ -0,0 +1,660 @@
|
||||
|
||||
#include "lsmtest.h"
|
||||
|
||||
typedef struct OomTest OomTest;
|
||||
struct OomTest {
|
||||
lsm_env *pEnv;
|
||||
int iNext; /* Next value to pass to testMallocOom() */
|
||||
int nFail; /* Number of OOM events injected */
|
||||
int bEnable;
|
||||
int rc; /* Test case error code */
|
||||
};
|
||||
|
||||
static void testOomStart(OomTest *p){
|
||||
memset(p, 0, sizeof(OomTest));
|
||||
p->iNext = 1;
|
||||
p->bEnable = 1;
|
||||
p->nFail = 1;
|
||||
p->pEnv = tdb_lsm_env();
|
||||
}
|
||||
|
||||
static void xOomHook(OomTest *p){
|
||||
p->nFail++;
|
||||
}
|
||||
|
||||
static int testOomContinue(OomTest *p){
|
||||
if( p->rc!=0 || (p->iNext>1 && p->nFail==0) ){
|
||||
return 0;
|
||||
}
|
||||
p->nFail = 0;
|
||||
testMallocOom(p->pEnv, p->iNext, 0, (void (*)(void*))xOomHook, (void *)p);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void testOomEnable(OomTest *p, int bEnable){
|
||||
p->bEnable = bEnable;
|
||||
testMallocOomEnable(p->pEnv, bEnable);
|
||||
}
|
||||
|
||||
static void testOomNext(OomTest *p){
|
||||
p->iNext++;
|
||||
}
|
||||
|
||||
static int testOomHit(OomTest *p){
|
||||
return (p->nFail>0);
|
||||
}
|
||||
|
||||
static int testOomFinish(OomTest *p){
|
||||
return p->rc;
|
||||
}
|
||||
|
||||
static void testOomAssert(OomTest *p, int bVal){
|
||||
if( bVal==0 ){
|
||||
test_failed();
|
||||
p->rc = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Test that the error code matches the state of the OomTest object passed
|
||||
** as the first argument. Specifically, check that rc is LSM_NOMEM if an
|
||||
** OOM error has already been injected, or LSM_OK if not.
|
||||
*/
|
||||
static void testOomAssertRc(OomTest *p, int rc){
|
||||
testOomAssert(p, rc==LSM_OK || rc==LSM_NOMEM);
|
||||
testOomAssert(p, testOomHit(p)==(rc==LSM_NOMEM) || p->bEnable==0 );
|
||||
}
|
||||
|
||||
static void testOomOpen(
|
||||
OomTest *pOom,
|
||||
const char *zName,
|
||||
lsm_db **ppDb,
|
||||
int *pRc
|
||||
){
|
||||
if( *pRc==LSM_OK ){
|
||||
int rc;
|
||||
rc = lsm_new(tdb_lsm_env(), ppDb);
|
||||
if( rc==LSM_OK ) rc = lsm_open(*ppDb, zName);
|
||||
testOomAssertRc(pOom, rc);
|
||||
*pRc = rc;
|
||||
}
|
||||
}
|
||||
|
||||
static void testOomFetch(
|
||||
OomTest *pOom,
|
||||
lsm_db *pDb,
|
||||
void *pKey, int nKey,
|
||||
void *pVal, int nVal,
|
||||
int *pRc
|
||||
){
|
||||
testOomAssertRc(pOom, *pRc);
|
||||
if( *pRc==LSM_OK ){
|
||||
lsm_cursor *pCsr;
|
||||
int rc;
|
||||
|
||||
rc = lsm_csr_open(pDb, &pCsr);
|
||||
if( rc==LSM_OK ) rc = lsm_csr_seek(pCsr, pKey, nKey, 0);
|
||||
testOomAssertRc(pOom, rc);
|
||||
|
||||
if( rc==LSM_OK ){
|
||||
const void *p; int n;
|
||||
testOomAssert(pOom, lsm_csr_valid(pCsr));
|
||||
|
||||
rc = lsm_csr_key(pCsr, &p, &n);
|
||||
testOomAssertRc(pOom, rc);
|
||||
testOomAssert(pOom, rc!=LSM_OK || (n==nKey && memcmp(pKey, p, nKey)==0) );
|
||||
}
|
||||
|
||||
if( rc==LSM_OK ){
|
||||
const void *p; int n;
|
||||
testOomAssert(pOom, lsm_csr_valid(pCsr));
|
||||
|
||||
rc = lsm_csr_value(pCsr, &p, &n);
|
||||
testOomAssertRc(pOom, rc);
|
||||
testOomAssert(pOom, rc!=LSM_OK || (n==nVal && memcmp(pVal, p, nVal)==0) );
|
||||
}
|
||||
|
||||
lsm_csr_close(pCsr);
|
||||
*pRc = rc;
|
||||
}
|
||||
}
|
||||
|
||||
static void testOomWrite(
|
||||
OomTest *pOom,
|
||||
lsm_db *pDb,
|
||||
void *pKey, int nKey,
|
||||
void *pVal, int nVal,
|
||||
int *pRc
|
||||
){
|
||||
testOomAssertRc(pOom, *pRc);
|
||||
if( *pRc==LSM_OK ){
|
||||
int rc;
|
||||
|
||||
rc = lsm_insert(pDb, pKey, nKey, pVal, nVal);
|
||||
testOomAssertRc(pOom, rc);
|
||||
|
||||
*pRc = rc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void testOomFetchStr(
|
||||
OomTest *pOom,
|
||||
lsm_db *pDb,
|
||||
const char *zKey,
|
||||
const char *zVal,
|
||||
int *pRc
|
||||
){
|
||||
int nKey = strlen(zKey);
|
||||
int nVal = strlen(zVal);
|
||||
testOomFetch(pOom, pDb, (void *)zKey, nKey, (void *)zVal, nVal, pRc);
|
||||
}
|
||||
|
||||
static void testOomFetchData(
|
||||
OomTest *pOom,
|
||||
lsm_db *pDb,
|
||||
Datasource *pData,
|
||||
int iKey,
|
||||
int *pRc
|
||||
){
|
||||
void *pKey; int nKey;
|
||||
void *pVal; int nVal;
|
||||
testDatasourceEntry(pData, iKey, &pKey, &nKey, &pVal, &nVal);
|
||||
testOomFetch(pOom, pDb, pKey, nKey, pVal, nVal, pRc);
|
||||
}
|
||||
|
||||
static void testOomWriteStr(
|
||||
OomTest *pOom,
|
||||
lsm_db *pDb,
|
||||
const char *zKey,
|
||||
const char *zVal,
|
||||
int *pRc
|
||||
){
|
||||
int nKey = strlen(zKey);
|
||||
int nVal = strlen(zVal);
|
||||
testOomWrite(pOom, pDb, (void *)zKey, nKey, (void *)zVal, nVal, pRc);
|
||||
}
|
||||
|
||||
static void testOomWriteData(
|
||||
OomTest *pOom,
|
||||
lsm_db *pDb,
|
||||
Datasource *pData,
|
||||
int iKey,
|
||||
int *pRc
|
||||
){
|
||||
void *pKey; int nKey;
|
||||
void *pVal; int nVal;
|
||||
testDatasourceEntry(pData, iKey, &pKey, &nKey, &pVal, &nVal);
|
||||
testOomWrite(pOom, pDb, pKey, nKey, pVal, nVal, pRc);
|
||||
}
|
||||
|
||||
static void testOomScan(
|
||||
OomTest *pOom,
|
||||
lsm_db *pDb,
|
||||
int bReverse,
|
||||
const void *pKey, int nKey,
|
||||
int nScan,
|
||||
int *pRc
|
||||
){
|
||||
if( *pRc==0 ){
|
||||
int rc;
|
||||
int iScan = 0;
|
||||
lsm_cursor *pCsr;
|
||||
int (*xAdvance)(lsm_cursor *);
|
||||
|
||||
|
||||
rc = lsm_csr_open(pDb, &pCsr);
|
||||
testOomAssertRc(pOom, rc);
|
||||
|
||||
if( rc==LSM_OK ){
|
||||
if( bReverse ){
|
||||
rc = lsm_csr_seek(pCsr, pKey, nKey, LSM_SEEK_LE);
|
||||
xAdvance = lsm_csr_prev;
|
||||
}else{
|
||||
rc = lsm_csr_seek(pCsr, pKey, nKey, LSM_SEEK_GE);
|
||||
xAdvance = lsm_csr_next;
|
||||
}
|
||||
}
|
||||
testOomAssertRc(pOom, rc);
|
||||
|
||||
while( rc==LSM_OK && lsm_csr_valid(pCsr) && iScan<nScan ){
|
||||
const void *p; int n;
|
||||
|
||||
rc = lsm_csr_key(pCsr, &p, &n);
|
||||
testOomAssertRc(pOom, rc);
|
||||
if( rc==LSM_OK ){
|
||||
rc = lsm_csr_value(pCsr, &p, &n);
|
||||
testOomAssertRc(pOom, rc);
|
||||
}
|
||||
if( rc==LSM_OK ){
|
||||
rc = xAdvance(pCsr);
|
||||
testOomAssertRc(pOom, rc);
|
||||
}
|
||||
iScan++;
|
||||
}
|
||||
|
||||
lsm_csr_close(pCsr);
|
||||
*pRc = rc;
|
||||
}
|
||||
}
|
||||
|
||||
#define LSMTEST6_TESTDB "testdb.lsm"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
void testDeleteLsmdb(const char *zFile){
|
||||
char *zLog = testMallocPrintf("%s-log", zFile);
|
||||
char *zShm = testMallocPrintf("%s-shm", zFile);
|
||||
unlink(zFile);
|
||||
unlink(zLog);
|
||||
unlink(zShm);
|
||||
testFree(zLog);
|
||||
testFree(zShm);
|
||||
}
|
||||
|
||||
static void copy_file(const char *zFrom, const char *zTo){
|
||||
|
||||
if( access(zFrom, F_OK) ){
|
||||
unlink(zTo);
|
||||
}else{
|
||||
int fd1;
|
||||
int fd2;
|
||||
off_t sz;
|
||||
off_t i;
|
||||
struct stat buf;
|
||||
u8 *aBuf;
|
||||
|
||||
fd1 = open(zFrom, O_RDONLY, 0644);
|
||||
fd2 = open(zTo, O_RDWR | O_CREAT, 0644);
|
||||
|
||||
fstat(fd1, &buf);
|
||||
sz = buf.st_size;
|
||||
ftruncate(fd2, sz);
|
||||
|
||||
aBuf = testMalloc(4096);
|
||||
for(i=0; i<sz; i+=4096){
|
||||
int nByte = MIN(4096, sz - i);
|
||||
read(fd1, aBuf, nByte);
|
||||
write(fd2, aBuf, nByte);
|
||||
}
|
||||
testFree(aBuf);
|
||||
|
||||
close(fd1);
|
||||
close(fd2);
|
||||
}
|
||||
}
|
||||
|
||||
void testCopyLsmdb(const char *zFrom, const char *zTo){
|
||||
char *zLog1 = testMallocPrintf("%s-log", zFrom);
|
||||
char *zLog2 = testMallocPrintf("%s-log", zTo);
|
||||
char *zShm1 = testMallocPrintf("%s-shm", zFrom);
|
||||
char *zShm2 = testMallocPrintf("%s-shm", zTo);
|
||||
|
||||
unlink(zShm2);
|
||||
unlink(zLog2);
|
||||
unlink(zTo);
|
||||
copy_file(zFrom, zTo);
|
||||
copy_file(zLog1, zLog2);
|
||||
copy_file(zShm1, zShm2);
|
||||
|
||||
testFree(zLog1); testFree(zLog2); testFree(zShm1); testFree(zShm2);
|
||||
}
|
||||
|
||||
/*
|
||||
** File zFile is the path to a database. This function makes backups
|
||||
** of the database file and its log as follows:
|
||||
**
|
||||
** cp $(zFile) $(zFile)-save
|
||||
** cp $(zFile)-$(zAux) $(zFile)-save-$(zAux)
|
||||
**
|
||||
** Function testRestoreDb() can be used to copy the files back in the
|
||||
** other direction.
|
||||
*/
|
||||
void testSaveDb(const char *zFile, const char *zAux){
|
||||
char *zLog = testMallocPrintf("%s-%s", zFile, zAux);
|
||||
char *zFileSave = testMallocPrintf("%s-save", zFile);
|
||||
char *zLogSave = testMallocPrintf("%s-%s-save", zFile, zAux);
|
||||
|
||||
unlink(zFileSave);
|
||||
unlink(zLogSave);
|
||||
copy_file(zFile, zFileSave);
|
||||
copy_file(zLog, zLogSave);
|
||||
|
||||
testFree(zLog); testFree(zFileSave); testFree(zLogSave);
|
||||
}
|
||||
|
||||
/*
|
||||
** File zFile is the path to a database. This function restores
|
||||
** a backup of the database made by a previous call to testSaveDb().
|
||||
** Specifically, it does the equivalent of:
|
||||
**
|
||||
** cp $(zFile)-save $(zFile)
|
||||
** cp $(zFile)-save-$(zAux) $(zFile)-$(zAux)
|
||||
*/
|
||||
void testRestoreDb(const char *zFile, const char *zAux){
|
||||
char *zLog = testMallocPrintf("%s-%s", zFile, zAux);
|
||||
char *zFileSave = testMallocPrintf("%s-save", zFile);
|
||||
char *zLogSave = testMallocPrintf("%s-%s-save", zFile, zAux);
|
||||
|
||||
copy_file(zFileSave, zFile);
|
||||
copy_file(zLogSave, zLog);
|
||||
|
||||
testFree(zLog); testFree(zFileSave); testFree(zLogSave);
|
||||
}
|
||||
|
||||
|
||||
static int lsmWriteStr(lsm_db *pDb, const char *zKey, const char *zVal){
|
||||
int nKey = strlen(zKey);
|
||||
int nVal = strlen(zVal);
|
||||
return lsm_insert(pDb, (void *)zKey, nKey, (void *)zVal, nVal);
|
||||
}
|
||||
|
||||
static void setup_delete_db(){
|
||||
testDeleteLsmdb(LSMTEST6_TESTDB);
|
||||
}
|
||||
|
||||
/*
|
||||
** Create a small database. With the following content:
|
||||
**
|
||||
** "one" -> "one"
|
||||
** "two" -> "four"
|
||||
** "three" -> "nine"
|
||||
** "four" -> "sixteen"
|
||||
** "five" -> "twentyfive"
|
||||
** "six" -> "thirtysix"
|
||||
** "seven" -> "fourtynine"
|
||||
** "eight" -> "sixtyfour"
|
||||
*/
|
||||
static void setup_populate_db(){
|
||||
const char *azStr[] = {
|
||||
"one", "one",
|
||||
"two", "four",
|
||||
"three", "nine",
|
||||
"four", "sixteen",
|
||||
"five", "twentyfive",
|
||||
"six", "thirtysix",
|
||||
"seven", "fourtynine",
|
||||
"eight", "sixtyfour",
|
||||
};
|
||||
int rc;
|
||||
int ii;
|
||||
lsm_db *pDb;
|
||||
|
||||
testDeleteLsmdb(LSMTEST6_TESTDB);
|
||||
|
||||
rc = lsm_new(tdb_lsm_env(), &pDb);
|
||||
if( rc==LSM_OK ) rc = lsm_open(pDb, LSMTEST6_TESTDB);
|
||||
|
||||
for(ii=0; rc==LSM_OK && ii<ArraySize(azStr); ii+=2){
|
||||
rc = lsmWriteStr(pDb, azStr[ii], azStr[ii+1]);
|
||||
}
|
||||
lsm_close(pDb);
|
||||
|
||||
testSaveDb(LSMTEST6_TESTDB, "log");
|
||||
assert( rc==LSM_OK );
|
||||
}
|
||||
|
||||
static Datasource *getDatasource(void){
|
||||
const DatasourceDefn defn = { TEST_DATASOURCE_RANDOM, 10, 15, 200, 250 };
|
||||
return testDatasourceNew(&defn);
|
||||
}
|
||||
|
||||
/*
|
||||
** Set up a database file with the following properties:
|
||||
**
|
||||
** * Page size is 1024 bytes.
|
||||
** * Block size is 64 KB.
|
||||
** * Contains 5000 key-value pairs starting at 0 from the
|
||||
** datasource returned getDatasource().
|
||||
*/
|
||||
static void setup_populate_db2(){
|
||||
Datasource *pData;
|
||||
int ii;
|
||||
int rc;
|
||||
int nBlocksize = 64*1024;
|
||||
int nPagesize = 1024;
|
||||
int nWritebuffer = 4*1024;
|
||||
lsm_db *pDb;
|
||||
|
||||
testDeleteLsmdb(LSMTEST6_TESTDB);
|
||||
rc = lsm_new(tdb_lsm_env(), &pDb);
|
||||
if( rc==LSM_OK ) rc = lsm_open(pDb, LSMTEST6_TESTDB);
|
||||
|
||||
lsm_config(pDb, LSM_CONFIG_BLOCK_SIZE, &nBlocksize);
|
||||
lsm_config(pDb, LSM_CONFIG_PAGE_SIZE, &nPagesize);
|
||||
lsm_config(pDb, LSM_CONFIG_AUTOFLUSH, &nWritebuffer);
|
||||
|
||||
pData = getDatasource();
|
||||
for(ii=0; rc==LSM_OK && ii<5000; ii++){
|
||||
void *pKey; int nKey;
|
||||
void *pVal; int nVal;
|
||||
testDatasourceEntry(pData, ii, &pKey, &nKey, &pVal, &nVal);
|
||||
lsm_insert(pDb, pKey, nKey, pVal, nVal);
|
||||
}
|
||||
testDatasourceFree(pData);
|
||||
lsm_close(pDb);
|
||||
|
||||
testSaveDb(LSMTEST6_TESTDB, "log");
|
||||
assert( rc==LSM_OK );
|
||||
}
|
||||
|
||||
/*
|
||||
** Test the results of OOM conditions in lsm_new().
|
||||
*/
|
||||
static void simple_oom_1(OomTest *pOom){
|
||||
int rc;
|
||||
lsm_db *pDb;
|
||||
|
||||
rc = lsm_new(tdb_lsm_env(), &pDb);
|
||||
testOomAssertRc(pOom, rc);
|
||||
|
||||
lsm_close(pDb);
|
||||
}
|
||||
|
||||
/*
|
||||
** Test the results of OOM conditions in lsm_open().
|
||||
*/
|
||||
static void simple_oom_2(OomTest *pOom){
|
||||
int rc;
|
||||
lsm_db *pDb;
|
||||
|
||||
rc = lsm_new(tdb_lsm_env(), &pDb);
|
||||
if( rc==LSM_OK ){
|
||||
rc = lsm_open(pDb, "testdb.lsm");
|
||||
}
|
||||
testOomAssertRc(pOom, rc);
|
||||
|
||||
lsm_close(pDb);
|
||||
}
|
||||
|
||||
/*
|
||||
** Test the results of OOM conditions in simple fetch operations.
|
||||
*/
|
||||
static void simple_oom_3(OomTest *pOom){
|
||||
int rc = LSM_OK;
|
||||
lsm_db *pDb;
|
||||
|
||||
testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc);
|
||||
|
||||
testOomFetchStr(pOom, pDb, "four", "sixteen", &rc);
|
||||
testOomFetchStr(pOom, pDb, "seven", "fourtynine", &rc);
|
||||
testOomFetchStr(pOom, pDb, "one", "one", &rc);
|
||||
testOomFetchStr(pOom, pDb, "eight", "sixtyfour", &rc);
|
||||
|
||||
lsm_close(pDb);
|
||||
}
|
||||
|
||||
/*
|
||||
** Test the results of OOM conditions in simple write operations.
|
||||
*/
|
||||
static void simple_oom_4(OomTest *pOom){
|
||||
int rc = LSM_OK;
|
||||
lsm_db *pDb;
|
||||
|
||||
testDeleteLsmdb(LSMTEST6_TESTDB);
|
||||
testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc);
|
||||
|
||||
testOomWriteStr(pOom, pDb, "123", "onetwothree", &rc);
|
||||
testOomWriteStr(pOom, pDb, "456", "fourfivesix", &rc);
|
||||
testOomWriteStr(pOom, pDb, "789", "seveneightnine", &rc);
|
||||
testOomWriteStr(pOom, pDb, "123", "teneleventwelve", &rc);
|
||||
testOomWriteStr(pOom, pDb, "456", "fourteenfifteensixteen", &rc);
|
||||
|
||||
lsm_close(pDb);
|
||||
}
|
||||
|
||||
static void simple_oom_5(OomTest *pOom){
|
||||
Datasource *pData = getDatasource();
|
||||
int rc = LSM_OK;
|
||||
lsm_db *pDb;
|
||||
|
||||
testRestoreDb(LSMTEST6_TESTDB, "log");
|
||||
testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc);
|
||||
|
||||
testOomFetchData(pOom, pDb, pData, 3333, &rc);
|
||||
testOomFetchData(pOom, pDb, pData, 0, &rc);
|
||||
testOomFetchData(pOom, pDb, pData, 4999, &rc);
|
||||
|
||||
lsm_close(pDb);
|
||||
testDatasourceFree(pData);
|
||||
}
|
||||
|
||||
static void simple_oom_6(OomTest *pOom){
|
||||
Datasource *pData = getDatasource();
|
||||
int rc = LSM_OK;
|
||||
lsm_db *pDb;
|
||||
|
||||
testRestoreDb(LSMTEST6_TESTDB, "log");
|
||||
testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc);
|
||||
|
||||
testOomWriteData(pOom, pDb, pData, 5000, &rc);
|
||||
testOomWriteData(pOom, pDb, pData, 5001, &rc);
|
||||
testOomWriteData(pOom, pDb, pData, 5002, &rc);
|
||||
testOomFetchData(pOom, pDb, pData, 5001, &rc);
|
||||
testOomFetchData(pOom, pDb, pData, 1234, &rc);
|
||||
|
||||
lsm_close(pDb);
|
||||
testDatasourceFree(pData);
|
||||
}
|
||||
|
||||
static void simple_oom_7(OomTest *pOom){
|
||||
Datasource *pData = getDatasource();
|
||||
int rc = LSM_OK;
|
||||
lsm_db *pDb;
|
||||
|
||||
testRestoreDb(LSMTEST6_TESTDB, "log");
|
||||
testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc);
|
||||
testOomScan(pOom, pDb, 0, "abc", 3, 20, &rc);
|
||||
lsm_close(pDb);
|
||||
testDatasourceFree(pData);
|
||||
}
|
||||
|
||||
static void simple_oom_8(OomTest *pOom){
|
||||
Datasource *pData = getDatasource();
|
||||
int rc = LSM_OK;
|
||||
lsm_db *pDb;
|
||||
testRestoreDb(LSMTEST6_TESTDB, "log");
|
||||
testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc);
|
||||
testOomScan(pOom, pDb, 1, "xyz", 3, 20, &rc);
|
||||
lsm_close(pDb);
|
||||
testDatasourceFree(pData);
|
||||
}
|
||||
|
||||
/*
|
||||
** This test case has two clients connected to a database. The first client
|
||||
** hits an OOM while writing to the database. Check that the second
|
||||
** connection is still able to query the db following the OOM.
|
||||
*/
|
||||
static void simple_oom2_1(OomTest *pOom){
|
||||
const int nRecord = 100; /* Number of records initially in db */
|
||||
const int nIns = 10; /* Number of records inserted with OOM */
|
||||
|
||||
Datasource *pData = getDatasource();
|
||||
int rc = LSM_OK;
|
||||
lsm_db *pDb1;
|
||||
lsm_db *pDb2;
|
||||
int i;
|
||||
|
||||
testDeleteLsmdb(LSMTEST6_TESTDB);
|
||||
|
||||
/* Open the two connections. Initialize the in-memory tree so that it
|
||||
** contains 100 records. Do all this with OOM injection disabled. */
|
||||
testOomEnable(pOom, 0);
|
||||
testOomOpen(pOom, LSMTEST6_TESTDB, &pDb1, &rc);
|
||||
testOomOpen(pOom, LSMTEST6_TESTDB, &pDb2, &rc);
|
||||
for(i=0; i<nRecord; i++){
|
||||
testOomWriteData(pOom, pDb1, pData, i, &rc);
|
||||
}
|
||||
testOomEnable(pOom, 1);
|
||||
assert( rc==0 );
|
||||
|
||||
/* Insert 10 more records using pDb1. Stop when an OOM is encountered. */
|
||||
for(i=nRecord; i<nRecord+nIns; i++){
|
||||
testOomWriteData(pOom, pDb1, pData, i, &rc);
|
||||
if( rc ) break;
|
||||
}
|
||||
testOomAssertRc(pOom, rc);
|
||||
|
||||
/* Switch off OOM injection. Write a few rows using pDb2. Then check
|
||||
** that the database may be successfully queried. */
|
||||
testOomEnable(pOom, 0);
|
||||
rc = 0;
|
||||
for(; i<nRecord+nIns && rc==0; i++){
|
||||
testOomWriteData(pOom, pDb2, pData, i, &rc);
|
||||
}
|
||||
for(i=0; i<nRecord+nIns; i++) testOomFetchData(pOom, pDb2, pData, i, &rc);
|
||||
testOomEnable(pOom, 1);
|
||||
|
||||
lsm_close(pDb1);
|
||||
lsm_close(pDb2);
|
||||
testDatasourceFree(pData);
|
||||
}
|
||||
|
||||
|
||||
static void do_test_oom1(const char *zPattern, int *pRc){
|
||||
struct SimpleOom {
|
||||
const char *zName;
|
||||
void (*xSetup)(void);
|
||||
void (*xFunc)(OomTest *);
|
||||
} aSimple[] = {
|
||||
{ "oom1.lsm.1", setup_delete_db, simple_oom_1 },
|
||||
{ "oom1.lsm.2", setup_delete_db, simple_oom_2 },
|
||||
{ "oom1.lsm.3", setup_populate_db, simple_oom_3 },
|
||||
{ "oom1.lsm.4", setup_delete_db, simple_oom_4 },
|
||||
{ "oom1.lsm.5", setup_populate_db2, simple_oom_5 },
|
||||
{ "oom1.lsm.6", setup_populate_db2, simple_oom_6 },
|
||||
{ "oom1.lsm.7", setup_populate_db2, simple_oom_7 },
|
||||
{ "oom1.lsm.8", setup_populate_db2, simple_oom_8 },
|
||||
|
||||
{ "oom2.lsm.1", setup_delete_db, simple_oom2_1 },
|
||||
};
|
||||
int i;
|
||||
|
||||
for(i=0; i<ArraySize(aSimple); i++){
|
||||
if( *pRc==0 && testCaseBegin(pRc, zPattern, "%s", aSimple[i].zName) ){
|
||||
OomTest t;
|
||||
|
||||
if( aSimple[i].xSetup ){
|
||||
aSimple[i].xSetup();
|
||||
}
|
||||
|
||||
for(testOomStart(&t); testOomContinue(&t); testOomNext(&t)){
|
||||
aSimple[i].xFunc(&t);
|
||||
}
|
||||
|
||||
printf("(%d injections).", t.iNext-2);
|
||||
testCaseFinish( (*pRc = testOomFinish(&t)) );
|
||||
testMallocOom(tdb_lsm_env(), 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void test_oom(
|
||||
const char *zPattern, /* Run test cases that match this pattern */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
do_test_oom1(zPattern, pRc);
|
||||
}
|
206
ext/lsm1/lsm-test/lsmtest7.c
Normal file
206
ext/lsm1/lsm-test/lsmtest7.c
Normal file
@ -0,0 +1,206 @@
|
||||
|
||||
|
||||
#include "lsmtest.h"
|
||||
|
||||
|
||||
/*
|
||||
** Test that the rules for when lsm_csr_next() and lsm_csr_prev() are
|
||||
** enforced. Specifically:
|
||||
**
|
||||
** * Both functions always return LSM_MISUSE if the cursor is at EOF
|
||||
** when they are called.
|
||||
**
|
||||
** * lsm_csr_next() may only be used after lsm_csr_seek(LSM_SEEK_GE) or
|
||||
** lsm_csr_first().
|
||||
**
|
||||
** * lsm_csr_prev() may only be used after lsm_csr_seek(LSM_SEEK_LE) or
|
||||
** lsm_csr_last().
|
||||
*/
|
||||
static void do_test_api1_lsm(lsm_db *pDb, int *pRc){
|
||||
int ret;
|
||||
lsm_cursor *pCsr;
|
||||
lsm_cursor *pCsr2;
|
||||
int nKey;
|
||||
const void *pKey;
|
||||
|
||||
ret = lsm_csr_open(pDb, &pCsr);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
|
||||
ret = lsm_csr_next(pCsr);
|
||||
testCompareInt(LSM_MISUSE, ret, pRc);
|
||||
ret = lsm_csr_prev(pCsr);
|
||||
testCompareInt(LSM_MISUSE, ret, pRc);
|
||||
|
||||
ret = lsm_csr_seek(pCsr, "jjj", 3, LSM_SEEK_GE);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
ret = lsm_csr_next(pCsr);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
ret = lsm_csr_prev(pCsr);
|
||||
testCompareInt(LSM_MISUSE, ret, pRc);
|
||||
|
||||
ret = lsm_csr_seek(pCsr, "jjj", 3, LSM_SEEK_LE);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
ret = lsm_csr_next(pCsr);
|
||||
testCompareInt(LSM_MISUSE, ret, pRc);
|
||||
ret = lsm_csr_prev(pCsr);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
|
||||
ret = lsm_csr_seek(pCsr, "jjj", 3, LSM_SEEK_LEFAST);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
ret = lsm_csr_next(pCsr);
|
||||
testCompareInt(LSM_MISUSE, ret, pRc);
|
||||
ret = lsm_csr_prev(pCsr);
|
||||
testCompareInt(LSM_MISUSE, ret, pRc);
|
||||
|
||||
ret = lsm_csr_key(pCsr, &pKey, &nKey);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
|
||||
ret = lsm_csr_open(pDb, &pCsr2);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
|
||||
ret = lsm_csr_seek(pCsr2, pKey, nKey, LSM_SEEK_EQ);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
testCompareInt(1, lsm_csr_valid(pCsr2), pRc);
|
||||
ret = lsm_csr_next(pCsr2);
|
||||
testCompareInt(LSM_MISUSE, ret, pRc);
|
||||
ret = lsm_csr_prev(pCsr2);
|
||||
testCompareInt(LSM_MISUSE, ret, pRc);
|
||||
|
||||
lsm_csr_close(pCsr2);
|
||||
|
||||
ret = lsm_csr_first(pCsr);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
ret = lsm_csr_next(pCsr);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
ret = lsm_csr_prev(pCsr);
|
||||
testCompareInt(LSM_MISUSE, ret, pRc);
|
||||
|
||||
ret = lsm_csr_last(pCsr);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
ret = lsm_csr_prev(pCsr);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
ret = lsm_csr_next(pCsr);
|
||||
testCompareInt(LSM_MISUSE, ret, pRc);
|
||||
|
||||
ret = lsm_csr_first(pCsr);
|
||||
while( lsm_csr_valid(pCsr) ){
|
||||
ret = lsm_csr_next(pCsr);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
}
|
||||
ret = lsm_csr_next(pCsr);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
ret = lsm_csr_prev(pCsr);
|
||||
testCompareInt(LSM_MISUSE, ret, pRc);
|
||||
|
||||
ret = lsm_csr_last(pCsr);
|
||||
while( lsm_csr_valid(pCsr) ){
|
||||
ret = lsm_csr_prev(pCsr);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
}
|
||||
ret = lsm_csr_prev(pCsr);
|
||||
testCompareInt(LSM_OK, ret, pRc);
|
||||
ret = lsm_csr_next(pCsr);
|
||||
testCompareInt(LSM_MISUSE, ret, pRc);
|
||||
|
||||
lsm_csr_close(pCsr);
|
||||
}
|
||||
|
||||
static void do_test_api1(const char *zPattern, int *pRc){
|
||||
if( testCaseBegin(pRc, zPattern, "api1.lsm") ){
|
||||
const DatasourceDefn defn = { TEST_DATASOURCE_RANDOM, 10, 15, 200, 250 };
|
||||
Datasource *pData;
|
||||
TestDb *pDb;
|
||||
int rc = 0;
|
||||
|
||||
pDb = testOpen("lsm_lomem", 1, &rc);
|
||||
pData = testDatasourceNew(&defn);
|
||||
testWriteDatasourceRange(pDb, pData, 0, 1000, pRc);
|
||||
|
||||
do_test_api1_lsm(tdb_lsm(pDb), pRc);
|
||||
|
||||
testDatasourceFree(pData);
|
||||
testClose(&pDb);
|
||||
|
||||
testCaseFinish(*pRc);
|
||||
}
|
||||
}
|
||||
|
||||
static lsm_db *newLsmConnection(
|
||||
const char *zDb,
|
||||
int nPgsz,
|
||||
int nBlksz,
|
||||
int *pRc
|
||||
){
|
||||
lsm_db *db = 0;
|
||||
if( *pRc==0 ){
|
||||
int n1 = nPgsz;
|
||||
int n2 = nBlksz;
|
||||
*pRc = lsm_new(tdb_lsm_env(), &db);
|
||||
if( *pRc==0 ){
|
||||
if( n1 ) lsm_config(db, LSM_CONFIG_PAGE_SIZE, &n1);
|
||||
if( n2 ) lsm_config(db, LSM_CONFIG_BLOCK_SIZE, &n2);
|
||||
*pRc = lsm_open(db, "testdb.lsm");
|
||||
}
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
static void testPagesize(lsm_db *db, int nPgsz, int nBlksz, int *pRc){
|
||||
if( *pRc==0 ){
|
||||
int n1 = 0;
|
||||
int n2 = 0;
|
||||
|
||||
lsm_config(db, LSM_CONFIG_PAGE_SIZE, &n1);
|
||||
lsm_config(db, LSM_CONFIG_BLOCK_SIZE, &n2);
|
||||
|
||||
testCompareInt(n1, nPgsz, pRc);
|
||||
testCompareInt(n2, nBlksz, pRc);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Test case "api2" tests that the default page and block sizes of a
|
||||
** database may only be modified before lsm_open() is called. And that
|
||||
** after lsm_open() is called lsm_config() may be used to read the
|
||||
** actual page and block size of the db.
|
||||
*/
|
||||
static void do_test_api2(const char *zPattern, int *pRc){
|
||||
if( *pRc==0 && testCaseBegin(pRc, zPattern, "api2.lsm") ){
|
||||
lsm_db *db1 = 0;
|
||||
lsm_db *db2 = 0;
|
||||
|
||||
testDeleteLsmdb("testdb.lsm");
|
||||
db1 = newLsmConnection("testdb.lsm", 0, 0, pRc);
|
||||
testPagesize(db1, 4096, 2*1024*1024, pRc);
|
||||
db2 = newLsmConnection("testdb.lsm", 1024, 64*1024, pRc);
|
||||
testPagesize(db2, 4096, 2*1024*1024, pRc);
|
||||
lsm_close(db1);
|
||||
lsm_close(db2);
|
||||
|
||||
testDeleteLsmdb("testdb.lsm");
|
||||
db1 = newLsmConnection("testdb.lsm", 1024, 64*1024, pRc);
|
||||
testPagesize(db1, 1024, 64*1024, pRc);
|
||||
db2 = newLsmConnection("testdb.lsm", 0, 0, pRc);
|
||||
testPagesize(db2, 1024, 64*1024, pRc);
|
||||
lsm_close(db1);
|
||||
lsm_close(db2);
|
||||
|
||||
testDeleteLsmdb("testdb.lsm");
|
||||
db1 = newLsmConnection("testdb.lsm", 8192, 1*1024*1024, pRc);
|
||||
testPagesize(db1, 8192, 1*1024*1024, pRc);
|
||||
db2 = newLsmConnection("testdb.lsm", 1024, 64*1024, pRc);
|
||||
testPagesize(db2, 8192, 1*1024*1024, pRc);
|
||||
lsm_close(db1);
|
||||
lsm_close(db2);
|
||||
|
||||
testCaseFinish(*pRc);
|
||||
}
|
||||
}
|
||||
|
||||
void test_api(
|
||||
const char *zPattern, /* Run test cases that match this pattern */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
do_test_api1(zPattern, pRc);
|
||||
do_test_api2(zPattern, pRc);
|
||||
}
|
322
ext/lsm1/lsm-test/lsmtest8.c
Normal file
322
ext/lsm1/lsm-test/lsmtest8.c
Normal file
@ -0,0 +1,322 @@
|
||||
|
||||
/*
|
||||
** This file contains test cases to verify that "live-recovery" following
|
||||
** a mid-transaction failure of a writer process.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
** This test file includes lsmInt.h to get access to the definition of the
|
||||
** ShmHeader structure. This is required to cause strategic damage to the
|
||||
** shared memory header as part of recovery testing.
|
||||
*/
|
||||
#include "lsmInt.h"
|
||||
|
||||
#include "lsmtest.h"
|
||||
|
||||
typedef struct SetupStep SetupStep;
|
||||
struct SetupStep {
|
||||
int bFlush; /* Flush to disk and checkpoint */
|
||||
int iInsStart; /* First key-value from ds to insert */
|
||||
int nIns; /* Number of rows to insert */
|
||||
int iDelStart; /* First key from ds to delete */
|
||||
int nDel; /* Number of rows to delete */
|
||||
};
|
||||
|
||||
static void doSetupStep(
|
||||
TestDb *pDb,
|
||||
Datasource *pData,
|
||||
const SetupStep *pStep,
|
||||
int *pRc
|
||||
){
|
||||
testWriteDatasourceRange(pDb, pData, pStep->iInsStart, pStep->nIns, pRc);
|
||||
testDeleteDatasourceRange(pDb, pData, pStep->iDelStart, pStep->nDel, pRc);
|
||||
if( *pRc==0 ){
|
||||
int nSave = -1;
|
||||
int nBuf = 64;
|
||||
lsm_db *db = tdb_lsm(pDb);
|
||||
|
||||
lsm_config(db, LSM_CONFIG_AUTOFLUSH, &nSave);
|
||||
lsm_config(db, LSM_CONFIG_AUTOFLUSH, &nBuf);
|
||||
lsm_begin(db, 1);
|
||||
lsm_commit(db, 0);
|
||||
lsm_config(db, LSM_CONFIG_AUTOFLUSH, &nSave);
|
||||
|
||||
*pRc = lsm_work(db, 0, 0, 0);
|
||||
if( *pRc==0 ){
|
||||
*pRc = lsm_checkpoint(db, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void doSetupStepArray(
|
||||
TestDb *pDb,
|
||||
Datasource *pData,
|
||||
const SetupStep *aStep,
|
||||
int nStep
|
||||
){
|
||||
int i;
|
||||
for(i=0; i<nStep; i++){
|
||||
int rc = 0;
|
||||
doSetupStep(pDb, pData, &aStep[i], &rc);
|
||||
assert( rc==0 );
|
||||
}
|
||||
}
|
||||
|
||||
static void setupDatabase1(TestDb *pDb, Datasource **ppData){
|
||||
const SetupStep aStep[] = {
|
||||
{ 0, 1, 2000, 0, 0 },
|
||||
{ 1, 0, 0, 0, 0 },
|
||||
{ 0, 10001, 1000, 0, 0 },
|
||||
};
|
||||
const DatasourceDefn defn = {TEST_DATASOURCE_RANDOM, 12, 16, 100, 500};
|
||||
Datasource *pData;
|
||||
|
||||
pData = testDatasourceNew(&defn);
|
||||
doSetupStepArray(pDb, pData, aStep, ArraySize(aStep));
|
||||
if( ppData ){
|
||||
*ppData = pData;
|
||||
}else{
|
||||
testDatasourceFree(pData);
|
||||
}
|
||||
}
|
||||
|
||||
#include <stdio.h>
|
||||
void testReadFile(const char *zFile, int iOff, void *pOut, int nByte, int *pRc){
|
||||
if( *pRc==0 ){
|
||||
FILE *fd;
|
||||
fd = fopen(zFile, "r");
|
||||
if( fd==0 ){
|
||||
*pRc = 1;
|
||||
}else{
|
||||
if( 0!=fseek(fd, iOff, SEEK_SET) ){
|
||||
*pRc = 1;
|
||||
}else{
|
||||
if( nByte!=fread(pOut, 1, nByte, fd) ){
|
||||
*pRc = 1;
|
||||
}
|
||||
}
|
||||
fclose(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void testWriteFile(
|
||||
const char *zFile,
|
||||
int iOff,
|
||||
void *pOut,
|
||||
int nByte,
|
||||
int *pRc
|
||||
){
|
||||
if( *pRc==0 ){
|
||||
FILE *fd;
|
||||
fd = fopen(zFile, "r+");
|
||||
if( fd==0 ){
|
||||
*pRc = 1;
|
||||
}else{
|
||||
if( 0!=fseek(fd, iOff, SEEK_SET) ){
|
||||
*pRc = 1;
|
||||
}else{
|
||||
if( nByte!=fwrite(pOut, 1, nByte, fd) ){
|
||||
*pRc = 1;
|
||||
}
|
||||
}
|
||||
fclose(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ShmHeader *getShmHeader(const char *zDb){
|
||||
int rc = 0;
|
||||
char *zShm = testMallocPrintf("%s-shm", zDb);
|
||||
ShmHeader *pHdr;
|
||||
|
||||
pHdr = testMalloc(sizeof(ShmHeader));
|
||||
testReadFile(zShm, 0, (void *)pHdr, sizeof(ShmHeader), &rc);
|
||||
assert( rc==0 );
|
||||
|
||||
return pHdr;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function makes a copy of the three files associated with LSM
|
||||
** database zDb (i.e. if zDb is "test.db", it makes copies of "test.db",
|
||||
** "test.db-log" and "test.db-shm").
|
||||
**
|
||||
** It then opens a new database connection to the copy with the xLock() call
|
||||
** instrumented so that it appears that some other process already connected
|
||||
** to the db (holding a shared lock on DMS2). This prevents recovery from
|
||||
** running. Then:
|
||||
**
|
||||
** 1) Check that the checksum of the database is zCksum.
|
||||
** 2) Write a few keys to the database. Then delete the same keys.
|
||||
** 3) Check that the checksum is zCksum.
|
||||
** 4) Flush the db to disk and run a checkpoint.
|
||||
** 5) Check once more that the checksum is still zCksum.
|
||||
*/
|
||||
static void doLiveRecovery(const char *zDb, const char *zCksum, int *pRc){
|
||||
const DatasourceDefn defn = {TEST_DATASOURCE_RANDOM, 20, 25, 100, 500};
|
||||
Datasource *pData;
|
||||
const char *zCopy = "testcopy.lsm";
|
||||
char zCksum2[TEST_CKSUM_BYTES];
|
||||
TestDb *pDb = 0;
|
||||
int rc;
|
||||
|
||||
pData = testDatasourceNew(&defn);
|
||||
|
||||
testCopyLsmdb(zDb, zCopy);
|
||||
rc = tdb_lsm_open("test_no_recovery=1", zCopy, 0, &pDb);
|
||||
if( rc==0 ){
|
||||
ShmHeader *pHdr;
|
||||
lsm_db *db;
|
||||
testCksumDatabase(pDb, zCksum2);
|
||||
testCompareStr(zCksum, zCksum2, &rc);
|
||||
|
||||
testWriteDatasourceRange(pDb, pData, 1, 10, &rc);
|
||||
testDeleteDatasourceRange(pDb, pData, 1, 10, &rc);
|
||||
|
||||
/* Test that the two tree-headers are now consistent. */
|
||||
pHdr = getShmHeader(zCopy);
|
||||
if( rc==0 && memcmp(&pHdr->hdr1, &pHdr->hdr2, sizeof(pHdr->hdr1)) ){
|
||||
rc = 1;
|
||||
}
|
||||
testFree(pHdr);
|
||||
|
||||
if( rc==0 ){
|
||||
int nBuf = 64;
|
||||
db = tdb_lsm(pDb);
|
||||
lsm_config(db, LSM_CONFIG_AUTOFLUSH, &nBuf);
|
||||
lsm_begin(db, 1);
|
||||
lsm_commit(db, 0);
|
||||
rc = lsm_work(db, 0, 0, 0);
|
||||
}
|
||||
|
||||
testCksumDatabase(pDb, zCksum2);
|
||||
testCompareStr(zCksum, zCksum2, &rc);
|
||||
}
|
||||
|
||||
testDatasourceFree(pData);
|
||||
testClose(&pDb);
|
||||
testDeleteLsmdb(zCopy);
|
||||
*pRc = rc;
|
||||
}
|
||||
|
||||
static void doWriterCrash1(int *pRc){
|
||||
const int nWrite = 2000;
|
||||
const int nStep = 10;
|
||||
const int iWriteStart = 20000;
|
||||
int rc = 0;
|
||||
TestDb *pDb = 0;
|
||||
Datasource *pData = 0;
|
||||
|
||||
rc = tdb_lsm_open("autowork=0", "testdb.lsm", 1, &pDb);
|
||||
if( rc==0 ){
|
||||
int iDot = 0;
|
||||
char zCksum[TEST_CKSUM_BYTES];
|
||||
int i;
|
||||
setupDatabase1(pDb, &pData);
|
||||
testCksumDatabase(pDb, zCksum);
|
||||
testBegin(pDb, 2, &rc);
|
||||
for(i=0; rc==0 && i<nWrite; i+=nStep){
|
||||
testCaseProgress(i, nWrite, testCaseNDot(), &iDot);
|
||||
testWriteDatasourceRange(pDb, pData, iWriteStart+i, nStep, &rc);
|
||||
doLiveRecovery("testdb.lsm", zCksum, &rc);
|
||||
}
|
||||
}
|
||||
testCommit(pDb, 0, &rc);
|
||||
testClose(&pDb);
|
||||
testDatasourceFree(pData);
|
||||
*pRc = rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This test case verifies that inconsistent tree-headers in shared-memory
|
||||
** are resolved correctly.
|
||||
*/
|
||||
static void doWriterCrash2(int *pRc){
|
||||
int rc = 0;
|
||||
TestDb *pDb = 0;
|
||||
Datasource *pData = 0;
|
||||
|
||||
rc = tdb_lsm_open("autowork=0", "testdb.lsm", 1, &pDb);
|
||||
if( rc==0 ){
|
||||
ShmHeader *pHdr1;
|
||||
ShmHeader *pHdr2;
|
||||
char zCksum1[TEST_CKSUM_BYTES];
|
||||
char zCksum2[TEST_CKSUM_BYTES];
|
||||
|
||||
pHdr1 = testMalloc(sizeof(ShmHeader));
|
||||
pHdr2 = testMalloc(sizeof(ShmHeader));
|
||||
setupDatabase1(pDb, &pData);
|
||||
|
||||
/* Grab a copy of the shared-memory header. And the db checksum */
|
||||
testReadFile("testdb.lsm-shm", 0, (void *)pHdr1, sizeof(ShmHeader), &rc);
|
||||
testCksumDatabase(pDb, zCksum1);
|
||||
|
||||
/* Modify the database */
|
||||
testBegin(pDb, 2, &rc);
|
||||
testWriteDatasourceRange(pDb, pData, 30000, 200, &rc);
|
||||
testCommit(pDb, 0, &rc);
|
||||
|
||||
/* Grab a second copy of the shared-memory header. And the db checksum */
|
||||
testReadFile("testdb.lsm-shm", 0, (void *)pHdr2, sizeof(ShmHeader), &rc);
|
||||
testCksumDatabase(pDb, zCksum2);
|
||||
doLiveRecovery("testdb.lsm", zCksum2, &rc);
|
||||
|
||||
/* If both tree-headers are valid, tree-header-1 is used. */
|
||||
memcpy(&pHdr2->hdr1, &pHdr1->hdr1, sizeof(pHdr1->hdr1));
|
||||
pHdr2->bWriter = 1;
|
||||
testWriteFile("testdb.lsm-shm", 0, (void *)pHdr2, sizeof(ShmHeader), &rc);
|
||||
doLiveRecovery("testdb.lsm", zCksum1, &rc);
|
||||
|
||||
/* If both tree-headers are valid, tree-header-1 is used. */
|
||||
memcpy(&pHdr2->hdr1, &pHdr2->hdr2, sizeof(pHdr1->hdr1));
|
||||
memcpy(&pHdr2->hdr2, &pHdr1->hdr1, sizeof(pHdr1->hdr1));
|
||||
pHdr2->bWriter = 1;
|
||||
testWriteFile("testdb.lsm-shm", 0, (void *)pHdr2, sizeof(ShmHeader), &rc);
|
||||
doLiveRecovery("testdb.lsm", zCksum2, &rc);
|
||||
|
||||
/* If tree-header 1 is invalid, tree-header-2 is used */
|
||||
memcpy(&pHdr2->hdr2, &pHdr2->hdr1, sizeof(pHdr1->hdr1));
|
||||
pHdr2->hdr1.aCksum[0] = 5;
|
||||
pHdr2->hdr1.aCksum[0] = 6;
|
||||
pHdr2->bWriter = 1;
|
||||
testWriteFile("testdb.lsm-shm", 0, (void *)pHdr2, sizeof(ShmHeader), &rc);
|
||||
doLiveRecovery("testdb.lsm", zCksum2, &rc);
|
||||
|
||||
/* If tree-header 2 is invalid, tree-header-1 is used */
|
||||
memcpy(&pHdr2->hdr1, &pHdr2->hdr2, sizeof(pHdr1->hdr1));
|
||||
pHdr2->hdr2.aCksum[0] = 5;
|
||||
pHdr2->hdr2.aCksum[0] = 6;
|
||||
pHdr2->bWriter = 1;
|
||||
testWriteFile("testdb.lsm-shm", 0, (void *)pHdr2, sizeof(ShmHeader), &rc);
|
||||
doLiveRecovery("testdb.lsm", zCksum2, &rc);
|
||||
|
||||
testFree(pHdr1);
|
||||
testFree(pHdr2);
|
||||
testClose(&pDb);
|
||||
}
|
||||
|
||||
*pRc = rc;
|
||||
}
|
||||
|
||||
void do_writer_crash_test(const char *zPattern, int *pRc){
|
||||
struct Test {
|
||||
const char *zName;
|
||||
void (*xFunc)(int *);
|
||||
} aTest[] = {
|
||||
{ "writercrash1.lsm", doWriterCrash1 },
|
||||
{ "writercrash2.lsm", doWriterCrash2 },
|
||||
};
|
||||
int i;
|
||||
for(i=0; i<ArraySize(aTest); i++){
|
||||
struct Test *p = &aTest[i];
|
||||
if( testCaseBegin(pRc, zPattern, p->zName) ){
|
||||
p->xFunc(pRc);
|
||||
testCaseFinish(*pRc);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
138
ext/lsm1/lsm-test/lsmtest9.c
Normal file
138
ext/lsm1/lsm-test/lsmtest9.c
Normal file
@ -0,0 +1,138 @@
|
||||
|
||||
#include "lsmtest.h"
|
||||
|
||||
#define DATA_SEQUENTIAL TEST_DATASOURCE_SEQUENCE
|
||||
#define DATA_RANDOM TEST_DATASOURCE_RANDOM
|
||||
|
||||
typedef struct Datatest4 Datatest4;
|
||||
|
||||
/*
|
||||
** Test overview:
|
||||
**
|
||||
** 1. Insert (Datatest4.nRec) records into a database.
|
||||
**
|
||||
** 2. Repeat (Datatest4.nRepeat) times:
|
||||
**
|
||||
** 2a. Delete 2/3 of the records in the database.
|
||||
**
|
||||
** 2b. Run lsm_work(nMerge=1).
|
||||
**
|
||||
** 2c. Insert as many records as were deleted in 2a.
|
||||
**
|
||||
** 2d. Check database content is as expected.
|
||||
**
|
||||
** 2e. If (Datatest4.bReopen) is true, close and reopen the database.
|
||||
*/
|
||||
struct Datatest4 {
|
||||
/* Datasource definition */
|
||||
DatasourceDefn defn;
|
||||
|
||||
int nRec;
|
||||
int nRepeat;
|
||||
int bReopen;
|
||||
};
|
||||
|
||||
static void doDataTest4(
|
||||
const char *zSystem, /* Database system to test */
|
||||
Datatest4 *p, /* Structure containing test parameters */
|
||||
int *pRc /* OUT: Error code */
|
||||
){
|
||||
lsm_db *db = 0;
|
||||
TestDb *pDb;
|
||||
TestDb *pControl;
|
||||
Datasource *pData;
|
||||
int i;
|
||||
int rc = 0;
|
||||
int iDot = 0;
|
||||
|
||||
int nRecOn3 = (p->nRec / 3);
|
||||
int iData = 0;
|
||||
|
||||
/* Start the test case, open a database and allocate the datasource. */
|
||||
rc = testControlDb(&pControl);
|
||||
pDb = testOpen(zSystem, 1, &rc);
|
||||
pData = testDatasourceNew(&p->defn);
|
||||
if( rc==0 ) db = tdb_lsm(pDb);
|
||||
|
||||
testWriteDatasourceRange(pControl, pData, iData, nRecOn3*3, &rc);
|
||||
testWriteDatasourceRange(pDb, pData, iData, nRecOn3*3, &rc);
|
||||
|
||||
for(i=0; rc==0 && i<p->nRepeat; i++){
|
||||
|
||||
testDeleteDatasourceRange(pControl, pData, iData, nRecOn3*2, &rc);
|
||||
testDeleteDatasourceRange(pDb, pData, iData, nRecOn3*2, &rc);
|
||||
|
||||
if( db ){
|
||||
int nDone;
|
||||
#if 0
|
||||
fprintf(stderr, "lsm_work() start...\n"); fflush(stderr);
|
||||
#endif
|
||||
do {
|
||||
nDone = 0;
|
||||
rc = lsm_work(db, 1, (1<<30), &nDone);
|
||||
}while( rc==0 && nDone>0 );
|
||||
#if 0
|
||||
fprintf(stderr, "lsm_work() done...\n"); fflush(stderr);
|
||||
#endif
|
||||
}
|
||||
|
||||
if( i+1<p->nRepeat ){
|
||||
iData += (nRecOn3*2);
|
||||
testWriteDatasourceRange(pControl, pData, iData+nRecOn3, nRecOn3*2, &rc);
|
||||
testWriteDatasourceRange(pDb, pData, iData+nRecOn3, nRecOn3*2, &rc);
|
||||
|
||||
testCompareDb(pData, nRecOn3*3, iData, pControl, pDb, &rc);
|
||||
|
||||
/* If Datatest4.bReopen is true, close and reopen the database */
|
||||
if( p->bReopen ){
|
||||
testReopen(&pDb, &rc);
|
||||
if( rc==0 ) db = tdb_lsm(pDb);
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the progress dots... */
|
||||
testCaseProgress(i, p->nRepeat, testCaseNDot(), &iDot);
|
||||
}
|
||||
|
||||
testClose(&pDb);
|
||||
testClose(&pControl);
|
||||
testDatasourceFree(pData);
|
||||
testCaseFinish(rc);
|
||||
*pRc = rc;
|
||||
}
|
||||
|
||||
static char *getName4(const char *zSystem, Datatest4 *pTest){
|
||||
char *zRet;
|
||||
char *zData;
|
||||
zData = testDatasourceName(&pTest->defn);
|
||||
zRet = testMallocPrintf("data4.%s.%s.%d.%d.%d",
|
||||
zSystem, zData, pTest->nRec, pTest->nRepeat, pTest->bReopen
|
||||
);
|
||||
testFree(zData);
|
||||
return zRet;
|
||||
}
|
||||
|
||||
void test_data_4(
|
||||
const char *zSystem, /* Database system name */
|
||||
const char *zPattern, /* Run test cases that match this pattern */
|
||||
int *pRc /* IN/OUT: Error code */
|
||||
){
|
||||
Datatest4 aTest[] = {
|
||||
/* defn, nRec, nRepeat, bReopen */
|
||||
{ {DATA_RANDOM, 20,25, 500,600}, 10000, 10, 0 },
|
||||
{ {DATA_RANDOM, 20,25, 500,600}, 10000, 10, 1 },
|
||||
};
|
||||
|
||||
int i;
|
||||
|
||||
for(i=0; *pRc==LSM_OK && i<ArraySize(aTest); i++){
|
||||
char *zName = getName4(zSystem, &aTest[i]);
|
||||
if( testCaseBegin(pRc, zPattern, "%s", zName) ){
|
||||
doDataTest4(zSystem, &aTest[i], pRc);
|
||||
}
|
||||
testFree(zName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
75
ext/lsm1/lsm-test/lsmtest_bt.c
Normal file
75
ext/lsm1/lsm-test/lsmtest_bt.c
Normal file
@ -0,0 +1,75 @@
|
||||
|
||||
#include "lsmtest.h"
|
||||
#include "bt.h"
|
||||
|
||||
int do_bt(int nArg, char **azArg){
|
||||
struct Option {
|
||||
const char *zName;
|
||||
int bPgno;
|
||||
int eOpt;
|
||||
} aOpt [] = {
|
||||
{ "dbhdr", 0, BT_INFO_HDRDUMP },
|
||||
{ "filename", 0, BT_INFO_FILENAME },
|
||||
{ "block_freelist", 0, BT_INFO_BLOCK_FREELIST },
|
||||
{ "page_freelist", 0, BT_INFO_PAGE_FREELIST },
|
||||
{ "filename", 0, BT_INFO_FILENAME },
|
||||
{ "page", 1, BT_INFO_PAGEDUMP },
|
||||
{ "page_ascii", 1, BT_INFO_PAGEDUMP_ASCII },
|
||||
{ "leaks", 0, BT_INFO_PAGE_LEAKS },
|
||||
{ 0, 0 }
|
||||
};
|
||||
int iOpt;
|
||||
int rc;
|
||||
bt_info buf;
|
||||
char *zOpt;
|
||||
char *zFile;
|
||||
|
||||
bt_db *db = 0;
|
||||
|
||||
if( nArg<2 ){
|
||||
testPrintUsage("FILENAME OPTION ...");
|
||||
return -1;
|
||||
}
|
||||
zFile = azArg[0];
|
||||
zOpt = azArg[1];
|
||||
|
||||
rc = testArgSelect(aOpt, "option", zOpt, &iOpt);
|
||||
if( rc!=0 ) return rc;
|
||||
if( nArg!=2+aOpt[iOpt].bPgno ){
|
||||
testPrintFUsage("FILENAME %s %s", zOpt, aOpt[iOpt].bPgno ? "PGNO" : "");
|
||||
return -4;
|
||||
}
|
||||
|
||||
rc = sqlite4BtNew(sqlite4_env_default(), 0, &db);
|
||||
if( rc!=SQLITE4_OK ){
|
||||
testPrintError("sqlite4BtNew() failed: %d", rc);
|
||||
return -2;
|
||||
}
|
||||
rc = sqlite4BtOpen(db, zFile);
|
||||
if( rc!=SQLITE4_OK ){
|
||||
testPrintError("sqlite4BtOpen() failed: %d", rc);
|
||||
return -3;
|
||||
}
|
||||
|
||||
buf.eType = aOpt[iOpt].eOpt;
|
||||
buf.pgno = 0;
|
||||
sqlite4_buffer_init(&buf.output, 0);
|
||||
|
||||
if( aOpt[iOpt].bPgno ){
|
||||
buf.pgno = (u32)atoi(azArg[2]);
|
||||
}
|
||||
|
||||
rc = sqlite4BtControl(db, BT_CONTROL_INFO, &buf);
|
||||
if( rc!=SQLITE4_OK ){
|
||||
testPrintError("sqlite4BtControl() failed: %d\n", rc);
|
||||
return -4;
|
||||
}
|
||||
|
||||
printf("%s\n", (char*)buf.output.p);
|
||||
sqlite4_buffer_clear(&buf.output);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
96
ext/lsm1/lsm-test/lsmtest_datasource.c
Normal file
96
ext/lsm1/lsm-test/lsmtest_datasource.c
Normal file
@ -0,0 +1,96 @@
|
||||
|
||||
|
||||
#include "lsmtest.h"
|
||||
|
||||
struct Datasource {
|
||||
int eType;
|
||||
|
||||
int nMinKey;
|
||||
int nMaxKey;
|
||||
int nMinVal;
|
||||
int nMaxVal;
|
||||
|
||||
char *aKey;
|
||||
char *aVal;
|
||||
};
|
||||
|
||||
void testDatasourceEntry(
|
||||
Datasource *p,
|
||||
int iData,
|
||||
void **ppKey, int *pnKey,
|
||||
void **ppVal, int *pnVal
|
||||
){
|
||||
assert( (ppKey==0)==(pnKey==0) );
|
||||
assert( (ppVal==0)==(pnVal==0) );
|
||||
|
||||
if( ppKey ){
|
||||
int nKey = 0;
|
||||
switch( p->eType ){
|
||||
case TEST_DATASOURCE_RANDOM: {
|
||||
int nRange = (1 + p->nMaxKey - p->nMinKey);
|
||||
nKey = (int)( testPrngValue((u32)iData) % nRange ) + p->nMinKey;
|
||||
testPrngString((u32)iData, p->aKey, nKey);
|
||||
break;
|
||||
}
|
||||
case TEST_DATASOURCE_SEQUENCE:
|
||||
nKey = sprintf(p->aKey, "%012d", iData);
|
||||
break;
|
||||
}
|
||||
*ppKey = p->aKey;
|
||||
*pnKey = nKey;
|
||||
}
|
||||
if( ppVal ){
|
||||
u32 nVal = testPrngValue((u32)iData)%(1+p->nMaxVal-p->nMinVal)+p->nMinVal;
|
||||
testPrngString((u32)~iData, p->aVal, (int)nVal);
|
||||
*ppVal = p->aVal;
|
||||
*pnVal = (int)nVal;
|
||||
}
|
||||
}
|
||||
|
||||
void testDatasourceFree(Datasource *p){
|
||||
testFree(p);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to a nul-terminated string that corresponds to the
|
||||
** contents of the datasource-definition passed as the first argument.
|
||||
** The caller should eventually free the returned pointer using testFree().
|
||||
*/
|
||||
char *testDatasourceName(const DatasourceDefn *p){
|
||||
char *zRet;
|
||||
zRet = testMallocPrintf("%s.(%d-%d).(%d-%d)",
|
||||
(p->eType==TEST_DATASOURCE_SEQUENCE ? "seq" : "rnd"),
|
||||
p->nMinKey, p->nMaxKey,
|
||||
p->nMinVal, p->nMaxVal
|
||||
);
|
||||
return zRet;
|
||||
}
|
||||
|
||||
Datasource *testDatasourceNew(const DatasourceDefn *pDefn){
|
||||
Datasource *p;
|
||||
int nMinKey;
|
||||
int nMaxKey;
|
||||
int nMinVal;
|
||||
int nMaxVal;
|
||||
|
||||
if( pDefn->eType==TEST_DATASOURCE_SEQUENCE ){
|
||||
nMinKey = 128;
|
||||
nMaxKey = 128;
|
||||
}else{
|
||||
nMinKey = MAX(0, pDefn->nMinKey);
|
||||
nMaxKey = MAX(nMinKey, pDefn->nMaxKey);
|
||||
}
|
||||
nMinVal = MAX(0, pDefn->nMinVal);
|
||||
nMaxVal = MAX(nMinVal, pDefn->nMaxVal);
|
||||
|
||||
p = (Datasource *)testMalloc(sizeof(Datasource) + nMaxKey + nMaxVal + 1);
|
||||
p->eType = pDefn->eType;
|
||||
p->nMinKey = nMinKey;
|
||||
p->nMinVal = nMinVal;
|
||||
p->nMaxKey = nMaxKey;
|
||||
p->nMaxVal = nMaxVal;
|
||||
|
||||
p->aKey = (char *)&p[1];
|
||||
p->aVal = &p->aKey[nMaxKey];
|
||||
return p;
|
||||
};
|
177
ext/lsm1/lsm-test/lsmtest_func.c
Normal file
177
ext/lsm1/lsm-test/lsmtest_func.c
Normal file
@ -0,0 +1,177 @@
|
||||
|
||||
#include "lsmtest.h"
|
||||
|
||||
|
||||
int do_work(int nArg, char **azArg){
|
||||
struct Option {
|
||||
const char *zName;
|
||||
} aOpt [] = {
|
||||
{ "-nmerge" },
|
||||
{ "-nkb" },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
lsm_db *pDb;
|
||||
int rc;
|
||||
int i;
|
||||
const char *zDb;
|
||||
int nMerge = 1;
|
||||
int nKB = (1<<30);
|
||||
|
||||
if( nArg==0 ) goto usage;
|
||||
zDb = azArg[nArg-1];
|
||||
for(i=0; i<(nArg-1); i++){
|
||||
int iSel;
|
||||
rc = testArgSelect(aOpt, "option", azArg[i], &iSel);
|
||||
if( rc ) return rc;
|
||||
switch( iSel ){
|
||||
case 0:
|
||||
i++;
|
||||
if( i==(nArg-1) ) goto usage;
|
||||
nMerge = atoi(azArg[i]);
|
||||
break;
|
||||
case 1:
|
||||
i++;
|
||||
if( i==(nArg-1) ) goto usage;
|
||||
nKB = atoi(azArg[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc = lsm_new(0, &pDb);
|
||||
if( rc!=LSM_OK ){
|
||||
testPrintError("lsm_open(): rc=%d\n", rc);
|
||||
}else{
|
||||
rc = lsm_open(pDb, zDb);
|
||||
if( rc!=LSM_OK ){
|
||||
testPrintError("lsm_open(): rc=%d\n", rc);
|
||||
}else{
|
||||
int n = -1;
|
||||
lsm_config(pDb, LSM_CONFIG_BLOCK_SIZE, &n);
|
||||
n = n*2;
|
||||
lsm_config(pDb, LSM_CONFIG_AUTOCHECKPOINT, &n);
|
||||
|
||||
rc = lsm_work(pDb, nMerge, nKB, 0);
|
||||
if( rc!=LSM_OK ){
|
||||
testPrintError("lsm_work(): rc=%d\n", rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
if( rc==LSM_OK ){
|
||||
rc = lsm_checkpoint(pDb, 0);
|
||||
}
|
||||
|
||||
lsm_close(pDb);
|
||||
return rc;
|
||||
|
||||
usage:
|
||||
testPrintUsage("?-optimize? ?-n N? DATABASE");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** lsmtest show ?-config LSM-CONFIG? DATABASE ?COMMAND ?PGNO??
|
||||
*/
|
||||
int do_show(int nArg, char **azArg){
|
||||
lsm_db *pDb;
|
||||
int rc;
|
||||
const char *zDb;
|
||||
|
||||
int eOpt = LSM_INFO_DB_STRUCTURE;
|
||||
unsigned int iPg = 0;
|
||||
int bConfig = 0;
|
||||
const char *zConfig = "";
|
||||
|
||||
struct Option {
|
||||
const char *zName;
|
||||
int bConfig;
|
||||
int eOpt;
|
||||
} aOpt [] = {
|
||||
{ "array", 0, LSM_INFO_ARRAY_STRUCTURE },
|
||||
{ "array-pages", 0, LSM_INFO_ARRAY_PAGES },
|
||||
{ "blocksize", 1, LSM_CONFIG_BLOCK_SIZE },
|
||||
{ "pagesize", 1, LSM_CONFIG_PAGE_SIZE },
|
||||
{ "freelist", 0, LSM_INFO_FREELIST },
|
||||
{ "page-ascii", 0, LSM_INFO_PAGE_ASCII_DUMP },
|
||||
{ "page-hex", 0, LSM_INFO_PAGE_HEX_DUMP },
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
char *z = 0;
|
||||
int iDb = 0; /* Index of DATABASE in azArg[] */
|
||||
|
||||
/* Check if there is a "-config" option: */
|
||||
if( nArg>2 && strlen(azArg[0])>1
|
||||
&& memcmp(azArg[0], "-config", strlen(azArg[0]))==0
|
||||
){
|
||||
zConfig = azArg[1];
|
||||
iDb = 2;
|
||||
}
|
||||
if( nArg<(iDb+1) ) goto usage;
|
||||
|
||||
if( nArg>(iDb+1) ){
|
||||
rc = testArgSelect(aOpt, "option", azArg[iDb+1], &eOpt);
|
||||
if( rc!=0 ) return rc;
|
||||
bConfig = aOpt[eOpt].bConfig;
|
||||
eOpt = aOpt[eOpt].eOpt;
|
||||
if( (bConfig==0 && eOpt==LSM_INFO_FREELIST)
|
||||
|| (bConfig==1 && eOpt==LSM_CONFIG_BLOCK_SIZE)
|
||||
|| (bConfig==1 && eOpt==LSM_CONFIG_PAGE_SIZE)
|
||||
){
|
||||
if( nArg!=(iDb+2) ) goto usage;
|
||||
}else{
|
||||
if( nArg!=(iDb+3) ) goto usage;
|
||||
iPg = atoi(azArg[iDb+2]);
|
||||
}
|
||||
}
|
||||
zDb = azArg[iDb];
|
||||
|
||||
rc = lsm_new(0, &pDb);
|
||||
tdb_lsm_configure(pDb, zConfig);
|
||||
if( rc!=LSM_OK ){
|
||||
testPrintError("lsm_new(): rc=%d\n", rc);
|
||||
}else{
|
||||
rc = lsm_open(pDb, zDb);
|
||||
if( rc!=LSM_OK ){
|
||||
testPrintError("lsm_open(): rc=%d\n", rc);
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==LSM_OK ){
|
||||
if( bConfig==0 ){
|
||||
switch( eOpt ){
|
||||
case LSM_INFO_DB_STRUCTURE:
|
||||
case LSM_INFO_FREELIST:
|
||||
rc = lsm_info(pDb, eOpt, &z);
|
||||
break;
|
||||
case LSM_INFO_ARRAY_STRUCTURE:
|
||||
case LSM_INFO_ARRAY_PAGES:
|
||||
case LSM_INFO_PAGE_ASCII_DUMP:
|
||||
case LSM_INFO_PAGE_HEX_DUMP:
|
||||
rc = lsm_info(pDb, eOpt, iPg, &z);
|
||||
break;
|
||||
default:
|
||||
assert( !"no chance" );
|
||||
}
|
||||
|
||||
if( rc==LSM_OK ){
|
||||
printf("%s\n", z ? z : "");
|
||||
fflush(stdout);
|
||||
}
|
||||
lsm_free(lsm_get_env(pDb), z);
|
||||
}else{
|
||||
int iRes = -1;
|
||||
lsm_config(pDb, eOpt, &iRes);
|
||||
printf("%d\n", iRes);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
lsm_close(pDb);
|
||||
return rc;
|
||||
|
||||
usage:
|
||||
testPrintUsage("DATABASE ?array|page-ascii|page-hex PGNO?");
|
||||
return -1;
|
||||
}
|
254
ext/lsm1/lsm-test/lsmtest_io.c
Normal file
254
ext/lsm1/lsm-test/lsmtest_io.c
Normal file
@ -0,0 +1,254 @@
|
||||
|
||||
/*
|
||||
** SUMMARY
|
||||
**
|
||||
** This file implements the 'io' subcommand of the test program. It is used
|
||||
** for testing the performance of various combinations of write() and fsync()
|
||||
** system calls. All operations occur on a single file, which may or may not
|
||||
** exist when a test is started.
|
||||
**
|
||||
** A test consists of a series of commands. Each command is either a write
|
||||
** or an fsync. A write is specified as "<amount>@<offset>", where <amount>
|
||||
** is the amount of data written, and <offset> is the offset of the file
|
||||
** to write to. An <amount> or an <offset> is specified as an integer number
|
||||
** of bytes. Or, if postfixed with a "K", "M" or "G", an integer number of
|
||||
** KB, MB or GB, respectively. An fsync is simply "S". All commands are
|
||||
** case-insensitive.
|
||||
**
|
||||
** Example test program:
|
||||
**
|
||||
** 2M@6M 1492K@4M S 4096@4K S
|
||||
**
|
||||
** This program writes 2 MB of data starting at the offset 6MB offset of
|
||||
** the file, followed by 1492 KB of data written at the 4MB offset of the
|
||||
** file, followed by a call to fsync(), a write of 4KB of data at byte
|
||||
** offset 4096, and finally another call to fsync().
|
||||
**
|
||||
** Commands may either be specified on the command line (one command per
|
||||
** command line argument) or read from stdin. Commands read from stdin
|
||||
** must be separated by white-space.
|
||||
**
|
||||
** COMMAND LINE INVOCATION
|
||||
**
|
||||
** The sub-command implemented in this file must be invoked with at least
|
||||
** two arguments - the path to the file to write to and the page-size to
|
||||
** use for writing. If there are more than two arguments, then each
|
||||
** subsequent argument is assumed to be a test command. If there are exactly
|
||||
** two arguments, the test commands are read from stdin.
|
||||
**
|
||||
** A write command does not result in a single call to system call write().
|
||||
** Instead, the specified region is written sequentially using one or
|
||||
** more calls to write(), each of which writes not more than one page of
|
||||
** data. For example, if the page-size is 4KB, the command "2M@6M" results
|
||||
** in 512 calls to write(), each of which writes 4KB of data.
|
||||
**
|
||||
** EXAMPLES
|
||||
**
|
||||
** Two equivalent examples:
|
||||
**
|
||||
** $ lsmtest io testfile.db 4KB 2M@6M 1492K@4M S 4096@4K S
|
||||
** 3544K written in 129 ms
|
||||
** $ echo "2M@6M 1492K@4M S 4096@4K S" | lsmtest io testfile.db 4096
|
||||
** 3544K written in 127 ms
|
||||
**
|
||||
*/
|
||||
|
||||
#include "lsmtest.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
|
||||
typedef struct IoContext IoContext;
|
||||
|
||||
struct IoContext {
|
||||
int fd;
|
||||
int nWrite;
|
||||
};
|
||||
|
||||
/*
|
||||
** As isspace(3)
|
||||
*/
|
||||
static int safe_isspace(char c){
|
||||
if( c&0x80) return 0;
|
||||
return isspace(c);
|
||||
}
|
||||
|
||||
/*
|
||||
** As isdigit(3)
|
||||
*/
|
||||
static int safe_isdigit(char c){
|
||||
if( c&0x80) return 0;
|
||||
return isdigit(c);
|
||||
}
|
||||
|
||||
static i64 getNextSize(char *zIn, char **pzOut, int *pRc){
|
||||
i64 iRet = 0;
|
||||
if( *pRc==0 ){
|
||||
char *z = zIn;
|
||||
|
||||
if( !safe_isdigit(*z) ){
|
||||
*pRc = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Process digits */
|
||||
while( safe_isdigit(*z) ){
|
||||
iRet = iRet*10 + (*z - '0');
|
||||
z++;
|
||||
}
|
||||
|
||||
/* Process suffix */
|
||||
switch( *z ){
|
||||
case 'k': case 'K':
|
||||
iRet = iRet * 1024;
|
||||
z++;
|
||||
break;
|
||||
|
||||
case 'm': case 'M':
|
||||
iRet = iRet * 1024 * 1024;
|
||||
z++;
|
||||
break;
|
||||
|
||||
case 'g': case 'G':
|
||||
iRet = iRet * 1024 * 1024 * 1024;
|
||||
z++;
|
||||
break;
|
||||
}
|
||||
|
||||
if( pzOut ) *pzOut = z;
|
||||
}
|
||||
return iRet;
|
||||
}
|
||||
|
||||
static int doOneCmd(
|
||||
IoContext *pCtx,
|
||||
u8 *aData,
|
||||
int pgsz,
|
||||
char *zCmd,
|
||||
char **pzOut
|
||||
){
|
||||
char c;
|
||||
char *z = zCmd;
|
||||
|
||||
while( safe_isspace(*z) ) z++;
|
||||
c = *z;
|
||||
|
||||
if( c==0 ){
|
||||
if( pzOut ) *pzOut = z;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if( c=='s' || c=='S' ){
|
||||
if( pzOut ) *pzOut = &z[1];
|
||||
return fdatasync(pCtx->fd);
|
||||
}
|
||||
|
||||
if( safe_isdigit(c) ){
|
||||
i64 iOff = 0;
|
||||
int nByte = 0;
|
||||
int rc = 0;
|
||||
int nPg;
|
||||
int iPg;
|
||||
|
||||
nByte = getNextSize(z, &z, &rc);
|
||||
if( rc || *z!='@' ) goto bad_command;
|
||||
z++;
|
||||
iOff = getNextSize(z, &z, &rc);
|
||||
if( rc || (safe_isspace(*z)==0 && *z!='\0') ) goto bad_command;
|
||||
if( pzOut ) *pzOut = z;
|
||||
|
||||
nPg = (nByte+pgsz-1) / pgsz;
|
||||
lseek(pCtx->fd, iOff, SEEK_SET);
|
||||
for(iPg=0; iPg<nPg; iPg++){
|
||||
write(pCtx->fd, aData, pgsz);
|
||||
}
|
||||
pCtx->nWrite += nByte/1024;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bad_command:
|
||||
testPrintError("unrecognized command: %s", zCmd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int readStdin(char **pzOut){
|
||||
int nAlloc = 128;
|
||||
char *zOut = 0;
|
||||
int nOut = 0;
|
||||
|
||||
while( !feof(stdin) ){
|
||||
int nRead;
|
||||
|
||||
nAlloc = nAlloc*2;
|
||||
zOut = realloc(zOut, nAlloc);
|
||||
nRead = fread(&zOut[nOut], 1, nAlloc-nOut-1, stdin);
|
||||
|
||||
if( nRead==0 ) break;
|
||||
nOut += nRead;
|
||||
zOut[nOut] = '\0';
|
||||
}
|
||||
|
||||
*pzOut = zOut;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int do_io(int nArg, char **azArg){
|
||||
IoContext ctx;
|
||||
int pgsz;
|
||||
char *zFile;
|
||||
char *zPgsz;
|
||||
int i;
|
||||
int rc = 0;
|
||||
|
||||
char *zStdin = 0;
|
||||
char *z;
|
||||
|
||||
u8 *aData;
|
||||
|
||||
memset(&ctx, 0, sizeof(IoContext));
|
||||
if( nArg<2 ){
|
||||
testPrintUsage("FILE PGSZ ?CMD-1 ...?");
|
||||
return -1;
|
||||
}
|
||||
zFile = azArg[0];
|
||||
zPgsz = azArg[1];
|
||||
|
||||
pgsz = getNextSize(zPgsz, 0, &rc);
|
||||
if( pgsz<=0 ){
|
||||
testPrintError("Ridiculous page size: %d", pgsz);
|
||||
return -1;
|
||||
}
|
||||
aData = malloc(pgsz);
|
||||
memset(aData, 0x77, pgsz);
|
||||
|
||||
ctx.fd = open(zFile, O_RDWR|O_CREAT, 0644);
|
||||
if( ctx.fd<0 ){
|
||||
perror("open: ");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if( nArg==2 ){
|
||||
readStdin(&zStdin);
|
||||
testTimeInit();
|
||||
z = zStdin;
|
||||
while( *z && rc==0 ){
|
||||
rc = doOneCmd(&ctx, aData, pgsz, z, &z);
|
||||
}
|
||||
}else{
|
||||
testTimeInit();
|
||||
for(i=2; i<nArg; i++){
|
||||
rc = doOneCmd(&ctx, aData, pgsz, azArg[i], 0);
|
||||
}
|
||||
}
|
||||
|
||||
printf("%dK written in %d ms\n", ctx.nWrite, testTimeGet());
|
||||
|
||||
free(zStdin);
|
||||
close(ctx.fd);
|
||||
|
||||
return 0;
|
||||
}
|
1544
ext/lsm1/lsm-test/lsmtest_main.c
Normal file
1544
ext/lsm1/lsm-test/lsmtest_main.c
Normal file
File diff suppressed because it is too large
Load Diff
409
ext/lsm1/lsm-test/lsmtest_mem.c
Normal file
409
ext/lsm1/lsm-test/lsmtest_mem.c
Normal file
@ -0,0 +1,409 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#define ArraySize(x) ((int)(sizeof(x) / sizeof((x)[0])))
|
||||
|
||||
#define MIN(x,y) ((x)<(y) ? (x) : (y))
|
||||
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned char u8;
|
||||
typedef long long int i64;
|
||||
typedef unsigned long long int u64;
|
||||
|
||||
#if defined(__GLIBC__) && defined(LSM_DEBUG_MEM)
|
||||
extern int backtrace(void**,int);
|
||||
extern void backtrace_symbols_fd(void*const*,int,int);
|
||||
# define TM_BACKTRACE 12
|
||||
#else
|
||||
# define backtrace(A,B) 1
|
||||
# define backtrace_symbols_fd(A,B,C)
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct TmBlockHdr TmBlockHdr;
|
||||
typedef struct TmAgg TmAgg;
|
||||
typedef struct TmGlobal TmGlobal;
|
||||
|
||||
struct TmGlobal {
|
||||
/* Linked list of all currently outstanding allocations. And a table of
|
||||
** all allocations, past and present, indexed by backtrace() info. */
|
||||
TmBlockHdr *pFirst;
|
||||
#ifdef TM_BACKTRACE
|
||||
TmAgg *aHash[10000];
|
||||
#endif
|
||||
|
||||
/* Underlying malloc/realloc/free functions */
|
||||
void *(*xMalloc)(int); /* underlying malloc(3) function */
|
||||
void *(*xRealloc)(void *, int); /* underlying realloc(3) function */
|
||||
void (*xFree)(void *); /* underlying free(3) function */
|
||||
|
||||
/* Mutex to protect pFirst and aHash */
|
||||
void (*xEnterMutex)(TmGlobal*); /* Call this to enter the mutex */
|
||||
void (*xLeaveMutex)(TmGlobal*); /* Call this to leave mutex */
|
||||
void (*xDelMutex)(TmGlobal*); /* Call this to delete mutex */
|
||||
void *pMutex; /* Mutex handle */
|
||||
|
||||
void *xSaveMalloc;
|
||||
void *xSaveRealloc;
|
||||
void *xSaveFree;
|
||||
|
||||
/* OOM injection scheduling. If nCountdown is greater than zero when a
|
||||
** malloc attempt is made, it is decremented. If this means nCountdown
|
||||
** transitions from 1 to 0, then the allocation fails. If bPersist is true
|
||||
** when this happens, nCountdown is then incremented back to 1 (so that the
|
||||
** next attempt fails too).
|
||||
*/
|
||||
int nCountdown;
|
||||
int bPersist;
|
||||
int bEnable;
|
||||
void (*xHook)(void *);
|
||||
void *pHookCtx;
|
||||
};
|
||||
|
||||
struct TmBlockHdr {
|
||||
TmBlockHdr *pNext;
|
||||
TmBlockHdr *pPrev;
|
||||
int nByte;
|
||||
#ifdef TM_BACKTRACE
|
||||
TmAgg *pAgg;
|
||||
#endif
|
||||
u32 iForeGuard;
|
||||
};
|
||||
|
||||
#ifdef TM_BACKTRACE
|
||||
struct TmAgg {
|
||||
int nAlloc; /* Number of allocations at this path */
|
||||
int nByte; /* Total number of bytes allocated */
|
||||
int nOutAlloc; /* Number of outstanding allocations */
|
||||
int nOutByte; /* Number of outstanding bytes */
|
||||
void *aFrame[TM_BACKTRACE]; /* backtrace() output */
|
||||
TmAgg *pNext; /* Next object in hash-table collision */
|
||||
};
|
||||
#endif
|
||||
|
||||
#define FOREGUARD 0x80F5E153
|
||||
#define REARGUARD 0xE4676B53
|
||||
static const u32 rearguard = REARGUARD;
|
||||
|
||||
#define ROUND8(x) (((x)+7)&~7)
|
||||
|
||||
#define BLOCK_HDR_SIZE (ROUND8( sizeof(TmBlockHdr) ))
|
||||
|
||||
static void lsmtest_oom_error(void){
|
||||
static int nErr = 0;
|
||||
nErr++;
|
||||
}
|
||||
|
||||
static void tmEnterMutex(TmGlobal *pTm){
|
||||
pTm->xEnterMutex(pTm);
|
||||
}
|
||||
static void tmLeaveMutex(TmGlobal *pTm){
|
||||
pTm->xLeaveMutex(pTm);
|
||||
}
|
||||
|
||||
static void *tmMalloc(TmGlobal *pTm, int nByte){
|
||||
TmBlockHdr *pNew; /* New allocation header block */
|
||||
u8 *pUser; /* Return value */
|
||||
int nReq; /* Total number of bytes requested */
|
||||
|
||||
assert( sizeof(rearguard)==4 );
|
||||
nReq = BLOCK_HDR_SIZE + nByte + 4;
|
||||
pNew = (TmBlockHdr *)pTm->xMalloc(nReq);
|
||||
memset(pNew, 0, sizeof(TmBlockHdr));
|
||||
|
||||
tmEnterMutex(pTm);
|
||||
assert( pTm->nCountdown>=0 );
|
||||
assert( pTm->bPersist==0 || pTm->bPersist==1 );
|
||||
|
||||
if( pTm->bEnable && pTm->nCountdown==1 ){
|
||||
/* Simulate an OOM error. */
|
||||
lsmtest_oom_error();
|
||||
pTm->xFree(pNew);
|
||||
pTm->nCountdown = pTm->bPersist;
|
||||
if( pTm->xHook ) pTm->xHook(pTm->pHookCtx);
|
||||
pUser = 0;
|
||||
}else{
|
||||
if( pTm->bEnable && pTm->nCountdown ) pTm->nCountdown--;
|
||||
|
||||
pNew->iForeGuard = FOREGUARD;
|
||||
pNew->nByte = nByte;
|
||||
pNew->pNext = pTm->pFirst;
|
||||
|
||||
if( pTm->pFirst ){
|
||||
pTm->pFirst->pPrev = pNew;
|
||||
}
|
||||
pTm->pFirst = pNew;
|
||||
|
||||
pUser = &((u8 *)pNew)[BLOCK_HDR_SIZE];
|
||||
memset(pUser, 0x56, nByte);
|
||||
memcpy(&pUser[nByte], &rearguard, 4);
|
||||
|
||||
#ifdef TM_BACKTRACE
|
||||
{
|
||||
TmAgg *pAgg;
|
||||
int i;
|
||||
u32 iHash = 0;
|
||||
void *aFrame[TM_BACKTRACE];
|
||||
memset(aFrame, 0, sizeof(aFrame));
|
||||
backtrace(aFrame, TM_BACKTRACE);
|
||||
|
||||
for(i=0; i<ArraySize(aFrame); i++){
|
||||
iHash += (u64)(aFrame[i]) + (iHash<<3);
|
||||
}
|
||||
iHash = iHash % ArraySize(pTm->aHash);
|
||||
|
||||
for(pAgg=pTm->aHash[iHash]; pAgg; pAgg=pAgg->pNext){
|
||||
if( memcmp(pAgg->aFrame, aFrame, sizeof(aFrame))==0 ) break;
|
||||
}
|
||||
if( !pAgg ){
|
||||
pAgg = (TmAgg *)pTm->xMalloc(sizeof(TmAgg));
|
||||
memset(pAgg, 0, sizeof(TmAgg));
|
||||
memcpy(pAgg->aFrame, aFrame, sizeof(aFrame));
|
||||
pAgg->pNext = pTm->aHash[iHash];
|
||||
pTm->aHash[iHash] = pAgg;
|
||||
}
|
||||
pAgg->nAlloc++;
|
||||
pAgg->nByte += nByte;
|
||||
pAgg->nOutAlloc++;
|
||||
pAgg->nOutByte += nByte;
|
||||
pNew->pAgg = pAgg;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
tmLeaveMutex(pTm);
|
||||
return pUser;
|
||||
}
|
||||
|
||||
static void tmFree(TmGlobal *pTm, void *p){
|
||||
if( p ){
|
||||
TmBlockHdr *pHdr;
|
||||
u8 *pUser = (u8 *)p;
|
||||
|
||||
tmEnterMutex(pTm);
|
||||
pHdr = (TmBlockHdr *)&pUser[BLOCK_HDR_SIZE * -1];
|
||||
assert( pHdr->iForeGuard==FOREGUARD );
|
||||
assert( 0==memcmp(&pUser[pHdr->nByte], &rearguard, 4) );
|
||||
|
||||
if( pHdr->pPrev ){
|
||||
assert( pHdr->pPrev->pNext==pHdr );
|
||||
pHdr->pPrev->pNext = pHdr->pNext;
|
||||
}else{
|
||||
assert( pHdr==pTm->pFirst );
|
||||
pTm->pFirst = pHdr->pNext;
|
||||
}
|
||||
if( pHdr->pNext ){
|
||||
assert( pHdr->pNext->pPrev==pHdr );
|
||||
pHdr->pNext->pPrev = pHdr->pPrev;
|
||||
}
|
||||
|
||||
#ifdef TM_BACKTRACE
|
||||
pHdr->pAgg->nOutAlloc--;
|
||||
pHdr->pAgg->nOutByte -= pHdr->nByte;
|
||||
#endif
|
||||
|
||||
tmLeaveMutex(pTm);
|
||||
memset(pUser, 0x58, pHdr->nByte);
|
||||
memset(pHdr, 0x57, sizeof(TmBlockHdr));
|
||||
pTm->xFree(pHdr);
|
||||
}
|
||||
}
|
||||
|
||||
static void *tmRealloc(TmGlobal *pTm, void *p, int nByte){
|
||||
void *pNew;
|
||||
|
||||
pNew = tmMalloc(pTm, nByte);
|
||||
if( pNew && p ){
|
||||
TmBlockHdr *pHdr;
|
||||
u8 *pUser = (u8 *)p;
|
||||
pHdr = (TmBlockHdr *)&pUser[BLOCK_HDR_SIZE * -1];
|
||||
memcpy(pNew, p, MIN(nByte, pHdr->nByte));
|
||||
tmFree(pTm, p);
|
||||
}
|
||||
return pNew;
|
||||
}
|
||||
|
||||
static void tmMallocOom(
|
||||
TmGlobal *pTm,
|
||||
int nCountdown,
|
||||
int bPersist,
|
||||
void (*xHook)(void *),
|
||||
void *pHookCtx
|
||||
){
|
||||
assert( nCountdown>=0 );
|
||||
assert( bPersist==0 || bPersist==1 );
|
||||
pTm->nCountdown = nCountdown;
|
||||
pTm->bPersist = bPersist;
|
||||
pTm->xHook = xHook;
|
||||
pTm->pHookCtx = pHookCtx;
|
||||
pTm->bEnable = 1;
|
||||
}
|
||||
|
||||
static void tmMallocOomEnable(
|
||||
TmGlobal *pTm,
|
||||
int bEnable
|
||||
){
|
||||
pTm->bEnable = bEnable;
|
||||
}
|
||||
|
||||
static void tmMallocCheck(
|
||||
TmGlobal *pTm,
|
||||
int *pnLeakAlloc,
|
||||
int *pnLeakByte,
|
||||
FILE *pFile
|
||||
){
|
||||
TmBlockHdr *pHdr;
|
||||
int nLeak = 0;
|
||||
int nByte = 0;
|
||||
|
||||
if( pTm==0 ) return;
|
||||
|
||||
for(pHdr=pTm->pFirst; pHdr; pHdr=pHdr->pNext){
|
||||
nLeak++;
|
||||
nByte += pHdr->nByte;
|
||||
}
|
||||
if( pnLeakAlloc ) *pnLeakAlloc = nLeak;
|
||||
if( pnLeakByte ) *pnLeakByte = nByte;
|
||||
|
||||
#ifdef TM_BACKTRACE
|
||||
if( pFile ){
|
||||
int i;
|
||||
fprintf(pFile, "LEAKS\n");
|
||||
for(i=0; i<ArraySize(pTm->aHash); i++){
|
||||
TmAgg *pAgg;
|
||||
for(pAgg=pTm->aHash[i]; pAgg; pAgg=pAgg->pNext){
|
||||
if( pAgg->nOutAlloc ){
|
||||
int j;
|
||||
fprintf(pFile, "%d %d ", pAgg->nOutByte, pAgg->nOutAlloc);
|
||||
for(j=0; j<TM_BACKTRACE; j++){
|
||||
fprintf(pFile, "%p ", pAgg->aFrame[j]);
|
||||
}
|
||||
fprintf(pFile, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
fprintf(pFile, "\nALLOCATIONS\n");
|
||||
for(i=0; i<ArraySize(pTm->aHash); i++){
|
||||
TmAgg *pAgg;
|
||||
for(pAgg=pTm->aHash[i]; pAgg; pAgg=pAgg->pNext){
|
||||
int j;
|
||||
fprintf(pFile, "%d %d ", pAgg->nByte, pAgg->nAlloc);
|
||||
for(j=0; j<TM_BACKTRACE; j++) fprintf(pFile, "%p ", pAgg->aFrame[j]);
|
||||
fprintf(pFile, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)pFile;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#include "lsm.h"
|
||||
#include "stdlib.h"
|
||||
|
||||
typedef struct LsmMutex LsmMutex;
|
||||
struct LsmMutex {
|
||||
lsm_env *pEnv;
|
||||
lsm_mutex *pMutex;
|
||||
};
|
||||
|
||||
static void tmLsmMutexEnter(TmGlobal *pTm){
|
||||
LsmMutex *p = (LsmMutex *)pTm->pMutex;
|
||||
p->pEnv->xMutexEnter(p->pMutex);
|
||||
}
|
||||
static void tmLsmMutexLeave(TmGlobal *pTm){
|
||||
LsmMutex *p = (LsmMutex *)(pTm->pMutex);
|
||||
p->pEnv->xMutexLeave(p->pMutex);
|
||||
}
|
||||
static void tmLsmMutexDel(TmGlobal *pTm){
|
||||
LsmMutex *p = (LsmMutex *)pTm->pMutex;
|
||||
pTm->xFree(p);
|
||||
}
|
||||
static void *tmLsmMalloc(int n){ return malloc(n); }
|
||||
static void tmLsmFree(void *ptr){ free(ptr); }
|
||||
static void *tmLsmRealloc(void *ptr, int n){ return realloc(ptr, n); }
|
||||
|
||||
static void *tmLsmEnvMalloc(lsm_env *p, int n){
|
||||
return tmMalloc((TmGlobal *)(p->pMemCtx), n);
|
||||
}
|
||||
static void tmLsmEnvFree(lsm_env *p, void *ptr){
|
||||
tmFree((TmGlobal *)(p->pMemCtx), ptr);
|
||||
}
|
||||
static void *tmLsmEnvRealloc(lsm_env *p, void *ptr, int n){
|
||||
return tmRealloc((TmGlobal *)(p->pMemCtx), ptr, n);
|
||||
}
|
||||
|
||||
void testMallocInstall(lsm_env *pEnv){
|
||||
TmGlobal *pGlobal;
|
||||
LsmMutex *pMutex;
|
||||
assert( pEnv->pMemCtx==0 );
|
||||
|
||||
/* Allocate and populate a TmGlobal structure. */
|
||||
pGlobal = (TmGlobal *)tmLsmMalloc(sizeof(TmGlobal));
|
||||
memset(pGlobal, 0, sizeof(TmGlobal));
|
||||
pGlobal->xMalloc = tmLsmMalloc;
|
||||
pGlobal->xRealloc = tmLsmRealloc;
|
||||
pGlobal->xFree = tmLsmFree;
|
||||
pMutex = (LsmMutex *)pGlobal->xMalloc(sizeof(LsmMutex));
|
||||
pMutex->pEnv = pEnv;
|
||||
pEnv->xMutexStatic(pEnv, LSM_MUTEX_HEAP, &pMutex->pMutex);
|
||||
pGlobal->xEnterMutex = tmLsmMutexEnter;
|
||||
pGlobal->xLeaveMutex = tmLsmMutexLeave;
|
||||
pGlobal->xDelMutex = tmLsmMutexDel;
|
||||
pGlobal->pMutex = (void *)pMutex;
|
||||
|
||||
pGlobal->xSaveMalloc = (void *)pEnv->xMalloc;
|
||||
pGlobal->xSaveRealloc = (void *)pEnv->xRealloc;
|
||||
pGlobal->xSaveFree = (void *)pEnv->xFree;
|
||||
|
||||
/* Set up pEnv to the use the new TmGlobal */
|
||||
pEnv->pMemCtx = (void *)pGlobal;
|
||||
pEnv->xMalloc = tmLsmEnvMalloc;
|
||||
pEnv->xRealloc = tmLsmEnvRealloc;
|
||||
pEnv->xFree = tmLsmEnvFree;
|
||||
}
|
||||
|
||||
void testMallocUninstall(lsm_env *pEnv){
|
||||
TmGlobal *p = (TmGlobal *)pEnv->pMemCtx;
|
||||
pEnv->pMemCtx = 0;
|
||||
if( p ){
|
||||
pEnv->xMalloc = (void *(*)(lsm_env*, int))(p->xSaveMalloc);
|
||||
pEnv->xRealloc = (void *(*)(lsm_env*, void*, int))(p->xSaveRealloc);
|
||||
pEnv->xFree = (void (*)(lsm_env*, void*))(p->xSaveFree);
|
||||
p->xDelMutex(p);
|
||||
tmLsmFree(p);
|
||||
}
|
||||
}
|
||||
|
||||
void testMallocCheck(
|
||||
lsm_env *pEnv,
|
||||
int *pnLeakAlloc,
|
||||
int *pnLeakByte,
|
||||
FILE *pFile
|
||||
){
|
||||
if( pEnv->pMemCtx==0 ){
|
||||
*pnLeakAlloc = 0;
|
||||
*pnLeakByte = 0;
|
||||
}else{
|
||||
tmMallocCheck((TmGlobal *)(pEnv->pMemCtx), pnLeakAlloc, pnLeakByte, pFile);
|
||||
}
|
||||
}
|
||||
|
||||
void testMallocOom(
|
||||
lsm_env *pEnv,
|
||||
int nCountdown,
|
||||
int bPersist,
|
||||
void (*xHook)(void *),
|
||||
void *pHookCtx
|
||||
){
|
||||
TmGlobal *pTm = (TmGlobal *)(pEnv->pMemCtx);
|
||||
tmMallocOom(pTm, nCountdown, bPersist, xHook, pHookCtx);
|
||||
}
|
||||
|
||||
void testMallocOomEnable(lsm_env *pEnv, int bEnable){
|
||||
TmGlobal *pTm = (TmGlobal *)(pEnv->pMemCtx);
|
||||
tmMallocOomEnable(pTm, bEnable);
|
||||
}
|
827
ext/lsm1/lsm-test/lsmtest_tdb.c
Normal file
827
ext/lsm1/lsm-test/lsmtest_tdb.c
Normal file
@ -0,0 +1,827 @@
|
||||
|
||||
/*
|
||||
** 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>
|
||||
#include <unistd.h>
|
||||
#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);
|
||||
|
||||
*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 },
|
||||
#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;
|
||||
}
|
||||
|
||||
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( 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.
|
||||
*************************************************************************/
|
167
ext/lsm1/lsm-test/lsmtest_tdb.h
Normal file
167
ext/lsm1/lsm-test/lsmtest_tdb.h
Normal file
@ -0,0 +1,167 @@
|
||||
|
||||
/*
|
||||
** This file is the interface to a very simple database library used for
|
||||
** testing. The interface is similar to that of the LSM. The main virtue
|
||||
** of this library is that the same API may be used to access a key-value
|
||||
** store implemented by LSM, SQLite or another database system. Which
|
||||
** makes it easy to use for correctness and performance tests.
|
||||
*/
|
||||
|
||||
#ifndef __WRAPPER_H_
|
||||
#define __WRAPPER_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "lsm.h"
|
||||
|
||||
typedef struct TestDb TestDb;
|
||||
|
||||
/*
|
||||
** Open a new database connection. The first argument is the name of the
|
||||
** database library to use. e.g. something like:
|
||||
**
|
||||
** "sqlite3"
|
||||
** "lsm"
|
||||
**
|
||||
** See function tdb_system_name() for a list of available database systems.
|
||||
**
|
||||
** The second argument is the name of the database to open (e.g. a filename).
|
||||
**
|
||||
** If the third parameter is non-zero, then any existing database by the
|
||||
** name of zDb is removed before opening a new one. If it is zero, then an
|
||||
** existing database may be opened.
|
||||
*/
|
||||
int tdb_open(const char *zLibrary, const char *zDb, int bClear, TestDb **ppDb);
|
||||
|
||||
/*
|
||||
** Close a database handle.
|
||||
*/
|
||||
int tdb_close(TestDb *pDb);
|
||||
|
||||
/*
|
||||
** Write a new key/value into the database.
|
||||
*/
|
||||
int tdb_write(TestDb *pDb, void *pKey, int nKey, void *pVal, int nVal);
|
||||
|
||||
/*
|
||||
** Delete a key from the database.
|
||||
*/
|
||||
int tdb_delete(TestDb *pDb, void *pKey, int nKey);
|
||||
|
||||
/*
|
||||
** Delete a range of keys from the database.
|
||||
*/
|
||||
int tdb_delete_range(TestDb *, void *pKey1, int nKey1, void *pKey2, int nKey2);
|
||||
|
||||
/*
|
||||
** Query the database for key (pKey/nKey). If no entry is found, set *ppVal
|
||||
** to 0 and *pnVal to -1 before returning. Otherwise, set *ppVal and *pnVal
|
||||
** to a pointer to and size of the value associated with (pKey/nKey).
|
||||
*/
|
||||
int tdb_fetch(TestDb *pDb, void *pKey, int nKey, void **ppVal, int *pnVal);
|
||||
|
||||
/*
|
||||
** Open and close nested transactions. Currently, these functions only
|
||||
** work for SQLite3 and LSM systems. Use the tdb_transaction_support()
|
||||
** function to determine if a given TestDb handle supports these methods.
|
||||
**
|
||||
** These functions and the iLevel parameter follow the same conventions as
|
||||
** the SQLite 4 transaction interface. Note that this is slightly different
|
||||
** from the way LSM does things. As follows:
|
||||
**
|
||||
** tdb_begin():
|
||||
** A successful call to tdb_begin() with (iLevel>1) guarantees that
|
||||
** there are at least (iLevel-1) write transactions open. If iLevel==1,
|
||||
** then it guarantees that at least a read-transaction is open. Calling
|
||||
** tdb_begin() with iLevel==0 is a no-op.
|
||||
**
|
||||
** tdb_commit():
|
||||
** A successful call to tdb_commit() with (iLevel>1) guarantees that
|
||||
** there are at most (iLevel-1) write transactions open. If iLevel==1,
|
||||
** then it guarantees that there are no write transactions open (although
|
||||
** a read-transaction may remain open). Calling tdb_commit() with
|
||||
** iLevel==0 ensures that all transactions, read or write, have been
|
||||
** closed and committed.
|
||||
**
|
||||
** tdb_rollback():
|
||||
** This call is similar to tdb_commit(), except that instead of committing
|
||||
** transactions, it reverts them. For example, calling tdb_rollback() with
|
||||
** iLevel==2 ensures that there is at most one write transaction open, and
|
||||
** restores the database to the state that it was in when that transaction
|
||||
** was opened.
|
||||
**
|
||||
** In other words, tdb_commit() just closes transactions - tdb_rollback()
|
||||
** closes transactions and then restores the database to the state it
|
||||
** was in before those transactions were even opened.
|
||||
*/
|
||||
int tdb_begin(TestDb *pDb, int iLevel);
|
||||
int tdb_commit(TestDb *pDb, int iLevel);
|
||||
int tdb_rollback(TestDb *pDb, int iLevel);
|
||||
|
||||
/*
|
||||
** Return true if transactions are supported, or false otherwise.
|
||||
*/
|
||||
int tdb_transaction_support(TestDb *pDb);
|
||||
|
||||
/*
|
||||
** Return the name of the database library (as passed to tdb_open()) used
|
||||
** by the handled passed as the first argument.
|
||||
*/
|
||||
const char *tdb_library_name(TestDb *pDb);
|
||||
|
||||
/*
|
||||
** Scan a range of database keys. Invoke the callback function for each
|
||||
** key visited.
|
||||
*/
|
||||
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)
|
||||
);
|
||||
|
||||
const char *tdb_system_name(int i);
|
||||
|
||||
int tdb_lsm_open(const char *zCfg, const char *zDb, int bClear, TestDb **ppDb);
|
||||
|
||||
/*
|
||||
** If the TestDb handle passed as an argument is a wrapper around an LSM
|
||||
** database, return the LSM handle. Otherwise, if the argument is some other
|
||||
** database system, return NULL.
|
||||
*/
|
||||
lsm_db *tdb_lsm(TestDb *pDb);
|
||||
|
||||
/*
|
||||
** Return a pointer to the lsm_env object used by all lsm database
|
||||
** connections initialized as a copy of the object returned by
|
||||
** lsm_default_env(). It may be modified (e.g. to override functions)
|
||||
** if the caller can guarantee that it is not already in use.
|
||||
*/
|
||||
lsm_env *tdb_lsm_env(void);
|
||||
|
||||
/*
|
||||
** The following functions only work with LSM database handles. It is
|
||||
** illegal to call them with any other type of database handle specified
|
||||
** as an argument.
|
||||
*/
|
||||
void tdb_lsm_enable_log(TestDb *pDb, int bEnable);
|
||||
void tdb_lsm_application_crash(TestDb *pDb);
|
||||
void tdb_lsm_prepare_system_crash(TestDb *pDb);
|
||||
void tdb_lsm_system_crash(TestDb *pDb);
|
||||
void tdb_lsm_prepare_sync_crash(TestDb *pDb, int iSync);
|
||||
|
||||
|
||||
void tdb_lsm_safety(TestDb *pDb, int eMode);
|
||||
void tdb_lsm_config_work_hook(TestDb *pDb, void (*)(lsm_db *, void *), void *);
|
||||
void tdb_lsm_write_hook(TestDb *, void(*)(void*,int,lsm_i64,int,int), void*);
|
||||
int tdb_lsm_config_str(TestDb *pDb, const char *zStr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* End of the 'extern "C"' block */
|
||||
#endif
|
||||
|
||||
#endif
|
370
ext/lsm1/lsm-test/lsmtest_tdb2.cc
Normal file
370
ext/lsm1/lsm-test/lsmtest_tdb2.cc
Normal file
@ -0,0 +1,370 @@
|
||||
|
||||
|
||||
#include "lsmtest.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef HAVE_KYOTOCABINET
|
||||
#include "kcpolydb.h"
|
||||
extern "C" {
|
||||
struct KcDb {
|
||||
TestDb base;
|
||||
kyotocabinet::TreeDB* db;
|
||||
char *pVal;
|
||||
};
|
||||
}
|
||||
|
||||
int test_kc_open(const char *zFilename, int bClear, TestDb **ppDb){
|
||||
KcDb *pKcDb;
|
||||
int ok;
|
||||
int rc = 0;
|
||||
|
||||
if( bClear ){
|
||||
char *zCmd = sqlite3_mprintf("rm -rf %s\n", zFilename);
|
||||
system(zCmd);
|
||||
sqlite3_free(zCmd);
|
||||
}
|
||||
|
||||
pKcDb = (KcDb *)malloc(sizeof(KcDb));
|
||||
memset(pKcDb, 0, sizeof(KcDb));
|
||||
|
||||
|
||||
pKcDb->db = new kyotocabinet::TreeDB();
|
||||
pKcDb->db->tune_page(TESTDB_DEFAULT_PAGE_SIZE);
|
||||
pKcDb->db->tune_page_cache(
|
||||
TESTDB_DEFAULT_PAGE_SIZE * TESTDB_DEFAULT_CACHE_SIZE
|
||||
);
|
||||
ok = pKcDb->db->open(zFilename,
|
||||
kyotocabinet::PolyDB::OWRITER | kyotocabinet::PolyDB::OCREATE
|
||||
);
|
||||
if( ok==0 ){
|
||||
free(pKcDb);
|
||||
pKcDb = 0;
|
||||
rc = 1;
|
||||
}
|
||||
|
||||
*ppDb = (TestDb *)pKcDb;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int test_kc_close(TestDb *pDb){
|
||||
KcDb *pKcDb = (KcDb *)pDb;
|
||||
if( pKcDb->pVal ){
|
||||
delete [] pKcDb->pVal;
|
||||
}
|
||||
pKcDb->db->close();
|
||||
delete pKcDb->db;
|
||||
free(pKcDb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_kc_write(TestDb *pDb, void *pKey, int nKey, void *pVal, int nVal){
|
||||
KcDb *pKcDb = (KcDb *)pDb;
|
||||
int ok;
|
||||
|
||||
ok = pKcDb->db->set((const char *)pKey, nKey, (const char *)pVal, nVal);
|
||||
return (ok ? 0 : 1);
|
||||
}
|
||||
|
||||
int test_kc_delete(TestDb *pDb, void *pKey, int nKey){
|
||||
KcDb *pKcDb = (KcDb *)pDb;
|
||||
int ok;
|
||||
|
||||
ok = pKcDb->db->remove((const char *)pKey, nKey);
|
||||
return (ok ? 0 : 1);
|
||||
}
|
||||
|
||||
int test_kc_delete_range(
|
||||
TestDb *pDb,
|
||||
void *pKey1, int nKey1,
|
||||
void *pKey2, int nKey2
|
||||
){
|
||||
int res;
|
||||
KcDb *pKcDb = (KcDb *)pDb;
|
||||
kyotocabinet::DB::Cursor* pCur = pKcDb->db->cursor();
|
||||
|
||||
if( pKey1 ){
|
||||
res = pCur->jump((const char *)pKey1, nKey1);
|
||||
}else{
|
||||
res = pCur->jump();
|
||||
}
|
||||
|
||||
while( 1 ){
|
||||
const char *pKey; size_t nKey;
|
||||
const char *pVal; size_t nVal;
|
||||
|
||||
pKey = pCur->get(&nKey, &pVal, &nVal);
|
||||
if( pKey==0 ) break;
|
||||
|
||||
#ifndef NDEBUG
|
||||
if( pKey1 ){
|
||||
res = memcmp(pKey, pKey1, MIN((size_t)nKey1, nKey));
|
||||
assert( res>0 || (res==0 && nKey>nKey1) );
|
||||
}
|
||||
#endif
|
||||
|
||||
if( pKey2 ){
|
||||
res = memcmp(pKey, pKey2, MIN((size_t)nKey2, nKey));
|
||||
if( res>0 || (res==0 && (size_t)nKey2<nKey) ){
|
||||
delete [] pKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pCur->remove();
|
||||
delete [] pKey;
|
||||
}
|
||||
|
||||
delete pCur;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_kc_fetch(
|
||||
TestDb *pDb,
|
||||
void *pKey,
|
||||
int nKey,
|
||||
void **ppVal,
|
||||
int *pnVal
|
||||
){
|
||||
KcDb *pKcDb = (KcDb *)pDb;
|
||||
size_t nVal;
|
||||
|
||||
if( pKcDb->pVal ){
|
||||
delete [] pKcDb->pVal;
|
||||
pKcDb->pVal = 0;
|
||||
}
|
||||
|
||||
pKcDb->pVal = pKcDb->db->get((const char *)pKey, nKey, &nVal);
|
||||
if( pKcDb->pVal ){
|
||||
*ppVal = pKcDb->pVal;
|
||||
*pnVal = nVal;
|
||||
}else{
|
||||
*ppVal = 0;
|
||||
*pnVal = -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_kc_scan(
|
||||
TestDb *pDb, /* Database handle */
|
||||
void *pCtx, /* Context pointer to pass to xCallback */
|
||||
int bReverse, /* True for a reverse order scan */
|
||||
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)
|
||||
){
|
||||
KcDb *pKcDb = (KcDb *)pDb;
|
||||
kyotocabinet::DB::Cursor* pCur = pKcDb->db->cursor();
|
||||
int res;
|
||||
|
||||
if( bReverse==0 ){
|
||||
if( pKey1 ){
|
||||
res = pCur->jump((const char *)pKey1, nKey1);
|
||||
}else{
|
||||
res = pCur->jump();
|
||||
}
|
||||
}else{
|
||||
if( pKey2 ){
|
||||
res = pCur->jump_back((const char *)pKey2, nKey2);
|
||||
}else{
|
||||
res = pCur->jump_back();
|
||||
}
|
||||
}
|
||||
|
||||
while( res ){
|
||||
const char *pKey; size_t nKey;
|
||||
const char *pVal; size_t nVal;
|
||||
pKey = pCur->get(&nKey, &pVal, &nVal);
|
||||
|
||||
if( bReverse==0 && pKey2 ){
|
||||
res = memcmp(pKey, pKey2, MIN((size_t)nKey2, nKey));
|
||||
if( res>0 || (res==0 && (size_t)nKey2<nKey) ){
|
||||
delete [] pKey;
|
||||
break;
|
||||
}
|
||||
}else if( bReverse!=0 && pKey1 ){
|
||||
res = memcmp(pKey, pKey1, MIN((size_t)nKey1, nKey));
|
||||
if( res<0 || (res==0 && (size_t)nKey1>nKey) ){
|
||||
delete [] pKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
xCallback(pCtx, (void *)pKey, (int)nKey, (void *)pVal, (int)nVal);
|
||||
delete [] pKey;
|
||||
|
||||
if( bReverse ){
|
||||
res = pCur->step_back();
|
||||
}else{
|
||||
res = pCur->step();
|
||||
}
|
||||
}
|
||||
|
||||
delete pCur;
|
||||
return 0;
|
||||
}
|
||||
#endif /* HAVE_KYOTOCABINET */
|
||||
|
||||
#ifdef HAVE_MDB
|
||||
#include "lmdb.h"
|
||||
|
||||
extern "C" {
|
||||
struct MdbDb {
|
||||
TestDb base;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
};
|
||||
}
|
||||
|
||||
int test_mdb_open(
|
||||
const char *zSpec,
|
||||
const char *zFilename,
|
||||
int bClear,
|
||||
TestDb **ppDb
|
||||
){
|
||||
MDB_txn *txn;
|
||||
MdbDb *pMdb;
|
||||
int rc;
|
||||
|
||||
if( bClear ){
|
||||
char *zCmd = sqlite3_mprintf("rm -rf %s\n", zFilename);
|
||||
system(zCmd);
|
||||
sqlite3_free(zCmd);
|
||||
}
|
||||
|
||||
pMdb = (MdbDb *)malloc(sizeof(MdbDb));
|
||||
memset(pMdb, 0, sizeof(MdbDb));
|
||||
|
||||
rc = mdb_env_create(&pMdb->env);
|
||||
if( rc==0 ) rc = mdb_env_set_mapsize(pMdb->env, 1*1024*1024*1024);
|
||||
if( rc==0 ) rc = mdb_env_open(pMdb->env, zFilename, MDB_NOSYNC|MDB_NOSUBDIR, 0600);
|
||||
if( rc==0 ) rc = mdb_txn_begin(pMdb->env, NULL, 0, &txn);
|
||||
if( rc==0 ){
|
||||
rc = mdb_open(txn, NULL, 0, &pMdb->dbi);
|
||||
mdb_txn_commit(txn);
|
||||
}
|
||||
|
||||
*ppDb = (TestDb *)pMdb;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int test_mdb_close(TestDb *pDb){
|
||||
MdbDb *pMdb = (MdbDb *)pDb;
|
||||
|
||||
mdb_close(pMdb->env, pMdb->dbi);
|
||||
mdb_env_close(pMdb->env);
|
||||
free(pMdb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_mdb_write(TestDb *pDb, void *pKey, int nKey, void *pVal, int nVal){
|
||||
int rc;
|
||||
MdbDb *pMdb = (MdbDb *)pDb;
|
||||
MDB_val val;
|
||||
MDB_val key;
|
||||
MDB_txn *txn;
|
||||
|
||||
val.mv_size = nVal;
|
||||
val.mv_data = pVal;
|
||||
key.mv_size = nKey;
|
||||
key.mv_data = pKey;
|
||||
|
||||
rc = mdb_txn_begin(pMdb->env, NULL, 0, &txn);
|
||||
if( rc==0 ){
|
||||
rc = mdb_put(txn, pMdb->dbi, &key, &val, 0);
|
||||
if( rc==0 ){
|
||||
rc = mdb_txn_commit(txn);
|
||||
}else{
|
||||
mdb_txn_abort(txn);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int test_mdb_delete(TestDb *pDb, void *pKey, int nKey){
|
||||
int rc;
|
||||
MdbDb *pMdb = (MdbDb *)pDb;
|
||||
MDB_val key;
|
||||
MDB_txn *txn;
|
||||
|
||||
key.mv_size = nKey;
|
||||
key.mv_data = pKey;
|
||||
rc = mdb_txn_begin(pMdb->env, NULL, 0, &txn);
|
||||
if( rc==0 ){
|
||||
rc = mdb_del(txn, pMdb->dbi, &key, 0);
|
||||
if( rc==0 ){
|
||||
rc = mdb_txn_commit(txn);
|
||||
}else{
|
||||
mdb_txn_abort(txn);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int test_mdb_fetch(
|
||||
TestDb *pDb,
|
||||
void *pKey,
|
||||
int nKey,
|
||||
void **ppVal,
|
||||
int *pnVal
|
||||
){
|
||||
int rc;
|
||||
MdbDb *pMdb = (MdbDb *)pDb;
|
||||
MDB_val key;
|
||||
MDB_txn *txn;
|
||||
|
||||
key.mv_size = nKey;
|
||||
key.mv_data = pKey;
|
||||
|
||||
rc = mdb_txn_begin(pMdb->env, NULL, MDB_RDONLY, &txn);
|
||||
if( rc==0 ){
|
||||
MDB_val val = {0, 0};
|
||||
rc = mdb_get(txn, pMdb->dbi, &key, &val);
|
||||
if( rc==MDB_NOTFOUND ){
|
||||
rc = 0;
|
||||
*ppVal = 0;
|
||||
*pnVal = -1;
|
||||
}else{
|
||||
*ppVal = val.mv_data;
|
||||
*pnVal = val.mv_size;
|
||||
}
|
||||
mdb_txn_commit(txn);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int test_mdb_scan(
|
||||
TestDb *pDb, /* Database handle */
|
||||
void *pCtx, /* Context pointer to pass to xCallback */
|
||||
int bReverse, /* True for a reverse order scan */
|
||||
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)
|
||||
){
|
||||
MdbDb *pMdb = (MdbDb *)pDb;
|
||||
int rc;
|
||||
MDB_cursor_op op = bReverse ? MDB_PREV : MDB_NEXT;
|
||||
MDB_txn *txn;
|
||||
|
||||
rc = mdb_txn_begin(pMdb->env, NULL, MDB_RDONLY, &txn);
|
||||
if( rc==0 ){
|
||||
MDB_cursor *csr;
|
||||
MDB_val key = {0, 0};
|
||||
MDB_val val = {0, 0};
|
||||
|
||||
rc = mdb_cursor_open(txn, pMdb->dbi, &csr);
|
||||
if( rc==0 ){
|
||||
while( mdb_cursor_get(csr, &key, &val, op)==0 ){
|
||||
xCallback(pCtx, key.mv_data, key.mv_size, val.mv_data, val.mv_size);
|
||||
}
|
||||
mdb_cursor_close(csr);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* HAVE_MDB */
|
||||
|
1363
ext/lsm1/lsm-test/lsmtest_tdb3.c
Normal file
1363
ext/lsm1/lsm-test/lsmtest_tdb3.c
Normal file
File diff suppressed because it is too large
Load Diff
982
ext/lsm1/lsm-test/lsmtest_tdb4.c
Normal file
982
ext/lsm1/lsm-test/lsmtest_tdb4.c
Normal file
@ -0,0 +1,982 @@
|
||||
|
||||
/*
|
||||
** This file contains the TestDb bt wrapper.
|
||||
*/
|
||||
|
||||
#include "lsmtest_tdb.h"
|
||||
#include "lsmtest.h"
|
||||
#include <unistd.h>
|
||||
#include "bt.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct BtDb BtDb;
|
||||
typedef struct BtFile BtFile;
|
||||
|
||||
/* Background checkpointer interface (see implementations below). */
|
||||
typedef struct bt_ckpter bt_ckpter;
|
||||
static int bgc_attach(BtDb *pDb, const char*);
|
||||
static int bgc_detach(BtDb *pDb);
|
||||
|
||||
/*
|
||||
** Each database or log file opened by a database handle is wrapped by
|
||||
** an object of the following type.
|
||||
*/
|
||||
struct BtFile {
|
||||
BtDb *pBt; /* Database handle that opened this file */
|
||||
bt_env *pVfs; /* Underlying VFS */
|
||||
bt_file *pFile; /* File handle belonging to underlying VFS */
|
||||
int nSectorSize; /* Size of sectors in bytes */
|
||||
int nSector; /* Allocated size of nSector array */
|
||||
u8 **apSector; /* Original sector data */
|
||||
};
|
||||
|
||||
/*
|
||||
** nCrashSync:
|
||||
** If this value is non-zero, then a "crash-test" is running. If
|
||||
** nCrashSync==1, then the crash is simulated during the very next
|
||||
** call to the xSync() VFS method (on either the db or log file).
|
||||
** If nCrashSync==2, the following call to xSync(), and so on.
|
||||
**
|
||||
** bCrash:
|
||||
** After a crash is simulated, this variable is set. Any subsequent
|
||||
** attempts to write to a file or modify the file system in any way
|
||||
** fail once this is set. All the caller can do is close the connection.
|
||||
**
|
||||
** bFastInsert:
|
||||
** If this variable is set to true, then a BT_CONTROL_FAST_INSERT_OP
|
||||
** control is issued before each callto BtReplace() or BtCsrOpen().
|
||||
*/
|
||||
struct BtDb {
|
||||
TestDb base; /* Base class */
|
||||
bt_db *pBt; /* bt database handle */
|
||||
sqlite4_env *pEnv; /* SQLite environment (for malloc/free) */
|
||||
bt_env *pVfs; /* Underlying VFS */
|
||||
int bFastInsert; /* True to use fast-insert */
|
||||
|
||||
/* Space for bt_fetch() results */
|
||||
u8 *aBuffer; /* Space to store results */
|
||||
int nBuffer; /* Allocated size of aBuffer[] in bytes */
|
||||
int nRef;
|
||||
|
||||
/* Background checkpointer used by mt connections */
|
||||
bt_ckpter *pCkpter;
|
||||
|
||||
/* Stuff used for crash test simulation */
|
||||
BtFile *apFile[2]; /* Database and log files used by pBt */
|
||||
bt_env env; /* Private VFS for this object */
|
||||
int nCrashSync; /* Number of syncs until crash (see above) */
|
||||
int bCrash; /* True once a crash has been simulated */
|
||||
};
|
||||
|
||||
static int btVfsFullpath(
|
||||
sqlite4_env *pEnv,
|
||||
bt_env *pVfs,
|
||||
const char *z,
|
||||
char **pzOut
|
||||
){
|
||||
BtDb *pBt = (BtDb*)pVfs->pVfsCtx;
|
||||
if( pBt->bCrash ) return SQLITE4_IOERR;
|
||||
return pBt->pVfs->xFullpath(pEnv, pBt->pVfs, z, pzOut);
|
||||
}
|
||||
|
||||
static int btVfsOpen(
|
||||
sqlite4_env *pEnv,
|
||||
bt_env *pVfs,
|
||||
const char *zFile,
|
||||
int flags, bt_file **ppFile
|
||||
){
|
||||
BtFile *p;
|
||||
BtDb *pBt = (BtDb*)pVfs->pVfsCtx;
|
||||
int rc;
|
||||
|
||||
if( pBt->bCrash ) return SQLITE4_IOERR;
|
||||
|
||||
p = (BtFile*)testMalloc(sizeof(BtFile));
|
||||
if( !p ) return SQLITE4_NOMEM;
|
||||
if( flags & BT_OPEN_DATABASE ){
|
||||
pBt->apFile[0] = p;
|
||||
}else if( flags & BT_OPEN_LOG ){
|
||||
pBt->apFile[1] = p;
|
||||
}
|
||||
if( (flags & BT_OPEN_SHARED)==0 ){
|
||||
p->pBt = pBt;
|
||||
}
|
||||
p->pVfs = pBt->pVfs;
|
||||
|
||||
rc = pBt->pVfs->xOpen(pEnv, pVfs, zFile, flags, &p->pFile);
|
||||
if( rc!=SQLITE4_OK ){
|
||||
testFree(p);
|
||||
p = 0;
|
||||
}else{
|
||||
pBt->nRef++;
|
||||
}
|
||||
|
||||
*ppFile = (bt_file*)p;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int btVfsSize(bt_file *pFile, sqlite4_int64 *piRes){
|
||||
BtFile *p = (BtFile*)pFile;
|
||||
if( p->pBt && p->pBt->bCrash ) return SQLITE4_IOERR;
|
||||
return p->pVfs->xSize(p->pFile, piRes);
|
||||
}
|
||||
|
||||
static int btVfsRead(bt_file *pFile, sqlite4_int64 iOff, void *pBuf, int nBuf){
|
||||
BtFile *p = (BtFile*)pFile;
|
||||
if( p->pBt && p->pBt->bCrash ) return SQLITE4_IOERR;
|
||||
return p->pVfs->xRead(p->pFile, iOff, pBuf, nBuf);
|
||||
}
|
||||
|
||||
static int btFlushSectors(BtFile *p, int iFile){
|
||||
sqlite4_int64 iSz;
|
||||
int rc;
|
||||
int i;
|
||||
u8 *aTmp = 0;
|
||||
|
||||
rc = p->pBt->pVfs->xSize(p->pFile, &iSz);
|
||||
for(i=0; rc==SQLITE4_OK && i<p->nSector; i++){
|
||||
if( p->pBt->bCrash && p->apSector[i] ){
|
||||
|
||||
/* The system is simulating a crash. There are three choices for
|
||||
** this sector:
|
||||
**
|
||||
** 1) Leave it as it is (simulating a successful write),
|
||||
** 2) Restore the original data (simulating a lost write),
|
||||
** 3) Populate the disk sector with garbage data.
|
||||
*/
|
||||
sqlite4_int64 iSOff = p->nSectorSize*i;
|
||||
int nWrite = MIN(p->nSectorSize, iSz - iSOff);
|
||||
|
||||
if( nWrite ){
|
||||
u8 *aWrite = 0;
|
||||
int iOpt = (testPrngValue(i) % 3) + 1;
|
||||
if( iOpt==1 ){
|
||||
aWrite = p->apSector[i];
|
||||
}else if( iOpt==3 ){
|
||||
if( aTmp==0 ) aTmp = testMalloc(p->nSectorSize);
|
||||
aWrite = aTmp;
|
||||
testPrngArray(i*13, (u32*)aWrite, nWrite/sizeof(u32));
|
||||
}
|
||||
|
||||
#if 0
|
||||
fprintf(stderr, "handle sector %d of %s with %s\n", i,
|
||||
iFile==0 ? "db" : "log",
|
||||
iOpt==1 ? "rollback" : iOpt==2 ? "write" : "omit"
|
||||
);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
if( aWrite ){
|
||||
rc = p->pBt->pVfs->xWrite(p->pFile, iSOff, aWrite, nWrite);
|
||||
}
|
||||
}
|
||||
}
|
||||
testFree(p->apSector[i]);
|
||||
p->apSector[i] = 0;
|
||||
}
|
||||
|
||||
testFree(aTmp);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int btSaveSectors(BtFile *p, sqlite4_int64 iOff, int nBuf){
|
||||
int rc;
|
||||
sqlite4_int64 iSz; /* Size of file on disk */
|
||||
int iFirst; /* First sector affected */
|
||||
int iSector; /* Current sector */
|
||||
int iLast; /* Last sector affected */
|
||||
|
||||
if( p->nSectorSize==0 ){
|
||||
p->nSectorSize = p->pBt->pVfs->xSectorSize(p->pFile);
|
||||
if( p->nSectorSize<512 ) p->nSectorSize = 512;
|
||||
}
|
||||
iLast = (iOff+nBuf-1) / p->nSectorSize;
|
||||
iFirst = iOff / p->nSectorSize;
|
||||
|
||||
rc = p->pBt->pVfs->xSize(p->pFile, &iSz);
|
||||
for(iSector=iFirst; rc==SQLITE4_OK && iSector<=iLast; iSector++){
|
||||
int nRead;
|
||||
sqlite4_int64 iSOff = iSector * p->nSectorSize;
|
||||
u8 *aBuf = testMalloc(p->nSectorSize);
|
||||
nRead = MIN(p->nSectorSize, (iSz - iSOff));
|
||||
if( nRead>0 ){
|
||||
rc = p->pBt->pVfs->xRead(p->pFile, iSOff, aBuf, nRead);
|
||||
}
|
||||
|
||||
while( rc==SQLITE4_OK && iSector>=p->nSector ){
|
||||
int nNew = p->nSector + 32;
|
||||
u8 **apNew = (u8**)testMalloc(nNew * sizeof(u8*));
|
||||
memcpy(apNew, p->apSector, p->nSector*sizeof(u8*));
|
||||
testFree(p->apSector);
|
||||
p->apSector = apNew;
|
||||
p->nSector = nNew;
|
||||
}
|
||||
|
||||
p->apSector[iSector] = aBuf;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int btVfsWrite(bt_file *pFile, sqlite4_int64 iOff, void *pBuf, int nBuf){
|
||||
BtFile *p = (BtFile*)pFile;
|
||||
if( p->pBt && p->pBt->bCrash ) return SQLITE4_IOERR;
|
||||
if( p->pBt && p->pBt->nCrashSync ){
|
||||
btSaveSectors(p, iOff, nBuf);
|
||||
}
|
||||
return p->pVfs->xWrite(p->pFile, iOff, pBuf, nBuf);
|
||||
}
|
||||
|
||||
static int btVfsTruncate(bt_file *pFile, sqlite4_int64 iOff){
|
||||
BtFile *p = (BtFile*)pFile;
|
||||
if( p->pBt && p->pBt->bCrash ) return SQLITE4_IOERR;
|
||||
return p->pVfs->xTruncate(p->pFile, iOff);
|
||||
}
|
||||
|
||||
static int btVfsSync(bt_file *pFile){
|
||||
int rc = SQLITE4_OK;
|
||||
BtFile *p = (BtFile*)pFile;
|
||||
BtDb *pBt = p->pBt;
|
||||
|
||||
if( pBt ){
|
||||
if( pBt->bCrash ) return SQLITE4_IOERR;
|
||||
if( pBt->nCrashSync ){
|
||||
pBt->nCrashSync--;
|
||||
pBt->bCrash = (pBt->nCrashSync==0);
|
||||
if( pBt->bCrash ){
|
||||
btFlushSectors(pBt->apFile[0], 0);
|
||||
btFlushSectors(pBt->apFile[1], 1);
|
||||
rc = SQLITE4_IOERR;
|
||||
}else{
|
||||
btFlushSectors(p, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE4_OK ){
|
||||
rc = p->pVfs->xSync(p->pFile);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int btVfsSectorSize(bt_file *pFile){
|
||||
BtFile *p = (BtFile*)pFile;
|
||||
return p->pVfs->xSectorSize(p->pFile);
|
||||
}
|
||||
|
||||
static void btDeref(BtDb *p){
|
||||
p->nRef--;
|
||||
assert( p->nRef>=0 );
|
||||
if( p->nRef<=0 ) testFree(p);
|
||||
}
|
||||
|
||||
static int btVfsClose(bt_file *pFile){
|
||||
BtFile *p = (BtFile*)pFile;
|
||||
BtDb *pBt = p->pBt;
|
||||
int rc;
|
||||
if( pBt ){
|
||||
btFlushSectors(p, 0);
|
||||
if( p==pBt->apFile[0] ) pBt->apFile[0] = 0;
|
||||
if( p==pBt->apFile[1] ) pBt->apFile[1] = 0;
|
||||
}
|
||||
testFree(p->apSector);
|
||||
rc = p->pVfs->xClose(p->pFile);
|
||||
#if 0
|
||||
btDeref(p->pBt);
|
||||
#endif
|
||||
testFree(p);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int btVfsUnlink(sqlite4_env *pEnv, bt_env *pVfs, const char *zFile){
|
||||
BtDb *pBt = (BtDb*)pVfs->pVfsCtx;
|
||||
if( pBt->bCrash ) return SQLITE4_IOERR;
|
||||
return pBt->pVfs->xUnlink(pEnv, pBt->pVfs, zFile);
|
||||
}
|
||||
|
||||
static int btVfsLock(bt_file *pFile, int iLock, int eType){
|
||||
BtFile *p = (BtFile*)pFile;
|
||||
if( p->pBt && p->pBt->bCrash ) return SQLITE4_IOERR;
|
||||
return p->pVfs->xLock(p->pFile, iLock, eType);
|
||||
}
|
||||
|
||||
static int btVfsTestLock(bt_file *pFile, int iLock, int nLock, int eType){
|
||||
BtFile *p = (BtFile*)pFile;
|
||||
if( p->pBt && p->pBt->bCrash ) return SQLITE4_IOERR;
|
||||
return p->pVfs->xTestLock(p->pFile, iLock, nLock, eType);
|
||||
}
|
||||
|
||||
static int btVfsShmMap(bt_file *pFile, int iChunk, int sz, void **ppOut){
|
||||
BtFile *p = (BtFile*)pFile;
|
||||
if( p->pBt && p->pBt->bCrash ) return SQLITE4_IOERR;
|
||||
return p->pVfs->xShmMap(p->pFile, iChunk, sz, ppOut);
|
||||
}
|
||||
|
||||
static void btVfsShmBarrier(bt_file *pFile){
|
||||
BtFile *p = (BtFile*)pFile;
|
||||
return p->pVfs->xShmBarrier(p->pFile);
|
||||
}
|
||||
|
||||
static int btVfsShmUnmap(bt_file *pFile, int bDelete){
|
||||
BtFile *p = (BtFile*)pFile;
|
||||
if( p->pBt && p->pBt->bCrash ) return SQLITE4_IOERR;
|
||||
return p->pVfs->xShmUnmap(p->pFile, bDelete);
|
||||
}
|
||||
|
||||
static int bt_close(TestDb *pTestDb){
|
||||
BtDb *p = (BtDb*)pTestDb;
|
||||
int rc = sqlite4BtClose(p->pBt);
|
||||
free(p->aBuffer);
|
||||
if( p->apFile[0] ) p->apFile[0]->pBt = 0;
|
||||
if( p->apFile[1] ) p->apFile[1]->pBt = 0;
|
||||
bgc_detach(p);
|
||||
testFree(p);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int btMinTransaction(BtDb *p, int iMin, int *piLevel){
|
||||
int iLevel;
|
||||
int rc = SQLITE4_OK;
|
||||
|
||||
iLevel = sqlite4BtTransactionLevel(p->pBt);
|
||||
if( iLevel<iMin ){
|
||||
rc = sqlite4BtBegin(p->pBt, iMin);
|
||||
*piLevel = iLevel;
|
||||
}else{
|
||||
*piLevel = -1;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
static int btRestoreTransaction(BtDb *p, int iLevel, int rcin){
|
||||
int rc = rcin;
|
||||
if( iLevel>=0 ){
|
||||
if( rc==SQLITE4_OK ){
|
||||
rc = sqlite4BtCommit(p->pBt, iLevel);
|
||||
}else{
|
||||
sqlite4BtRollback(p->pBt, iLevel);
|
||||
}
|
||||
assert( iLevel==sqlite4BtTransactionLevel(p->pBt) );
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int bt_write(TestDb *pTestDb, void *pK, int nK, void *pV, int nV){
|
||||
BtDb *p = (BtDb*)pTestDb;
|
||||
int iLevel;
|
||||
int rc;
|
||||
|
||||
rc = btMinTransaction(p, 2, &iLevel);
|
||||
if( rc==SQLITE4_OK ){
|
||||
if( p->bFastInsert ) sqlite4BtControl(p->pBt, BT_CONTROL_FAST_INSERT_OP, 0);
|
||||
rc = sqlite4BtReplace(p->pBt, pK, nK, pV, nV);
|
||||
rc = btRestoreTransaction(p, iLevel, rc);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int bt_delete(TestDb *pTestDb, void *pK, int nK){
|
||||
return bt_write(pTestDb, pK, nK, 0, -1);
|
||||
}
|
||||
|
||||
static int bt_delete_range(
|
||||
TestDb *pTestDb,
|
||||
void *pKey1, int nKey1,
|
||||
void *pKey2, int nKey2
|
||||
){
|
||||
BtDb *p = (BtDb*)pTestDb;
|
||||
bt_cursor *pCsr = 0;
|
||||
int rc = SQLITE4_OK;
|
||||
int iLevel;
|
||||
|
||||
rc = btMinTransaction(p, 2, &iLevel);
|
||||
if( rc==SQLITE4_OK ){
|
||||
if( p->bFastInsert ) sqlite4BtControl(p->pBt, BT_CONTROL_FAST_INSERT_OP, 0);
|
||||
rc = sqlite4BtCsrOpen(p->pBt, 0, &pCsr);
|
||||
}
|
||||
while( rc==SQLITE4_OK ){
|
||||
const void *pK;
|
||||
int n;
|
||||
int nCmp;
|
||||
int res;
|
||||
|
||||
rc = sqlite4BtCsrSeek(pCsr, pKey1, nKey1, BT_SEEK_GE);
|
||||
if( rc==SQLITE4_INEXACT ) rc = SQLITE4_OK;
|
||||
if( rc!=SQLITE4_OK ) break;
|
||||
|
||||
rc = sqlite4BtCsrKey(pCsr, &pK, &n);
|
||||
if( rc!=SQLITE4_OK ) break;
|
||||
|
||||
nCmp = MIN(n, nKey1);
|
||||
res = memcmp(pKey1, pK, nCmp);
|
||||
assert( res<0 || (res==0 && nKey1<=n) );
|
||||
if( res==0 && nKey1==n ){
|
||||
rc = sqlite4BtCsrNext(pCsr);
|
||||
if( rc!=SQLITE4_OK ) break;
|
||||
rc = sqlite4BtCsrKey(pCsr, &pK, &n);
|
||||
if( rc!=SQLITE4_OK ) break;
|
||||
}
|
||||
|
||||
nCmp = MIN(n, nKey2);
|
||||
res = memcmp(pKey2, pK, nCmp);
|
||||
if( res<0 || (res==0 && nKey2<=n) ) break;
|
||||
|
||||
rc = sqlite4BtDelete(pCsr);
|
||||
}
|
||||
if( rc==SQLITE4_NOTFOUND ) rc = SQLITE4_OK;
|
||||
|
||||
sqlite4BtCsrClose(pCsr);
|
||||
|
||||
rc = btRestoreTransaction(p, iLevel, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int bt_fetch(
|
||||
TestDb *pTestDb,
|
||||
void *pK, int nK,
|
||||
void **ppVal, int *pnVal
|
||||
){
|
||||
BtDb *p = (BtDb*)pTestDb;
|
||||
bt_cursor *pCsr = 0;
|
||||
int iLevel;
|
||||
int rc = SQLITE4_OK;
|
||||
|
||||
iLevel = sqlite4BtTransactionLevel(p->pBt);
|
||||
if( iLevel==0 ){
|
||||
rc = sqlite4BtBegin(p->pBt, 1);
|
||||
if( rc!=SQLITE4_OK ) return rc;
|
||||
}
|
||||
|
||||
if( p->bFastInsert ) sqlite4BtControl(p->pBt, BT_CONTROL_FAST_INSERT_OP, 0);
|
||||
rc = sqlite4BtCsrOpen(p->pBt, 0, &pCsr);
|
||||
if( rc==SQLITE4_OK ){
|
||||
rc = sqlite4BtCsrSeek(pCsr, pK, nK, BT_SEEK_EQ);
|
||||
if( rc==SQLITE4_OK ){
|
||||
const void *pV = 0;
|
||||
int nV = 0;
|
||||
rc = sqlite4BtCsrData(pCsr, 0, -1, &pV, &nV);
|
||||
if( rc==SQLITE4_OK ){
|
||||
if( nV>p->nBuffer ){
|
||||
free(p->aBuffer);
|
||||
p->aBuffer = (u8*)malloc(nV*2);
|
||||
p->nBuffer = nV*2;
|
||||
}
|
||||
memcpy(p->aBuffer, pV, nV);
|
||||
*pnVal = nV;
|
||||
*ppVal = (void*)(p->aBuffer);
|
||||
}
|
||||
|
||||
}else if( rc==SQLITE4_INEXACT || rc==SQLITE4_NOTFOUND ){
|
||||
*ppVal = 0;
|
||||
*pnVal = -1;
|
||||
rc = SQLITE4_OK;
|
||||
}
|
||||
sqlite4BtCsrClose(pCsr);
|
||||
}
|
||||
|
||||
if( iLevel==0 ) sqlite4BtCommit(p->pBt, 0);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int bt_scan(
|
||||
TestDb *pTestDb,
|
||||
void *pCtx,
|
||||
int bReverse,
|
||||
void *pFirst, int nFirst,
|
||||
void *pLast, int nLast,
|
||||
void (*xCallback)(void *, void *, int , void *, int)
|
||||
){
|
||||
BtDb *p = (BtDb*)pTestDb;
|
||||
bt_cursor *pCsr = 0;
|
||||
int rc;
|
||||
int iLevel;
|
||||
|
||||
rc = btMinTransaction(p, 1, &iLevel);
|
||||
|
||||
if( rc==SQLITE4_OK ){
|
||||
if( p->bFastInsert ) sqlite4BtControl(p->pBt, BT_CONTROL_FAST_INSERT_OP, 0);
|
||||
rc = sqlite4BtCsrOpen(p->pBt, 0, &pCsr);
|
||||
}
|
||||
if( rc==SQLITE4_OK ){
|
||||
if( bReverse ){
|
||||
if( pLast ){
|
||||
rc = sqlite4BtCsrSeek(pCsr, pLast, nLast, BT_SEEK_LE);
|
||||
}else{
|
||||
rc = sqlite4BtCsrLast(pCsr);
|
||||
}
|
||||
}else{
|
||||
rc = sqlite4BtCsrSeek(pCsr, pFirst, nFirst, BT_SEEK_GE);
|
||||
}
|
||||
if( rc==SQLITE4_INEXACT ) rc = SQLITE4_OK;
|
||||
|
||||
while( rc==SQLITE4_OK ){
|
||||
const void *pK = 0; int nK = 0;
|
||||
const void *pV = 0; int nV = 0;
|
||||
|
||||
rc = sqlite4BtCsrKey(pCsr, &pK, &nK);
|
||||
if( rc==SQLITE4_OK ){
|
||||
rc = sqlite4BtCsrData(pCsr, 0, -1, &pV, &nV);
|
||||
}
|
||||
|
||||
if( rc!=SQLITE4_OK ) break;
|
||||
if( bReverse ){
|
||||
if( pFirst ){
|
||||
int res;
|
||||
int nCmp = MIN(nK, nFirst);
|
||||
res = memcmp(pFirst, pK, nCmp);
|
||||
if( res>0 || (res==0 && nK<nFirst) ) break;
|
||||
}
|
||||
}else{
|
||||
if( pLast ){
|
||||
int res;
|
||||
int nCmp = MIN(nK, nLast);
|
||||
res = memcmp(pLast, pK, nCmp);
|
||||
if( res<0 || (res==0 && nK>nLast) ) break;
|
||||
}
|
||||
}
|
||||
|
||||
xCallback(pCtx, (void*)pK, nK, (void*)pV, nV);
|
||||
if( bReverse ){
|
||||
rc = sqlite4BtCsrPrev(pCsr);
|
||||
}else{
|
||||
rc = sqlite4BtCsrNext(pCsr);
|
||||
}
|
||||
}
|
||||
if( rc==SQLITE4_NOTFOUND ) rc = SQLITE4_OK;
|
||||
|
||||
sqlite4BtCsrClose(pCsr);
|
||||
}
|
||||
|
||||
rc = btRestoreTransaction(p, iLevel, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int bt_begin(TestDb *pTestDb, int iLvl){
|
||||
BtDb *p = (BtDb*)pTestDb;
|
||||
int rc = sqlite4BtBegin(p->pBt, iLvl);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int bt_commit(TestDb *pTestDb, int iLvl){
|
||||
BtDb *p = (BtDb*)pTestDb;
|
||||
int rc = sqlite4BtCommit(p->pBt, iLvl);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int bt_rollback(TestDb *pTestDb, int iLvl){
|
||||
BtDb *p = (BtDb*)pTestDb;
|
||||
int rc = sqlite4BtRollback(p->pBt, iLvl);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int testParseOption(
|
||||
const char **pzIn, /* IN/OUT: pointer to next option */
|
||||
const char **pzOpt, /* OUT: nul-terminated option name */
|
||||
const char **pzArg, /* OUT: nul-terminated option argument */
|
||||
char *pSpace /* Temporary space for output params */
|
||||
){
|
||||
const char *p = *pzIn;
|
||||
const char *pStart;
|
||||
int n;
|
||||
|
||||
char *pOut = pSpace;
|
||||
|
||||
while( *p==' ' ) p++;
|
||||
pStart = p;
|
||||
while( *p && *p!='=' ) p++;
|
||||
if( *p==0 ) return 1;
|
||||
|
||||
n = (p - pStart);
|
||||
memcpy(pOut, pStart, n);
|
||||
*pzOpt = pOut;
|
||||
pOut += n;
|
||||
*pOut++ = '\0';
|
||||
|
||||
p++;
|
||||
pStart = p;
|
||||
while( *p && *p!=' ' ) p++;
|
||||
n = (p - pStart);
|
||||
|
||||
memcpy(pOut, pStart, n);
|
||||
*pzArg = pOut;
|
||||
pOut += n;
|
||||
*pOut++ = '\0';
|
||||
|
||||
*pzIn = p;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int testParseInt(const char *z, int *piVal){
|
||||
int i = 0;
|
||||
const char *p = z;
|
||||
|
||||
while( *p>='0' && *p<='9' ){
|
||||
i = i*10 + (*p - '0');
|
||||
p++;
|
||||
}
|
||||
if( *p=='K' || *p=='k' ){
|
||||
i = i * 1024;
|
||||
p++;
|
||||
}else if( *p=='M' || *p=='m' ){
|
||||
i = i * 1024 * 1024;
|
||||
p++;
|
||||
}
|
||||
|
||||
if( *p ) return SQLITE4_ERROR;
|
||||
*piVal = i;
|
||||
return SQLITE4_OK;
|
||||
}
|
||||
|
||||
static int testBtConfigure(BtDb *pDb, const char *zCfg, int *pbMt){
|
||||
int rc = SQLITE4_OK;
|
||||
|
||||
if( zCfg ){
|
||||
struct CfgParam {
|
||||
const char *zParam;
|
||||
int eParam;
|
||||
} aParam[] = {
|
||||
{ "safety", BT_CONTROL_SAFETY },
|
||||
{ "autockpt", BT_CONTROL_AUTOCKPT },
|
||||
{ "multiproc", BT_CONTROL_MULTIPROC },
|
||||
{ "blksz", BT_CONTROL_BLKSZ },
|
||||
{ "pagesz", BT_CONTROL_PAGESZ },
|
||||
{ "mt", -1 },
|
||||
{ "fastinsert", -2 },
|
||||
{ 0, 0 }
|
||||
};
|
||||
const char *z = zCfg;
|
||||
int n = strlen(z);
|
||||
char *aSpace;
|
||||
const char *zOpt;
|
||||
const char *zArg;
|
||||
|
||||
aSpace = (char*)testMalloc(n+2);
|
||||
while( rc==SQLITE4_OK && 0==testParseOption(&z, &zOpt, &zArg, aSpace) ){
|
||||
int i;
|
||||
int iVal;
|
||||
rc = testArgSelect(aParam, "param", zOpt, &i);
|
||||
if( rc!=SQLITE4_OK ) break;
|
||||
|
||||
rc = testParseInt(zArg, &iVal);
|
||||
if( rc!=SQLITE4_OK ) break;
|
||||
|
||||
switch( aParam[i].eParam ){
|
||||
case -1:
|
||||
*pbMt = iVal;
|
||||
break;
|
||||
case -2:
|
||||
pDb->bFastInsert = 1;
|
||||
break;
|
||||
default:
|
||||
rc = sqlite4BtControl(pDb->pBt, aParam[i].eParam, (void*)&iVal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
testFree(aSpace);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int test_bt_open(
|
||||
const char *zSpec,
|
||||
const char *zFilename,
|
||||
int bClear,
|
||||
TestDb **ppDb
|
||||
){
|
||||
|
||||
static const DatabaseMethods SqlMethods = {
|
||||
bt_close,
|
||||
bt_write,
|
||||
bt_delete,
|
||||
bt_delete_range,
|
||||
bt_fetch,
|
||||
bt_scan,
|
||||
bt_begin,
|
||||
bt_commit,
|
||||
bt_rollback
|
||||
};
|
||||
BtDb *p = 0;
|
||||
bt_db *pBt = 0;
|
||||
int rc;
|
||||
sqlite4_env *pEnv = sqlite4_env_default();
|
||||
|
||||
if( bClear && zFilename && zFilename[0] ){
|
||||
char *zLog = sqlite3_mprintf("%s-wal", zFilename);
|
||||
unlink(zFilename);
|
||||
unlink(zLog);
|
||||
sqlite3_free(zLog);
|
||||
}
|
||||
|
||||
rc = sqlite4BtNew(pEnv, 0, &pBt);
|
||||
if( rc==SQLITE4_OK ){
|
||||
int mt = 0; /* True for multi-threaded connection */
|
||||
|
||||
p = (BtDb*)testMalloc(sizeof(BtDb));
|
||||
p->base.pMethods = &SqlMethods;
|
||||
p->pBt = pBt;
|
||||
p->pEnv = pEnv;
|
||||
p->nRef = 1;
|
||||
|
||||
p->env.pVfsCtx = (void*)p;
|
||||
p->env.xFullpath = btVfsFullpath;
|
||||
p->env.xOpen = btVfsOpen;
|
||||
p->env.xSize = btVfsSize;
|
||||
p->env.xRead = btVfsRead;
|
||||
p->env.xWrite = btVfsWrite;
|
||||
p->env.xTruncate = btVfsTruncate;
|
||||
p->env.xSync = btVfsSync;
|
||||
p->env.xSectorSize = btVfsSectorSize;
|
||||
p->env.xClose = btVfsClose;
|
||||
p->env.xUnlink = btVfsUnlink;
|
||||
p->env.xLock = btVfsLock;
|
||||
p->env.xTestLock = btVfsTestLock;
|
||||
p->env.xShmMap = btVfsShmMap;
|
||||
p->env.xShmBarrier = btVfsShmBarrier;
|
||||
p->env.xShmUnmap = btVfsShmUnmap;
|
||||
|
||||
sqlite4BtControl(pBt, BT_CONTROL_GETVFS, (void*)&p->pVfs);
|
||||
sqlite4BtControl(pBt, BT_CONTROL_SETVFS, (void*)&p->env);
|
||||
|
||||
rc = testBtConfigure(p, zSpec, &mt);
|
||||
if( rc==SQLITE4_OK ){
|
||||
rc = sqlite4BtOpen(pBt, zFilename);
|
||||
}
|
||||
|
||||
if( rc==SQLITE4_OK && mt ){
|
||||
int nAuto = 0;
|
||||
rc = bgc_attach(p, zSpec);
|
||||
sqlite4BtControl(pBt, BT_CONTROL_AUTOCKPT, (void*)&nAuto);
|
||||
}
|
||||
}
|
||||
|
||||
if( rc!=SQLITE4_OK && p ){
|
||||
bt_close(&p->base);
|
||||
}
|
||||
|
||||
*ppDb = &p->base;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int test_fbt_open(
|
||||
const char *zSpec,
|
||||
const char *zFilename,
|
||||
int bClear,
|
||||
TestDb **ppDb
|
||||
){
|
||||
return test_bt_open("fast=1", zFilename, bClear, ppDb);
|
||||
}
|
||||
|
||||
int test_fbts_open(
|
||||
const char *zSpec,
|
||||
const char *zFilename,
|
||||
int bClear,
|
||||
TestDb **ppDb
|
||||
){
|
||||
return test_bt_open("fast=1 blksz=32K pagesz=512", zFilename, bClear, ppDb);
|
||||
}
|
||||
|
||||
|
||||
void tdb_bt_prepare_sync_crash(TestDb *pTestDb, int iSync){
|
||||
BtDb *p = (BtDb*)pTestDb;
|
||||
assert( pTestDb->pMethods->xClose==bt_close );
|
||||
assert( p->bCrash==0 );
|
||||
p->nCrashSync = iSync;
|
||||
}
|
||||
|
||||
bt_db *tdb_bt(TestDb *pDb){
|
||||
if( pDb->pMethods->xClose==bt_close ){
|
||||
return ((BtDb *)pDb)->pBt;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
** Beginning of code for background checkpointer.
|
||||
*/
|
||||
|
||||
struct bt_ckpter {
|
||||
sqlite4_buffer file; /* File name */
|
||||
sqlite4_buffer spec; /* Options */
|
||||
int nLogsize; /* Minimum log size to checkpoint */
|
||||
int nRef; /* Number of clients */
|
||||
|
||||
int bDoWork; /* Set by client threads */
|
||||
pthread_t ckpter_thread; /* Checkpointer thread */
|
||||
pthread_cond_t ckpter_cond; /* Condition var the ckpter waits on */
|
||||
pthread_mutex_t ckpter_mutex; /* Mutex used with ckpter_cond */
|
||||
|
||||
bt_ckpter *pNext; /* Next object in list at gBgc.pCkpter */
|
||||
};
|
||||
|
||||
static struct GlobalBackgroundCheckpointer {
|
||||
bt_ckpter *pCkpter; /* Linked list of checkpointers */
|
||||
} gBgc;
|
||||
|
||||
static void *bgc_main(void *pArg){
|
||||
BtDb *pDb = 0;
|
||||
int rc;
|
||||
int mt;
|
||||
bt_ckpter *pCkpter = (bt_ckpter*)pArg;
|
||||
|
||||
rc = test_bt_open("", (char*)pCkpter->file.p, 0, (TestDb**)&pDb);
|
||||
assert( rc==SQLITE4_OK );
|
||||
rc = testBtConfigure(pDb, (char*)pCkpter->spec.p, &mt);
|
||||
|
||||
while( pCkpter->nRef>0 ){
|
||||
bt_db *db = pDb->pBt;
|
||||
int nLog = 0;
|
||||
|
||||
sqlite4BtBegin(db, 1);
|
||||
sqlite4BtCommit(db, 0);
|
||||
sqlite4BtControl(db, BT_CONTROL_LOGSIZE, (void*)&nLog);
|
||||
|
||||
if( nLog>=pCkpter->nLogsize ){
|
||||
int rc;
|
||||
bt_checkpoint ckpt;
|
||||
memset(&ckpt, 0, sizeof(bt_checkpoint));
|
||||
ckpt.nFrameBuffer = nLog/2;
|
||||
rc = sqlite4BtControl(db, BT_CONTROL_CHECKPOINT, (void*)&ckpt);
|
||||
assert( rc==SQLITE4_OK );
|
||||
sqlite4BtControl(db, BT_CONTROL_LOGSIZE, (void*)&nLog);
|
||||
}
|
||||
|
||||
/* The thread will wake up when it is signaled either because another
|
||||
** thread has created some work for this one or because the connection
|
||||
** is being closed. */
|
||||
pthread_mutex_lock(&pCkpter->ckpter_mutex);
|
||||
if( pCkpter->bDoWork==0 ){
|
||||
pthread_cond_wait(&pCkpter->ckpter_cond, &pCkpter->ckpter_mutex);
|
||||
}
|
||||
pCkpter->bDoWork = 0;
|
||||
pthread_mutex_unlock(&pCkpter->ckpter_mutex);
|
||||
}
|
||||
|
||||
if( pDb ) bt_close((TestDb*)pDb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bgc_logsize_cb(void *pCtx, int nLogsize){
|
||||
bt_ckpter *p = (bt_ckpter*)pCtx;
|
||||
if( nLogsize>=p->nLogsize ){
|
||||
pthread_mutex_lock(&p->ckpter_mutex);
|
||||
p->bDoWork = 1;
|
||||
pthread_cond_signal(&p->ckpter_cond);
|
||||
pthread_mutex_unlock(&p->ckpter_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static int bgc_attach(BtDb *pDb, const char *zSpec){
|
||||
int rc;
|
||||
int n;
|
||||
bt_info info;
|
||||
bt_ckpter *pCkpter;
|
||||
|
||||
/* Figure out the full path to the database opened by handle pDb. */
|
||||
info.eType = BT_INFO_FILENAME;
|
||||
info.pgno = 0;
|
||||
sqlite4_buffer_init(&info.output, 0);
|
||||
rc = sqlite4BtControl(pDb->pBt, BT_CONTROL_INFO, (void*)&info);
|
||||
if( rc!=SQLITE4_OK ) return rc;
|
||||
|
||||
sqlite4_mutex_enter(sqlite4_mutex_alloc(pDb->pEnv, SQLITE4_MUTEX_STATIC_KV));
|
||||
|
||||
/* Search for an existing bt_ckpter object. */
|
||||
n = info.output.n;
|
||||
for(pCkpter=gBgc.pCkpter; pCkpter; pCkpter=pCkpter->pNext){
|
||||
if( n==pCkpter->file.n && 0==memcmp(info.output.p, pCkpter->file.p, n) ){
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Failed to find a suitable checkpointer. Create a new one. */
|
||||
if( pCkpter==0 ){
|
||||
bt_logsizecb cb;
|
||||
|
||||
pCkpter = testMalloc(sizeof(bt_ckpter));
|
||||
memcpy(&pCkpter->file, &info.output, sizeof(sqlite4_buffer));
|
||||
info.output.p = 0;
|
||||
pCkpter->pNext = gBgc.pCkpter;
|
||||
pCkpter->nLogsize = 1000;
|
||||
gBgc.pCkpter = pCkpter;
|
||||
pCkpter->nRef = 1;
|
||||
|
||||
sqlite4_buffer_init(&pCkpter->spec, 0);
|
||||
rc = sqlite4_buffer_set(&pCkpter->spec, zSpec, strlen(zSpec)+1);
|
||||
assert( rc==SQLITE4_OK );
|
||||
|
||||
/* Kick off the checkpointer thread. */
|
||||
if( rc==0 ) rc = pthread_cond_init(&pCkpter->ckpter_cond, 0);
|
||||
if( rc==0 ) rc = pthread_mutex_init(&pCkpter->ckpter_mutex, 0);
|
||||
if( rc==0 ){
|
||||
rc = pthread_create(&pCkpter->ckpter_thread, 0, bgc_main, (void*)pCkpter);
|
||||
}
|
||||
assert( rc==0 ); /* todo: Fix this */
|
||||
|
||||
/* Set up the logsize callback for the client thread */
|
||||
cb.pCtx = (void*)pCkpter;
|
||||
cb.xLogsize = bgc_logsize_cb;
|
||||
sqlite4BtControl(pDb->pBt, BT_CONTROL_LOGSIZECB, (void*)&cb);
|
||||
}else{
|
||||
pCkpter->nRef++;
|
||||
}
|
||||
|
||||
/* Assuming a checkpointer was encountered or effected, attach the
|
||||
** connection to it. */
|
||||
if( pCkpter ){
|
||||
pDb->pCkpter = pCkpter;
|
||||
}
|
||||
|
||||
sqlite4_mutex_leave(sqlite4_mutex_alloc(pDb->pEnv, SQLITE4_MUTEX_STATIC_KV));
|
||||
sqlite4_buffer_clear(&info.output);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int bgc_detach(BtDb *pDb){
|
||||
int rc = SQLITE4_OK;
|
||||
bt_ckpter *pCkpter = pDb->pCkpter;
|
||||
if( pCkpter ){
|
||||
int bShutdown = 0; /* True if this is the last reference */
|
||||
|
||||
sqlite4_mutex_enter(sqlite4_mutex_alloc(pDb->pEnv,SQLITE4_MUTEX_STATIC_KV));
|
||||
pCkpter->nRef--;
|
||||
if( pCkpter->nRef==0 ){
|
||||
bt_ckpter **pp;
|
||||
|
||||
*pp = pCkpter->pNext;
|
||||
for(pp=&gBgc.pCkpter; *pp!=pCkpter; pp=&((*pp)->pNext));
|
||||
bShutdown = 1;
|
||||
}
|
||||
sqlite4_mutex_leave(sqlite4_mutex_alloc(pDb->pEnv,SQLITE4_MUTEX_STATIC_KV));
|
||||
|
||||
if( bShutdown ){
|
||||
void *pDummy;
|
||||
|
||||
/* Signal the checkpointer thread. */
|
||||
pthread_mutex_lock(&pCkpter->ckpter_mutex);
|
||||
pCkpter->bDoWork = 1;
|
||||
pthread_cond_signal(&pCkpter->ckpter_cond);
|
||||
pthread_mutex_unlock(&pCkpter->ckpter_mutex);
|
||||
|
||||
/* Join the checkpointer thread. */
|
||||
pthread_join(pCkpter->ckpter_thread, &pDummy);
|
||||
pthread_cond_destroy(&pCkpter->ckpter_cond);
|
||||
pthread_mutex_destroy(&pCkpter->ckpter_mutex);
|
||||
|
||||
sqlite4_buffer_clear(&pCkpter->file);
|
||||
sqlite4_buffer_clear(&pCkpter->spec);
|
||||
testFree(pCkpter);
|
||||
}
|
||||
|
||||
pDb->pCkpter = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** End of background checkpointer.
|
||||
*************************************************************************/
|
||||
|
||||
|
220
ext/lsm1/lsm-test/lsmtest_util.c
Normal file
220
ext/lsm1/lsm-test/lsmtest_util.c
Normal file
@ -0,0 +1,220 @@
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
/*
|
||||
** Global variables used within this module.
|
||||
*/
|
||||
static struct TestutilGlobal {
|
||||
char **argv;
|
||||
int argc;
|
||||
} g = {0, 0};
|
||||
|
||||
static struct TestutilRnd {
|
||||
unsigned int aRand1[2048]; /* Bits 0..10 */
|
||||
unsigned int aRand2[2048]; /* Bits 11..21 */
|
||||
unsigned int aRand3[1024]; /* Bits 22..31 */
|
||||
} r;
|
||||
|
||||
/*************************************************************************
|
||||
** The following block is a copy of the implementation of SQLite function
|
||||
** sqlite3_randomness. This version has two important differences:
|
||||
**
|
||||
** 1. It always uses the same seed. So the sequence of random data output
|
||||
** is the same for every run of the program.
|
||||
**
|
||||
** 2. It is not threadsafe.
|
||||
*/
|
||||
static struct sqlite3PrngType {
|
||||
unsigned char i, j; /* State variables */
|
||||
unsigned char s[256]; /* State variables */
|
||||
} sqlite3Prng = {
|
||||
0xAF, 0x28,
|
||||
{
|
||||
0x71, 0xF5, 0xB4, 0x6E, 0x80, 0xAB, 0x1D, 0xB8,
|
||||
0xFB, 0xB7, 0x49, 0xBF, 0xFF, 0x72, 0x2D, 0x14,
|
||||
0x79, 0x09, 0xE3, 0x78, 0x76, 0xB0, 0x2C, 0x0A,
|
||||
0x8E, 0x23, 0xEE, 0xDF, 0xE0, 0x9A, 0x2F, 0x67,
|
||||
0xE1, 0xBE, 0x0E, 0xA7, 0x08, 0x97, 0xEB, 0x77,
|
||||
0x78, 0xBA, 0x9D, 0xCA, 0x49, 0x4C, 0x60, 0x9A,
|
||||
0xF6, 0xBD, 0xDA, 0x7F, 0xBC, 0x48, 0x58, 0x52,
|
||||
0xE5, 0xCD, 0x83, 0x72, 0x23, 0x52, 0xFF, 0x6D,
|
||||
0xEF, 0x0F, 0x82, 0x29, 0xA0, 0x83, 0x3F, 0x7D,
|
||||
0xA4, 0x88, 0x31, 0xE7, 0x88, 0x92, 0x3B, 0x9B,
|
||||
0x3B, 0x2C, 0xC2, 0x4C, 0x71, 0xA2, 0xB0, 0xEA,
|
||||
0x36, 0xD0, 0x00, 0xF1, 0xD3, 0x39, 0x17, 0x5D,
|
||||
0x2A, 0x7A, 0xE4, 0xAD, 0xE1, 0x64, 0xCE, 0x0F,
|
||||
0x9C, 0xD9, 0xF5, 0xED, 0xB0, 0x22, 0x5E, 0x62,
|
||||
0x97, 0x02, 0xA3, 0x8C, 0x67, 0x80, 0xFC, 0x88,
|
||||
0x14, 0x0B, 0x15, 0x10, 0x0F, 0xC7, 0x40, 0xD4,
|
||||
0xF1, 0xF9, 0x0E, 0x1A, 0xCE, 0xB9, 0x1E, 0xA1,
|
||||
0x72, 0x8E, 0xD7, 0x78, 0x39, 0xCD, 0xF4, 0x5D,
|
||||
0x2A, 0x59, 0x26, 0x34, 0xF2, 0x73, 0x0B, 0xA0,
|
||||
0x02, 0x51, 0x2C, 0x03, 0xA3, 0xA7, 0x43, 0x13,
|
||||
0xE8, 0x98, 0x2B, 0xD2, 0x53, 0xF8, 0xEE, 0x91,
|
||||
0x7D, 0xE7, 0xE3, 0xDA, 0xD5, 0xBB, 0xC0, 0x92,
|
||||
0x9D, 0x98, 0x01, 0x2C, 0xF9, 0xB9, 0xA0, 0xEB,
|
||||
0xCF, 0x32, 0xFA, 0x01, 0x49, 0xA5, 0x1D, 0x9A,
|
||||
0x76, 0x86, 0x3F, 0x40, 0xD4, 0x89, 0x8F, 0x9C,
|
||||
0xE2, 0xE3, 0x11, 0x31, 0x37, 0xB2, 0x49, 0x28,
|
||||
0x35, 0xC0, 0x99, 0xB6, 0xD0, 0xBC, 0x66, 0x35,
|
||||
0xF7, 0x83, 0x5B, 0xD7, 0x37, 0x1A, 0x2B, 0x18,
|
||||
0xA6, 0xFF, 0x8D, 0x7C, 0x81, 0xA8, 0xFC, 0x9E,
|
||||
0xC4, 0xEC, 0x80, 0xD0, 0x98, 0xA7, 0x76, 0xCC,
|
||||
0x9C, 0x2F, 0x7B, 0xFF, 0x8E, 0x0E, 0xBB, 0x90,
|
||||
0xAE, 0x13, 0x06, 0xF5, 0x1C, 0x4E, 0x52, 0xF7
|
||||
}
|
||||
};
|
||||
|
||||
/* Generate and return single random byte */
|
||||
static unsigned char randomByte(void){
|
||||
unsigned char t;
|
||||
sqlite3Prng.i++;
|
||||
t = sqlite3Prng.s[sqlite3Prng.i];
|
||||
sqlite3Prng.j += t;
|
||||
sqlite3Prng.s[sqlite3Prng.i] = sqlite3Prng.s[sqlite3Prng.j];
|
||||
sqlite3Prng.s[sqlite3Prng.j] = t;
|
||||
t += sqlite3Prng.s[sqlite3Prng.i];
|
||||
return sqlite3Prng.s[t];
|
||||
}
|
||||
|
||||
/*
|
||||
** Return N random bytes.
|
||||
*/
|
||||
static void randomBlob(int nBuf, unsigned char *zBuf){
|
||||
int i;
|
||||
for(i=0; i<nBuf; i++){
|
||||
zBuf[i] = randomByte();
|
||||
}
|
||||
}
|
||||
/*
|
||||
** End of code copied from SQLite.
|
||||
*************************************************************************/
|
||||
|
||||
|
||||
int testPrngInit(void){
|
||||
randomBlob(sizeof(r.aRand1), (unsigned char *)r.aRand1);
|
||||
randomBlob(sizeof(r.aRand2), (unsigned char *)r.aRand2);
|
||||
randomBlob(sizeof(r.aRand3), (unsigned char *)r.aRand3);
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int testPrngValue(unsigned int iVal){
|
||||
return
|
||||
r.aRand1[iVal & 0x000007FF] ^
|
||||
r.aRand2[(iVal>>11) & 0x000007FF] ^
|
||||
r.aRand3[(iVal>>22) & 0x000003FF]
|
||||
;
|
||||
}
|
||||
|
||||
void testPrngArray(unsigned int iVal, unsigned int *aOut, int nOut){
|
||||
int i;
|
||||
for(i=0; i<nOut; i++){
|
||||
aOut[i] = testPrngValue(iVal+i);
|
||||
}
|
||||
}
|
||||
|
||||
void testPrngString(unsigned int iVal, char *aOut, int nOut){
|
||||
int i;
|
||||
for(i=0; i<(nOut-1); i++){
|
||||
aOut[i] = 'a' + (testPrngValue(iVal+i) % 26);
|
||||
}
|
||||
aOut[i] = '\0';
|
||||
}
|
||||
|
||||
void testErrorInit(int argc, char **argv){
|
||||
g.argc = argc;
|
||||
g.argv = argv;
|
||||
}
|
||||
|
||||
void testPrintError(const char *zFormat, ...){
|
||||
va_list ap;
|
||||
va_start(ap, zFormat);
|
||||
vfprintf(stderr, zFormat, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void testPrintFUsage(const char *zFormat, ...){
|
||||
va_list ap;
|
||||
va_start(ap, zFormat);
|
||||
fprintf(stderr, "Usage: %s %s ", g.argv[0], g.argv[1]);
|
||||
vfprintf(stderr, zFormat, ap);
|
||||
fprintf(stderr, "\n");
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void testPrintUsage(const char *zArgs){
|
||||
testPrintError("Usage: %s %s %s\n", g.argv[0], g.argv[1], zArgs);
|
||||
}
|
||||
|
||||
|
||||
static void argError(void *aData, const char *zType, int sz, const char *zArg){
|
||||
struct Entry { const char *zName; };
|
||||
struct Entry *pEntry;
|
||||
const char *zPrev = 0;
|
||||
|
||||
testPrintError("unrecognized %s \"%s\": must be ", zType, zArg);
|
||||
for(pEntry=(struct Entry *)aData;
|
||||
pEntry->zName;
|
||||
pEntry=(struct Entry *)&((unsigned char *)pEntry)[sz]
|
||||
){
|
||||
if( zPrev ){ testPrintError("%s, ", zPrev); }
|
||||
zPrev = pEntry->zName;
|
||||
}
|
||||
testPrintError("or %s\n", zPrev);
|
||||
}
|
||||
|
||||
int testArgSelectX(
|
||||
void *aData,
|
||||
const char *zType,
|
||||
int sz,
|
||||
const char *zArg,
|
||||
int *piOut
|
||||
){
|
||||
struct Entry { const char *zName; };
|
||||
struct Entry *pEntry;
|
||||
int nArg = strlen(zArg);
|
||||
|
||||
int i = 0;
|
||||
int iOut = -1;
|
||||
int nOut = 0;
|
||||
|
||||
for(pEntry=(struct Entry *)aData;
|
||||
pEntry->zName;
|
||||
pEntry=(struct Entry *)&((unsigned char *)pEntry)[sz]
|
||||
){
|
||||
int nName = strlen(pEntry->zName);
|
||||
if( nArg<=nName && memcmp(pEntry->zName, zArg, nArg)==0 ){
|
||||
iOut = i;
|
||||
if( nName==nArg ){
|
||||
nOut = 1;
|
||||
break;
|
||||
}
|
||||
nOut++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if( nOut!=1 ){
|
||||
argError(aData, zType, sz, zArg);
|
||||
}else{
|
||||
*piOut = iOut;
|
||||
}
|
||||
return (nOut!=1);
|
||||
}
|
||||
|
||||
struct timeval zero_time;
|
||||
|
||||
void testTimeInit(void){
|
||||
gettimeofday(&zero_time, 0);
|
||||
}
|
||||
|
||||
int testTimeGet(void){
|
||||
struct timeval now;
|
||||
gettimeofday(&now, 0);
|
||||
return
|
||||
(((int)now.tv_sec - (int)zero_time.tv_sec)*1000) +
|
||||
(((int)now.tv_usec - (int)zero_time.tv_usec)/1000);
|
||||
}
|
@ -12,6 +12,9 @@
|
||||
**
|
||||
** Unix-specific run-time environment implementation for LSM.
|
||||
*/
|
||||
|
||||
#ifndef WIN32
|
||||
|
||||
#if defined(__GNUC__) || defined(__TINYC__)
|
||||
/* workaround for ftruncate() visibility on gcc. */
|
||||
# ifndef _XOPEN_SOURCE
|
||||
@ -739,3 +742,5 @@ lsm_env *lsm_default_env(void){
|
||||
};
|
||||
return &posix_env;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
748
ext/lsm1/lsm_win32.c
Normal file
748
ext/lsm1/lsm_win32.c
Normal file
@ -0,0 +1,748 @@
|
||||
/*
|
||||
** 2011-12-03
|
||||
**
|
||||
** The author disclaims copyright to this source code. In place of
|
||||
** a legal notice, here is a blessing:
|
||||
**
|
||||
** May you do good and not evil.
|
||||
** May you find forgiveness for yourself and forgive others.
|
||||
** May you share freely, never taking more than you give.
|
||||
**
|
||||
*************************************************************************
|
||||
**
|
||||
** Unix-specific run-time environment implementation for LSM.
|
||||
*/
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
#if defined(__GNUC__) || defined(__TINYC__)
|
||||
/* workaround for ftruncate() visibility on gcc. */
|
||||
# ifndef _XOPEN_SOURCE
|
||||
# define _XOPEN_SOURCE 500
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "lsmInt.h"
|
||||
|
||||
/*
|
||||
** An open file is an instance of the following object
|
||||
*/
|
||||
typedef struct Win32File Win32File;
|
||||
struct Win32File {
|
||||
lsm_env *pEnv; /* The run-time environment */
|
||||
const char *zName; /* Full path to file */
|
||||
|
||||
HANDLE h; /* Open file descriptor */
|
||||
HANDLE shmh; /* File descriptor for *-shm file */
|
||||
|
||||
void *pMap; /* Pointer to mapping of file fd */
|
||||
off_t nMap; /* Size of mapping at pMap in bytes */
|
||||
int nShm; /* Number of entries in array apShm[] */
|
||||
void **apShm; /* Array of 32K shared memory segments */
|
||||
};
|
||||
|
||||
static char *win32ShmFile(Win32File *p){
|
||||
char *zShm;
|
||||
int nName = strlen(p->zName);
|
||||
zShm = (char *)lsmMalloc(p->pEnv, nName+4+1);
|
||||
if( zShm ){
|
||||
memcpy(zShm, p->zName, nName);
|
||||
memcpy(&zShm[nName], "-shm", 5);
|
||||
}
|
||||
return zShm;
|
||||
}
|
||||
|
||||
static int lsmWin32OsOpen(
|
||||
lsm_env *pEnv,
|
||||
const char *zFile,
|
||||
int flags,
|
||||
lsm_file **ppFile
|
||||
){
|
||||
int rc = LSM_OK;
|
||||
Win32File *p;
|
||||
|
||||
p = lsm_malloc(pEnv, sizeof(Win32File));
|
||||
if( p==0 ){
|
||||
rc = LSM_NOMEM;
|
||||
}else{
|
||||
int bReadonly = (flags & LSM_OPEN_READONLY);
|
||||
int oflags = (bReadonly ? O_RDONLY : (O_RDWR|O_CREAT));
|
||||
memset(p, 0, sizeof(Win32File));
|
||||
p->zName = zFile;
|
||||
p->pEnv = pEnv;
|
||||
|
||||
CreateFile((LPCWSTR)zConverted,
|
||||
dwDesiredAccess,
|
||||
dwShareMode, NULL,
|
||||
dwCreationDisposition,
|
||||
dwFlagsAndAttributes,
|
||||
NULL))==INVALID_HANDLE_VALUE &&
|
||||
winRetryIoerr(&cnt, &lastErrno) ){
|
||||
|
||||
|
||||
p->fd = open(zFile, oflags, 0644);
|
||||
if( p->fd<0 ){
|
||||
lsm_free(pEnv, p);
|
||||
p = 0;
|
||||
if( errno==ENOENT ){
|
||||
rc = lsmErrorBkpt(LSM_IOERR_NOENT);
|
||||
}else{
|
||||
rc = LSM_IOERR_BKPT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*ppFile = (lsm_file *)p;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int lsmWin32OsWrite(
|
||||
lsm_file *pFile, /* File to write to */
|
||||
lsm_i64 iOff, /* Offset to write to */
|
||||
void *pData, /* Write data from this buffer */
|
||||
int nData /* Bytes of data to write */
|
||||
){
|
||||
int rc = LSM_OK;
|
||||
Win32File *p = (Win32File *)pFile;
|
||||
off_t offset;
|
||||
|
||||
offset = lseek(p->fd, (off_t)iOff, SEEK_SET);
|
||||
if( offset!=iOff ){
|
||||
rc = LSM_IOERR_BKPT;
|
||||
}else{
|
||||
ssize_t prc = write(p->fd, pData, (size_t)nData);
|
||||
if( prc<0 ) rc = LSM_IOERR_BKPT;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int lsmWin32OsTruncate(
|
||||
lsm_file *pFile, /* File to write to */
|
||||
lsm_i64 nSize /* Size to truncate file to */
|
||||
){
|
||||
Win32File *p = (Win32File *)pFile;
|
||||
int rc = LSM_OK; /* Return code */
|
||||
int prc; /* Posix Return Code */
|
||||
struct stat sStat; /* Result of fstat() invocation */
|
||||
|
||||
prc = fstat(p->fd, &sStat);
|
||||
if( prc==0 && sStat.st_size>nSize ){
|
||||
prc = ftruncate(p->fd, (off_t)nSize);
|
||||
}
|
||||
if( prc<0 ) rc = LSM_IOERR_BKPT;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int lsmWin32OsRead(
|
||||
lsm_file *pFile, /* File to read from */
|
||||
lsm_i64 iOff, /* Offset to read from */
|
||||
void *pData, /* Read data into this buffer */
|
||||
int nData /* Bytes of data to read */
|
||||
){
|
||||
int rc = LSM_OK;
|
||||
Win32File *p = (Win32File *)pFile;
|
||||
off_t offset;
|
||||
|
||||
offset = lseek(p->fd, (off_t)iOff, SEEK_SET);
|
||||
if( offset!=iOff ){
|
||||
rc = LSM_IOERR_BKPT;
|
||||
}else{
|
||||
ssize_t prc = read(p->fd, pData, (size_t)nData);
|
||||
if( prc<0 ){
|
||||
rc = LSM_IOERR_BKPT;
|
||||
}else if( prc<nData ){
|
||||
memset(&((u8 *)pData)[prc], 0, nData - prc);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int lsmWin32OsSync(lsm_file *pFile){
|
||||
int rc = LSM_OK;
|
||||
|
||||
#ifndef LSM_NO_SYNC
|
||||
Win32File *p = (Win32File *)pFile;
|
||||
int prc = 0;
|
||||
|
||||
if( p->pMap ){
|
||||
prc = msync(p->pMap, p->nMap, MS_SYNC);
|
||||
}
|
||||
if( prc==0 ) prc = fdatasync(p->fd);
|
||||
if( prc<0 ) rc = LSM_IOERR_BKPT;
|
||||
#else
|
||||
(void)pFile;
|
||||
#endif
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int lsmWin32OsSectorSize(lsm_file *pFile){
|
||||
return 512;
|
||||
}
|
||||
|
||||
static int lsmWin32OsRemap(
|
||||
lsm_file *pFile,
|
||||
lsm_i64 iMin,
|
||||
void **ppOut,
|
||||
lsm_i64 *pnOut
|
||||
){
|
||||
off_t iSz;
|
||||
int prc;
|
||||
Win32File *p = (Win32File *)pFile;
|
||||
struct stat buf;
|
||||
|
||||
/* If the file is between 0 and 2MB in size, extend it in chunks of 256K.
|
||||
** Thereafter, in chunks of 1MB at a time. */
|
||||
const int aIncrSz[] = {256*1024, 1024*1024};
|
||||
int nIncrSz = aIncrSz[iMin>(2*1024*1024)];
|
||||
|
||||
if( p->pMap ){
|
||||
munmap(p->pMap, p->nMap);
|
||||
*ppOut = p->pMap = 0;
|
||||
*pnOut = p->nMap = 0;
|
||||
}
|
||||
|
||||
if( iMin>=0 ){
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
prc = fstat(p->fd, &buf);
|
||||
if( prc!=0 ) return LSM_IOERR_BKPT;
|
||||
iSz = buf.st_size;
|
||||
if( iSz<iMin ){
|
||||
iSz = ((iMin + nIncrSz-1) / nIncrSz) * nIncrSz;
|
||||
prc = ftruncate(p->fd, iSz);
|
||||
if( prc!=0 ) return LSM_IOERR_BKPT;
|
||||
}
|
||||
|
||||
p->pMap = mmap(0, iSz, PROT_READ|PROT_WRITE, MAP_SHARED, p->fd, 0);
|
||||
p->nMap = iSz;
|
||||
}
|
||||
|
||||
*ppOut = p->pMap;
|
||||
*pnOut = p->nMap;
|
||||
return LSM_OK;
|
||||
}
|
||||
|
||||
static int lsmWin32OsFullpath(
|
||||
lsm_env *pEnv,
|
||||
const char *zName,
|
||||
char *zOut,
|
||||
int *pnOut
|
||||
){
|
||||
int nBuf = *pnOut;
|
||||
int nReq;
|
||||
|
||||
if( zName[0]!='/' ){
|
||||
char *z;
|
||||
char *zTmp;
|
||||
int nTmp = 512;
|
||||
zTmp = lsmMalloc(pEnv, nTmp);
|
||||
while( zTmp ){
|
||||
z = getcwd(zTmp, nTmp);
|
||||
if( z || errno!=ERANGE ) break;
|
||||
nTmp = nTmp*2;
|
||||
zTmp = lsmReallocOrFree(pEnv, zTmp, nTmp);
|
||||
}
|
||||
if( zTmp==0 ) return LSM_NOMEM_BKPT;
|
||||
if( z==0 ) return LSM_IOERR_BKPT;
|
||||
assert( z==zTmp );
|
||||
|
||||
nTmp = strlen(zTmp);
|
||||
nReq = nTmp + 1 + strlen(zName) + 1;
|
||||
if( nReq<=nBuf ){
|
||||
memcpy(zOut, zTmp, nTmp);
|
||||
zOut[nTmp] = '/';
|
||||
memcpy(&zOut[nTmp+1], zName, strlen(zName)+1);
|
||||
}
|
||||
lsmFree(pEnv, zTmp);
|
||||
}else{
|
||||
nReq = strlen(zName)+1;
|
||||
if( nReq<=nBuf ){
|
||||
memcpy(zOut, zName, strlen(zName)+1);
|
||||
}
|
||||
}
|
||||
|
||||
*pnOut = nReq;
|
||||
return LSM_OK;
|
||||
}
|
||||
|
||||
static int lsmWin32OsFileid(
|
||||
lsm_file *pFile,
|
||||
void *pBuf,
|
||||
int *pnBuf
|
||||
){
|
||||
int prc;
|
||||
int nBuf;
|
||||
int nReq;
|
||||
Win32File *p = (Win32File *)pFile;
|
||||
struct stat buf;
|
||||
|
||||
nBuf = *pnBuf;
|
||||
nReq = (sizeof(buf.st_dev) + sizeof(buf.st_ino));
|
||||
*pnBuf = nReq;
|
||||
if( nReq>nBuf ) return LSM_OK;
|
||||
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
prc = fstat(p->fd, &buf);
|
||||
if( prc!=0 ) return LSM_IOERR_BKPT;
|
||||
|
||||
memcpy(pBuf, &buf.st_dev, sizeof(buf.st_dev));
|
||||
memcpy(&(((u8 *)pBuf)[sizeof(buf.st_dev)]), &buf.st_ino, sizeof(buf.st_ino));
|
||||
return LSM_OK;
|
||||
}
|
||||
|
||||
static int lsmWin32OsUnlink(lsm_env *pEnv, const char *zFile){
|
||||
int prc = unlink(zFile);
|
||||
return prc ? LSM_IOERR_BKPT : LSM_OK;
|
||||
}
|
||||
|
||||
int lsmWin32OsLock(lsm_file *pFile, int iLock, int eType){
|
||||
int rc = LSM_OK;
|
||||
Win32File *p = (Win32File *)pFile;
|
||||
static const short aType[3] = { F_UNLCK, F_RDLCK, F_WRLCK };
|
||||
struct flock lock;
|
||||
|
||||
assert( aType[LSM_LOCK_UNLOCK]==F_UNLCK );
|
||||
assert( aType[LSM_LOCK_SHARED]==F_RDLCK );
|
||||
assert( aType[LSM_LOCK_EXCL]==F_WRLCK );
|
||||
assert( eType>=0 && eType<array_size(aType) );
|
||||
assert( iLock>0 && iLock<=32 );
|
||||
|
||||
memset(&lock, 0, sizeof(lock));
|
||||
lock.l_whence = SEEK_SET;
|
||||
lock.l_len = 1;
|
||||
lock.l_type = aType[eType];
|
||||
lock.l_start = (4096-iLock);
|
||||
|
||||
if( fcntl(p->fd, F_SETLK, &lock) ){
|
||||
int e = errno;
|
||||
if( e==EACCES || e==EAGAIN ){
|
||||
rc = LSM_BUSY;
|
||||
}else{
|
||||
rc = LSM_IOERR_BKPT;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int lsmWin32OsTestLock(lsm_file *pFile, int iLock, int nLock, int eType){
|
||||
int rc = LSM_OK;
|
||||
Win32File *p = (Win32File *)pFile;
|
||||
static const short aType[3] = { 0, F_RDLCK, F_WRLCK };
|
||||
struct flock lock;
|
||||
|
||||
assert( eType==LSM_LOCK_SHARED || eType==LSM_LOCK_EXCL );
|
||||
assert( aType[LSM_LOCK_SHARED]==F_RDLCK );
|
||||
assert( aType[LSM_LOCK_EXCL]==F_WRLCK );
|
||||
assert( eType>=0 && eType<array_size(aType) );
|
||||
assert( iLock>0 && iLock<=32 );
|
||||
|
||||
memset(&lock, 0, sizeof(lock));
|
||||
lock.l_whence = SEEK_SET;
|
||||
lock.l_len = nLock;
|
||||
lock.l_type = aType[eType];
|
||||
lock.l_start = (4096-iLock);
|
||||
|
||||
if( fcntl(p->fd, F_GETLK, &lock) ){
|
||||
rc = LSM_IOERR_BKPT;
|
||||
}else if( lock.l_type!=F_UNLCK ){
|
||||
rc = LSM_BUSY;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int lsmWin32OsShmMap(lsm_file *pFile, int iChunk, int sz, void **ppShm){
|
||||
Win32File *p = (Win32File *)pFile;
|
||||
|
||||
*ppShm = 0;
|
||||
assert( sz==LSM_SHM_CHUNK_SIZE );
|
||||
if( iChunk>=p->nShm ){
|
||||
int i;
|
||||
void **apNew;
|
||||
int nNew = iChunk+1;
|
||||
off_t nReq = nNew * LSM_SHM_CHUNK_SIZE;
|
||||
struct stat sStat;
|
||||
|
||||
/* If the shared-memory file has not been opened, open it now. */
|
||||
if( p->shmfd<=0 ){
|
||||
char *zShm = win32ShmFile(p);
|
||||
if( !zShm ) return LSM_NOMEM_BKPT;
|
||||
p->shmfd = open(zShm, O_RDWR|O_CREAT, 0644);
|
||||
lsmFree(p->pEnv, zShm);
|
||||
if( p->shmfd<0 ){
|
||||
return LSM_IOERR_BKPT;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the shared-memory file is not large enough to contain the
|
||||
** requested chunk, cause it to grow. */
|
||||
if( fstat(p->shmfd, &sStat) ){
|
||||
return LSM_IOERR_BKPT;
|
||||
}
|
||||
if( sStat.st_size<nReq ){
|
||||
if( ftruncate(p->shmfd, nReq) ){
|
||||
return LSM_IOERR_BKPT;
|
||||
}
|
||||
}
|
||||
|
||||
apNew = (void **)lsmRealloc(p->pEnv, p->apShm, sizeof(void *) * nNew);
|
||||
if( !apNew ) return LSM_NOMEM_BKPT;
|
||||
for(i=p->nShm; i<nNew; i++){
|
||||
apNew[i] = 0;
|
||||
}
|
||||
p->apShm = apNew;
|
||||
p->nShm = nNew;
|
||||
}
|
||||
|
||||
if( p->apShm[iChunk]==0 ){
|
||||
p->apShm[iChunk] = mmap(0, LSM_SHM_CHUNK_SIZE,
|
||||
PROT_READ|PROT_WRITE, MAP_SHARED, p->shmfd, iChunk*LSM_SHM_CHUNK_SIZE
|
||||
);
|
||||
if( p->apShm[iChunk]==0 ) return LSM_IOERR_BKPT;
|
||||
}
|
||||
|
||||
*ppShm = p->apShm[iChunk];
|
||||
return LSM_OK;
|
||||
}
|
||||
|
||||
void lsmWin32OsShmBarrier(void){
|
||||
}
|
||||
|
||||
int lsmWin32OsShmUnmap(lsm_file *pFile, int bDelete){
|
||||
Win32File *p = (Win32File *)pFile;
|
||||
if( p->shmfd>0 ){
|
||||
int i;
|
||||
for(i=0; i<p->nShm; i++){
|
||||
if( p->apShm[i] ){
|
||||
munmap(p->apShm[i], LSM_SHM_CHUNK_SIZE);
|
||||
p->apShm[i] = 0;
|
||||
}
|
||||
}
|
||||
close(p->shmfd);
|
||||
p->shmfd = 0;
|
||||
if( bDelete ){
|
||||
char *zShm = win32ShmFile(p);
|
||||
if( zShm ) unlink(zShm);
|
||||
lsmFree(p->pEnv, zShm);
|
||||
}
|
||||
}
|
||||
return LSM_OK;
|
||||
}
|
||||
|
||||
|
||||
static int lsmWin32OsClose(lsm_file *pFile){
|
||||
Win32File *p = (Win32File *)pFile;
|
||||
lsmWin32OsShmUnmap(pFile, 0);
|
||||
if( p->pMap ) munmap(p->pMap, p->nMap);
|
||||
close(p->fd);
|
||||
lsm_free(p->pEnv, p->apShm);
|
||||
lsm_free(p->pEnv, p);
|
||||
return LSM_OK;
|
||||
}
|
||||
|
||||
static int lsmWin32OsSleep(lsm_env *pEnv, int us){
|
||||
usleep(us);
|
||||
return LSM_OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
** Memory allocation routines.
|
||||
*/
|
||||
#define BLOCK_HDR_SIZE ROUND8( sizeof(size_t) )
|
||||
|
||||
static void *lsmWin32OsMalloc(lsm_env *pEnv, size_t N){
|
||||
unsigned char * m;
|
||||
N += BLOCK_HDR_SIZE;
|
||||
m = (unsigned char *)malloc(N);
|
||||
*((size_t*)m) = N;
|
||||
return m + BLOCK_HDR_SIZE;
|
||||
}
|
||||
|
||||
static void lsmWin32OsFree(lsm_env *pEnv, void *p){
|
||||
if(p){
|
||||
free( ((unsigned char *)p) - BLOCK_HDR_SIZE );
|
||||
}
|
||||
}
|
||||
|
||||
static void *lsmWin32OsRealloc(lsm_env *pEnv, void *p, size_t N){
|
||||
unsigned char * m = (unsigned char *)p;
|
||||
if(1>N){
|
||||
lsmWin32OsFree( pEnv, p );
|
||||
return NULL;
|
||||
}else if(NULL==p){
|
||||
return lsmWin32OsMalloc(pEnv, N);
|
||||
}else{
|
||||
void * re = NULL;
|
||||
m -= BLOCK_HDR_SIZE;
|
||||
#if 0 /* arguable: don't shrink */
|
||||
size_t * sz = (size_t*)m;
|
||||
if(*sz >= (size_t)N){
|
||||
return p;
|
||||
}
|
||||
#endif
|
||||
re = realloc( m, N + BLOCK_HDR_SIZE );
|
||||
if(re){
|
||||
m = (unsigned char *)re;
|
||||
*((size_t*)m) = N;
|
||||
return m + BLOCK_HDR_SIZE;
|
||||
}else{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static size_t lsmWin32OsMSize(lsm_env *pEnv, void *p){
|
||||
unsigned char * m = (unsigned char *)p;
|
||||
return *((size_t*)(m-BLOCK_HDR_SIZE));
|
||||
}
|
||||
#undef BLOCK_HDR_SIZE
|
||||
|
||||
|
||||
#ifdef LSM_MUTEX_WIN32
|
||||
/*************************************************************************
|
||||
** Mutex methods for pthreads based systems. If LSM_MUTEX_WIN32 is
|
||||
** missing then a no-op implementation of mutexes found below will be
|
||||
** used instead.
|
||||
*/
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct PthreadMutex PthreadMutex;
|
||||
struct PthreadMutex {
|
||||
lsm_env *pEnv;
|
||||
pthread_mutex_t mutex;
|
||||
#ifdef LSM_DEBUG
|
||||
pthread_t owner;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef LSM_DEBUG
|
||||
# define LSM_PTHREAD_STATIC_MUTEX { 0, PTHREAD_MUTEX_INITIALIZER, 0 }
|
||||
#else
|
||||
# define LSM_PTHREAD_STATIC_MUTEX { 0, PTHREAD_MUTEX_INITIALIZER }
|
||||
#endif
|
||||
|
||||
static int lsmWin32OsMutexStatic(
|
||||
lsm_env *pEnv,
|
||||
int iMutex,
|
||||
lsm_mutex **ppStatic
|
||||
){
|
||||
static PthreadMutex sMutex[2] = {
|
||||
LSM_PTHREAD_STATIC_MUTEX,
|
||||
LSM_PTHREAD_STATIC_MUTEX
|
||||
};
|
||||
|
||||
assert( iMutex==LSM_MUTEX_GLOBAL || iMutex==LSM_MUTEX_HEAP );
|
||||
assert( LSM_MUTEX_GLOBAL==1 && LSM_MUTEX_HEAP==2 );
|
||||
|
||||
*ppStatic = (lsm_mutex *)&sMutex[iMutex-1];
|
||||
return LSM_OK;
|
||||
}
|
||||
|
||||
static int lsmWin32OsMutexNew(lsm_env *pEnv, lsm_mutex **ppNew){
|
||||
PthreadMutex *pMutex; /* Pointer to new mutex */
|
||||
pthread_mutexattr_t attr; /* Attributes object */
|
||||
|
||||
pMutex = (PthreadMutex *)lsmMallocZero(pEnv, sizeof(PthreadMutex));
|
||||
if( !pMutex ) return LSM_NOMEM_BKPT;
|
||||
|
||||
pMutex->pEnv = pEnv;
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
pthread_mutex_init(&pMutex->mutex, &attr);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
|
||||
*ppNew = (lsm_mutex *)pMutex;
|
||||
return LSM_OK;
|
||||
}
|
||||
|
||||
static void lsmWin32OsMutexDel(lsm_mutex *p){
|
||||
PthreadMutex *pMutex = (PthreadMutex *)p;
|
||||
pthread_mutex_destroy(&pMutex->mutex);
|
||||
lsmFree(pMutex->pEnv, pMutex);
|
||||
}
|
||||
|
||||
static void lsmWin32OsMutexEnter(lsm_mutex *p){
|
||||
PthreadMutex *pMutex = (PthreadMutex *)p;
|
||||
pthread_mutex_lock(&pMutex->mutex);
|
||||
|
||||
#ifdef LSM_DEBUG
|
||||
assert( !pthread_equal(pMutex->owner, pthread_self()) );
|
||||
pMutex->owner = pthread_self();
|
||||
assert( pthread_equal(pMutex->owner, pthread_self()) );
|
||||
#endif
|
||||
}
|
||||
|
||||
static int lsmWin32OsMutexTry(lsm_mutex *p){
|
||||
int ret;
|
||||
PthreadMutex *pMutex = (PthreadMutex *)p;
|
||||
ret = pthread_mutex_trylock(&pMutex->mutex);
|
||||
#ifdef LSM_DEBUG
|
||||
if( ret==0 ){
|
||||
assert( !pthread_equal(pMutex->owner, pthread_self()) );
|
||||
pMutex->owner = pthread_self();
|
||||
assert( pthread_equal(pMutex->owner, pthread_self()) );
|
||||
}
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void lsmWin32OsMutexLeave(lsm_mutex *p){
|
||||
PthreadMutex *pMutex = (PthreadMutex *)p;
|
||||
#ifdef LSM_DEBUG
|
||||
assert( pthread_equal(pMutex->owner, pthread_self()) );
|
||||
pMutex->owner = 0;
|
||||
assert( !pthread_equal(pMutex->owner, pthread_self()) );
|
||||
#endif
|
||||
pthread_mutex_unlock(&pMutex->mutex);
|
||||
}
|
||||
|
||||
#ifdef LSM_DEBUG
|
||||
static int lsmWin32OsMutexHeld(lsm_mutex *p){
|
||||
PthreadMutex *pMutex = (PthreadMutex *)p;
|
||||
return pMutex ? pthread_equal(pMutex->owner, pthread_self()) : 1;
|
||||
}
|
||||
static int lsmWin32OsMutexNotHeld(lsm_mutex *p){
|
||||
PthreadMutex *pMutex = (PthreadMutex *)p;
|
||||
return pMutex ? !pthread_equal(pMutex->owner, pthread_self()) : 1;
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
** End of pthreads mutex implementation.
|
||||
*************************************************************************/
|
||||
#else
|
||||
/*************************************************************************
|
||||
** Noop mutex implementation
|
||||
*/
|
||||
typedef struct NoopMutex NoopMutex;
|
||||
struct NoopMutex {
|
||||
lsm_env *pEnv; /* Environment handle (for xFree()) */
|
||||
int bHeld; /* True if mutex is held */
|
||||
int bStatic; /* True for a static mutex */
|
||||
};
|
||||
static NoopMutex aStaticNoopMutex[2] = {
|
||||
{0, 0, 1},
|
||||
{0, 0, 1},
|
||||
};
|
||||
|
||||
static int lsmWin32OsMutexStatic(
|
||||
lsm_env *pEnv,
|
||||
int iMutex,
|
||||
lsm_mutex **ppStatic
|
||||
){
|
||||
assert( iMutex>=1 && iMutex<=(int)array_size(aStaticNoopMutex) );
|
||||
*ppStatic = (lsm_mutex *)&aStaticNoopMutex[iMutex-1];
|
||||
return LSM_OK;
|
||||
}
|
||||
static int lsmWin32OsMutexNew(lsm_env *pEnv, lsm_mutex **ppNew){
|
||||
NoopMutex *p;
|
||||
p = (NoopMutex *)lsmMallocZero(pEnv, sizeof(NoopMutex));
|
||||
if( p ) p->pEnv = pEnv;
|
||||
*ppNew = (lsm_mutex *)p;
|
||||
return (p ? LSM_OK : LSM_NOMEM_BKPT);
|
||||
}
|
||||
static void lsmWin32OsMutexDel(lsm_mutex *pMutex) {
|
||||
NoopMutex *p = (NoopMutex *)pMutex;
|
||||
assert( p->bStatic==0 && p->pEnv );
|
||||
lsmFree(p->pEnv, p);
|
||||
}
|
||||
static void lsmWin32OsMutexEnter(lsm_mutex *pMutex){
|
||||
NoopMutex *p = (NoopMutex *)pMutex;
|
||||
assert( p->bHeld==0 );
|
||||
p->bHeld = 1;
|
||||
}
|
||||
static int lsmWin32OsMutexTry(lsm_mutex *pMutex){
|
||||
NoopMutex *p = (NoopMutex *)pMutex;
|
||||
assert( p->bHeld==0 );
|
||||
p->bHeld = 1;
|
||||
return 0;
|
||||
}
|
||||
static void lsmWin32OsMutexLeave(lsm_mutex *pMutex){
|
||||
NoopMutex *p = (NoopMutex *)pMutex;
|
||||
assert( p->bHeld==1 );
|
||||
p->bHeld = 0;
|
||||
}
|
||||
#ifdef LSM_DEBUG
|
||||
static int lsmWin32OsMutexHeld(lsm_mutex *pMutex){
|
||||
NoopMutex *p = (NoopMutex *)pMutex;
|
||||
return p ? p->bHeld : 1;
|
||||
}
|
||||
static int lsmWin32OsMutexNotHeld(lsm_mutex *pMutex){
|
||||
NoopMutex *p = (NoopMutex *)pMutex;
|
||||
return p ? !p->bHeld : 1;
|
||||
}
|
||||
#endif
|
||||
/***************************************************************************/
|
||||
#endif /* else LSM_MUTEX_NONE */
|
||||
|
||||
/* Without LSM_DEBUG, the MutexHeld tests are never called */
|
||||
#ifndef LSM_DEBUG
|
||||
# define lsmWin32OsMutexHeld 0
|
||||
# define lsmWin32OsMutexNotHeld 0
|
||||
#endif
|
||||
|
||||
lsm_env *lsm_default_env(void){
|
||||
static lsm_env win32_env = {
|
||||
sizeof(lsm_env), /* nByte */
|
||||
1, /* iVersion */
|
||||
/***** file i/o ******************/
|
||||
0, /* pVfsCtx */
|
||||
lsmWin32OsFullpath, /* xFullpath */
|
||||
lsmWin32OsOpen, /* xOpen */
|
||||
lsmWin32OsRead, /* xRead */
|
||||
lsmWin32OsWrite, /* xWrite */
|
||||
lsmWin32OsTruncate, /* xTruncate */
|
||||
lsmWin32OsSync, /* xSync */
|
||||
lsmWin32OsSectorSize, /* xSectorSize */
|
||||
lsmWin32OsRemap, /* xRemap */
|
||||
lsmWin32OsFileid, /* xFileid */
|
||||
lsmWin32OsClose, /* xClose */
|
||||
lsmWin32OsUnlink, /* xUnlink */
|
||||
lsmWin32OsLock, /* xLock */
|
||||
lsmWin32OsTestLock, /* xTestLock */
|
||||
lsmWin32OsShmMap, /* xShmMap */
|
||||
lsmWin32OsShmBarrier, /* xShmBarrier */
|
||||
lsmWin32OsShmUnmap, /* xShmUnmap */
|
||||
/***** memory allocation *********/
|
||||
0, /* pMemCtx */
|
||||
lsmWin32OsMalloc, /* xMalloc */
|
||||
lsmWin32OsRealloc, /* xRealloc */
|
||||
lsmWin32OsFree, /* xFree */
|
||||
lsmWin32OsMSize, /* xSize */
|
||||
/***** mutexes *********************/
|
||||
0, /* pMutexCtx */
|
||||
lsmWin32OsMutexStatic, /* xMutexStatic */
|
||||
lsmWin32OsMutexNew, /* xMutexNew */
|
||||
lsmWin32OsMutexDel, /* xMutexDel */
|
||||
lsmWin32OsMutexEnter, /* xMutexEnter */
|
||||
lsmWin32OsMutexTry, /* xMutexTry */
|
||||
lsmWin32OsMutexLeave, /* xMutexLeave */
|
||||
lsmWin32OsMutexHeld, /* xMutexHeld */
|
||||
lsmWin32OsMutexNotHeld, /* xMutexNotHeld */
|
||||
/***** other *********************/
|
||||
lsmWin32OsSleep, /* xSleep */
|
||||
};
|
||||
return &win32_env;
|
||||
}
|
||||
|
||||
#endif
|
40
manifest
40
manifest
@ -1,5 +1,5 @@
|
||||
C LSM1:\sFix\sthe\sinteger\skey\sencoding\sso\sthat\snegative\skeys\sare\sin\snumeric\sorder.
|
||||
D 2016-02-24T13:35:22.043
|
||||
C Add\stest\scode\sfor\sLSM\sto\sthe\sext/lsm1/lsm-test\sdirectory.
|
||||
D 2017-06-01T16:13:57.519
|
||||
F Makefile.in 4e90dc1521879022aa9479268a4cd141d1771142
|
||||
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
|
||||
F Makefile.msc 28fc4ee02333996d31b3602b39eeb8e609a89ce4
|
||||
@ -200,7 +200,30 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c
|
||||
F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43
|
||||
F ext/icu/icu.c b2732aef0b076e4276d9b39b5a33cec7a05e1413
|
||||
F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
|
||||
F ext/lsm1/Makefile 9eed37365e0df2fe58a3011c6aacc4646f22a4ef
|
||||
F ext/lsm1/Makefile e10dbcffacc9288704c270b1f74c0811c3ef1b9b526036797d13aadddc9caa59
|
||||
F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86
|
||||
F ext/lsm1/lsm-test/lsmtest.h e7057a3f9db71938496fc8ef081c9f45623b25bfd8499b3550d1ea7123143e90
|
||||
F ext/lsm1/lsm-test/lsmtest1.c 27c3cf6512514b25a145154ae4e54d053d883b2f7f52ed214747b5ebaceedd3e
|
||||
F ext/lsm1/lsm-test/lsmtest2.c 02bd747ead150c6b0cdfeeb1637cabe21f977ab21265762036b04e76202f4fc0
|
||||
F ext/lsm1/lsm-test/lsmtest3.c 9ab87528a36dbf4a61d7c8ad954f5ee368c0878c127b84b942b2e2abe522de26
|
||||
F ext/lsm1/lsm-test/lsmtest4.c d258d6a245db5d8eaede096e2368d23f859c5e92c80ab9122463f708514fe10c
|
||||
F ext/lsm1/lsm-test/lsmtest5.c 8d5242a0f870d65eeada191c8945781fed9cb8ece3886573790ebd373b62dac5
|
||||
F ext/lsm1/lsm-test/lsmtest6.c 9d5db9ff638152c8c3948dc1077dd302deff4fcb3d30cc71ac769915797bbb1d
|
||||
F ext/lsm1/lsm-test/lsmtest7.c aece33086e531898ecbf218d51c0be7a0d6c461219268f1a8e89ab0e8bf64654
|
||||
F ext/lsm1/lsm-test/lsmtest8.c b87a1279b0cfbb39df1fff50074696fbf5d83822349f65706fab6d618a7a52fa
|
||||
F ext/lsm1/lsm-test/lsmtest9.c 88245ce0fdd01678be548100e584cacdacab90208624223aac9029e4f90535fe
|
||||
F ext/lsm1/lsm-test/lsmtest_bt.c d70d9a9be5eef9360af1251dd083948d74fd30137a08f61bef995f7ac04e037f
|
||||
F ext/lsm1/lsm-test/lsmtest_datasource.c 5d770be191d0ca51315926723009b2c25c0b4b8136840494ef710ac324aa916c
|
||||
F ext/lsm1/lsm-test/lsmtest_func.c 159aa401bc8032bfa3d8cf2977bd687abebab880255895a5eb45770d626fa38d
|
||||
F ext/lsm1/lsm-test/lsmtest_io.c 2b71d1208a5671b3836fb95cdb4c0263ae7eb51542d16ef7f6bb9b651b15f194
|
||||
F ext/lsm1/lsm-test/lsmtest_main.c db1d641afd8ba4ca8c6f37b388054ac981f8ba934411e754fe175058f417d372
|
||||
F ext/lsm1/lsm-test/lsmtest_mem.c 996b1e76cc876e8d765182a2f14159b1acbf56cbf86d286173e13e970b79a945
|
||||
F ext/lsm1/lsm-test/lsmtest_tdb.c f40971180a55013e75760995b0716ac4540450466bf329201767aba04f5467c1
|
||||
F ext/lsm1/lsm-test/lsmtest_tdb.h de1ee8c71a7ef61d964e40e057cffea387d7b58a51d95905ab909937d24e4a91
|
||||
F ext/lsm1/lsm-test/lsmtest_tdb2.cc 99ea7f2dd9c7536c8fb9bdd329e4cfeb76899f3ddf6f48bdd3926e016922b715
|
||||
F ext/lsm1/lsm-test/lsmtest_tdb3.c c55afd81231094ee0fd5b83dca6cff5540ba044e571b5f34bf25cedb26ff8d0e
|
||||
F ext/lsm1/lsm-test/lsmtest_tdb4.c 47e8bb5eba266472d690fb8264f1855ebdba0ae5a0e541e35fcda61ebf1d277f
|
||||
F ext/lsm1/lsm-test/lsmtest_util.c 0c2b7c1d109fbd6b7b9a2780f1315e2438a973d18afea5c4eccf94e8827c8260
|
||||
F ext/lsm1/lsm.h 0f6f64ff071471cb87bf98beb8386566f30ea001
|
||||
F ext/lsm1/lsmInt.h bc270dd81b3355c7410b06a6d54dd3eb9493a3e8
|
||||
F ext/lsm1/lsm_ckpt.c e7907e782fe2e95de0833675e35e726e487cc4cd
|
||||
@ -213,9 +236,10 @@ F ext/lsm1/lsm_shared.c 54cc3a5157c6abd77f7d3ae60708b9f7bf022b3c
|
||||
F ext/lsm1/lsm_sorted.c 4a9e3ffecda87b379ed757b59c9cbcd84a80b55c
|
||||
F ext/lsm1/lsm_str.c 77ebdd5040ddf267a6f724d4c83132d2dce8a226
|
||||
F ext/lsm1/lsm_tree.c 5d9fb2bc58a1a70c75126bd8d7198f7b627e165b
|
||||
F ext/lsm1/lsm_unix.c fcaf5b6738713f1229dc0e1a90393ecf24f787f2
|
||||
F ext/lsm1/lsm_unix.c ff6d0a89861c90193f21e35ea5dfea389b480e46085c1d7ff931833b77c3ff30
|
||||
F ext/lsm1/lsm_varint.c b19ae9bd26b5a1e8402fb8a564b25d9542338a41
|
||||
F ext/lsm1/lsm_vtab.c fff303ce03168eca9e333add3c1429b3471674b0
|
||||
F ext/lsm1/lsm_win32.c de84068b4df2999a9cefcf67aae19b0733203f772ca58f7e0c2b0bc87136d2e3
|
||||
F ext/misc/amatch.c a1a8f66c29d40bd71b075546ddeddb477b17a2bb
|
||||
F ext/misc/closure.c 0d2a038df8fbae7f19de42e7c7d71f2e4dc88704
|
||||
F ext/misc/compress.c 122faa92d25033d6c3f07c39231de074ab3d2e83
|
||||
@ -1445,7 +1469,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f
|
||||
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
|
||||
F tool/warnings.sh a98af506df552f3b3c0d904f94e4cdc4e1a6d598
|
||||
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
|
||||
P fac4f4ecee21a460e527a0e2ffa24094b74e17dd
|
||||
R 9f78396a287b58b8dc55076320f7b772
|
||||
U drh
|
||||
Z 4d07009afed81bdff10146a4248806a2
|
||||
P f92dc4187f6046fcb3ab63449efd7f3738594adc
|
||||
R 74f8632bb5f7bfdb807bffd1f01db75d
|
||||
U dan
|
||||
Z 765d188223533ed5b1d4be9ec2f35903
|
||||
|
@ -1 +1 @@
|
||||
f92dc4187f6046fcb3ab63449efd7f3738594adc
|
||||
bb7436e84a315baf05f00e6cab396017e3f287ea404d32e0cc4f389fa1194dec
|
Loading…
x
Reference in New Issue
Block a user