summaryrefslogtreecommitdiffstats
path: root/ext/lsm1/lsm-test/lsmtest_tdb4.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/lsm1/lsm-test/lsmtest_tdb4.c')
-rw-r--r--ext/lsm1/lsm-test/lsmtest_tdb4.c980
1 files changed, 980 insertions, 0 deletions
diff --git a/ext/lsm1/lsm-test/lsmtest_tdb4.c b/ext/lsm1/lsm-test/lsmtest_tdb4.c
new file mode 100644
index 0000000..1f92928
--- /dev/null
+++ b/ext/lsm1/lsm-test/lsmtest_tdb4.c
@@ -0,0 +1,980 @@
+
+/*
+** 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.
+*************************************************************************/