Index: lsm-test/lsmtest.h ================================================================== --- lsm-test/lsmtest.h +++ lsm-test/lsmtest.h @@ -44,10 +44,11 @@ }; 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); @@ -119,12 +120,14 @@ 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 *); /* testio.c */ int testVfsConfigureDb(TestDb *pDb); @@ -192,13 +195,12 @@ void testDeleteDatasource(TestDb *, Datasource *, int, int *); void testDeleteDatasourceRange(TestDb *, Datasource *, int, int, int *); /* test1.c */ -void test_data_1(TestDb *pDb, int *pRc); -void test_data_2(TestDb *pDb, int *pRc); -void test_data_3(const char *, const char *, int *pRc); +void test_data_1(const char *, const char *, int *pRc); +void test_data_2(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); typedef struct CksumDb CksumDb; Index: lsm-test/lsmtest1.c ================================================================== --- lsm-test/lsmtest1.c +++ lsm-test/lsmtest1.c @@ -2,17 +2,12 @@ #include "lsmtest.h" #define DATA_SEQUENTIAL TEST_DATASOURCE_SEQUENCE #define DATA_RANDOM TEST_DATASOURCE_RANDOM -typedef struct Datatest Datatest; -typedef struct MinMax MinMax; - -struct MinMax { - int nMin; - int nMax; -}; +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: ** @@ -21,11 +16,11 @@ ** 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 Datatest.nVerify inserts or +** 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 @@ -32,11 +27,11 @@ ** 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 Datatest { +struct Datatest1 { /* Datasource definition */ DatasourceDefn defn; /* Test procedure parameters */ int nRow; /* Number of rows to insert then delete */ @@ -43,11 +38,52 @@ 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 */ }; -static char *getName(const char *zSystem, Datatest *pTest){ +/* +** 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 @@ -213,13 +249,13 @@ printf("%s\n", (char *)pKey); fflush(stdout); } #endif -static void doDataTest( +static void doDataTest1( const char *zSystem, /* Database system to test */ - Datatest *p, /* Structure containing test parameters */ + Datatest1 *p, /* Structure containing test parameters */ int *pRc /* OUT: Error code */ ){ int i; int iDot; int rc = LSM_OK; @@ -278,16 +314,16 @@ testCaseFinish(rc); *pRc = rc; } -void test_data_3( +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 */ ){ - Datatest aTest[] = { + Datatest1 aTest[] = { { {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}, @@ -304,10 +340,149 @@ int i; for(i=0; *pRc==LSM_OK && idefn); + rc = testControlDb(&pControl); + + if( tdb_lsm(pDb) ){ + int nBuf = 32 * 1024 * 1024; + lsm_config(tdb_lsm(pDb), LSM_CONFIG_WRITE_BUFFER, &nBuf); + } + + for(i=0; rc==0 && inIter; 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 && iinWrite; 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 }, + +#if 0 + { {DATA_RANDOM, 20,25, 100,200}, 200, 50 } +#endif + }; + + int i; + + for(i=0; *pRc==LSM_OK && izName) ){ Index: lsm-test/lsmtest_main.c ================================================================== --- lsm-test/lsmtest_main.c +++ lsm-test/lsmtest_main.c @@ -71,11 +71,23 @@ void *pKey, int nKey, /* Key to query database for */ int *pRc /* IN/OUT: Error code */ ){ if( *pRc==0 ){ int rc; - rc = tdb_delete(pDb, pKey, nKey); + *pRc = rc = tdb_delete(pDb, pKey, nKey); + testSetError(rc); + } +} +void testDeleteRange( + TestDb *pDb, /* Database handle */ + void *pKey1, int nKey1, + void *pKey2, int nKey2, + int *pRc /* IN/OUT: Error code */ +){ + if( *pRc==0 ){ + int rc; + *pRc = rc = tdb_delete_range(pDb, pKey1, nKey1, pKey2, nKey2); testSetError(rc); } } void testBegin(TestDb *pDb, int iTrans, int *pRc){ @@ -122,11 +134,11 @@ ){ int nVal = (zVal ? strlen(zVal) : 0); testFetch(pDb, (void *)zKey, strlen(zKey), (void *)zVal, nVal, pRc); } -static void testFetchCompare( +void testFetchCompare( TestDb *pDb1, TestDb *pDb2, void *pKey, int nKey, int *pRc ){ @@ -133,10 +145,13 @@ int rc; void *pDbVal1; void *pDbVal2; int nDbVal1; int nDbVal2; + + static int nCall = 0; + nCall++; rc = tdb_fetch(pDb1, pKey, nKey, &pDbVal1, &nDbVal1); testSetError(rc); rc = tdb_fetch(pDb2, pKey, nKey, &pDbVal2, &nDbVal2); @@ -183,11 +198,14 @@ ScanResult *p = (ScanResult *)pCtx; u8 *aKey = (u8 *)pKey; u8 *aVal = (u8 *)pVal; int i; - if( test_scan_debug ) printf("%.*s\n", nKey, (char *)pKey); + if( test_scan_debug ){ + printf("%d: %.*s\n", p->nRow, nKey, (char *)pKey); + fflush(stdout); + } #if 0 if( test_scan_debug ) printf("%.20s\n", (char *)pVal); #endif #if 0 @@ -371,10 +389,16 @@ void *testMalloc(int n){ void *pRet = malloc(n); memset(pRet, 0, n); return pRet; } + +void *testMallocCopy(void *pCopy, int nByte){ + void *pRet = testMalloc(nByte); + memcpy(pRet, pCopy, nByte); + return pRet; +} void *testRealloc(void *p, int n){ return realloc(p, n); } @@ -434,11 +458,12 @@ } for(j=0; tdb_system_name(j); j++){ rc = 0; - test_data_3(tdb_system_name(j), zPattern, &rc); + test_data_1(tdb_system_name(j), zPattern, &rc); + test_data_2(tdb_system_name(j), zPattern, &rc); test_rollback(tdb_system_name(j), zPattern, &rc); test_mc(tdb_system_name(j), zPattern, &rc); test_mt(tdb_system_name(j), zPattern, &rc); if( rc ) nFail++; Index: lsm-test/lsmtest_tdb.c ================================================================== --- lsm-test/lsmtest_tdb.c +++ lsm-test/lsmtest_tdb.c @@ -179,10 +179,11 @@ static int test_leveldb_open(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 @@ -309,10 +310,11 @@ struct SqlDb { TestDb base; sqlite3 *db; sqlite3_stmt *pInsert; sqlite3_stmt *pDelete; + sqlite3_stmt *pDeleteRange; sqlite3_stmt *pFetch; sqlite3_stmt *apScan[8]; int nOpenTrans; @@ -323,10 +325,11 @@ 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]); @@ -358,10 +361,22 @@ 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, @@ -510,19 +525,21 @@ static int sql_open(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= ?1 ORDER BY k"; @@ -548,10 +565,11 @@ 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)) @@ -646,10 +664,16 @@ } 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); } Index: lsm-test/lsmtest_tdb.h ================================================================== --- lsm-test/lsmtest_tdb.h +++ lsm-test/lsmtest_tdb.h @@ -48,10 +48,15 @@ /* ** 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). */ Index: lsm-test/lsmtest_tdb3.c ================================================================== --- lsm-test/lsmtest_tdb3.c +++ lsm-test/lsmtest_tdb3.c @@ -452,10 +452,19 @@ static int test_lsm_delete(TestDb *pTestDb, void *pKey, int nKey){ LsmDb *pDb = (LsmDb *)pTestDb; return lsm_delete(pDb->db, pKey, nKey); } + +static int test_lsm_delete_range( + TestDb *pTestDb, + void *pKey1, int nKey1, + void *pKey2, int nKey2 +){ + LsmDb *pDb = (LsmDb *)pTestDb; + return lsm_delete_range(pDb->db, pKey1, nKey1, pKey2, nKey2); +} static int test_lsm_fetch( TestDb *pTestDb, void *pKey, int nKey, @@ -728,10 +737,11 @@ ){ static const DatabaseMethods LsmMethods = { test_lsm_close, test_lsm_write, test_lsm_delete, + test_lsm_delete_range, test_lsm_fetch, test_lsm_scan, test_lsm_begin, test_lsm_commit, test_lsm_rollback @@ -1113,56 +1123,19 @@ return rc; } -static int test_lsm_mt( - const char *zFilename, /* File to open */ - int nWorker, /* Either 1 or 2, for worker threads */ - int bClear, /* True to delete any existing db */ - TestDb **ppDb /* OUT: TestDb database handle */ -){ - LsmDb *pDb; - int rc; - - rc = test_lsm_open(zFilename, bClear, ppDb); - pDb = (LsmDb *)*ppDb; - - assert( nWorker==1 || nWorker==2 ); - - /* Turn off auto-work and configure a work-hook on the client connection. */ - if( rc==0 ){ - int bAutowork = 0; - lsm_config(pDb->db, LSM_CONFIG_AUTOWORK, &bAutowork); - lsm_config_work_hook(pDb->db, mt_client_work_hook, (void *)pDb); - } - - if( rc==0 ){ - pDb->aWorker = (LsmWorker *)testMalloc(sizeof(LsmWorker) * nWorker); - memset(pDb->aWorker, 0, sizeof(LsmWorker) * nWorker); - pDb->nWorker = nWorker; - - rc = mt_start_worker(pDb, 0, zFilename, 0, LSM_WORK_FLUSH, - nWorker==1 ? 512 : 0, 1 - ); - } - - if( rc==0 && nWorker==2 ){ - rc = mt_start_worker(pDb, 1, zFilename, 0, 0, 512, 0); - } - - return rc; -} - int test_lsm_mt2(const char *zFilename, int bClear, TestDb **ppDb){ - return test_lsm_mt(zFilename, 1, bClear, ppDb); + const char *zCfg = "threads=2"; + return testLsmOpen(zCfg, zFilename, bClear, ppDb); } int test_lsm_mt3(const char *zFilename, int bClear, TestDb **ppDb){ - return test_lsm_mt(zFilename, 2, bClear, ppDb); + const char *zCfg = "threads=3"; + return testLsmOpen(zCfg, zFilename, bClear, ppDb); } - #else static void mt_shutdown(LsmDb *pDb) { unused_parameter(pDb); } Index: src/lsm.h ================================================================== --- src/lsm.h +++ src/lsm.h @@ -378,12 +378,15 @@ ** key value does not exist in the database. */ int lsm_delete(lsm_db *, const void *pKey, int nKey); /* -** Delete all database entries with keys that are greater than or equal to -** (pKey1/nKey1) and smaller than or equal to (pKey2/nKey2). +** Delete all database entries with keys that are greater than (pKey1/nKey1) +** and smaller than (pKey2/nKey2). Note that keys (pKey1/nKey1) and +** (pKey2/nKey2) themselves, if they exist in the database, are not deleted. +** +** Return LSM_OK if successful, or an LSM error code otherwise. */ int lsm_delete_range(lsm_db *, const void *pKey1, int nKey1, const void *pKey2, int nKey2 ); Index: src/lsmInt.h ================================================================== --- src/lsmInt.h +++ src/lsmInt.h @@ -145,10 +145,22 @@ */ #define LSM_MAX_FREELIST_ENTRIES 100 #define LSM_ATTEMPTS_BEFORE_PROTOCOL 10000 + +/* +** Each entry stored in the LSM (or in-memory tree structure) has an +** associated mask of the following flags. +*/ +#define LSM_START_DELETE 0x01 /* Start of open-ended delete range */ +#define LSM_END_DELETE 0x02 /* End of open-ended delete range */ +#define LSM_POINT_DELETE 0x04 /* Delete this key */ +#define LSM_INSERT 0x08 /* Insert this key and value */ +#define LSM_SEPARATOR 0x10 /* True if entry is separator key only */ +#define LSM_SYSTEMKEY 0x20 /* True if entry is a system key (FREELIST) */ + /* ** A string that can grow by appending. */ struct LsmString { lsm_env *pEnv; /* Run-time environment */ @@ -376,10 +388,11 @@ int nInput; /* Number of input runs being merged */ MergeInput *aInput; /* Array nInput entries in size */ MergeInput splitkey; /* Location in file of current splitkey */ int nSkip; /* Number of separators entries to skip */ int iOutputOff; /* Write offset on output page */ + Pgno iCurrentPtr; /* Current pointer value */ int bHierReadonly; /* True if b-tree heirarchies are read-only */ }; /* ** The first argument to this macro is a pointer to a Segment structure. @@ -541,15 +554,17 @@ int lsmTreeCursorSeek(TreeCursor *pCsr, void *pKey, int nKey, int *pRes); int lsmTreeCursorNext(TreeCursor *pCsr); int lsmTreeCursorPrev(TreeCursor *pCsr); int lsmTreeCursorEnd(TreeCursor *pCsr, int bLast); void lsmTreeCursorReset(TreeCursor *pCsr); -int lsmTreeCursorKey(TreeCursor *pCsr, void **ppKey, int *pnKey); +int lsmTreeCursorKey(TreeCursor *pCsr, int *pFlags, void **ppKey, int *pnKey); +int lsmTreeCursorFlags(TreeCursor *pCsr); int lsmTreeCursorValue(TreeCursor *pCsr, void **ppVal, int *pnVal); int lsmTreeCursorValid(TreeCursor *pCsr); int lsmTreeCursorSave(TreeCursor *pCsr); +void lsmFlagsToString(int flags, char *zFlags); /* ** Functions from file "mem.c". */ int lsmPoolNew(lsm_env *pEnv, Mempool **ppPool); Index: src/lsm_ckpt.c ================================================================== --- src/lsm_ckpt.c +++ src/lsm_ckpt.c @@ -62,10 +62,11 @@ ** 6. For each segment in the merge: ** 5a. Page number of next cell to read during merge ** 5b. Cell number of next cell to read during merge ** 7. Page containing current split-key. ** 8. Cell within page containing current split-key. +** 9. Current pointer value. ** ** The freelist. ** ** 1. Number of free-list entries stored in checkpoint header. ** 2. For each entry: @@ -310,10 +311,11 @@ ckptSetValue(p, iOut++, pMerge->aInput[i].iPg, pRc); ckptSetValue(p, iOut++, pMerge->aInput[i].iCell, pRc); } ckptSetValue(p, iOut++, pMerge->splitkey.iPg, pRc); ckptSetValue(p, iOut++, pMerge->splitkey.iCell, pRc); + ckptSetValue(p, iOut++, pMerge->iCurrentPtr, pRc); } *piOut = iOut; } @@ -499,10 +501,11 @@ pMerge->aInput[i].iPg = (Pgno)aInt[iIn++]; pMerge->aInput[i].iCell = (int)aInt[iIn++]; } pMerge->splitkey.iPg = (Pgno)aInt[iIn++]; pMerge->splitkey.iCell = (int)aInt[iIn++]; + pMerge->iCurrentPtr = (int)aInt[iIn++]; /* Set *piIn and return LSM_OK. */ *piIn = iIn; return LSM_OK; } Index: src/lsm_file.c ================================================================== --- src/lsm_file.c +++ src/lsm_file.c @@ -727,10 +727,11 @@ Page *p; int iHash; int rc = LSM_OK; assert( iPg>=fsFirstPageOnBlock(pFS, 1) ); + *ppPg = 0; if( pFS->bUseMmap ){ i64 iEnd = (i64)iPg * pFS->nPagesize; fsGrowMapping(pFS, iEnd, &rc); if( rc!=LSM_OK ) return rc; Index: src/lsm_main.c ================================================================== --- src/lsm_main.c +++ src/lsm_main.c @@ -477,15 +477,13 @@ va_end(ap); return rc; } -/* -** Write a new value into the database. -*/ -int lsm_write( - lsm_db *pDb, /* Database connection */ +static int doWriteOp( + lsm_db *pDb, + int bDeleteRange, const void *pKey, int nKey, /* Key to write or delete */ const void *pVal, int nVal /* Value to write. Or nVal==-1 for a delete */ ){ int rc = LSM_OK; /* Return code */ int bCommit = 0; /* True to commit before returning */ @@ -494,11 +492,15 @@ bCommit = 1; rc = lsm_begin(pDb, 1); } if( rc==LSM_OK ){ - rc = lsmLogWrite(pDb, (void *)pKey, nKey, (void *)pVal, nVal); + if( bDeleteRange==0 ){ + rc = lsmLogWrite(pDb, (void *)pKey, nKey, (void *)pVal, nVal); + }else{ + /* TODO */ + } } lsmSortedSaveTreeCursors(pDb); if( rc==LSM_OK ){ @@ -511,11 +513,16 @@ if( nQuant>pDb->nTreeLimit ){ nQuant = pDb->nTreeLimit; } nBefore = lsmTreeSize(pDb); - rc = lsmTreeInsert(pDb, (void *)pKey, nKey, (void *)pVal, nVal); + if( bDeleteRange ){ + rc = lsmTreeDelete(pDb, (void *)pKey, nKey, (void *)pVal, nVal); + }else{ + rc = lsmTreeInsert(pDb, (void *)pKey, nKey, (void *)pVal, nVal); + } + nAfter = lsmTreeSize(pDb); nDiff = (nAfter/nQuant) - (nBefore/nQuant); if( rc==LSM_OK && pDb->bAutowork && nDiff!=0 ){ rc = lsmSortedAutoWork(pDb, nDiff * LSM_AUTOWORK_QUANT); } @@ -531,16 +538,42 @@ } } return rc; } + +/* +** Write a new value into the database. +*/ +int lsm_write( + lsm_db *db, /* Database connection */ + const void *pKey, int nKey, /* Key to write or delete */ + const void *pVal, int nVal /* Value to write. Or nVal==-1 for a delete */ +){ + return doWriteOp(db, 0, pKey, nKey, pVal, nVal); +} /* ** Delete a value from the database. */ -int lsm_delete(lsm_db *pDb, const void *pKey, int nKey){ - return lsm_write(pDb, pKey, nKey, 0, -1); +int lsm_delete(lsm_db *db, const void *pKey, int nKey){ + return doWriteOp(db, 0, pKey, nKey, 0, -1); +} + +/* +** Delete a range of database keys. +*/ +int lsm_delete_range( + lsm_db *db, /* Database handle */ + const void *pKey1, int nKey1, /* Lower bound of range to delete */ + const void *pKey2, int nKey2 /* Upper bound of range to delete */ +){ + int rc = LSM_OK; + if( db->xCmp((void *)pKey1, nKey1, (void *)pKey2, nKey2)<0 ){ + rc = doWriteOp(db, 1, pKey1, nKey1, pKey2, nKey2); + } + return rc; } /* ** Open a new cursor handle. ** Index: src/lsm_shared.c ================================================================== --- src/lsm_shared.c +++ src/lsm_shared.c @@ -633,20 +633,18 @@ ** been flushed to disk. The significance of this is that once the snapshot ** created to hold the updated state of the database is synced to disk, log ** file space can be recycled. */ void lsmFinishWork(lsm_db *pDb, int bFlush, int nOvfl, int *pRc){ - /* If no error has occurred, serialize the worker snapshot and write - ** it to shared memory. */ - - assert( pDb->pWorker ); - assert( pDb->pWorker->nFreelistOvfl==0 || nOvfl==0 ); - if( *pRc==LSM_OK ){ - *pRc = lsmCheckpointSaveWorker(pDb, bFlush, nOvfl); - } - + assert( *pRc!=0 || pDb->pWorker ); if( pDb->pWorker ){ + /* If no error has occurred, serialize the worker snapshot and write + ** it to shared memory. */ + assert( pDb->pWorker->nFreelistOvfl==0 || nOvfl==0 ); + if( *pRc==LSM_OK ){ + *pRc = lsmCheckpointSaveWorker(pDb, bFlush, nOvfl); + } lsmFreeSnapshot(pDb->pEnv, pDb->pWorker); pDb->pWorker = 0; } lsmShmLock(pDb, LSM_LOCK_WORKER, LSM_LOCK_UNLOCK, 0); Index: src/lsm_sorted.c ================================================================== --- src/lsm_sorted.c +++ src/lsm_sorted.c @@ -40,76 +40,49 @@ ** ** aaaaa bbbbb ccc