diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/btmutex.c | 139 | ||||
-rw-r--r-- | src/btree.c | 2 | ||||
-rw-r--r-- | src/btree.h | 20 | ||||
-rw-r--r-- | src/btreeInt.h | 5 | ||||
-rw-r--r-- | src/build.c | 8 | ||||
-rw-r--r-- | src/os_unix.c | 2 | ||||
-rw-r--r-- | src/sqliteInt.h | 12 | ||||
-rw-r--r-- | src/tclsqlite.c | 4 | ||||
-rw-r--r-- | src/test_fuzzer.c | 944 | ||||
-rw-r--r-- | src/test_multiplex.c | 560 | ||||
-rw-r--r-- | src/test_multiplex.h | 91 | ||||
-rw-r--r-- | src/test_syscall.c | 39 | ||||
-rw-r--r-- | src/test_wholenumber.c | 311 | ||||
-rw-r--r-- | src/vdbe.c | 100 | ||||
-rw-r--r-- | src/vdbeInt.h | 13 | ||||
-rw-r--r-- | src/vdbeaux.c | 172 |
16 files changed, 2022 insertions, 400 deletions
diff --git a/src/btmutex.c b/src/btmutex.c index 201291a3e..42d0bdcbc 100644 --- a/src/btmutex.c +++ b/src/btmutex.c @@ -39,15 +39,34 @@ static void lockBtreeMutex(Btree *p){ ** clear the p->locked boolean. */ static void unlockBtreeMutex(Btree *p){ + BtShared *pBt = p->pBt; assert( p->locked==1 ); - assert( sqlite3_mutex_held(p->pBt->mutex) ); + assert( sqlite3_mutex_held(pBt->mutex) ); assert( sqlite3_mutex_held(p->db->mutex) ); - assert( p->db==p->pBt->db ); + assert( p->db==pBt->db ); - sqlite3_mutex_leave(p->pBt->mutex); + pBt->iMutexCounter++; + sqlite3_mutex_leave(pBt->mutex); p->locked = 0; } +#ifdef SQLITE_DEBUG +/* +** Return the number of times that the mutex has been exited for +** the given btree. +** +** This is a small circular counter that wraps around to zero on +** overflow. It is used only for sanity checking - to verify that +** mutexes are held continously by asserting that the value of +** this counter at the beginning of a region is the same as at +** the end. +*/ +u32 sqlite3BtreeMutexCounter(Btree *p){ + assert( p->locked==1 || p->sharable==0 ); + return p->pBt->iMutexCounter; +} +#endif + /* ** Enter a mutex on the given BTree object. ** @@ -92,6 +111,24 @@ void sqlite3BtreeEnter(Btree *p){ p->wantToLock++; if( p->locked ) return; + /* Increment the mutex counter on all locked btrees in the same + ** database connection. This simulates the unlocking that would + ** occur on a worst-case mutex dead-lock avoidance scenario. + */ +#ifdef SQLITE_DEBUG + { + int ii; + sqlite3 *db = p->db; + Btree *pOther; + for(ii=0; ii<db->nDb; ii++){ + if( ii==1 ) continue; + pOther = db->aDb[ii].pBt; + if( pOther==0 || pOther->sharable==0 || pOther->locked==0 ) continue; + pOther->pBt->iMutexCounter++; + } + } +#endif + /* In most cases, we should be able to acquire the lock we ** want without having to go throught the ascending lock ** procedure that follows. Just be sure not to block. @@ -195,7 +232,7 @@ void sqlite3BtreeEnterAll(sqlite3 *db){ if( !p->locked ){ assert( p->wantToLock==1 ); while( p->pPrev ) p = p->pPrev; - /* Reason for ALWAYS: There must be at least on unlocked Btree in + /* Reason for ALWAYS: There must be at least one unlocked Btree in ** the chain. Otherwise the !p->locked test above would have failed */ while( p->locked && ALWAYS(p->pNext) ) p = p->pNext; for(pLater = p->pNext; pLater; pLater=pLater->pNext){ @@ -251,97 +288,17 @@ int sqlite3BtreeHoldsAllMutexes(sqlite3 *db){ } #endif /* NDEBUG */ +#else /* SQLITE_THREADSAFE>0 above. SQLITE_THREADSAFE==0 below */ /* -** Add a new Btree pointer to a BtreeMutexArray. -** if the pointer can possibly be shared with -** another database connection. +** The following are special cases for mutex enter routines for use +** in single threaded applications that use shared cache. Except for +** these two routines, all mutex operations are no-ops in that case and +** are null #defines in btree.h. ** -** The pointers are kept in sorted order by pBtree->pBt. That -** way when we go to enter all the mutexes, we can enter them -** in order without every having to backup and retry and without -** worrying about deadlock. -** -** The number of shared btrees will always be small (usually 0 or 1) -** so an insertion sort is an adequate algorithm here. +** If shared cache is disabled, then all btree mutex routines, including +** the ones below, are no-ops and are null #defines in btree.h. */ -void sqlite3BtreeMutexArrayInsert(BtreeMutexArray *pArray, Btree *pBtree){ - int i, j; - BtShared *pBt; - if( pBtree==0 || pBtree->sharable==0 ) return; -#ifndef NDEBUG - { - for(i=0; i<pArray->nMutex; i++){ - assert( pArray->aBtree[i]!=pBtree ); - } - } -#endif - assert( pArray->nMutex>=0 ); - assert( pArray->nMutex<ArraySize(pArray->aBtree)-1 ); - pBt = pBtree->pBt; - for(i=0; i<pArray->nMutex; i++){ - assert( pArray->aBtree[i]!=pBtree ); - if( pArray->aBtree[i]->pBt>pBt ){ - for(j=pArray->nMutex; j>i; j--){ - pArray->aBtree[j] = pArray->aBtree[j-1]; - } - pArray->aBtree[i] = pBtree; - pArray->nMutex++; - return; - } - } - pArray->aBtree[pArray->nMutex++] = pBtree; -} - -/* -** Enter the mutex of every btree in the array. This routine is -** called at the beginning of sqlite3VdbeExec(). The mutexes are -** exited at the end of the same function. -*/ -void sqlite3BtreeMutexArrayEnter(BtreeMutexArray *pArray){ - int i; - for(i=0; i<pArray->nMutex; i++){ - Btree *p = pArray->aBtree[i]; - /* Some basic sanity checking */ - assert( i==0 || pArray->aBtree[i-1]->pBt<p->pBt ); - assert( !p->locked || p->wantToLock>0 ); - - /* We should already hold a lock on the database connection */ - assert( sqlite3_mutex_held(p->db->mutex) ); - - /* The Btree is sharable because only sharable Btrees are entered - ** into the array in the first place. */ - assert( p->sharable ); - - p->wantToLock++; - if( !p->locked ){ - lockBtreeMutex(p); - } - } -} - -/* -** Leave the mutex of every btree in the group. -*/ -void sqlite3BtreeMutexArrayLeave(BtreeMutexArray *pArray){ - int i; - for(i=0; i<pArray->nMutex; i++){ - Btree *p = pArray->aBtree[i]; - /* Some basic sanity checking */ - assert( i==0 || pArray->aBtree[i-1]->pBt<p->pBt ); - assert( p->locked ); - assert( p->wantToLock>0 ); - - /* We should already hold a lock on the database connection */ - assert( sqlite3_mutex_held(p->db->mutex) ); - - p->wantToLock--; - if( p->wantToLock==0 ){ - unlockBtreeMutex(p); - } - } -} -#else void sqlite3BtreeEnter(Btree *p){ p->pBt->db = p->db; } diff --git a/src/btree.c b/src/btree.c index 088c555fc..d86b9edad 100644 --- a/src/btree.c +++ b/src/btree.c @@ -7990,7 +7990,7 @@ int sqlite3BtreeIsInBackup(Btree *p){ ** ** Just before the shared-btree is closed, the function passed as the ** xFree argument when the memory allocation was made is invoked on the -** blob of allocated memory. This function should not call sqlite3_free() +** blob of allocated memory. The xFree function should not call sqlite3_free() ** on the memory, the btree layer does that. */ void *sqlite3BtreeSchema(Btree *p, int nBytes, void(*xFree)(void *)){ diff --git a/src/btree.h b/src/btree.h index 468723b33..4fd4f6768 100644 --- a/src/btree.h +++ b/src/btree.h @@ -39,18 +39,6 @@ typedef struct Btree Btree; typedef struct BtCursor BtCursor; typedef struct BtShared BtShared; -typedef struct BtreeMutexArray BtreeMutexArray; - -/* -** This structure records all of the Btrees that need to hold -** a mutex before we enter sqlite3VdbeExec(). The Btrees are -** are placed in aBtree[] in order of aBtree[]->pBt. That way, -** we can always lock and unlock them all quickly. -*/ -struct BtreeMutexArray { - int nMutex; - Btree *aBtree[SQLITE_MAX_ATTACHED+1]; -}; int sqlite3BtreeOpen( @@ -228,23 +216,19 @@ void sqlite3BtreeCursorList(Btree*); void sqlite3BtreeEnterCursor(BtCursor*); void sqlite3BtreeLeaveCursor(BtCursor*); void sqlite3BtreeLeaveAll(sqlite3*); - void sqlite3BtreeMutexArrayEnter(BtreeMutexArray*); - void sqlite3BtreeMutexArrayLeave(BtreeMutexArray*); - void sqlite3BtreeMutexArrayInsert(BtreeMutexArray*, Btree*); #ifndef NDEBUG /* These routines are used inside assert() statements only. */ int sqlite3BtreeHoldsMutex(Btree*); int sqlite3BtreeHoldsAllMutexes(sqlite3*); + u32 sqlite3BtreeMutexCounter(Btree*); #endif #else # define sqlite3BtreeLeave(X) +# define sqlite3BtreeMutexCounter(X) 0 # define sqlite3BtreeEnterCursor(X) # define sqlite3BtreeLeaveCursor(X) # define sqlite3BtreeLeaveAll(X) -# define sqlite3BtreeMutexArrayEnter(X) -# define sqlite3BtreeMutexArrayLeave(X) -# define sqlite3BtreeMutexArrayInsert(X,Y) # define sqlite3BtreeHoldsMutex(X) 1 # define sqlite3BtreeHoldsAllMutexes(X) 1 diff --git a/src/btreeInt.h b/src/btreeInt.h index 0e71195b0..2aee063d3 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -336,7 +336,7 @@ struct BtLock { ** All fields in this structure are accessed under sqlite3.mutex. ** The pBt pointer itself may not be changed while there exists cursors ** in the referenced BtShared that point back to this Btree since those -** cursors have to do go through this Btree to find their BtShared and +** cursors have to go through this Btree to find their BtShared and ** they often do so without holding sqlite3.mutex. */ struct Btree { @@ -426,7 +426,7 @@ struct BtShared { u32 nPage; /* Number of pages in the database */ void *pSchema; /* Pointer to space allocated by sqlite3BtreeSchema() */ void (*xFreeSchema)(void*); /* Destructor for BtShared.pSchema */ - sqlite3_mutex *mutex; /* Non-recursive mutex required to access this struct */ + sqlite3_mutex *mutex; /* Non-recursive mutex required to access this object */ Bitvec *pHasContent; /* Set of pages moved to free-list this transaction */ #ifndef SQLITE_OMIT_SHARED_CACHE int nRef; /* Number of references to this structure */ @@ -435,6 +435,7 @@ struct BtShared { Btree *pWriter; /* Btree with currently open write transaction */ u8 isExclusive; /* True if pWriter has an EXCLUSIVE lock on the db */ u8 isPending; /* If waiting for read-locks to clear */ + u16 iMutexCounter; /* The number of mutex_leave(mutex) calls */ #endif u8 *pTmpSpace; /* BtShared.pageSize bytes of space for tmp use */ }; diff --git a/src/build.c b/src/build.c index bf3273fc8..b4528347d 100644 --- a/src/build.c +++ b/src/build.c @@ -148,7 +148,7 @@ void sqlite3FinishCoding(Parse *pParse){ ** on each used database. */ if( pParse->cookieGoto>0 ){ - tAttachMask mask; + yDbMask mask; int iDb; sqlite3VdbeJumpHere(v, pParse->cookieGoto-1); for(iDb=0, mask=1; iDb<db->nDb; mask<<=1, iDb++){ @@ -3444,12 +3444,12 @@ void sqlite3CodeVerifySchema(Parse *pParse, int iDb){ } if( iDb>=0 ){ sqlite3 *db = pToplevel->db; - tAttachMask mask; + yDbMask mask; assert( iDb<db->nDb ); assert( db->aDb[iDb].pBt!=0 || iDb==1 ); assert( iDb<SQLITE_MAX_ATTACHED+2 ); - mask = ((tAttachMask)1)<<iDb; + mask = ((yDbMask)1)<<iDb; if( (pToplevel->cookieMask & mask)==0 ){ pToplevel->cookieMask |= mask; pToplevel->cookieValue[iDb] = db->aDb[iDb].pSchema->schema_cookie; @@ -3476,7 +3476,7 @@ void sqlite3CodeVerifySchema(Parse *pParse, int iDb){ void sqlite3BeginWriteOperation(Parse *pParse, int setStatement, int iDb){ Parse *pToplevel = sqlite3ParseToplevel(pParse); sqlite3CodeVerifySchema(pParse, iDb); - pToplevel->writeMask |= ((tAttachMask)1)<<iDb; + pToplevel->writeMask |= ((yDbMask)1)<<iDb; pToplevel->isMultiWrite |= setStatement; } diff --git a/src/os_unix.c b/src/os_unix.c index f04b6af27..67dd06fc1 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -394,10 +394,10 @@ static int unixSetSystemCall( /* If no zName is given, restore all system calls to their default ** settings and return NULL */ + rc = SQLITE_OK; for(i=0; i<sizeof(aSyscall)/sizeof(aSyscall[0]); i++){ if( aSyscall[i].pDefault ){ aSyscall[i].pCurrent = aSyscall[i].pDefault; - rc = SQLITE_OK; } } }else{ diff --git a/src/sqliteInt.h b/src/sqliteInt.h index d31ed4ae6..ce1e106df 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -2133,11 +2133,13 @@ struct TriggerPrg { TriggerPrg *pNext; /* Next entry in Parse.pTriggerPrg list */ }; -/* Datatype for the bitmask of all attached databases */ +/* +** The yDbMask datatype for the bitmask of all attached databases. +*/ #if SQLITE_MAX_ATTACHED>30 - typedef sqlite3_uint64 tAttachMask; + typedef sqlite3_uint64 yDbMask; #else - typedef unsigned int tAttachMask; + typedef unsigned int yDbMask; #endif /* @@ -2188,8 +2190,8 @@ struct Parse { int iReg; /* Reg with value of this column. 0 means none. */ int lru; /* Least recently used entry has the smallest value */ } aColCache[SQLITE_N_COLCACHE]; /* One for each column cache entry */ - tAttachMask writeMask; /* Start a write transaction on these databases */ - tAttachMask cookieMask; /* Bitmask of schema verified databases */ + yDbMask writeMask; /* Start a write transaction on these databases */ + yDbMask cookieMask; /* Bitmask of schema verified databases */ u8 isMultiWrite; /* True if statement may affect/insert multiple rows */ u8 mayAbort; /* True if statement may throw an ABORT exception */ int cookieGoto; /* Address of OP_Goto to cookie verifier subroutine */ diff --git a/src/tclsqlite.c b/src/tclsqlite.c index de0e992bd..e1f451541 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -3728,6 +3728,8 @@ static void init_all(Tcl_Interp *interp){ extern int Sqlitemultiplex_Init(Tcl_Interp*); extern int SqliteSuperlock_Init(Tcl_Interp*); extern int SqlitetestSyscall_Init(Tcl_Interp*); + extern int Sqlitetestfuzzer_Init(Tcl_Interp*); + extern int Sqlitetestwholenumber_Init(Tcl_Interp*); #if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK) extern int TestSession_Init(Tcl_Interp*); #endif @@ -3768,6 +3770,8 @@ static void init_all(Tcl_Interp *interp){ Sqlitemultiplex_Init(interp); SqliteSuperlock_Init(interp); SqlitetestSyscall_Init(interp); + Sqlitetestfuzzer_Init(interp); + Sqlitetestwholenumber_Init(interp); #if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK) TestSession_Init(interp); #endif diff --git a/src/test_fuzzer.c b/src/test_fuzzer.c new file mode 100644 index 000000000..cf5925717 --- /dev/null +++ b/src/test_fuzzer.c @@ -0,0 +1,944 @@ +/* +** 2011 March 24 +** +** 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. +** +************************************************************************* +** +** Code for demonstartion virtual table that generates variations +** on an input word at increasing edit distances from the original. +** +** A fuzzer virtual table is created like this: +** +** CREATE VIRTUAL TABLE temp.f USING fuzzer; +** +** The name of the new virtual table in the example above is "f". +** Note that all fuzzer virtual tables must be TEMP tables. The +** "temp." prefix in front of the table name is required when the +** table is being created. The "temp." prefix can be omitted when +** using the table as long as the name is unambiguous. +** +** Before being used, the fuzzer needs to be programmed by giving it +** character transformations and a cost associated with each transformation. +** Examples: +** +** INSERT INTO f(cFrom,cTo,Cost) VALUES('','a',100); +** +** The above statement says that the cost of inserting a letter 'a' is +** 100. (All costs are integers. We recommend that costs be scaled so +** that the average cost is around 100.) +** +** INSERT INTO f(cFrom,cTo,Cost) VALUES('b','',87); +** +** The above statement says that the cost of deleting a single letter +** 'b' is 87. +** +** INSERT INTO f(cFrom,cTo,Cost) VALUES('o','oe',38); +** INSERT INTO f(cFrom,cTo,Cost) VALUES('oe','o',40); +** +** This third example says that the cost of transforming the single +** letter "o" into the two-letter sequence "oe" is 38 and that the +** cost of transforming "oe" back into "o" is 40. +** +** After all the transformation costs have been set, the fuzzer table +** can be queried as follows: +** +** SELECT word, distance FROM f +** WHERE word MATCH 'abcdefg' +** AND distance<200; +** +** This first query outputs the string "abcdefg" and all strings that +** can be derived from that string by appling the specified transformations. +** The strings are output together with their total transformation cost +** (called "distance") and appear in order of increasing cost. No string +** is output more than once. If there are multiple ways to transform the +** target string into the output string then the lowest cost transform is +** the one that is returned. In the example, the search is limited to +** strings with a total distance of less than 200. +** +** It is important to put some kind of a limit on the fuzzer output. This +** can be either in the form of a LIMIT clause at the end of the query, +** or better, a "distance<NNN" constraint where NNN is some number. The +** running time and memory requirement is exponential in the value of NNN +** so you want to make sure that NNN is not too big. A value of NNN that +** is about twice the average transformation cost seems to give good results. +** +** The fuzzer table can be useful for tasks such as spelling correction. +** Suppose there is a second table vocabulary(w) where the w column contains +** all correctly spelled words. Let $word be a word you want to look up. +** +** SELECT vocabulary.w FROM f, vocabulary +** WHERE f.word MATCH $word +** AND f.distance<=200 +** AND f.word=vocabulary.w +** LIMIT 20 +** +** The query above gives the 20 closest words to the $word being tested. +** (Note that for good performance, the vocubulary.w column should be +** indexed.) +** +** A similar query can be used to find all words in the dictionary that +** begin with some prefix $prefix: +** +** SELECT vocabulary.w FROM f, vocabulary +** WHERE f.word MATCH $prefix +** AND f.distance<=200 +** AND vocabulary.w BETWEEN f.word AND (f.word || x'F7BFBFBF') +** LIMIT 50 +** +** This last query will show up to 50 words out of the vocabulary that +** match or nearly match the $prefix. +*/ +#include "sqlite3.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdio.h> + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* +** Forward declaration of objects used by this implementation +*/ +typedef struct fuzzer_vtab fuzzer_vtab; +typedef struct fuzzer_cursor fuzzer_cursor; +typedef struct fuzzer_rule fuzzer_rule; +typedef struct fuzzer_seen fuzzer_seen; +typedef struct fuzzer_stem fuzzer_stem; + +/* +** Type of the "cost" of an edit operation. Might be changed to +** "float" or "double" or "sqlite3_int64" in the future. +*/ +typedef int fuzzer_cost; + + +/* +** Each transformation rule is stored as an instance of this object. +** All rules are kept on a linked list sorted by rCost. +*/ +struct fuzzer_rule { + fuzzer_rule *pNext; /* Next rule in order of increasing rCost */ + fuzzer_cost rCost; /* Cost of this transformation */ + int nFrom, nTo; /* Length of the zFrom and zTo strings */ + char *zFrom; /* Transform from */ + char zTo[4]; /* Transform to (extra space appended) */ +}; + +/* +** A stem object is used to generate variants. It is also used to record +** previously generated outputs. +** +** Every stem is added to a hash table as it is output. Generation of +** duplicate stems is suppressed. +** +** Active stems (those that might generate new outputs) are kepts on a linked +** list sorted by increasing cost. The cost is the sum of rBaseCost and +** pRule->rCost. +*/ +struct fuzzer_stem { + char *zBasis; /* Word being fuzzed */ + int nBasis; /* Length of the zBasis string */ + const fuzzer_rule *pRule; /* Current rule to apply */ + int n; /* Apply pRule at this character offset */ + fuzzer_cost rBaseCost; /* Base cost of getting to zBasis */ + fuzzer_cost rCostX; /* Precomputed rBaseCost + pRule->rCost */ + fuzzer_stem *pNext; /* Next stem in rCost order */ + fuzzer_stem *pHash; /* Next stem with same hash on zBasis */ +}; + +/* +** A fuzzer virtual-table object +*/ +struct fuzzer_vtab { + sqlite3_vtab base; /* Base class - must be first */ + char *zClassName; /* Name of this class. Default: "fuzzer" */ + fuzzer_rule *pRule; /* All active rules in this fuzzer */ + fuzzer_rule *pNewRule; /* New rules to add when last cursor expires */ + int nCursor; /* Number of active cursors */ +}; + +#define FUZZER_HASH 4001 /* Hash table size */ +#define FUZZER_NQUEUE 20 /* Number of slots on the stem queue */ + +/* A fuzzer cursor object */ +struct fuzzer_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + sqlite3_int64 iRowid; /* The rowid of the current word */ + fuzzer_vtab *pVtab; /* The virtual table this cursor belongs to */ + fuzzer_cost rLimit; /* Maximum cost of any term */ + fuzzer_stem *pStem; /* Stem with smallest rCostX */ + fuzzer_stem *pDone; /* Stems already processed to completion */ + fuzzer_stem *aQueue[FUZZER_NQUEUE]; /* Queue of stems with higher rCostX */ + int mxQueue; /* Largest used index in aQueue[] */ + char *zBuf; /* Temporary use buffer */ + int nBuf; /* Bytes allocated for zBuf */ + int nStem; /* Number of stems allocated */ + fuzzer_rule nullRule; /* Null rule used first */ + fuzzer_stem *apHash[FUZZER_HASH]; /* Hash of previously generated terms */ +}; + +/* Methods for the fuzzer module */ +static int fuzzerConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + fuzzer_vtab *pNew; + int n; + if( strcmp(argv[1],"temp")!=0 ){ + *pzErr = sqlite3_mprintf("%s virtual tables must be TEMP", argv[0]); + return SQLITE_ERROR; + } + n = strlen(argv[0]) + 1; + pNew = sqlite3_malloc( sizeof(*pNew) + n ); + if( pNew==0 ) return SQLITE_NOMEM; + pNew->zClassName = (char*)&pNew[1]; + memcpy(pNew->zClassName, argv[0], n); + sqlite3_declare_vtab(db, "CREATE TABLE x(word,distance,cFrom,cTo,cost)"); + memset(pNew, 0, sizeof(*pNew)); + *ppVtab = &pNew->base; + return SQLITE_OK; +} +/* Note that for this virtual table, the xCreate and xConnect +** methods are identical. */ + +static int fuzzerDisconnect(sqlite3_vtab *pVtab){ + fuzzer_vtab *p = (fuzzer_vtab*)pVtab; + assert( p->nCursor==0 ); + do{ + while( p->pRule ){ + fuzzer_rule *pRule = p->pRule; + p->pRule = pRule->pNext; + sqlite3_free(pRule); + } + p->pRule = p->pNewRule; + p->pNewRule = 0; + }while( p->pRule ); + sqlite3_free(p); + return SQLITE_OK; +} +/* The xDisconnect and xDestroy methods are also the same */ + +/* +** The two input rule lists are both sorted in order of increasing +** cost. Merge them together into a single list, sorted by cost, and +** return a pointer to the head of that list. +*/ +static fuzzer_rule *fuzzerMergeRules(fuzzer_rule *pA, fuzzer_rule *pB){ + fuzzer_rule head; + fuzzer_rule *pTail; + + pTail = &head; + while( pA && pB ){ + if( pA->rCost<=pB->rCost ){ + pTail->pNext = pA; + pTail = pA; + pA = pA->pNext; + }else{ + pTail->pNext = pB; + pTail = pB; + pB = pB->pNext; + } + } + if( pA==0 ){ + pTail->pNext = pB; + }else{ + pTail->pNext = pA; + } + return head.pNext; +} + + +/* +** Open a new fuzzer cursor. +*/ +static int fuzzerOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ + fuzzer_vtab *p = (fuzzer_vtab*)pVTab; + fuzzer_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + pCur->pVtab = p; + *ppCursor = &pCur->base; + if( p->nCursor==0 && p->pNewRule ){ + unsigned int i; + fuzzer_rule *pX; + fuzzer_rule *a[15]; + for(i=0; i<sizeof(a)/sizeof(a[0]); i++) a[i] = 0; + while( (pX = p->pNewRule)!=0 ){ + p->pNewRule = pX->pNext; + pX->pNext = 0; + for(i=0; a[i] && i<sizeof(a)/sizeof(a[0])-1; i++){ + pX = fuzzerMergeRules(a[i], pX); + a[i] = 0; + } + a[i] = fuzzerMergeRules(a[i], pX); + } + for(pX=a[0], i=1; i<sizeof(a)/sizeof(a[0]); i++){ + pX = fuzzerMergeRules(a[i], pX); + } + p->pRule = fuzzerMergeRules(p->pRule, pX); + } + p->nCursor++; + return SQLITE_OK; +} + +/* +** Free all stems in a list. +*/ +static void fuzzerClearStemList(fuzzer_stem *pStem){ + while( pStem ){ + fuzzer_stem *pNext = pStem->pNext; + sqlite3_free(pStem); + pStem = pNext; + } +} + +/* +** Free up all the memory allocated by a cursor. Set it rLimit to 0 +** to indicate that it is at EOF. +*/ +static void fuzzerClearCursor(fuzzer_cursor *pCur, int clearHash){ + int i; + fuzzerClearStemList(pCur->pStem); + fuzzerClearStemList(pCur->pDone); + for(i=0; i<FUZZER_NQUEUE; i++) fuzzerClearStemList(pCur->aQueue[i]); + pCur->rLimit = (fuzzer_cost)0; + if( clearHash && pCur->nStem ){ + pCur->mxQueue = 0; + pCur->pStem = 0; + pCur->pDone = 0; + memset(pCur->aQueue, 0, sizeof(pCur->aQueue)); + memset(pCur->apHash, 0, sizeof(pCur->apHash)); + } + pCur->nStem = 0; +} + +/* +** Close a fuzzer cursor. +*/ +static int fuzzerClose(sqlite3_vtab_cursor *cur){ + fuzzer_cursor *pCur = (fuzzer_cursor *)cur; + fuzzerClearCursor(pCur, 0); + sqlite3_free(pCur->zBuf); + pCur->pVtab->nCursor--; + sqlite3_free(pCur); + return SQLITE_OK; +} + +/* +** Compute the current output term for a fuzzer_stem. +*/ +static int fuzzerRender( + fuzzer_stem *pStem, /* The stem to be rendered */ + char **pzBuf, /* Write results into this buffer. realloc if needed */ + int *pnBuf /* Size of the buffer */ +){ + const fuzzer_rule *pRule = pStem->pRule; + int n; + char *z; + + n = pStem->nBasis + pRule->nTo - pRule->nFrom; + if( (*pnBuf)<n+1 ){ + (*pzBuf) = sqlite3_realloc((*pzBuf), n+100); + if( (*pzBuf)==0 ) return SQLITE_NOMEM; + (*pnBuf) = n+100; + } + n = pStem->n; + z = *pzBuf; + if( n<0 ){ + memcpy(z, pStem->zBasis, pStem->nBasis+1); + }else{ + memcpy(z, pStem->zBasis, n); + memcpy(&z[n], pRule->zTo, pRule->nTo); + memcpy(&z[n+pRule->nTo], &pStem->zBasis[n+pRule->nFrom], + pStem->nBasis-n-pRule->nFrom+1); + } + return SQLITE_OK; +} + +/* +** Compute a hash on zBasis. +*/ +static unsigned int fuzzerHash(const char *z){ + unsigned int h = 0; + while( *z ){ h = (h<<3) ^ (h>>29) ^ *(z++); } + return h % FUZZER_HASH; +} + +/* +** Current cost of a stem +*/ +static fuzzer_cost fuzzerCost(fuzzer_stem *pStem){ + return pStem->rCostX = pStem->rBaseCost + pStem->pRule->rCost; +} + +#if 0 +/* +** Print a description of a fuzzer_stem on stderr. +*/ +static void fuzzerStemPrint( + const char *zPrefix, + fuzzer_stem *pStem, + const char *zSuffix +){ + if( pStem->n<0 ){ + fprintf(stderr, "%s[%s](%d)-->self%s", + zPrefix, + pStem->zBasis, pStem->rBaseCost, + zSuffix + ); + }else{ + char *zBuf = 0; + int nBuf = 0; + if( fuzzerRender(pStem, &zBuf, &nBuf)!=SQLITE_OK ) return; + fprintf(stderr, "%s[%s](%d)-->{%s}(%d)%s", + zPrefix, + pStem->zBasis, pStem->rBaseCost, zBuf, pStem->, + zSuffix + ); + sqlite3_free(zBuf); + } +} +#endif + +/* +** Return 1 if the string to which the cursor is point has already +** been emitted. Return 0 if not. Return -1 on a memory allocation +** failures. +*/ +static int fuzzerSeen(fuzzer_cursor *pCur, fuzzer_stem *pStem){ + unsigned int h; + fuzzer_stem *pLookup; + + if( fuzzerRender(pStem, &pCur->zBuf, &pCur->nBuf)==SQLITE_NOMEM ){ + return -1; + } + h = fuzzerHash(pCur->zBuf); + pLookup = pCur->apHash[h]; + while( pLookup && strcmp(pLookup->zBasis, pCur->zBuf)!=0 ){ + pLookup = pLookup->pHash; + } + return pLookup!=0; +} + +/* +** Advance a fuzzer_stem to its next value. Return 0 if there are +** no more values that can be generated by this fuzzer_stem. Return +** -1 on a memory allocation failure. +*/ +static int fuzzerAdvance(fuzzer_cursor *pCur, fuzzer_stem *pStem){ + const fuzzer_rule *pRule; + while( (pRule = pStem->pRule)!=0 ){ + while( pStem->n < pStem->nBasis - pRule->nFrom ){ + pStem->n++; + if( pRule->nFrom==0 + || memcmp(&pStem->zBasis[pStem->n], pRule->zFrom, pRule->nFrom)==0 + ){ + /* Found a rewrite case. Make sure it is not a duplicate */ + int rc = fuzzerSeen(pCur, pStem); + if( rc<0 ) return -1; + if( rc==0 ){ + fuzzerCost(pStem); + return 1; + } + } + } + pStem->n = -1; + pStem->pRule = pRule->pNext; + if( pStem->pRule && fuzzerCost(pStem)>pCur->rLimit ) pStem->pRule = 0; + } + return 0; +} + +/* +** The two input stem lists are both sorted in order of increasing +** rCostX. Merge them together into a single list, sorted by rCostX, and +** return a pointer to the head of that new list. +*/ +static fuzzer_stem *fuzzerMergeStems(fuzzer_stem *pA, fuzzer_stem *pB){ + fuzzer_stem head; + fuzzer_stem *pTail; + + pTail = &head; + while( pA && pB ){ + if( pA->rCostX<=pB->rCostX ){ + pTail->pNext = pA; + pTail = pA; + pA = pA->pNext; + }else{ + pTail->pNext = pB; + pTail = pB; + pB = pB->pNext; + } + } + if( pA==0 ){ + pTail->pNext = pB; + }else{ + pTail->pNext = pA; + } + return head.pNext; +} + +/* +** Load pCur->pStem with the lowest-cost stem. Return a pointer +** to the lowest-cost stem. +*/ +static fuzzer_stem *fuzzerLowestCostStem(fuzzer_cursor *pCur){ + fuzzer_stem *pBest, *pX; + int iBest; + int i; + + if( pCur->pStem==0 ){ + iBest = -1; + pBest = 0; + for(i=0; i<=pCur->mxQueue; i++){ + pX = pCur->aQueue[i]; + if( pX==0 ) continue; + if( pBest==0 || pBest->rCostX>pX->rCostX ){ + pBest = pX; + iBest = i; + } + } + if( pBest ){ + pCur->aQueue[iBest] = pBest->pNext; + pBest->pNext = 0; + pCur->pStem = pBest; + } + } + return pCur->pStem; +} + +/* +** Insert pNew into queue of pending stems. Then find the stem +** with the lowest rCostX and move it into pCur->pStem. +** list. The insert is done such the pNew is in the correct order +** according to fuzzer_stem.zBaseCost+fuzzer_stem.pRule->rCost. +*/ +static fuzzer_stem *fuzzerInsert(fuzzer_cursor *pCur, fuzzer_stem *pNew){ + fuzzer_stem *pX; + int i; + + /* If pCur->pStem exists and is greater than pNew, then make pNew + ** the new pCur->pStem and insert the old pCur->pStem instead. + */ + if( (pX = pCur->pStem)!=0 && pX->rCostX>pNew->rCostX ){ + pNew->pNext = 0; + pCur->pStem = pNew; + pNew = pX; + } + + /* Insert the new value */ + pNew->pNext = 0; + pX = pNew; + for(i=0; i<=pCur->mxQueue; i++){ + if( pCur->aQueue[i] ){ + pX = fuzzerMergeStems(pX, pCur->aQueue[i]); + pCur->aQueue[i] = 0; + }else{ + pCur->aQueue[i] = pX; + break; + } + } + if( i>pCur->mxQueue ){ + if( i<FUZZER_NQUEUE ){ + pCur->mxQueue = i; + pCur->aQueue[i] = pX; + }else{ + assert( pCur->mxQueue==FUZZER_NQUEUE-1 ); + pX = fuzzerMergeStems(pX, pCur->aQueue[FUZZER_NQUEUE-1]); + pCur->aQueue[FUZZER_NQUEUE-1] = pX; + } + } + + return fuzzerLowestCostStem(pCur); +} + +/* +** Allocate a new fuzzer_stem. Add it to the hash table but do not +** link it into either the pCur->pStem or pCur->pDone lists. +*/ +static fuzzer_stem *fuzzerNewStem( + fuzzer_cursor *pCur, + const char *zWord, + fuzzer_cost rBaseCost +){ + fuzzer_stem *pNew; + unsigned int h; + + pNew = sqlite3_malloc( sizeof(*pNew) + strlen(zWord) + 1 ); + if( pNew==0 ) return 0; + memset(pNew, 0, sizeof(*pNew)); + pNew->zBasis = (char*)&pNew[1]; + pNew->nBasis = strlen(zWord); + memcpy(pNew->zBasis, zWord, pNew->nBasis+1); + pNew->pRule = pCur->pVtab->pRule; + pNew->n = -1; + pNew->rBaseCost = pNew->rCostX = rBaseCost; + h = fuzzerHash(pNew->zBasis); + pNew->pHash = pCur->apHash[h]; + pCur->apHash[h] = pNew; + pCur->nStem++; + return pNew; +} + + +/* +** Advance a cursor to its next row of output +*/ +static int fuzzerNext(sqlite3_vtab_cursor *cur){ + fuzzer_cursor *pCur = (fuzzer_cursor*)cur; + int rc; + fuzzer_stem *pStem, *pNew; + + pCur->iRowid++; + + /* Use the element the cursor is currently point to to create + ** a new stem and insert the new stem into the priority queue. + */ + pStem = pCur->pStem; + if( pStem->rCostX>0 ){ + rc = fuzzerRender(pStem, &pCur->zBuf, &pCur->nBuf); + if( rc==SQLITE_NOMEM ) return SQLITE_NOMEM; + pNew = fuzzerNewStem(pCur, pCur->zBuf, pStem->rCostX); + if( pNew ){ + if( fuzzerAdvance(pCur, pNew)==0 ){ + pNew->pNext = pCur->pDone; + pCur->pDone = pNew; + }else{ + if( fuzzerInsert(pCur, pNew)==pNew ){ + return SQLITE_OK; + } + } + }else{ + return SQLITE_NOMEM; + } + } + + /* Adjust the priority queue so that the first element of the + ** stem list is the next lowest cost word. + */ + while( (pStem = pCur->pStem)!=0 ){ + if( fuzzerAdvance(pCur, pStem) ){ + pCur->pStem = 0; + pStem = fuzzerInsert(pCur, pStem); + if( (rc = fuzzerSeen(pCur, pStem))!=0 ){ + if( rc<0 ) return SQLITE_NOMEM; + continue; + } + return SQLITE_OK; /* New word found */ + } + pCur->pStem = 0; + pStem->pNext = pCur->pDone; + pCur->pDone = pStem; + if( fuzzerLowestCostStem(pCur) ){ + rc = fuzzerSeen(pCur, pCur->pStem); + if( rc<0 ) return SQLITE_NOMEM; + if( rc==0 ){ + return SQLITE_OK; + } + } + } + + /* Reach this point only if queue has been exhausted and there is + ** nothing left to be output. */ + pCur->rLimit = (fuzzer_cost)0; + return SQLITE_OK; +} + +/* +** Called to "rewind" a cursor back to the beginning so that +** it starts its output over again. Always called at least once +** prior to any fuzzerColumn, fuzzerRowid, or fuzzerEof call. +*/ +static int fuzzerFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + fuzzer_cursor *pCur = (fuzzer_cursor *)pVtabCursor; + const char *zWord = 0; + fuzzer_stem *pStem; + + fuzzerClearCursor(pCur, 1); + pCur->rLimit = 2147483647; + if( idxNum==1 ){ + zWord = (const char*)sqlite3_value_text(argv[0]); + }else if( idxNum==2 ){ + pCur->rLimit = (fuzzer_cost)sqlite3_value_int(argv[0]); + }else if( idxNum==3 ){ + zWord = (const char*)sqlite3_value_text(argv[0]); + pCur->rLimit = (fuzzer_cost)sqlite3_value_int(argv[1]); + } + if( zWord==0 ) zWord = ""; + pCur->pStem = pStem = fuzzerNewStem(pCur, zWord, (fuzzer_cost)0); + if( pStem==0 ) return SQLITE_NOMEM; + pCur->nullRule.pNext = pCur->pVtab->pRule; + pCur->nullRule.rCost = 0; + pCur->nullRule.nFrom = 0; + pCur->nullRule.nTo = 0; + pCur->nullRule.zFrom = ""; + pStem->pRule = &pCur->nullRule; + pStem->n = pStem->nBasis; + pCur->iRowid = 1; + return SQLITE_OK; +} + +/* +** Only the word and distance columns have values. All other columns +** return NULL +*/ +static int fuzzerColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ + fuzzer_cursor *pCur = (fuzzer_cursor*)cur; + if( i==0 ){ + /* the "word" column */ + if( fuzzerRender(pCur->pStem, &pCur->zBuf, &pCur->nBuf)==SQLITE_NOMEM ){ + return SQLITE_NOMEM; + } + sqlite3_result_text(ctx, pCur->zBuf, -1, SQLITE_TRANSIENT); + }else if( i==1 ){ + /* the "distance" column */ + sqlite3_result_int(ctx, pCur->pStem->rCostX); + }else{ + /* All other columns are NULL */ + sqlite3_result_null(ctx); + } + return SQLITE_OK; +} + +/* +** The rowid. +*/ +static int fuzzerRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + fuzzer_cursor *pCur = (fuzzer_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** When the fuzzer_cursor.rLimit value is 0 or less, that is a signal +** that the cursor has nothing more to output. +*/ +static int fuzzerEof(sqlite3_vtab_cursor *cur){ + fuzzer_cursor *pCur = (fuzzer_cursor*)cur; + return pCur->rLimit<=(fuzzer_cost)0; +} + +/* +** Search for terms of these forms: +** +** word MATCH $str +** distance < $value +** distance <= $value +** +** The distance< and distance<= are both treated as distance<=. +** The query plan number is as follows: +** +** 0: None of the terms above are found +** 1: There is a "word MATCH" term with $str in filter.argv[0]. +** 2: There is a "distance<" term with $value in filter.argv[0]. +** 3: Both "word MATCH" and "distance<" with $str in argv[0] and +** $value in argv[1]. +*/ +static int fuzzerBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + int iPlan = 0; + int iDistTerm = -1; + int i; + const struct sqlite3_index_constraint *pConstraint; + pConstraint = pIdxInfo->aConstraint; + for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){ + if( pConstraint->usable==0 ) continue; + if( (iPlan & 1)==0 + && pConstraint->iColumn==0 + && pConstraint->op==SQLITE_INDEX_CONSTRAINT_MATCH + ){ + iPlan |= 1; + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + pIdxInfo->aConstraintUsage[i].omit = 1; + } + if( (iPlan & 2)==0 + && pConstraint->iColumn==1 + && (pConstraint->op==SQLITE_INDEX_CONSTRAINT_LT + || pConstraint->op==SQLITE_INDEX_CONSTRAINT_LE) + ){ + iPlan |= 2; + iDistTerm = i; + } + } + if( iPlan==2 ){ + pIdxInfo->aConstraintUsage[iDistTerm].argvIndex = 1; + }else if( iPlan==3 ){ + pIdxInfo->aConstraintUsage[iDistTerm].argvIndex = 2; + } + pIdxInfo->idxNum = iPlan; + if( pIdxInfo->nOrderBy==1 + && pIdxInfo->aOrderBy[0].iColumn==1 + && pIdxInfo->aOrderBy[0].desc==0 + ){ + pIdxInfo->orderByConsumed = 1; + } + pIdxInfo->estimatedCost = (double)10000; + + return SQLITE_OK; +} + +/* +** Disallow all attempts to DELETE or UPDATE. Only INSERTs are allowed. +** +** On an insert, the cFrom, cTo, and cost columns are used to construct +** a new rule. All other columns are ignored. The rule is ignored +** if cFrom and cTo are identical. A NULL value for cFrom or cTo is +** interpreted as an empty string. The cost must be positive. +*/ +static int fuzzerUpdate( + sqlite3_vtab *pVTab, + int argc, + sqlite3_value **argv, + sqlite_int64 *pRowid +){ + fuzzer_vtab *p = (fuzzer_vtab*)pVTab; + fuzzer_rule *pRule; + const char *zFrom; + int nFrom; + const char *zTo; + int nTo; + fuzzer_cost rCost; + if( argc!=7 ){ + sqlite3_free(pVTab->zErrMsg); + pVTab->zErrMsg = sqlite3_mprintf("cannot delete from a %s virtual table", + p->zClassName); + return SQLITE_CONSTRAINT; + } + if( sqlite3_value_type(argv[0])!=SQLITE_NULL ){ + sqlite3_free(pVTab->zErrMsg); + pVTab->zErrMsg = sqlite3_mprintf("cannot update a %s virtual table", + p->zClassName); + return SQLITE_CONSTRAINT; + } + zFrom = (char*)sqlite3_value_text(argv[4]); + if( zFrom==0 ) zFrom = ""; + zTo = (char*)sqlite3_value_text(argv[5]); + if( zTo==0 ) zTo = ""; + if( strcmp(zFrom,zTo)==0 ){ + /* Silently ignore null transformations */ + return SQLITE_OK; + } + rCost = sqlite3_value_int(argv[6]); + if( rCost<=0 ){ + sqlite3_free(pVTab->zErrMsg); + pVTab->zErrMsg = sqlite3_mprintf("cost must be positive"); + return SQLITE_CONSTRAINT; + } + nFrom = strlen(zFrom); + nTo = strlen(zTo); + pRule = sqlite3_malloc( sizeof(*pRule) + nFrom + nTo ); + if( pRule==0 ){ + return SQLITE_NOMEM; + } + pRule->zFrom = &pRule->zTo[nTo+1]; + pRule->nFrom = nFrom; + memcpy(pRule->zFrom, zFrom, nFrom+1); + memcpy(pRule->zTo, zTo, nTo+1); + pRule->nTo = nTo; + pRule->rCost = rCost; + pRule->pNext = p->pNewRule; + p->pNewRule = pRule; + return SQLITE_OK; +} + +/* +** A virtual table module that provides read-only access to a +** Tcl global variable namespace. +*/ +static sqlite3_module fuzzerModule = { + 0, /* iVersion */ + fuzzerConnect, + fuzzerConnect, + fuzzerBestIndex, + fuzzerDisconnect, + fuzzerDisconnect, + fuzzerOpen, /* xOpen - open a cursor */ + fuzzerClose, /* xClose - close a cursor */ + fuzzerFilter, /* xFilter - configure scan constraints */ + fuzzerNext, /* xNext - advance a cursor */ + fuzzerEof, /* xEof - check for end of scan */ + fuzzerColumn, /* xColumn - read data */ + fuzzerRowid, /* xRowid - read data */ + fuzzerUpdate, /* xUpdate - INSERT */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ +}; + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + + +/* +** Register the fuzzer virtual table +*/ +int fuzzer_register(sqlite3 *db){ + int rc = SQLITE_OK; +#ifndef SQLITE_OMIT_VIRTUALTABLE + rc = sqlite3_create_module(db, "fuzzer", &fuzzerModule, 0); +#endif + return rc; +} + +#ifdef SQLITE_TEST +#include <tcl.h> +/* +** Decode a pointer to an sqlite3 object. +*/ +extern int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb); + +/* +** Register the echo virtual table module. +*/ +static int register_fuzzer_module( + ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + sqlite3 *db; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + fuzzer_register(db); + return TCL_OK; +} + + +/* +** Register commands with the TCL interpreter. +*/ +int Sqlitetestfuzzer_Init(Tcl_Interp *interp){ + static struct { + char *zName; + Tcl_ObjCmdProc *xProc; + void *clientData; + } aObjCmd[] = { + { "register_fuzzer_module", register_fuzzer_module, 0 }, + }; + int i; + for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){ + Tcl_CreateObjCommand(interp, aObjCmd[i].zName, + aObjCmd[i].xProc, aObjCmd[i].clientData, 0); + } + return TCL_OK; +} + +#endif /* SQLITE_TEST */ diff --git a/src/test_multiplex.c b/src/test_multiplex.c index 72a71621b..d8a7db86e 100644 --- a/src/test_multiplex.c +++ b/src/test_multiplex.c @@ -22,7 +22,22 @@ #include "sqlite3.h" #include <string.h> #include <assert.h> -#include "sqliteInt.h" +#include "test_multiplex.h" + +#ifndef SQLITE_CORE + #define SQLITE_CORE 1 /* Disable the API redefinition in sqlite3ext.h */ +#endif +#include "sqlite3ext.h" + +/* +** These should be defined to be the same as the values in +** sqliteInt.h. They are defined seperately here so that +** the multiplex VFS shim can be built as a loadable +** module. +*/ +#define UNUSED_PARAMETER(x) (void)(x) +#define MAX_PAGE_SIZE 0x10000 +#define DEFAULT_SECTOR_SIZE 0x1000 /* ** For a build without mutexes, no-op the mutex calls. @@ -40,14 +55,18 @@ /************************ Shim Definitions ******************************/ +#define SQLITE_MULTIPLEX_VFS_NAME "multiplex" + /* This is the limit on the chunk size. It may be changed by calling -** the sqlite3_multiplex_set() interface. +** the xFileControl() interface. It will be rounded up to a +** multiple of MAX_PAGE_SIZE. We default it here to 1GB. */ -#define SQLITE_MULTIPLEX_CHUNK_SIZE 0x40000000 +#define SQLITE_MULTIPLEX_CHUNK_SIZE (MAX_PAGE_SIZE*16384) + /* Default limit on number of chunks. Care should be taken ** so that values for chunks numbers fit in the SQLITE_MULTIPLEX_EXT_FMT ** format specifier. It may be changed by calling -** the sqlite3_multiplex_set() interface. +** the xFileControl() interface. */ #define SQLITE_MULTIPLEX_MAX_CHUNKS 32 @@ -82,6 +101,9 @@ struct multiplexGroup { char *zName; /* Base filename of this group */ int nName; /* Length of base filename */ int flags; /* Flags used for original opening */ + int nChunkSize; /* Chunk size used for this group */ + int nMaxChunks; /* Max number of chunks for this group */ + int bEnabled; /* TRUE to use Multiplex VFS for this file */ multiplexGroup *pNext, *pPrev; /* Doubly linked list of all group objects */ }; @@ -140,11 +162,6 @@ static struct { */ multiplexGroup *pGroups; - /* Chunk params. - */ - int nChunkSize; - int nMaxChunks; - /* Storage for temp file names. Allocated during ** initialization to the max pathname of the underlying VFS. */ @@ -160,13 +177,28 @@ static struct { static void multiplexEnter(void){ sqlite3_mutex_enter(gMultiplex.pMutex); } static void multiplexLeave(void){ sqlite3_mutex_leave(gMultiplex.pMutex); } +/* +** Compute a string length that is limited to what can be stored in +** lower 30 bits of a 32-bit signed integer. +** +** The value returned will never be negative. Nor will it ever be greater +** than the actual length of the string. For very long strings (greater +** than 1GiB) the value returned might be less than the true string length. +*/ +int multiplexStrlen30(const char *z){ + const char *z2 = z; + if( z==0 ) return 0; + while( *z2 ){ z2++; } + return 0x3fffffff & (int)(z2 - z); +} + /* Translate an sqlite3_file* that is really a multiplexGroup* into ** the sqlite3_file* for the underlying original VFS. */ static sqlite3_file *multiplexSubOpen(multiplexConn *pConn, int iChunk, int *rc, int *pOutFlags){ multiplexGroup *pGroup = pConn->pGroup; sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ - if( iChunk<gMultiplex.nMaxChunks ){ + if( iChunk<pGroup->nMaxChunks ){ sqlite3_file *pSubOpen = pGroup->pReal[iChunk]; /* Real file descriptor */ if( !pGroup->bOpen[iChunk] ){ memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); @@ -191,6 +223,62 @@ static sqlite3_file *multiplexSubOpen(multiplexConn *pConn, int iChunk, int *rc, return NULL; } +/* +** This is the implementation of the multiplex_control() SQL function. +*/ +static void multiplexControlFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int rc = SQLITE_OK; + sqlite3 *db = sqlite3_context_db_handle(context); + int op; + int iVal; + + if( !db || argc!=2 ){ + rc = SQLITE_ERROR; + }else{ + /* extract params */ + op = sqlite3_value_int(argv[0]); + iVal = sqlite3_value_int(argv[1]); + /* map function op to file_control op */ + switch( op ){ + case 1: + op = MULTIPLEX_CTRL_ENABLE; + break; + case 2: + op = MULTIPLEX_CTRL_SET_CHUNK_SIZE; + break; + case 3: + op = MULTIPLEX_CTRL_SET_MAX_CHUNKS; + break; + default: + rc = SQLITE_NOTFOUND; + break; + } + } + if( rc==SQLITE_OK ){ + rc = sqlite3_file_control(db, 0, op, &iVal); + } + sqlite3_result_error_code(context, rc); +} + +/* +** This is the entry point to register the auto-extension for the +** multiplex_control() function. +*/ +static int multiplexFuncInit( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc; + rc = sqlite3_create_function(db, "multiplex_control", 2, SQLITE_ANY, + 0, multiplexControlFunc, 0, 0); + return rc; +} + /************************* VFS Method Wrappers *****************************/ /* @@ -212,7 +300,7 @@ static int multiplexOpen( multiplexGroup *pGroup; /* Corresponding multiplexGroup object */ sqlite3_file *pSubOpen; /* Real file descriptor */ sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ - int nName = sqlite3Strlen30(zName); + int nName = multiplexStrlen30(zName); int i; int sz; @@ -224,11 +312,11 @@ static int multiplexOpen( multiplexEnter(); pMultiplexOpen = (multiplexConn*)pConn; /* allocate space for group */ - sz = sizeof(multiplexGroup) /* multiplexGroup */ - + (sizeof(sqlite3_file *)*gMultiplex.nMaxChunks) /* pReal[] */ - + (pOrigVfs->szOsFile*gMultiplex.nMaxChunks) /* *pReal */ - + gMultiplex.nMaxChunks /* bOpen[] */ - + nName + 1; /* zName */ + sz = sizeof(multiplexGroup) /* multiplexGroup */ + + (sizeof(sqlite3_file *)*SQLITE_MULTIPLEX_MAX_CHUNKS) /* pReal[] */ + + (pOrigVfs->szOsFile*SQLITE_MULTIPLEX_MAX_CHUNKS) /* *pReal */ + + SQLITE_MULTIPLEX_MAX_CHUNKS /* bOpen[] */ + + nName + 1; /* zName */ #ifndef SQLITE_MULTIPLEX_EXT_OVWR sz += SQLITE_MULTIPLEX_EXT_SZ; assert(nName+SQLITE_MULTIPLEX_EXT_SZ < pOrigVfs->mxPathname); @@ -244,14 +332,18 @@ static int multiplexOpen( char *p = (char *)&pGroup[1]; pMultiplexOpen->pGroup = pGroup; memset(pGroup, 0, sz); + pGroup->bEnabled = -1; + pGroup->nChunkSize = SQLITE_MULTIPLEX_CHUNK_SIZE; + pGroup->nMaxChunks = SQLITE_MULTIPLEX_MAX_CHUNKS; pGroup->pReal = (sqlite3_file **)p; - p += (sizeof(sqlite3_file *)*gMultiplex.nMaxChunks); - for(i=0; i<gMultiplex.nMaxChunks; i++){ + p += (sizeof(sqlite3_file *)*pGroup->nMaxChunks); + for(i=0; i<pGroup->nMaxChunks; i++){ pGroup->pReal[i] = (sqlite3_file *)p; p += pOrigVfs->szOsFile; } + /* bOpen[] vals should all be zero from memset above */ pGroup->bOpen = p; - p += gMultiplex.nMaxChunks; + p += pGroup->nMaxChunks; pGroup->zName = p; /* save off base filename, name length, and original open flags */ memcpy(pGroup->zName, zName, nName+1); @@ -259,6 +351,14 @@ static int multiplexOpen( pGroup->flags = flags; pSubOpen = multiplexSubOpen(pMultiplexOpen, 0, &rc, pOutFlags); if( pSubOpen ){ + /* if this file is already larger than chunk size, disable + ** the multiplex feature. + */ + sqlite3_int64 sz; + int rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); + if( (rc2==SQLITE_OK) && (sz>pGroup->nChunkSize) ){ + pGroup->bEnabled = 0; + } if( pSubOpen->pMethods->iVersion==1 ){ pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV1; }else{ @@ -288,24 +388,29 @@ static int multiplexDelete( ){ sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ int rc = SQLITE_OK; - int nName = sqlite3Strlen30(zName); + int nName = multiplexStrlen30(zName); int i; UNUSED_PARAMETER(pVfs); multiplexEnter(); memcpy(gMultiplex.zName, zName, nName+1); - for(i=0; i<gMultiplex.nMaxChunks; i++){ + for(i=0; i<SQLITE_MULTIPLEX_MAX_CHUNKS; i++){ int rc2; int exists = 0; if( i ){ #ifdef SQLITE_MULTIPLEX_EXT_OVWR - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i); + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, + gMultiplex.zName+nName-SQLITE_MULTIPLEX_EXT_SZ, + SQLITE_MULTIPLEX_EXT_FMT, i); #else - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+nName, SQLITE_MULTIPLEX_EXT_FMT, i); + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, + gMultiplex.zName+nName, + SQLITE_MULTIPLEX_EXT_FMT, i); #endif } - rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, SQLITE_ACCESS_EXISTS, &exists); + rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, + SQLITE_ACCESS_EXISTS, &exists); if( rc2==SQLITE_OK && exists){ /* if it exists, delete it */ rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, syncDir); @@ -367,7 +472,7 @@ static int multiplexClose(sqlite3_file *pConn){ int i; multiplexEnter(); /* close any open handles */ - for(i=0; i<gMultiplex.nMaxChunks; i++){ + for(i=0; i<pGroup->nMaxChunks; i++){ if( pGroup->bOpen[i] ){ sqlite3_file *pSubOpen = pGroup->pReal[i]; int rc2 = pSubOpen->pMethods->xClose(pSubOpen); @@ -398,23 +503,29 @@ static int multiplexRead( sqlite3_int64 iOfst ){ multiplexConn *p = (multiplexConn*)pConn; + multiplexGroup *pGroup = p->pGroup; int rc = SQLITE_OK; multiplexEnter(); - while( iAmt > 0 ){ - int i = (int)(iOfst/gMultiplex.nChunkSize); - sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL); - if( pSubOpen ){ - int extra = ((int)(iOfst % gMultiplex.nChunkSize) + iAmt) - gMultiplex.nChunkSize; - if( extra<0 ) extra = 0; - iAmt -= extra; - rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst%gMultiplex.nChunkSize); - if( rc!=SQLITE_OK ) break; - pBuf = (char *)pBuf + iAmt; - iOfst += iAmt; - iAmt = extra; - }else{ - rc = SQLITE_IOERR_READ; - break; + if( !pGroup->bEnabled ){ + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + rc = ( !pSubOpen ) ? SQLITE_IOERR_READ : pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst); + }else{ + while( iAmt > 0 ){ + int i = (int)(iOfst / pGroup->nChunkSize); + sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL); + if( pSubOpen ){ + int extra = ((int)(iOfst % pGroup->nChunkSize) + iAmt) - pGroup->nChunkSize; + if( extra<0 ) extra = 0; + iAmt -= extra; + rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst % pGroup->nChunkSize); + if( rc!=SQLITE_OK ) break; + pBuf = (char *)pBuf + iAmt; + iOfst += iAmt; + iAmt = extra; + }else{ + rc = SQLITE_IOERR_READ; + break; + } } } multiplexLeave(); @@ -432,23 +543,29 @@ static int multiplexWrite( sqlite3_int64 iOfst ){ multiplexConn *p = (multiplexConn*)pConn; + multiplexGroup *pGroup = p->pGroup; int rc = SQLITE_OK; multiplexEnter(); - while( iAmt > 0 ){ - int i = (int)(iOfst/gMultiplex.nChunkSize); - sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL); - if( pSubOpen ){ - int extra = ((int)(iOfst % gMultiplex.nChunkSize) + iAmt) - gMultiplex.nChunkSize; - if( extra<0 ) extra = 0; - iAmt -= extra; - rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst%gMultiplex.nChunkSize); - if( rc!=SQLITE_OK ) break; - pBuf = (char *)pBuf + iAmt; - iOfst += iAmt; - iAmt = extra; - }else{ - rc = SQLITE_IOERR_WRITE; - break; + if( !pGroup->bEnabled ){ + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + rc = ( !pSubOpen ) ? SQLITE_IOERR_WRITE : pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst); + }else{ + while( iAmt > 0 ){ + int i = (int)(iOfst / pGroup->nChunkSize); + sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL); + if( pSubOpen ){ + int extra = ((int)(iOfst % pGroup->nChunkSize) + iAmt) - pGroup->nChunkSize; + if( extra<0 ) extra = 0; + iAmt -= extra; + rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst % pGroup->nChunkSize); + if( rc!=SQLITE_OK ) break; + pBuf = (char *)pBuf + iAmt; + iOfst += iAmt; + iAmt = extra; + }else{ + rc = SQLITE_IOERR_WRITE; + break; + } } } multiplexLeave(); @@ -463,35 +580,44 @@ static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){ multiplexConn *p = (multiplexConn*)pConn; multiplexGroup *pGroup = p->pGroup; int rc = SQLITE_OK; - int rc2; - int i; - sqlite3_file *pSubOpen; - sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ multiplexEnter(); - memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); - /* delete the chunks above the truncate limit */ - for(i=(int)(size/gMultiplex.nChunkSize)+1; i<gMultiplex.nMaxChunks; i++){ - /* close any open chunks before deleting them */ - if( pGroup->bOpen[i] ){ - pSubOpen = pGroup->pReal[i]; - rc2 = pSubOpen->pMethods->xClose(pSubOpen); - if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE; - pGroup->bOpen[i] = 0; - } + if( !pGroup->bEnabled ){ + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + rc = ( !pSubOpen ) ? SQLITE_IOERR_TRUNCATE : pSubOpen->pMethods->xTruncate(pSubOpen, size); + }else{ + int rc2; + int i; + sqlite3_file *pSubOpen; + sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ + memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); + /* delete the chunks above the truncate limit */ + for(i=(int)(size / pGroup->nChunkSize)+1; i<pGroup->nMaxChunks; i++){ + /* close any open chunks before deleting them */ + if( pGroup->bOpen[i] ){ + pSubOpen = pGroup->pReal[i]; + rc2 = pSubOpen->pMethods->xClose(pSubOpen); + if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE; + pGroup->bOpen[i] = 0; + } #ifdef SQLITE_MULTIPLEX_EXT_OVWR - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i); + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, + gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, + SQLITE_MULTIPLEX_EXT_FMT, i); #else - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, i); + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, + gMultiplex.zName+pGroup->nName, + SQLITE_MULTIPLEX_EXT_FMT, i); #endif - rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, 0); - if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE; - } - pSubOpen = multiplexSubOpen(p, (int)(size/gMultiplex.nChunkSize), &rc2, NULL); - if( pSubOpen ){ - rc2 = pSubOpen->pMethods->xTruncate(pSubOpen, size%gMultiplex.nChunkSize); - if( rc2!=SQLITE_OK ) rc = rc2; - }else{ - rc = SQLITE_IOERR_TRUNCATE; + rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, 0); + if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE; + } + pSubOpen = multiplexSubOpen(p, (int)(size / pGroup->nChunkSize), &rc2, NULL); + if( pSubOpen ){ + rc2 = pSubOpen->pMethods->xTruncate(pSubOpen, size % pGroup->nChunkSize); + if( rc2!=SQLITE_OK ) rc = rc2; + }else{ + rc = SQLITE_IOERR_TRUNCATE; + } } multiplexLeave(); return rc; @@ -505,7 +631,7 @@ static int multiplexSync(sqlite3_file *pConn, int flags){ int rc = SQLITE_OK; int i; multiplexEnter(); - for(i=0; i<gMultiplex.nMaxChunks; i++){ + for(i=0; i<pGroup->nMaxChunks; i++){ /* if we don't have it open, we don't need to sync it */ if( pGroup->bOpen[i] ){ sqlite3_file *pSubOpen = pGroup->pReal[i]; @@ -527,46 +653,56 @@ static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){ int rc2; int i; multiplexEnter(); - *pSize = 0; - for(i=0; i<gMultiplex.nMaxChunks; i++){ - sqlite3_file *pSubOpen = NULL; - /* if not opened already, check to see if the chunk exists */ - if( pGroup->bOpen[i] ){ - pSubOpen = pGroup->pReal[i]; - }else{ - sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ - int exists = 0; - memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); - if( i ){ + if( !pGroup->bEnabled ){ + sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + rc = ( !pSubOpen ) ? SQLITE_IOERR_FSTAT : pSubOpen->pMethods->xFileSize(pSubOpen, pSize); + }else{ + *pSize = 0; + for(i=0; i<pGroup->nMaxChunks; i++){ + sqlite3_file *pSubOpen = NULL; + /* if not opened already, check to see if the chunk exists */ + if( pGroup->bOpen[i] ){ + pSubOpen = pGroup->pReal[i]; + }else{ + sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs; /* Real VFS */ + int exists = 0; + memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1); + if( i ){ #ifdef SQLITE_MULTIPLEX_EXT_OVWR - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, i); + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, + gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, + SQLITE_MULTIPLEX_EXT_FMT, i); #else - sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, i); + sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, + gMultiplex.zName+pGroup->nName, + SQLITE_MULTIPLEX_EXT_FMT, i); #endif + } + rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, + SQLITE_ACCESS_EXISTS, &exists); + if( rc2==SQLITE_OK && exists){ + /* if it exists, open it */ + pSubOpen = multiplexSubOpen(p, i, &rc, NULL); + }else{ + /* stop at first "gap" */ + break; + } } - rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName, SQLITE_ACCESS_EXISTS, &exists); - if( rc2==SQLITE_OK && exists){ - /* if it exists, open it */ - pSubOpen = multiplexSubOpen(p, i, &rc, NULL); + if( pSubOpen ){ + sqlite3_int64 sz; + rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); + if( rc2!=SQLITE_OK ){ + rc = rc2; + }else{ + if( sz>pGroup->nChunkSize ){ + rc = SQLITE_IOERR_FSTAT; + } + *pSize += sz; + } }else{ - /* stop at first "gap" */ break; } } - if( pSubOpen ){ - sqlite3_int64 sz; - rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz); - if( rc2!=SQLITE_OK ){ - rc = rc2; - }else{ - if( sz>gMultiplex.nChunkSize ){ - rc = SQLITE_IOERR_FSTAT; - } - *pSize += sz; - } - }else{ - break; - } } multiplexLeave(); return rc; @@ -608,18 +744,62 @@ static int multiplexCheckReservedLock(sqlite3_file *pConn, int *pResOut){ return SQLITE_IOERR_CHECKRESERVEDLOCK; } -/* Pass xFileControl requests through to the original VFS unchanged. +/* Pass xFileControl requests through to the original VFS unchanged, +** except for any MULTIPLEX_CTRL_* requests here. */ static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){ multiplexConn *p = (multiplexConn*)pConn; - int rc; + multiplexGroup *pGroup = p->pGroup; + int rc = SQLITE_ERROR; sqlite3_file *pSubOpen; - if ( op==SQLITE_FCNTL_SIZE_HINT || op==SQLITE_FCNTL_CHUNK_SIZE ) return SQLITE_OK; - pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); - if( pSubOpen ){ - return pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); + + if( !gMultiplex.isInitialized ) return SQLITE_MISUSE; + switch( op ){ + case MULTIPLEX_CTRL_ENABLE: + if( pArg ) { + int bEnabled = *(int *)pArg; + pGroup->bEnabled = bEnabled; + rc = SQLITE_OK; + } + break; + case MULTIPLEX_CTRL_SET_CHUNK_SIZE: + if( pArg ) { + int nChunkSize = *(int *)pArg; + if( nChunkSize<1 ){ + rc = SQLITE_MISUSE; + }else{ + /* Round up to nearest multiple of MAX_PAGE_SIZE. */ + nChunkSize = (nChunkSize + (MAX_PAGE_SIZE-1)); + nChunkSize &= ~(MAX_PAGE_SIZE-1); + pGroup->nChunkSize = nChunkSize; + rc = SQLITE_OK; + } + } + break; + case MULTIPLEX_CTRL_SET_MAX_CHUNKS: + if( pArg ) { + int nMaxChunks = *(int *)pArg; + if(( nMaxChunks<1 ) || ( nMaxChunks>SQLITE_MULTIPLEX_MAX_CHUNKS )){ + rc = SQLITE_MISUSE; + }else{ + pGroup->nMaxChunks = nMaxChunks; + rc = SQLITE_OK; + } + } + break; + case SQLITE_FCNTL_SIZE_HINT: + case SQLITE_FCNTL_CHUNK_SIZE: + /* no-op these */ + rc = SQLITE_OK; + break; + default: + pSubOpen = multiplexSubOpen(p, 0, &rc, NULL); + if( pSubOpen ){ + rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); + } + break; } - return SQLITE_ERROR; + return rc; } /* Pass xSectorSize requests through to the original VFS unchanged. @@ -631,7 +811,7 @@ static int multiplexSectorSize(sqlite3_file *pConn){ if( pSubOpen ){ return pSubOpen->pMethods->xSectorSize(pSubOpen); } - return SQLITE_DEFAULT_SECTOR_SIZE; + return DEFAULT_SECTOR_SIZE; } /* Pass xDeviceCharacteristics requests through to the original VFS unchanged. @@ -706,9 +886,10 @@ static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){ /************************** Public Interfaces *****************************/ /* -** Initialize the multiplex VFS shim. Use the VFS named zOrigVfsName -** as the VFS that does the actual work. Use the default if -** zOrigVfsName==NULL. +** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize() +** +** Use the VFS named zOrigVfsName as the VFS that does the actual work. +** Use the default if zOrigVfsName==NULL. ** ** The multiplex VFS shim is named "multiplex". It will become the default ** VFS if makeDefault is non-zero. @@ -731,14 +912,12 @@ int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){ sqlite3_mutex_free(gMultiplex.pMutex); return SQLITE_NOMEM; } - gMultiplex.nChunkSize = SQLITE_MULTIPLEX_CHUNK_SIZE; - gMultiplex.nMaxChunks = SQLITE_MULTIPLEX_MAX_CHUNKS; gMultiplex.pGroups = NULL; gMultiplex.isInitialized = 1; gMultiplex.pOrigVfs = pOrigVfs; gMultiplex.sThisVfs = *pOrigVfs; gMultiplex.sThisVfs.szOsFile += sizeof(multiplexConn); - gMultiplex.sThisVfs.zName = "multiplex"; + gMultiplex.sThisVfs.zName = SQLITE_MULTIPLEX_VFS_NAME; gMultiplex.sThisVfs.xOpen = multiplexOpen; gMultiplex.sThisVfs.xDelete = multiplexDelete; gMultiplex.sThisVfs.xAccess = multiplexAccess; @@ -773,11 +952,14 @@ int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){ gMultiplex.sIoMethodsV2.xShmBarrier = multiplexShmBarrier; gMultiplex.sIoMethodsV2.xShmUnmap = multiplexShmUnmap; sqlite3_vfs_register(&gMultiplex.sThisVfs, makeDefault); + + sqlite3_auto_extension((void*)multiplexFuncInit); + return SQLITE_OK; } /* -** Shutdown the multiplex system. +** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown() ** ** All SQLite database connections must be closed before calling this ** routine. @@ -796,31 +978,9 @@ int sqlite3_multiplex_shutdown(void){ return SQLITE_OK; } -/* -** Adjust chunking params. VFS should be initialized first. -** No files should be open. Re-intializing will reset these -** to the default. -*/ -int sqlite3_multiplex_set( - int nChunkSize, /* Max chunk size */ - int nMaxChunks /* Max number of chunks */ -){ - if( !gMultiplex.isInitialized ) return SQLITE_MISUSE; - if( gMultiplex.pGroups ) return SQLITE_MISUSE; - if( nChunkSize<32 ) return SQLITE_MISUSE; - if( nMaxChunks<1 ) return SQLITE_MISUSE; - if( nMaxChunks>99 ) return SQLITE_MISUSE; - multiplexEnter(); - gMultiplex.nChunkSize = nChunkSize; - gMultiplex.nMaxChunks = nMaxChunks; - multiplexLeave(); - return SQLITE_OK; -} - /***************************** Test Code ***********************************/ #ifdef SQLITE_TEST #include <tcl.h> - extern const char *sqlite3TestErrorName(int); @@ -881,36 +1041,6 @@ static int test_multiplex_shutdown( } /* -** tclcmd: sqlite3_multiplex_set CHUNK_SIZE MAX_CHUNKS -*/ -static int test_multiplex_set( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - int nChunkSize; /* Max chunk size */ - int nMaxChunks; /* Max number of chunks */ - int rc; /* Value returned by sqlite3_multiplex_set() */ - - UNUSED_PARAMETER(clientData); - - /* Process arguments */ - if( objc!=3 ){ - Tcl_WrongNumArgs(interp, 1, objv, "CHUNK_SIZE MAX_CHUNKS"); - return TCL_ERROR; - } - if( Tcl_GetIntFromObj(interp, objv[1], &nChunkSize) ) return TCL_ERROR; - if( Tcl_GetIntFromObj(interp, objv[2], &nMaxChunks) ) return TCL_ERROR; - - /* Invoke sqlite3_multiplex_set() */ - rc = sqlite3_multiplex_set(nChunkSize, nMaxChunks); - - Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); - return TCL_OK; -} - -/* ** tclcmd: sqlite3_multiplex_dump */ static int test_multiplex_dump( @@ -943,16 +1073,16 @@ static int test_multiplex_dump( Tcl_NewIntObj(pGroup->flags)); /* count number of chunks with open handles */ - for(i=0; i<gMultiplex.nMaxChunks; i++){ + for(i=0; i<pGroup->nMaxChunks; i++){ if( pGroup->bOpen[i] ) nChunks++; } Tcl_ListObjAppendElement(interp, pGroupTerm, Tcl_NewIntObj(nChunks)); Tcl_ListObjAppendElement(interp, pGroupTerm, - Tcl_NewIntObj(gMultiplex.nChunkSize)); + Tcl_NewIntObj(pGroup->nChunkSize)); Tcl_ListObjAppendElement(interp, pGroupTerm, - Tcl_NewIntObj(gMultiplex.nMaxChunks)); + Tcl_NewIntObj(pGroup->nMaxChunks)); Tcl_ListObjAppendElement(interp, pResult, pGroupTerm); } @@ -962,6 +1092,68 @@ static int test_multiplex_dump( } /* +** Tclcmd: test_multiplex_control HANDLE DBNAME SUB-COMMAND ?INT-VALUE? +*/ +static int test_multiplex_control( + ClientData cd, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int rc; /* Return code from file_control() */ + int idx; /* Index in aSub[] */ + Tcl_CmdInfo cmdInfo; /* Command info structure for HANDLE */ + sqlite3 *db; /* Underlying db handle for HANDLE */ + int iValue = 0; + void *pArg = 0; + + struct SubCommand { + const char *zName; + int op; + int argtype; + } aSub[] = { + { "enable", MULTIPLEX_CTRL_ENABLE, 1 }, + { "chunk_size", MULTIPLEX_CTRL_SET_CHUNK_SIZE, 1 }, + { "max_chunks", MULTIPLEX_CTRL_SET_MAX_CHUNKS, 1 }, + { 0, 0, 0 } + }; + + if( objc!=5 ){ + Tcl_WrongNumArgs(interp, 1, objv, "HANDLE DBNAME SUB-COMMAND INT-VALUE"); + return TCL_ERROR; + } + + if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){ + Tcl_AppendResult(interp, "expected database handle, got \"", 0); + Tcl_AppendResult(interp, Tcl_GetString(objv[1]), "\"", 0); + return TCL_ERROR; + }else{ + db = *(sqlite3 **)cmdInfo.objClientData; + } + + rc = Tcl_GetIndexFromObjStruct( + interp, objv[3], aSub, sizeof(aSub[0]), "sub-command", 0, &idx + ); + if( rc!=TCL_OK ) return rc; + + switch( aSub[idx].argtype ){ + case 1: + if( Tcl_GetIntFromObj(interp, objv[4], &iValue) ){ + return TCL_ERROR; + } + pArg = (void *)&iValue; + break; + default: + Tcl_WrongNumArgs(interp, 4, objv, "SUB-COMMAND"); + return TCL_ERROR; + } + + rc = sqlite3_file_control(db, Tcl_GetString(objv[2]), aSub[idx].op, pArg); + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC); + return (rc==SQLITE_OK) ? TCL_OK : TCL_ERROR; +} + +/* ** This routine registers the custom TCL commands defined in this ** module. This should be the only procedure visible from outside ** of this module. @@ -973,8 +1165,8 @@ int Sqlitemultiplex_Init(Tcl_Interp *interp){ } aCmd[] = { { "sqlite3_multiplex_initialize", test_multiplex_initialize }, { "sqlite3_multiplex_shutdown", test_multiplex_shutdown }, - { "sqlite3_multiplex_set", test_multiplex_set }, { "sqlite3_multiplex_dump", test_multiplex_dump }, + { "sqlite3_multiplex_control", test_multiplex_control }, }; int i; diff --git a/src/test_multiplex.h b/src/test_multiplex.h new file mode 100644 index 000000000..ec1ba9bb2 --- /dev/null +++ b/src/test_multiplex.h @@ -0,0 +1,91 @@ +/* +** 2011 March 18 +** +** 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. +** +************************************************************************* +** +** This file contains a VFS "shim" - a layer that sits in between the +** pager and the real VFS. +** +** This particular shim enforces a multiplex system on DB files. +** This shim shards/partitions a single DB file into smaller +** "chunks" such that the total DB file size may exceed the maximum +** file size of the underlying file system. +** +*/ + +#ifndef _TEST_MULTIPLEX_H +#define _TEST_MULTIPLEX_H + +/* +** CAPI: File-control Operations Supported by Multiplex VFS +** +** Values interpreted by the xFileControl method of a Multiplex VFS db file-handle. +** +** MULTIPLEX_CTRL_ENABLE: +** This file control is used to enable or disable the multiplex +** shim. +** +** MULTIPLEX_CTRL_SET_CHUNK_SIZE: +** This file control is used to set the maximum allowed chunk +** size for a multiplex file set. The chunk size should be +** a multiple of SQLITE_MAX_PAGE_SIZE, and will be rounded up +** if not. +** +** MULTIPLEX_CTRL_SET_MAX_CHUNKS: +** This file control is used to set the maximum number of chunks +** allowed to be used for a mutliplex file set. +*/ +#define MULTIPLEX_CTRL_ENABLE 214014 +#define MULTIPLEX_CTRL_SET_CHUNK_SIZE 214015 +#define MULTIPLEX_CTRL_SET_MAX_CHUNKS 214016 + +/* +** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize() +** +** Use the VFS named zOrigVfsName as the VFS that does the actual work. +** Use the default if zOrigVfsName==NULL. +** +** The multiplex VFS shim is named "multiplex". It will become the default +** VFS if makeDefault is non-zero. +** +** An auto-extension is registered which will make the function +** multiplex_control() available to database connections. This +** function gives access to the xFileControl interface of the +** multiplex VFS shim. +** +** SELECT multiplex_control(<op>,<val>); +** +** <op>=1 MULTIPLEX_CTRL_ENABLE +** <val>=0 disable +** <val>=1 enable +** +** <op>=2 MULTIPLEX_CTRL_SET_CHUNK_SIZE +** <val> int, chunk size +** +** <op>=3 MULTIPLEX_CTRL_SET_MAX_CHUNKS +** <val> int, max chunks +** +** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once +** during start-up. +*/ +extern int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault); + +/* +** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown() +** +** All SQLite database connections must be closed before calling this +** routine. +** +** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while +** shutting down in order to free all remaining multiplex groups. +*/ +extern int sqlite3_multiplex_shutdown(void); + +#endif diff --git a/src/test_syscall.c b/src/test_syscall.c index dbac01d69..c570b71e8 100644 --- a/src/test_syscall.c +++ b/src/test_syscall.c @@ -214,6 +214,12 @@ static int ts_open(const char *zFile, int flags, int mode){ */ static int ts_close(int fd){ if( tsIsFail() ){ + /* Even if simulating an error, close the original file-descriptor. + ** This is to stop the test process from running out of file-descriptors + ** when running a long test. If a call to close() appears to fail, SQLite + ** never attempts to use the file-descriptor afterwards (or even to close + ** it a second time). */ + orig_close(fd); return -1; } return orig_close(fd); @@ -593,6 +599,24 @@ static int test_syscall_list( return TCL_OK; } +static int test_syscall_defaultvfs( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3_vfs *pVfs; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + + pVfs = sqlite3_vfs_find(0); + Tcl_SetObjResult(interp, Tcl_NewStringObj(pVfs->zName, -1)); + return TCL_OK; +} + static int test_syscall( void * clientData, Tcl_Interp *interp, @@ -603,13 +627,14 @@ static int test_syscall( const char *zName; Tcl_ObjCmdProc *xCmd; } aCmd[] = { - { "fault", test_syscall_fault }, - { "install", test_syscall_install }, - { "uninstall", test_syscall_uninstall }, - { "reset", test_syscall_reset }, - { "errno", test_syscall_errno }, - { "exists", test_syscall_exists }, - { "list", test_syscall_list }, + { "fault", test_syscall_fault }, + { "install", test_syscall_install }, + { "uninstall", test_syscall_uninstall }, + { "reset", test_syscall_reset }, + { "errno", test_syscall_errno }, + { "exists", test_syscall_exists }, + { "list", test_syscall_list }, + { "defaultvfs", test_syscall_defaultvfs }, { 0, 0 } }; int iCmd; diff --git a/src/test_wholenumber.c b/src/test_wholenumber.c new file mode 100644 index 000000000..150dc95ac --- /dev/null +++ b/src/test_wholenumber.c @@ -0,0 +1,311 @@ +/* +** 2011 April 02 +** +** 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. +** +************************************************************************* +** +** This file implements a virtual table that returns the whole numbers +** between 1 and 4294967295, inclusive. +** +** Example: +** +** CREATE VIRTUAL TABLE nums USING wholenumber; +** SELECT value FROM nums WHERE value<10; +** +** Results in: +** +** 1 2 3 4 5 6 7 8 9 +*/ +#include "sqlite3.h" +#include <assert.h> +#include <string.h> + +#ifndef SQLITE_OMIT_VIRTUALTABLE + + +/* A wholenumber cursor object */ +typedef struct wholenumber_cursor wholenumber_cursor; +struct wholenumber_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + unsigned iValue; /* Current value */ + unsigned mxValue; /* Maximum value */ +}; + +/* Methods for the wholenumber module */ +static int wholenumberConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + sqlite3_vtab *pNew; + pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); + if( pNew==0 ) return SQLITE_NOMEM; + sqlite3_declare_vtab(db, "CREATE TABLE x(value)"); + memset(pNew, 0, sizeof(*pNew)); + return SQLITE_OK; +} +/* Note that for this virtual table, the xCreate and xConnect +** methods are identical. */ + +static int wholenumberDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} +/* The xDisconnect and xDestroy methods are also the same */ + + +/* +** Open a new wholenumber cursor. +*/ +static int wholenumberOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + wholenumber_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Close a wholenumber cursor. +*/ +static int wholenumberClose(sqlite3_vtab_cursor *cur){ + sqlite3_free(cur); + return SQLITE_OK; +} + + +/* +** Advance a cursor to its next row of output +*/ +static int wholenumberNext(sqlite3_vtab_cursor *cur){ + wholenumber_cursor *pCur = (wholenumber_cursor*)cur; + pCur->iValue++; + return SQLITE_OK; +} + +/* +** Return the value associated with a wholenumber. +*/ +static int wholenumberColumn( + sqlite3_vtab_cursor *cur, + sqlite3_context *ctx, + int i +){ + wholenumber_cursor *pCur = (wholenumber_cursor*)cur; + sqlite3_result_int64(ctx, pCur->iValue); + return SQLITE_OK; +} + +/* +** The rowid. +*/ +static int wholenumberRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + wholenumber_cursor *pCur = (wholenumber_cursor*)cur; + *pRowid = pCur->iValue; + return SQLITE_OK; +} + +/* +** When the wholenumber_cursor.rLimit value is 0 or less, that is a signal +** that the cursor has nothing more to output. +*/ +static int wholenumberEof(sqlite3_vtab_cursor *cur){ + wholenumber_cursor *pCur = (wholenumber_cursor*)cur; + return pCur->iValue>pCur->mxValue || pCur->iValue==0; +} + +/* +** Called to "rewind" a cursor back to the beginning so that +** it starts its output over again. Always called at least once +** prior to any wholenumberColumn, wholenumberRowid, or wholenumberEof call. +** +** idxNum Constraints +** ------ --------------------- +** 0 (none) +** 1 value > $argv0 +** 2 value >= $argv0 +** 4 value < $argv0 +** 8 value <= $argv0 +** +** 5 value > $argv0 AND value < $argv1 +** 6 value >= $argv0 AND value < $argv1 +** 9 value > $argv0 AND value <= $argv1 +** 10 value >= $argv0 AND value <= $argv1 +*/ +static int wholenumberFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + wholenumber_cursor *pCur = (wholenumber_cursor *)pVtabCursor; + sqlite3_int64 v; + int i = 0; + pCur->iValue = 1; + pCur->mxValue = 0xffffffff; /* 4294967295 */ + if( idxNum & 3 ){ + v = sqlite3_value_int64(argv[0]) + (idxNum&1); + if( v>pCur->iValue && v<=pCur->mxValue ) pCur->iValue = v; + i++; + } + if( idxNum & 12 ){ + v = sqlite3_value_int64(argv[i]) - ((idxNum>>2)&1); + if( v>=pCur->iValue && v<pCur->mxValue ) pCur->mxValue = v; + } + return SQLITE_OK; +} + +/* +** Search for terms of these forms: +** +** (1) value > $value +** (2) value >= $value +** (4) value < $value +** (8) value <= $value +** +** idxNum is an ORed combination of 1 or 2 with 4 or 8. +*/ +static int wholenumberBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; + int idxNum = 0; + int argvIdx = 1; + int ltIdx = -1; + int gtIdx = -1; + const struct sqlite3_index_constraint *pConstraint; + pConstraint = pIdxInfo->aConstraint; + for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){ + if( pConstraint->usable==0 ) continue; + if( (idxNum & 3)==0 && pConstraint->op==SQLITE_INDEX_CONSTRAINT_GT ){ + idxNum |= 1; + ltIdx = i; + } + if( (idxNum & 3)==0 && pConstraint->op==SQLITE_INDEX_CONSTRAINT_GE ){ + idxNum |= 2; + ltIdx = i; + } + if( (idxNum & 12)==0 && pConstraint->op==SQLITE_INDEX_CONSTRAINT_LT ){ + idxNum |= 4; + gtIdx = i; + } + if( (idxNum & 12)==0 && pConstraint->op==SQLITE_INDEX_CONSTRAINT_LE ){ + idxNum |= 8; + gtIdx = i; + } + } + pIdxInfo->idxNum = idxNum; + if( ltIdx>=0 ){ + pIdxInfo->aConstraintUsage[ltIdx].argvIndex = argvIdx++; + pIdxInfo->aConstraintUsage[ltIdx].omit = 1; + } + if( gtIdx>=0 ){ + pIdxInfo->aConstraintUsage[gtIdx].argvIndex = argvIdx; + pIdxInfo->aConstraintUsage[gtIdx].omit = 1; + } + if( pIdxInfo->nOrderBy==1 + && pIdxInfo->aOrderBy[0].desc==0 + ){ + pIdxInfo->orderByConsumed = 1; + } + pIdxInfo->estimatedCost = (double)1; + return SQLITE_OK; +} + +/* +** A virtual table module that provides read-only access to a +** Tcl global variable namespace. +*/ +static sqlite3_module wholenumberModule = { + 0, /* iVersion */ + wholenumberConnect, + wholenumberConnect, + wholenumberBestIndex, + wholenumberDisconnect, + wholenumberDisconnect, + wholenumberOpen, /* xOpen - open a cursor */ + wholenumberClose, /* xClose - close a cursor */ + wholenumberFilter, /* xFilter - configure scan constraints */ + wholenumberNext, /* xNext - advance a cursor */ + wholenumberEof, /* xEof - check for end of scan */ + wholenumberColumn, /* xColumn - read data */ + wholenumberRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ +}; + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + + +/* +** Register the wholenumber virtual table +*/ +int wholenumber_register(sqlite3 *db){ + int rc = SQLITE_OK; +#ifndef SQLITE_OMIT_VIRTUALTABLE + rc = sqlite3_create_module(db, "wholenumber", &wholenumberModule, 0); +#endif + return rc; +} + +#ifdef SQLITE_TEST +#include <tcl.h> +/* +** Decode a pointer to an sqlite3 object. +*/ +extern int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb); + +/* +** Register the echo virtual table module. +*/ +static int register_wholenumber_module( + ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + sqlite3 *db; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + wholenumber_register(db); + return TCL_OK; +} + + +/* +** Register commands with the TCL interpreter. +*/ +int Sqlitetestwholenumber_Init(Tcl_Interp *interp){ + static struct { + char *zName; + Tcl_ObjCmdProc *xProc; + void *clientData; + } aObjCmd[] = { + { "register_wholenumber_module", register_wholenumber_module, 0 }, + }; + int i; + for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){ + Tcl_CreateObjCommand(interp, aObjCmd[i].zName, + aObjCmd[i].xProc, aObjCmd[i].clientData, 0); + } + return TCL_OK; +} + +#endif /* SQLITE_TEST */ diff --git a/src/vdbe.c b/src/vdbe.c index e85dc85f6..b0e66317c 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -581,7 +581,7 @@ int sqlite3VdbeExec( /*** INSERT STACK UNION HERE ***/ assert( p->magic==VDBE_MAGIC_RUN ); /* sqlite3_step() verifies this */ - sqlite3VdbeMutexArrayEnter(p); + sqlite3VdbeEnter(p); if( p->rc==SQLITE_NOMEM ){ /* This happens if a malloc() inside a call to sqlite3_column_text() or ** sqlite3_column_text16() failed. */ @@ -1404,6 +1404,7 @@ case OP_Function: { ctx.pColl = pOp[-1].p4.pColl; } (*ctx.pFunc->xFunc)(&ctx, n, apVal); /* IMP: R-24505-23230 */ + sqlite3VdbeMutexResync(p); if( db->mallocFailed ){ /* Even though a malloc() has failed, the implementation of the ** user function may have called an sqlite3_result_XXX() function @@ -1435,6 +1436,15 @@ case OP_Function: { if( sqlite3VdbeMemTooBig(pOut) ){ goto too_big; } + +#if 0 + /* The app-defined function has done something that as caused this + ** statement to expire. (Perhaps the function called sqlite3_exec() + ** with a CREATE TABLE statement.) + */ + if( p->expired ) rc = SQLITE_ABORT; +#endif + REGISTER_TRACE(pOp->p3, pOut); UPDATE_MAX_BLOBSIZE(pOut); break; @@ -2660,6 +2670,7 @@ case OP_Savepoint: { if( p1==SAVEPOINT_ROLLBACK && (db->flags&SQLITE_InternChanges)!=0 ){ sqlite3ExpirePreparedStatements(db); sqlite3ResetInternalSchema(db, 0); + sqlite3VdbeMutexResync(p); db->flags = (db->flags | SQLITE_InternChanges); } } @@ -2799,7 +2810,7 @@ case OP_Transaction: { Btree *pBt; assert( pOp->p1>=0 && pOp->p1<db->nDb ); - assert( (p->btreeMask & (1<<pOp->p1))!=0 ); + assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 ); pBt = db->aDb[pOp->p1].pBt; if( pBt ){ @@ -2855,7 +2866,7 @@ case OP_ReadCookie: { /* out2-prerelease */ assert( pOp->p3<SQLITE_N_BTREE_META ); assert( iDb>=0 && iDb<db->nDb ); assert( db->aDb[iDb].pBt!=0 ); - assert( (p->btreeMask & (1<<iDb))!=0 ); + assert( (p->btreeMask & (((yDbMask)1)<<iDb))!=0 ); sqlite3BtreeGetMeta(db->aDb[iDb].pBt, iCookie, (u32 *)&iMeta); pOut->u.i = iMeta; @@ -2876,7 +2887,7 @@ case OP_SetCookie: { /* in3 */ Db *pDb; assert( pOp->p2<SQLITE_N_BTREE_META ); assert( pOp->p1>=0 && pOp->p1<db->nDb ); - assert( (p->btreeMask & (1<<pOp->p1))!=0 ); + assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 ); pDb = &db->aDb[pOp->p1]; assert( pDb->pBt!=0 ); pIn3 = &aMem[pOp->p3]; @@ -2924,7 +2935,7 @@ case OP_VerifyCookie: { Btree *pBt; assert( pOp->p1>=0 && pOp->p1<db->nDb ); - assert( (p->btreeMask & (1<<pOp->p1))!=0 ); + assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 ); pBt = db->aDb[pOp->p1].pBt; if( pBt ){ sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&iMeta); @@ -2950,6 +2961,7 @@ case OP_VerifyCookie: { */ if( db->aDb[pOp->p1].pSchema->schema_cookie!=iMeta ){ sqlite3ResetInternalSchema(db, pOp->p1); + sqlite3VdbeMutexResync(p); } p->expired = 1; @@ -3028,7 +3040,7 @@ case OP_OpenWrite: { p2 = pOp->p2; iDb = pOp->p3; assert( iDb>=0 && iDb<db->nDb ); - assert( (p->btreeMask & (1<<iDb))!=0 ); + assert( (p->btreeMask & (((yDbMask)1)<<iDb))!=0 ); pDb = &db->aDb[iDb]; pX = pDb->pBt; assert( pX!=0 ); @@ -4565,7 +4577,7 @@ case OP_Destroy: { /* out2-prerelease */ }else{ iDb = pOp->p3; assert( iCnt==1 ); - assert( (p->btreeMask & (1<<iDb))!=0 ); + assert( (p->btreeMask & (((yDbMask)1)<<iDb))!=0 ); rc = sqlite3BtreeDropTable(db->aDb[iDb].pBt, pOp->p1, &iMoved); pOut->flags = MEM_Int; pOut->u.i = iMoved; @@ -4601,7 +4613,7 @@ case OP_Clear: { int nChange; nChange = 0; - assert( (p->btreeMask & (1<<pOp->p2))!=0 ); + assert( (p->btreeMask & (((yDbMask)1)<<pOp->p2))!=0 ); rc = sqlite3BtreeClearTable( db->aDb[pOp->p2].pBt, pOp->p1, (pOp->p3 ? &nChange : 0) ); @@ -4646,7 +4658,7 @@ case OP_CreateTable: { /* out2-prerelease */ pgno = 0; assert( pOp->p1>=0 && pOp->p1<db->nDb ); - assert( (p->btreeMask & (1<<pOp->p1))!=0 ); + assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 ); pDb = &db->aDb[pOp->p1]; assert( pDb->pBt!=0 ); if( pOp->opcode==OP_CreateTable ){ @@ -4674,26 +4686,22 @@ case OP_ParseSchema: { char *zSql; InitData initData; + /* Any prepared statement that invokes this opcode will hold mutexes + ** on every btree. This is a prerequisite for invoking + ** sqlite3InitCallback(). + */ +#ifdef SQLITE_DEBUG + for(iDb=0; iDb<db->nDb; iDb++){ + assert( iDb==1 || sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) ); + } +#endif + assert( p->btreeMask == ~(yDbMask)0 ); + + iDb = pOp->p1; assert( iDb>=0 && iDb<db->nDb ); - - /* Although the mutex on the BtShared object that corresponds to - ** database iDb (the database containing the sqlite_master table - ** read by this instruction) is currently held, it is necessary to - ** obtain the mutexes on all attached databases before checking if - ** the schema of iDb is loaded. This is because, at the start of - ** the sqlite3_exec() call below, SQLite will invoke - ** sqlite3BtreeEnterAll(). If all mutexes are not already held, the - ** iDb mutex may be temporarily released to avoid deadlock. If - ** this happens, then some other thread may delete the in-memory - ** schema of database iDb before the SQL statement runs. The schema - ** will not be reloaded becuase the db->init.busy flag is set. This - ** can result in a "no such table: sqlite_master" or "malformed - ** database schema" error being returned to the user. - */ - assert( sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) ); - sqlite3BtreeEnterAll(db); - if( ALWAYS(DbHasProperty(db, iDb, DB_SchemaLoaded)) ){ + assert( DbHasProperty(db, iDb, DB_SchemaLoaded) ); + /* Used to be a conditional */ { zMaster = SCHEMA_TABLE(iDb); initData.db = db; initData.iDb = pOp->p1; @@ -4714,7 +4722,6 @@ case OP_ParseSchema: { db->init.busy = 0; } } - sqlite3BtreeLeaveAll(db); if( rc==SQLITE_NOMEM ){ goto no_mem; } @@ -4815,7 +4822,7 @@ case OP_IntegrityCk: { } aRoot[j] = 0; assert( pOp->p5<db->nDb ); - assert( (p->btreeMask & (1<<pOp->p5))!=0 ); + assert( (p->btreeMask & (((yDbMask)1)<<pOp->p5))!=0 ); z = sqlite3BtreeIntegrityCheck(db->aDb[pOp->p5].pBt, aRoot, nRoot, (int)pnErr->u.i, &nErr); sqlite3DbFree(db, aRoot); @@ -5239,11 +5246,25 @@ case OP_AggStep: { ctx.pColl = pOp[-1].p4.pColl; } (ctx.pFunc->xStep)(&ctx, n, apVal); /* IMP: R-24505-23230 */ + sqlite3VdbeMutexResync(p); if( ctx.isError ){ sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3_value_text(&ctx.s)); rc = ctx.isError; } + + /* The app-defined function has done something that as caused this + ** statement to expire. (Perhaps the function called sqlite3_exec() + ** with a CREATE TABLE statement.) + */ +#if 0 + if( p->expired ){ + rc = SQLITE_ABORT; + break; + } +#endif + sqlite3VdbeMemRelease(&ctx.s); + break; } @@ -5265,8 +5286,11 @@ case OP_AggFinal: { pMem = &aMem[pOp->p1]; assert( (pMem->flags & ~(MEM_Null|MEM_Agg))==0 ); rc = sqlite3VdbeMemFinalize(pMem, pOp->p4.pFunc); + sqlite3VdbeMutexResync(p); if( rc ){ sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3_value_text(pMem)); + }else if( p->expired ){ + rc = SQLITE_ABORT; } sqlite3VdbeChangeEncoding(pMem, encoding); UPDATE_MAX_BLOBSIZE(pMem); @@ -5343,7 +5367,7 @@ case OP_JournalMode: { /* out2-prerelease */ /* This opcode is used in two places: PRAGMA journal_mode and ATTACH. ** In PRAGMA journal_mode, the sqlite3VdbeUsesBtree() routine is called - ** when the statment is prepared and so p->aMutex.nMutex>0. All mutexes + ** when the statement is prepared and so p->btreeMask!=0. All mutexes ** are already acquired. But when used in ATTACH, sqlite3VdbeUsesBtree() ** is not called when the statement is prepared because it requires the ** iDb index of the database as a parameter, and the database has not @@ -5352,12 +5376,11 @@ case OP_JournalMode: { /* out2-prerelease */ ** No other mutexes are required by the ATTACH command so this is safe ** to do. */ - assert( (p->btreeMask & (1<<pOp->p1))!=0 || p->aMutex.nMutex==0 ); - if( p->aMutex.nMutex==0 ){ + if( p->btreeMask==0 ){ /* This occurs right after ATTACH. Get a mutex on the newly ATTACHed ** database. */ sqlite3VdbeUsesBtree(p, pOp->p1); - sqlite3VdbeMutexArrayEnter(p); + sqlite3VdbeEnter(p); } pBt = db->aDb[pOp->p1].pBt; @@ -5457,7 +5480,7 @@ case OP_IncrVacuum: { /* jump */ Btree *pBt; assert( pOp->p1>=0 && pOp->p1<db->nDb ); - assert( (p->btreeMask & (1<<pOp->p1))!=0 ); + assert( (p->btreeMask & (((yDbMask)1)<<pOp->p1))!=0 ); pBt = db->aDb[pOp->p1].pBt; rc = sqlite3BtreeIncrVacuum(pBt); if( rc==SQLITE_DONE ){ @@ -5506,7 +5529,7 @@ case OP_TableLock: { if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommitted) ){ int p1 = pOp->p1; assert( p1>=0 && p1<db->nDb ); - assert( (p->btreeMask & (1<<p1))!=0 ); + assert( (p->btreeMask & (((yDbMask)1)<<p1))!=0 ); assert( isWriteLock==0 || isWriteLock==1 ); rc = sqlite3BtreeLockTable(db->aDb[p1].pBt, pOp->p2, isWriteLock); if( (rc&0xFF)==SQLITE_LOCKED ){ @@ -5995,13 +6018,16 @@ vdbe_error_halt: sqlite3VdbeHalt(p); if( rc==SQLITE_IOERR_NOMEM ) db->mallocFailed = 1; rc = SQLITE_ERROR; - if( resetSchemaOnFault ) sqlite3ResetInternalSchema(db, 0); + if( resetSchemaOnFault ){ + sqlite3ResetInternalSchema(db, 0); + sqlite3VdbeMutexResync(p); + } /* This is the only way out of this procedure. We have to ** release the mutexes on btrees that were acquired at the ** top. */ vdbe_return: - sqlite3BtreeMutexArrayLeave(&p->aMutex); + sqlite3VdbeLeave(p); return rc; /* Jump to here if a string or blob larger than SQLITE_MAX_LENGTH diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 911c29faf..05136cbeb 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -302,10 +302,10 @@ struct Vdbe { u8 readOnly; /* True for read-only statements */ u8 isPrepareV2; /* True if prepared with prepare_v2() */ int nChange; /* Number of db changes made since last reset */ - tAttachMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ + yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ + u32 iMutexCounter; /* Mutex counter upon sqlite3VdbeEnter() */ int iStatement; /* Statement number (or 0 if has not opened stmt) */ int aCounter[3]; /* Counters used by sqlite3_stmt_status() */ - BtreeMutexArray aMutex; /* An array of Btree used here and needing locks */ #ifndef SQLITE_OMIT_TRACE i64 startTime; /* Time when query started - used for profiling */ #endif @@ -408,6 +408,9 @@ int sqlite3VdbeFrameRestore(VdbeFrame *); void sqlite3VdbeMemStoreType(Mem *pMem); void sqlite3VdbePreUpdateHook( Vdbe *, VdbeCursor *, int, const char*, Table *, i64, int); +void sqlite3VdbeEnter(Vdbe*); +void sqlite3VdbeLeave(Vdbe*); +void sqlite3VdbeMutexResync(Vdbe*); #ifdef SQLITE_DEBUG void sqlite3VdbeMemPrepareToChange(Vdbe*,Mem*); @@ -419,12 +422,6 @@ int sqlite3VdbeCheckFk(Vdbe *, int); # define sqlite3VdbeCheckFk(p,i) 0 #endif -#ifndef SQLITE_OMIT_SHARED_CACHE -void sqlite3VdbeMutexArrayEnter(Vdbe *p); -#else -# define sqlite3VdbeMutexArrayEnter(p) -#endif - int sqlite3VdbeMemTranslate(Mem*, u8); #ifdef SQLITE_DEBUG void sqlite3VdbePrintSql(Vdbe*); diff --git a/src/vdbeaux.c b/src/vdbeaux.c index a4b0df8f4..16eeb5ce1 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -158,6 +158,11 @@ int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ pOp->p4.p = 0; pOp->p4type = P4_NOTUSED; p->expired = 0; + if( op==OP_ParseSchema ){ + /* Any program that uses the OP_ParseSchema opcode needs to lock + ** all btrees. */ + p->btreeMask = ~(yDbMask)0; + } #ifdef SQLITE_DEBUG pOp->zComment = 0; if( sqlite3VdbeAddopTrace ) sqlite3VdbePrintOp(0, i, &p->aOp[i]); @@ -458,7 +463,7 @@ VdbeOp *sqlite3VdbeTakeOpArray(Vdbe *p, int *pnOp, int *pnMaxArg){ assert( aOp && !p->db->mallocFailed ); /* Check that sqlite3VdbeUsesBtree() was not called on this VM */ - assert( p->aMutex.nMutex==0 ); + assert( p->btreeMask==0 ); resolveP2Values(p, pnMaxArg); *pnOp = p->nOp; @@ -946,22 +951,131 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){ /* ** Declare to the Vdbe that the BTree object at db->aDb[i] is used. ** -** The prepared statement has to know in advance which Btree objects -** will be used so that it can acquire mutexes on them all in sorted -** order (via sqlite3VdbeMutexArrayEnter(). Mutexes are acquired -** in order (and released in reverse order) to avoid deadlocks. +** The prepared statements need to know in advance the complete set of +** attached databases that they will be using. A mask of these databases +** is maintained in p->btreeMask and is used for locking and other purposes. */ void sqlite3VdbeUsesBtree(Vdbe *p, int i){ - tAttachMask mask; - assert( i>=0 && i<p->db->nDb && i<sizeof(tAttachMask)*8 ); + assert( i>=0 && i<p->db->nDb && i<sizeof(yDbMask)*8 ); assert( i<(int)sizeof(p->btreeMask)*8 ); - mask = ((u32)1)<<i; - if( (p->btreeMask & mask)==0 ){ - p->btreeMask |= mask; - sqlite3BtreeMutexArrayInsert(&p->aMutex, p->db->aDb[i].pBt); + p->btreeMask |= ((yDbMask)1)<<i; +} + +/* +** Compute the sum of all mutex counters for all btrees in the +** given prepared statement. +*/ +#ifndef SQLITE_OMIT_SHARED_CACHE +static u32 mutexCounterSum(Vdbe *p){ + u32 cntSum = 0; +#ifdef SQLITE_DEBUG + int i; + yDbMask mask; + sqlite3 *db = p->db; + Db *aDb = db->aDb; + int nDb = db->nDb; + for(i=0, mask=1; i<nDb; i++, mask += mask){ + if( i!=1 && (mask & p->btreeMask)!=0 && ALWAYS(aDb[i].pBt!=0) ){ + cntSum += sqlite3BtreeMutexCounter(aDb[i].pBt); + } + } +#else + UNUSED_PARAMETER(p); +#endif + return cntSum; +} +#endif + +/* +** If SQLite is compiled to support shared-cache mode and to be threadsafe, +** this routine obtains the mutex associated with each BtShared structure +** that may be accessed by the VM passed as an argument. In doing so it also +** sets the BtShared.db member of each of the BtShared structures, ensuring +** that the correct busy-handler callback is invoked if required. +** +** If SQLite is not threadsafe but does support shared-cache mode, then +** sqlite3BtreeEnter() is invoked to set the BtShared.db variables +** of all of BtShared structures accessible via the database handle +** associated with the VM. +** +** If SQLite is not threadsafe and does not support shared-cache mode, this +** function is a no-op. +** +** The p->btreeMask field is a bitmask of all btrees that the prepared +** statement p will ever use. Let N be the number of bits in p->btreeMask +** corresponding to btrees that use shared cache. Then the runtime of +** this routine is N*N. But as N is rarely more than 1, this should not +** be a problem. +*/ +void sqlite3VdbeEnter(Vdbe *p){ +#ifndef SQLITE_OMIT_SHARED_CACHE + int i; + yDbMask mask; + sqlite3 *db = p->db; + Db *aDb = db->aDb; + int nDb = db->nDb; + for(i=0, mask=1; i<nDb; i++, mask += mask){ + if( i!=1 && (mask & p->btreeMask)!=0 && ALWAYS(aDb[i].pBt!=0) ){ + sqlite3BtreeEnter(aDb[i].pBt); + } + } + p->iMutexCounter = mutexCounterSum(p); +#else + UNUSED_PARAMETER(p); +#endif +} + +/* +** Unlock all of the btrees previously locked by a call to sqlite3VdbeEnter(). +*/ +void sqlite3VdbeLeave(Vdbe *p){ +#ifndef SQLITE_OMIT_SHARED_CACHE + int i; + yDbMask mask; + sqlite3 *db = p->db; + Db *aDb = db->aDb; + int nDb = db->nDb; + + /* Assert that the all mutexes have been held continously since + ** the most recent sqlite3VdbeEnter() or sqlite3VdbeMutexResync(). + */ + assert( mutexCounterSum(p) == p->iMutexCounter ); + + for(i=0, mask=1; i<nDb; i++, mask += mask){ + if( i!=1 && (mask & p->btreeMask)!=0 && ALWAYS(aDb[i].pBt!=0) ){ + sqlite3BtreeLeave(aDb[i].pBt); + } } +#else + UNUSED_PARAMETER(p); +#endif } +/* +** Recompute the sum of the mutex counters on all btrees used by the +** prepared statement p. +** +** Call this routine while holding a sqlite3VdbeEnter() after doing something +** that might cause one or more of the individual mutexes held by the +** prepared statement to be released. Calling sqlite3BtreeEnter() on +** any BtShared mutex which is not used by the prepared statement is one +** way to cause one or more of the mutexes in the prepared statement +** to be temporarily released. The anti-deadlocking logic in +** sqlite3BtreeEnter() can cause mutexes to be released temporarily then +** reacquired. +** +** Calling this routine is an acknowledgement that some of the individual +** mutexes in the prepared statement might have been released and reacquired. +** So checks to verify that mutex-protected content did not change +** unexpectedly should accompany any call to this routine. +*/ +void sqlite3VdbeMutexResync(Vdbe *p){ +#if !defined(SQLITE_OMIT_SHARED_CACHE) && defined(SQLITE_DEBUG) + p->iMutexCounter = mutexCounterSum(p); +#else + UNUSED_PARAMETER(p); +#endif +} #if defined(VDBE_PROFILE) || defined(SQLITE_DEBUG) /* @@ -1961,33 +2075,6 @@ int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){ } /* -** If SQLite is compiled to support shared-cache mode and to be threadsafe, -** this routine obtains the mutex associated with each BtShared structure -** that may be accessed by the VM passed as an argument. In doing so it -** sets the BtShared.db member of each of the BtShared structures, ensuring -** that the correct busy-handler callback is invoked if required. -** -** If SQLite is not threadsafe but does support shared-cache mode, then -** sqlite3BtreeEnterAll() is invoked to set the BtShared.db variables -** of all of BtShared structures accessible via the database handle -** associated with the VM. Of course only a subset of these structures -** will be accessed by the VM, and we could use Vdbe.btreeMask to figure -** that subset out, but there is no advantage to doing so. -** -** If SQLite is not threadsafe and does not support shared-cache mode, this -** function is a no-op. -*/ -#ifndef SQLITE_OMIT_SHARED_CACHE -void sqlite3VdbeMutexArrayEnter(Vdbe *p){ -#if SQLITE_THREADSAFE - sqlite3BtreeMutexArrayEnter(&p->aMutex); -#else - sqlite3BtreeEnterAll(p->db); -#endif -} -#endif - -/* ** This function is called when a transaction opened by the database ** handle associated with the VM passed as an argument is about to be ** committed. If there are outstanding deferred foreign key constraint @@ -2059,7 +2146,7 @@ int sqlite3VdbeHalt(Vdbe *p){ int isSpecialError; /* Set to true if a 'special' error */ /* Lock all btrees used by the statement */ - sqlite3VdbeMutexArrayEnter(p); + sqlite3VdbeEnter(p); /* Check for one of the special errors */ mrc = p->rc & 0xff; @@ -2113,7 +2200,7 @@ int sqlite3VdbeHalt(Vdbe *p){ rc = sqlite3VdbeCheckFk(p, 1); if( rc!=SQLITE_OK ){ if( NEVER(p->readOnly) ){ - sqlite3BtreeMutexArrayLeave(&p->aMutex); + sqlite3VdbeLeave(p); return SQLITE_ERROR; } rc = SQLITE_CONSTRAINT; @@ -2125,7 +2212,7 @@ int sqlite3VdbeHalt(Vdbe *p){ rc = vdbeCommit(db, p); } if( rc==SQLITE_BUSY && p->readOnly ){ - sqlite3BtreeMutexArrayLeave(&p->aMutex); + sqlite3VdbeLeave(p); return SQLITE_BUSY; }else if( rc!=SQLITE_OK ){ p->rc = rc; @@ -2197,7 +2284,8 @@ int sqlite3VdbeHalt(Vdbe *p){ } /* Release the locks */ - sqlite3BtreeMutexArrayLeave(&p->aMutex); + sqlite3VdbeMutexResync(p); + sqlite3VdbeLeave(p); } /* We have successfully halted and closed the VM. Record this fact. */ |