diff --git a/libstuff/sqlite3.c b/libstuff/sqlite3.c index eb4004e37..ec420f73e 100644 --- a/libstuff/sqlite3.c +++ b/libstuff/sqlite3.c @@ -18,7 +18,7 @@ ** separate file. This file contains only code for the core SQLite library. ** ** The content in this amalgamation comes from Fossil check-in -** 012a04edd0ab7001f2635eeb766089201cbe with changes in files: +** 4b66dc55fe9a13d543e85ac7a592165bbb2a with changes in files: ** ** */ @@ -476,10 +476,10 @@ extern "C" { */ #define SQLITE_VERSION "3.54.0" #define SQLITE_VERSION_NUMBER 3054000 -#define SQLITE_SOURCE_ID "2026-05-12 17:42:11 012a04edd0ab7001f2635eeb766089201cbe372d54e5008e7138a5dbe522bf06" -#define SQLITE_SCM_BRANCH "hctree-bedrock" +#define SQLITE_SOURCE_ID "2026-05-27 18:37:40 4b66dc55fe9a13d543e85ac7a592165bbb2a15eed717a4604ae6a09c0bac5840" +#define SQLITE_SCM_BRANCH "hctree-bedrock-lcd-ex" #define SQLITE_SCM_TAGS "" -#define SQLITE_SCM_DATETIME "2026-05-12T17:42:11.503Z" +#define SQLITE_SCM_DATETIME "2026-05-27T18:37:40.605Z" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -2538,6 +2538,19 @@ struct sqlite3_mem_methods { ** recommended case) then the integer is always filled with zero, regardless ** if its initial value. ** +** +** [[SQLITE_CONFIG_SHAREDLOG_MAXSIZE]] +**
SQLITE_CONFIG_SHAREDLOG_MAXSIZE +**
This option is used to set the maximum size of a BEGIN CONCURRENT +** shared-log in bytes. The default value is 1GiB (1*1024*1024*1024). The +** argument to this configuration option must be a 64-bit signed integer +** (type sqlite3_int64) to use as the new limit. A negative parameter +** restores the default value, a value of zero disabled shared-logs +** altogether. There is no way to configure unlimited memory usage, but +** applications may instead configure a very large value (e.g. 1TiB). +** Shared-log entries are automatically discarded when their associated +** transactions are checkpointed, which prevents the shared-log from growing +** indefinitely in this case. */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ #define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ @@ -2569,6 +2582,7 @@ struct sqlite3_mem_methods { #define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */ #define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */ #define SQLITE_CONFIG_ROWID_IN_VIEW 30 /* int* */ +#define SQLITE_CONFIG_SHAREDLOG_MAXSIZE 31 /* sqlite3_int64 */ /* ** CAPI3REF: Database Connection Configuration Options @@ -17192,9 +17206,11 @@ SQLITE_PRIVATE int sqlite3PagerUsePage(Pager*, Pgno); SQLITE_PRIVATE void sqlite3PagerEndConcurrent(Pager*); SQLITE_PRIVATE int sqlite3PagerBeginConcurrent(Pager*); SQLITE_PRIVATE void sqlite3PagerDropExclusiveLock(Pager*); -SQLITE_PRIVATE int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage*); +SQLITE_PRIVATE int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage*, int); SQLITE_PRIVATE void sqlite3PagerSetDbsize(Pager *pPager, Pgno); SQLITE_PRIVATE int sqlite3PagerIsWal(Pager*); +SQLITE_PRIVATE u64 sqlite3PagerWalCommitId(Pager *pPager); +SQLITE_PRIVATE u64 sqlite3PagerWalLiveId(Pager *pPager); #else # define sqlite3PagerEndConcurrent(x) # define sqlite3PagerUsePage(x, y) SQLITE_OK @@ -17500,6 +17516,12 @@ SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor*); SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor*, int*); SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*, u8 flags); +#ifndef SQLITE_OMIT_CONCURRENT +SQLITE_PRIVATE void sqlite3BtreeCursorNoScan(BtCursor*); +#else +# define sqlite3BtreeCursorNoScan(x) +#endif + /* Allowed flags for sqlite3BtreeDelete() and sqlite3BtreeInsert() */ #define BTREE_SAVEPOSITION 0x02 /* Leave cursor pointing at NEXT or PREV */ #define BTREE_AUXDELETE 0x04 /* not the primary delete operation */ @@ -18444,6 +18466,7 @@ SQLITE_PRIVATE int sqlite3HctBtreeClearTableOfCursor(BtCursor*); SQLITE_PRIVATE int sqlite3HctBtreeCount(sqlite3*, BtCursor*, i64*); SQLITE_PRIVATE i64 sqlite3HctBtreeOffset(BtCursor*); SQLITE_PRIVATE int sqlite3HctBtreeIsEmpty(BtCursor*, int*); +SQLITE_PRIVATE void sqlite3HctBtreeCursorNoScan(BtCursor*); SQLITE_PRIVATE int sqlite3StockBtreeCursor(Btree*, Pgno, int, struct KeyInfo*, BtCursor*); SQLITE_PRIVATE sqlite3_uint64 sqlite3StockBtreeSeekCount(Btree*); SQLITE_PRIVATE Pgno sqlite3StockBtreeLastPage(Btree*); @@ -18523,6 +18546,7 @@ SQLITE_PRIVATE int sqlite3StockBtreeClearTableOfCursor(BtCursor*); SQLITE_PRIVATE int sqlite3StockBtreeCount(sqlite3*, BtCursor*, i64*); SQLITE_PRIVATE i64 sqlite3StockBtreeOffset(BtCursor*); SQLITE_PRIVATE int sqlite3StockBtreeIsEmpty(BtCursor*, int*); +SQLITE_PRIVATE void sqlite3StockBtreeCursorNoScan(BtCursor*); SQLITE_PRIVATE BtCursor *sqlite3StockBtreeFakeValidCursor(void); @@ -19208,6 +19232,16 @@ struct sqlite3 { #define CONCURRENT_OPEN 1 #define CONCURRENT_SCHEMA 2 +/* +** Maximum size in bytes of all allocations associated with a single +** BEGIN CONCURRENT shared-log. +*/ +#define SQLITE_DEFAULT_SHAREDLOG_MAXSIZE (1*1024*1024*1024) + +#ifndef SQLITE_OMIT_CONCURRENT +SQLITE_PRIVATE int sqlite3ConcurrentRegister(sqlite3 *db); +#endif + /* ** A macro to discover the encoding of a database. */ @@ -21747,6 +21781,9 @@ struct Sqlite3Config { int iOnceResetThreshold; /* When to reset OP_Once counters */ u32 szSorterRef; /* Min size in bytes to use sorter-refs */ unsigned int iPrngSeed; /* Alternative fixed seed for the PRNG */ +#ifndef SQLITE_OMIT_CONCURRENT + i64 nSharedLogMaxSize; /* Max number of all shared-log entries */ +#endif /* vvvv--- must be last ---vvv */ #ifdef SQLITE_DEBUG sqlite3_int64 aTune[SQLITE_NTUNE]; /* Tuning parameters */ @@ -24585,6 +24622,9 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = { 0x7ffffffe, /* iOnceResetThreshold */ SQLITE_DEFAULT_SORTERREF_SIZE, /* szSorterRef */ 0, /* iPrngSeed */ +#ifndef SQLITE_OMIT_CONCURRENT + SQLITE_DEFAULT_SHAREDLOG_MAXSIZE, /* nSharedLogMaxSize */ +#endif #ifdef SQLITE_DEBUG {0,0,0,0,0,0}, /* aTune */ #endif @@ -60317,6 +60357,9 @@ SQLITE_PRIVATE int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pPg, Bitvec *pRead, /* Upgrade the state of the client to take into account changes written ** by other connections */ SQLITE_PRIVATE int sqlite3WalUpgradeSnapshot(Wal *pWal); + +SQLITE_PRIVATE u64 sqlite3WalCommitId(Wal *pWal); +SQLITE_PRIVATE u64 sqlite3WalLiveId(Wal *pWal); #endif /* SQLITE_OMIT_CONCURRENT */ #ifdef SQLITE_ENABLE_ZIPVFS @@ -62204,6 +62247,25 @@ SQLITE_PRIVATE void sqlite3PagerEndConcurrent(Pager *pPager){ SQLITE_PRIVATE int sqlite3PagerIsWal(Pager *pPager){ return pPager->pWal!=0; } + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** If in wal-mode, return a 64-bit value that identifies the snapshot currently +** in use. Otherwise, return 0. +*/ +SQLITE_PRIVATE u64 sqlite3PagerWalCommitId(Pager *pPager){ + return pPager->pWal ? sqlite3WalCommitId(pPager->pWal) : 0; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** If in wal-mode, return a 64-bit value that identifies the snapshot +** currently at the head of the wal file. Otherwise, return 0. +*/ +SQLITE_PRIVATE u64 sqlite3PagerWalLiveId(Pager *pPager){ + return pPager->pWal ? sqlite3WalLiveId(pPager->pWal) : 0; +} + #endif /* SQLITE_OMIT_CONCURRENT */ /* @@ -65914,6 +65976,7 @@ SQLITE_PRIVATE int sqlite3PagerUsePage(Pager *pPager, Pgno pgno){ } return rc; } + #endif /* @@ -66918,9 +66981,10 @@ SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager *pPager, PgHdr *pPage1, u32 * #ifndef SQLITE_OMIT_CONCURRENT /* ** This function is called as part of committing an CONCURRENT transaction. -** At this point the wal WRITER lock is held, and all pages in the cache -** except for page 1 are compatible with the snapshot at the head of the -** wal file. +** At this point the wal WRITER lock is held. If parameter bReset is 0, +** all pages in the cache except for page 1 are compatible with the snapshot +** at the head of the wal file. Or, if bReset is non-zero, the cache is +** cleared of all pages except for page 1. ** ** This function updates the in-memory data structures and reloads the ** contents of page 1 so that the client is operating on the snapshot @@ -66928,7 +66992,7 @@ SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager *pPager, PgHdr *pPage1, u32 * ** ** SQLITE_OK is returned if successful, or an SQLite error code otherwise. */ -SQLITE_PRIVATE int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage *pPage1){ +SQLITE_PRIVATE int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage *pPage1, int bReset){ int rc; assert( pPager->pWal && pPager->pAllRead ); @@ -66936,7 +67000,9 @@ SQLITE_PRIVATE int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage *pPage1){ if( rc==SQLITE_OK ){ rc = readDbPage(pPage1); } - + if( bReset && rc==SQLITE_OK ){ + sqlite3PcacheTruncate(pPager->pPCache, 1); + } return rc; } @@ -73219,6 +73285,29 @@ SQLITE_PRIVATE int sqlite3WalUpgradeSnapshot(Wal *pWal){ } SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) return rc; } + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Return a 64-bit value that identifies the snapshot currently in use. +*/ +SQLITE_PRIVATE u64 sqlite3WalCommitId(Wal *pWal){ + return ((u64)(pWal->hdr.aCksum[0]) << 32) + pWal->hdr.aCksum[1]; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Return a 64-bit value that identifies the snapshot currently at the +** head of the wal file. +*/ +SQLITE_PRIVATE u64 sqlite3WalLiveId(Wal *pWal){ + WalIndexHdr hdr; + assert( pWal->writeLock ); + SEH_TRY { + walIndexLoadHdr(pWal, &hdr); + } SEH_EXCEPT( memset(&hdr, 0, sizeof(hdr)); ) + return ((u64)(hdr.aCksum[0]) << 32) + hdr.aCksum[1]; +} + #endif /* SQLITE_OMIT_CONCURRENT */ /* @@ -73289,9 +73378,10 @@ SQLITE_PRIVATE int sqlite3WalUndo( } if( walidxGetFile(&pWal->hdr)!=iWal ){ assert( bConcurrent && isWalMode2(pWal) ); - return SQLITE_OK; - } + assert( rc==SQLITE_OK ); + }else #endif + { assert( walidxGetFile(&pWal->hdr)==iWal ); for(iFrame=iNew+1; ALWAYS(rc==SQLITE_OK) && iFrame<=iMax; iFrame++){ @@ -73316,6 +73406,7 @@ SQLITE_PRIVATE int sqlite3WalUndo( rc = xUndo(pUndoCtx, pgno); } if( iMax!=iNew ) walCleanupHash(pWal); + } } SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) pWal->iReCksum = 0; @@ -74583,6 +74674,15 @@ typedef struct BtLock BtLock; typedef struct CellInfo CellInfo; typedef struct BtreePtrmap BtreePtrmap; +typedef struct BtConcurrent BtConcurrent; +typedef struct BtReadIndex BtReadIndex; +typedef struct BtReadIntkey BtReadIntkey; +typedef struct BtSharedLog BtSharedLog; +typedef struct BtSharedLogEntry BtSharedLogEntry; +typedef struct BtWrite BtWrite; +typedef struct BtWriteIndex BtWriteIndex; +typedef struct BtWriteIntkey BtWriteIntkey; + /* ** This is a magic string that appears at the beginning of every ** SQLite database in order to identify the file as a real database. @@ -74674,6 +74774,201 @@ struct BtLock { #define READ_LOCK 1 #define WRITE_LOCK 2 +/* +** All connections to a single database file that use BEGIN CONCURRENT within +** the process share an instance of this object. Its purpose is to store +** a list of BtSharedLogEntry objects associated with the database file. +*/ +struct BtSharedLog { + sqlite3_mutex *mutex; /* Mutex protecting this object */ + int nRef; /* Number of users of this struct */ + char *zFullname; /* Path identifying this database */ + + BtSharedLogEntry *pFirst; + BtSharedLogEntry *pLast; + i64 nByte; /* Total size of all entries in bytes */ + + /* Linked list protected by SQLITE_MUTEX_STATIC_MAIN */ + BtSharedLog *pSharedNext; /* Next shared log in process */ +}; + +/* +** Each time a BEGIN CONCURRENT transaction is committed, an instance of +** the following object is added to the list maintained by the database +** file's BtSharedLog object. +** +** iBaseId: +** The sqlite3WalCommitId() value at the head of the wal file when this +** transaction was written. +** +** iThisId: +** The sqlite3WalCommitId() value at the head of the wal file after this +** transaction was written. i.e. the snapshot created by this write. +** +** iFirstFrame: +** First wal frame written by this transaction. If in a *-wal2 file, the +** 0x80000000 bit is set. +** +** nFrame: +** Total number of frames written by this transaction. +** +** aIntkey/nIntkey: +** Array of rowids modified by this transaction. +** +** aIndex/nIndex: +** Array of index keys modified by this transaction. +** +** nByte: +** Total size in bytes of all allocations belonging to this object +** (including itself). +*/ +struct BtSharedLogEntry { + u64 iBaseId; /* Snapshot this transaction was based on */ + u64 iThisId; /* Snapshot this transaction created */ + u32 iFirstFrame; /* First frame written by this transaction */ + u32 nFrame; /* Number of frames written by transaction */ + + BtWriteIntkey *aIntkey; /* Writes to intkeys */ + int nIntkey; /* Size of aIntkey[] */ + BtWriteIndex *aIndex; /* Writes to indexes */ + int nIndex; /* Size of aIndex[] in bytes */ + + i64 nByte; /* Total size in bytes */ + + BtSharedLogEntry *pLogNext; /* Transaction commited after this one */ +}; + +/* +** Any more savepoints than this and the BtConcurrent object is retired. +*/ +#define BTCONC_MAX_SAVEPOINT 8 + +/* +** An single object of this type is permanently part of each BtShared +** object. It stores things related to BEGIN CONCURRENT transactions +** and the in-process shared-log. +*/ +struct BtConcurrent { + BtSharedLog *pBtLog; + int eState; /* One of the BTCONC_STATE_XXX values */ + u64 iBase; /* WalCommitId() transaction is prepared on */ + + /* Reads performed by this transaction */ + BtReadIntkey *aReadIntkey; + BtReadIndex *aReadIndex; + int nReadIntkey; + int nReadIndex; + int nReadIntkeyAlloc; + int nReadIndexAlloc; + + /* Changes made by this transaction. */ + BtWrite *aWrite; + int nWrite; + int nWriteAlloc; + + /* An UnpackedRecord structure created pKeyInfo->nKeyField==nUnpackedField */ + UnpackedRecord *pUnpacked; + int nUnpackedField; + + int aSvpt[BTCONC_MAX_SAVEPOINT];/* Set nWrite=aSvpt[i] when savepoint i rb. */ + int nSvpt; /* Current number of open savepoints */ + + /* Total size of all allocations managed by this object. */ + i64 nAlloc; +}; + +/* +** NONE: No BEGIN CONCURRENT transaction is open. +** INUSE: BEGIN CONCURRENT transaction is open and object is accumulating +** reads and writes. +** RETIRED: BEGIN CONCURRENT transaction is open but object is not in use. +** Because it grew too large or some such reason. +*/ +#define BTCONC_STATE_NONE 0 +#define BTCONC_STATE_INUSE 1 +#define BTCONC_STATE_RETIRED 2 + +/* +** Each read of an intkey or index btree by a BEGIN CONCURRENT transaction is +** represented by an object of one of the following types. +*/ +struct BtReadIntkey { + Pgno iRoot; /* Root page of table read */ + i64 iMin; /* Smallest rowid scanned */ + i64 iMax; /* Largest rowid scanned */ +}; + +/* +** Each scan of an index is represented by an instance of the following +** structure. The start of the range is encoded in (aRecMin, nRecMin, +** drc_min) and the end of the range by (aRecMax, nRecMax, drc_max). +** According to the sort order defined by pKeyInfo: +** +** (aRecMin, nRecMin, drc_min) <= (aRecMax, nRecMax, drc_max) +*/ +struct BtReadIndex { + Pgno iRoot; /* Root page of index b-tree read */ + i16 drc_min; + i16 drc_max; + KeyInfo *pKeyInfo; /* KeyInfo structure for b-tree */ + int nRecMin; /* Size of aRecFirst[] in bytes */ + int nRecMax; /* Size of aRecLast[] in bytes */ + u8 *aRecMin; /* Record for this change */ + u8 *aRecMax; /* Record for this change */ +}; + +/* +** During a BEGIN CONCURRENT transaction, each insert or delete of a b-tree +** key is represented by an instance of this structure. +** +** If the transaction is succesfully committed, the information in an +** array of this type is used to populate arrays of BtWriteIntkey and +** BtWriteIndex structs, which are stored as part of the shared-log +** entry to use for future conflict detection. +*/ +struct BtWrite { + Pgno iRoot; /* Root page of btree this change affects */ + int bDel; /* True for a delete, false for insert */ + KeyInfo *pKeyInfo; /* KeyInfo for index-btree, NULL for intkey */ + i64 iKey; /* Key for intkey updates */ + int nRec; /* Size of aRec[] in bytes */ + u8 *aRec; /* Record for this change */ +}; + +struct BtWriteIntkey { + Pgno iRoot; /* Root page of table read */ + i64 iKey; /* Key written */ +}; + +struct BtWriteIndex { + Pgno iRoot; /* Root page of table read */ + int nRec; /* Size of aRec[] in bytes (or 0) */ + u8 *aRec; /* Pointer to record */ +}; + +/* Values for BtRead.eRead - how the scan was started. */ +#define BTCONC_READ_FIRST 1 +#define BTCONC_READ_MOVETO 2 +#define BTCONC_READ_LAST 3 +#define BTCONC_READ_COUNT 4 + +SQLITE_PRIVATE int sqlite3BcSerializeRecord( + UnpackedRecord *pRec, /* Record to serialize */ + u8 **ppRec, /* OUT: buffer containing serialization */ + int *pnRec /* OUT: size of (*ppRec) in bytes */ +); + +SQLITE_PRIVATE int sqlite3BtreeSortReadArrays(BtConcurrent *pBtConc); + +SQLITE_PRIVATE void sqlite3BcLogIndexConflict( + const char *zTab, + const char *zIdx, + BtWriteIndex *pWrite, + BtReadIndex *pRead +); +SQLITE_PRIVATE const char *sqlite3BcCurrentSql(sqlite3 *db); + + /* A Btree handle ** ** A database connection contains a pointer to an instance of @@ -74818,6 +75113,7 @@ struct BtShared { u8 *pTmpSpace; /* Temp space sufficient to hold a single cell */ #ifndef SQLITE_OMIT_CONCURRENT BtreePtrmap *pMap; + BtConcurrent conc; #endif int nPreformatSize; /* Size of last cell written by TransferRow() */ }; @@ -74902,6 +75198,11 @@ struct BtCursor { Btree *pBtree; /* The Btree to which this cursor belongs */ Pgno *aOverflow; /* Cache of overflow page locations */ void *pKey; /* Saved key that was cursor last known position */ +#ifndef SQLITE_OMIT_CONCURRENT + int eScanType; /* BTCONC_READ_FIRST, LAST or MOVETO */ + int iScanDir; /* +1 if Next(), -1 if Prev(), 0 if neither */ + int iScanIndex; /* Index of pBt->conc.aReadXXX[] array plus 1 */ +#endif /* All fields above are zeroed when the cursor is allocated. See ** sqlite3BtreeCursorZero(). Fields that follow must be manually ** initialized. */ @@ -75110,6 +75411,7 @@ struct IntegrityCk { # define get2byteAligned(x) ((x)[0]<<8 | (x)[1]) #endif + /************** End of btreeInt.h ********************************************/ /************** Continuing where we left off in btmutex.c ********************/ #ifndef SQLITE_OMIT_SHARED_CACHE @@ -75459,6 +75761,7 @@ SQLITE_PRIVATE void sqlite3BtreeLeaveCursor(BtCursor *pCur){ #define sqlite3BtreeCount sqlite3StockBtreeCount #define sqlite3BtreeOffset sqlite3StockBtreeOffset #define sqlite3BtreeIsEmpty sqlite3StockBtreeIsEmpty +#define sqlite3BtreeCursorNoScan sqlite3StockBtreeCursorNoScan #define sqlite3BtreeCursor sqlite3StockBtreeCursor #ifdef SQLITE_DEBUG #define sqlite3BtreeSeekCount sqlite3StockBtreeSeekCount @@ -76122,10 +76425,71 @@ static int btreePtrmapStore( /* !defined(SQLITE_OMIT_CONCURRENT) ** -** Open savepoint iSavepoint, if it is not already open. +** Free the contents of the array of BtWrite objects (but not the +** array itself). +*/ +static void btreeBcFreeWriteArray(BtWrite *aWrite, int nWrite){ + int ii; + for(ii=0; iiconc.eState==BTCONC_STATE_INUSE ){ + if( nSvpt>=BTCONC_MAX_SAVEPOINT ){ + /* More than 8 nested savepoints. No logical OCC this transaction. */ + pBt->conc.eState = BTCONC_STATE_RETIRED; + }else if( nSvpt>pBt->conc.nSvpt ){ + int ii; + for(ii=pBt->conc.nSvpt; iiconc.aSvpt[ii] = pBt->conc.nWrite; + } + pBt->conc.nSvpt = nSvpt; + } + } +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** If this is a BEGIN CONCURRENT transaction, update the BtShared.conc +** object to reflect the fact that savepoint iSvpt has just been +** released (if op==SAVEPOINT_RELEASE) or rolled back (if +** op==SAVEPOINT_ROLLBACK). +*/ +static void btreeBcSavepointEnd(BtShared *pBt, int op, int iSvpt){ + assert( op==SAVEPOINT_ROLLBACK || op==SAVEPOINT_RELEASE ); + assert( iSvpt>=0 || (iSvpt==-1 && op==SAVEPOINT_ROLLBACK) ); + assert( pBt->conc.nSvpt>=0 ); + + if( pBt->conc.eState==BTCONC_STATE_INUSE ){ + assert( iSvpt>=0 ); + if( iSvptconc.nSvpt ){ + if( op==SAVEPOINT_RELEASE ){ + pBt->conc.nSvpt = iSvpt; + }else{ + int nNew = pBt->conc.aSvpt[iSvpt]; + btreeBcFreeWriteArray(&pBt->conc.aWrite[nNew], pBt->conc.nWrite - nNew); + pBt->conc.nWrite = nNew; + pBt->conc.nSvpt = iSvpt+1; + } + } + } +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Open savepoint iSvpt, if it is not already open. */ static int btreePtrmapBegin(BtShared *pBt, int nSvpt){ BtreePtrmap *pMap = pBt->pMap; + btreeBcSavepointBegin(pBt, nSvpt); if( pMap && nSvpt>pMap->nSvpt ){ int i; if( nSvpt>=pMap->nSvptAlloc ){ @@ -76155,6 +76519,7 @@ static int btreePtrmapBegin(BtShared *pBt, int nSvpt){ */ static void btreePtrmapEnd(BtShared *pBt, int op, int iSvpt){ BtreePtrmap *pMap = pBt->pMap; + btreeBcSavepointEnd(pBt, op, iSvpt); if( pMap ){ assert( op==SAVEPOINT_ROLLBACK || op==SAVEPOINT_RELEASE ); assert( iSvpt>=0 || (iSvpt==-1 && op==SAVEPOINT_ROLLBACK) ); @@ -76252,12 +76617,1503 @@ static void btreePtrmapCheck(BtShared *pBt, Pgno nPage){ } } + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** (*ppArray) points to an array of elements size szElem. (*pnArrayAlloc) +** is the current allocated size of the array, (*pnArray) is the currently +** used size. Ensure there is enough space to append an element. Return +** SQLITE_OK if successful, or SQLITE_NOMEM if an OOM occurs. +*/ +static int btreeBcGrowArray( + void **ppArray, + int *pnArray, + int *pnArrayAlloc, + int szElem +){ + if( (*pnArray)>=(*pnArrayAlloc) ){ + i64 nNew = (*pnArray)==0 ? 100 : ((*pnArray) * 2); + void *pNew = sqlite3_realloc64(*ppArray, nNew * szElem); + + if( pNew ){ + *ppArray = pNew; + *pnArrayAlloc = (int)nNew; + }else{ + return SQLITE_NOMEM_BKPT; + } + } + return SQLITE_OK; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** A new entry is to be appended to the write-array of the BtConcurrent +** object passed as the only argument. Ensure sufficient space is available. +** Return SQLITE_NOMEM if an OOM occurs, or SQLITE_OK otherwise. +*/ +static int btreeBcGrowWriteArray(BtConcurrent *pBtConc){ + return btreeBcGrowArray( + (void**)&pBtConc->aWrite, + &pBtConc->nWrite, + &pBtConc->nWriteAlloc, + sizeof(BtWrite) + ); +} + + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Global linked list of all shared-logs in the process. Protected by +** mutex SQLITE_MUTEX_STATIC_MAIN. +*/ +static BtSharedLog *pGlobalBtSharedLog = 0; + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Start using BtShared.conc, if it is not already in use. +*/ +static int btreeBcBeginConcurrent(BtShared *pBt){ + int rc = SQLITE_OK; + assert( pBt->conc.eState==BTCONC_STATE_NONE + || pBt->conc.eState==BTCONC_STATE_INUSE + || pBt->conc.eState==BTCONC_STATE_RETIRED + ); + assert( pBt->conc.eState==BTCONC_STATE_NONE || pBt->conc.pBtLog!=0 ); + if( pBt->conc.eState==BTCONC_STATE_NONE + && sqlite3GlobalConfig.nSharedLogMaxSize!=0 + ){ + + /* If this BtShared does not yet have a connection to the global + ** BtSharedLog object for this database, establish one now. Creating + ** the BtSharedLog if it does not already exist. */ + if( pBt->conc.pBtLog==0 ){ + const char *zFull = sqlite3PagerFilename(pBt->pPager, 0); + BtSharedLog *p = 0; + sqlite3_mutex_enter( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MAIN) ); + for(p=pGlobalBtSharedLog; p; p=p->pSharedNext){ + if( 0==strcmp(zFull, p->zFullname) ) break; + } + if( p ){ + p->nRef++; + }else{ + int nFull = sqlite3Strlen30(zFull) + 1; + p = (BtSharedLog*)sqlite3MallocZero(sizeof(BtSharedLog) + nFull); + if( p==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + p->zFullname = (char*)&p[1]; + memcpy(p->zFullname, zFull, nFull); + p->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + if( p->mutex==0 ){ + sqlite3_free(p); + p = 0; + rc = SQLITE_NOMEM_BKPT; + }else{ + p->nRef = 1; + p->pSharedNext = pGlobalBtSharedLog; + pGlobalBtSharedLog = p; + } + } + } + sqlite3_mutex_leave( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MAIN) ); + + pBt->conc.pBtLog = p; + } + + pBt->conc.eState = BTCONC_STATE_INUSE; + pBt->conc.iBase = sqlite3PagerWalCommitId(pBt->pPager); + pBt->conc.nSvpt = 0; + } + + return rc; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Free the BtSharedLogEntry object passed as the only argument. +*/ +static void btreeBcSharedLogEntryDelete(BtSharedLogEntry *pFree){ + int ii; + for(ii=0; iinIndex; ii++){ + BtWriteIndex *p = &pFree->aIndex[ii]; + sqlite3_free(p->aRec); + } + sqlite3_free(pFree); +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Remove and free the oldest entry in the BtSharedLog indicated by the +** only argument. +*/ +static void btreeBcRemoveOldest(BtSharedLog *pBtLog){ + BtSharedLogEntry *pFree = pBtLog->pFirst; + + pBtLog->pFirst = pFree->pLogNext; + if( pBtLog->pFirst==0 ){ + assert( pBtLog->pLast==pFree ); + pBtLog->pLast = 0; + } + pBtLog->nByte -= pFree->nByte; + +#ifdef SQLITE_DEBUG + { + /* Check that BtSharedLog.nByte is the sum of the nByte member of + ** all log entries. */ + i64 nTotal = 0; + BtSharedLogEntry *pEntry = pBtLog->pFirst; + for( ; pEntry; pEntry=pEntry->pLogNext) nTotal += pEntry->nByte; + assert( nTotal==pBtLog->nByte ); + } +#endif + + btreeBcSharedLogEntryDelete(pFree); +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Macro to implement an in-place sort of an array of objects. Arguments: +** +** Type: The type of the array. e.g. BtReadIntkey +** aObj: The array. +** nObj: The size of the array in objects. +** compare_pA_pB: An expression that evaluates to true if (*pA)<=(*pB) +** when evaluated, where pA and pB are pointers to +** members of the aObj array, type (Type*). +*/ +#define BT_MERGESORT_BODY(Type, aObj, nObj, compare_pA_pB) { \ + if( nObj>1 ){ \ + Type *aTemp; \ + Type *pSrc; \ + Type *pDst; \ + int width; \ + int i; \ + aTemp = sqlite3Malloc(sizeof(Type) * nObj); \ + if( aTemp==0 ){ \ + return SQLITE_NOMEM; \ + } \ + pSrc = aObj; \ + pDst = aTemp; \ + for(width=1; widthnObj ) mid = nObj; \ + if( right>nObj ) right = nObj; \ + p = left; \ + q = mid; \ + k = left; \ + while( piRootiRoot) + || (pA->iRoot==pB->iRoot && pA->iMin<=pB->iMin) + )); + if( nRead>1 ){ + BtReadIntkey *pIn; + BtReadIntkey *pOut = &aRead[0]; + BtReadIntkey * const pEof = &aRead[nRead]; + + for(pIn=&aRead[1]; pInpIn[-1].iRoot) + || (pIn[0].iRoot==pIn[-1].iRoot && pIn[0].iMin>=pIn[-1].iMin) + ); + + if( pIn->iRoot==pOut->iRoot + && (pIn->iMin<=pOut->iMax || (pIn->iMin==pOut->iMax+1)) + ){ + /* Range pIn overlaps with the previous range pOut. The second + ** part of the if() statement above cannot be written more simply + ** due to potential integer overflow. */ + if( pIn->iMax>pOut->iMax ){ + pOut->iMax = pIn->iMax; + } + }else{ + /* No overlap. Start a new range. */ + *(++pOut) = *pIn; + } + } + + *pnRead = (pOut - aRead) + 1; + } + + return SQLITE_OK; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Return the result of comparing the two records: +** +** (aLeft, nLeft, drc_left) - (aRight, nRight, drc_right) +** +** Argument pUnpacked is guaranteed to point to an UnpackedRecord structure +** large enough to decode one of the records. However, the values of +** UnpackedRecord.pKeyInfo and UnpackedRecord.nField must be set manually +** before it is used. +*/ +static int btreeBcRecordCompare( + UnpackedRecord *pUnpacked, + KeyInfo *pKeyInfo, + const u8 *aLeft, int nLeft, int drc_left, + const u8 *aRight, int nRight, int drc_right +){ + int res = 0; + if( aLeft==0 ){ + /* If the record is NULL, then the value depends on the drc. (drc>0) + ** implies the smallest possible value, (drc<0) implies the largest + ** possible valaue */ + res = drc_left * -1; + assert( drc_left!=0 ); + }else if( aRight==0 ){ + res = drc_right; + assert( drc_right!=0 ); + }else{ + pUnpacked->pKeyInfo = pKeyInfo; + pUnpacked->nField = pKeyInfo->nKeyField + 1; + sqlite3VdbeRecordUnpack(nRight, aRight, pUnpacked); + res = sqlite3VdbeRecordCompare(nLeft, aLeft, pUnpacked); + if( res==0 ){ + res = (drc_right - drc_left); + } + } + + if( res<0 ){ + res = -1; + }else if( res>0 ){ + res = +1; + } + + assert( res>=-1 && res<=+1 ); + return res; +} + + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Return the result of comparing the two BtReadIndex arguments: +** +** (*pA) - (*pB) +** +** Argument pUnpacked is guaranteed to point to an UnpackedRecord structure +** large enough to use with btreeBcRecordCompare(). +*/ +static int btreeBcReadIndexCmp( + UnpackedRecord *pUnpacked, + BtReadIndex *pA, + BtReadIndex *pB +){ + if( pA->iRootiRoot ) return -1; + if( pA->iRoot>pB->iRoot ) return +1; + + return btreeBcRecordCompare(pUnpacked, pA->pKeyInfo, + pA->aRecMin, pA->nRecMin, pA->drc_min, + pB->aRecMin, pB->nRecMin, pB->drc_min + ); +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Sort the BtConcurrent.aReadIndex[] array by root page number, then +** by minimum record value. Then merge overlapping ranges. +** +** Return SQLITE_NOMEM if an OOM is encountered, or SQLITE_OK otherwise. +*/ +int btreeBcReadIndexSort(BtConcurrent *pBtConc){ + int nRead = pBtConc->nReadIndex; + BtReadIndex *aRead = pBtConc->aReadIndex; + + BT_MERGESORT_BODY(BtReadIndex, aRead, nRead, ( + btreeBcReadIndexCmp(pBtConc->pUnpacked, pA, pB)<=0 + )); + + /* Merge overlapping ranges */ + if( nRead>1 ){ + BtReadIndex *pIn; + BtReadIndex *pOut = &aRead[0]; + BtReadIndex * const pEof = &aRead[nRead]; + + for(pIn=&aRead[1]; pIniRoot==pOut->iRoot ){ + + /* Compare aRecMin of this range to the aRecMax of the previous. */ + int res = btreeBcRecordCompare( + pBtConc->pUnpacked, pIn->pKeyInfo, + pIn->aRecMin, pIn->nRecMin, pIn->drc_min, + pOut->aRecMax, pOut->nRecMax, pOut->drc_max + ); + + if( res<=0 ){ + + if( pIn->aRecMax!=pIn->aRecMin || pOut->aRecMax!=pOut->aRecMin ){ + /* This range overlaps with the previous one. This call is to + ** figure out if it is entirely encapsulated within the previous + ** range (if res<=0) or if it only partially overlaps and the + ** max of the previous range needs to be extended (res>0). + ** + ** This test is not required if both pOut and pIn are an exact + ** key searchs, not true ranges (if aRecMax==aRecMin). In that + ** case, pIn is always wholly encapsulated by pOut. */ + res = btreeBcRecordCompare( + pBtConc->pUnpacked, pIn->pKeyInfo, + pIn->aRecMax, pIn->nRecMax, pIn->drc_max, + pOut->aRecMax, pOut->nRecMax, pOut->drc_max + ); + } + + sqlite3KeyInfoUnref(pIn->pKeyInfo); + if( pIn->aRecMin!=pIn->aRecMax ) sqlite3_free(pIn->aRecMin); + + if( res>0 ){ + if( pOut->aRecMax!=pOut->aRecMin ) sqlite3_free(pOut->aRecMax); + pOut->aRecMax = pIn->aRecMax; + pOut->nRecMax = pIn->nRecMax; + pOut->drc_max = pIn->drc_max; + }else{ + sqlite3_free(pIn->aRecMax); + } + + continue; + } + } + + /* This range does not overlap with the previous. Copy it directly + ** to the output. */ + *(++pOut) = *pIn; + } + + pBtConc->nReadIndex = 1 + pOut - aRead; + } + + return SQLITE_OK; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Sort the aWrite[] array by root page and key value. +** +** Return SQLITE_NOMEM if an OOM is encountered, or SQLITE_OK otherwise. +*/ +static int btreeBcWriteIntkeySort( + BtWriteIntkey *aWrite, + int nWrite +){ + BT_MERGESORT_BODY(BtWriteIntkey, aWrite, nWrite, ( + pA->iRootiRoot + || (pA->iRoot==pB->iRoot && pA->iKey <= pB->iKey) + )); + return SQLITE_OK; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Helper function for btreeBcWriteIndexSort(). +*/ +static int btreeBcWriteIndexCmp( + UnpackedRecord *pUnpacked, + BtWrite *aWrite, + BtWriteIndex *pA, + BtWriteIndex *pB +){ + BtWrite *pWA = &aWrite[pA->iRoot]; + BtWrite *pWB = &aWrite[pB->iRoot]; + int res; + + if( pWA->iRootiRoot ) return -1; + if( pWA->iRoot>pWB->iRoot ) return +1; + + return btreeBcRecordCompare( + pUnpacked, pWA->pKeyInfo, pA->aRec, pA->nRec, 0, pB->aRec, pB->nRec, 0 + ); +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Sort the aWriteIndex[] array, first in order of root page, then record. +** At this point the BtWriteIndex.iRoot values are actually indexes +** into the BtConcurrent.aWrite[] array. When sorting, this index is +** used to find the actual root page number and the required KeyInfo +** struct. +** +** After sorting, replace the BtWriteIndex.iRoot values with the actual +** value from the aWrite[] array. +** +** Return SQLITE_NOMEM if an OOM is encountered, or SQLITE_OK otherwise. +*/ +int btreeBcWriteIndexSort( + BtWriteIndex *aWriteIndex, + int nWriteIndex, + BtConcurrent *pBtConc +){ + int ii; + + BT_MERGESORT_BODY(BtWriteIndex, aWriteIndex, nWriteIndex, ( + btreeBcWriteIndexCmp(pBtConc->pUnpacked, pBtConc->aWrite, pA, pB)<=0 + )); + + /* Replace the aWriteIndex[ii].iRoot values with actual root page numbers */ + for(ii=0; iiaWrite[ aWriteIndex[ii].iRoot ].iRoot; + } + + return SQLITE_OK; +} + +/* +** Argument iRoot is the root page number of a b-tree within the database. +** Figure out which table or index this is using the schema managed by +** pBt. Return the name of the table. If pzIdx is not NULL and the root +** page belongs to an index, set (*pzIdx) to the name of the index. +** +** This is used to identify the specific table and index on which a +** conflict occurred in an sqlite3_log() message. +*/ +static const char *btreeBcRootToObject( + BtShared *pBt, /* Shared b-tree object */ + Pgno iRoot, /* Root page of table or index */ + const char **pzIdx /* OUT: Index name (or NULL) */ +){ + Schema *pSchema = pBt->pSchema; + const char *zRet = 0; + if( pSchema ){ + HashElem *pE = sqliteHashFirst(&pSchema->tblHash); + for( ; zRet==0 && pE; pE=sqliteHashNext(pE)){ + Table *pTab = (Table*)sqliteHashData(pE); + if( pTab->tnum==iRoot ){ + zRet = pTab->zName; + break; + }else{ + Index *pIdx; + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->tnum==iRoot ){ + zRet = pTab->zName; + if( pzIdx ) *pzIdx = pIdx->zName; + } + } + } + } + } + + return zRet; +} + + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Check if any of the reads accumulated in pBtConc conflict with the +** writes in the aWrite[] array. If so, return SQLITE_BUSY_SNAPSHOT. +** Otherwise, if no conflicts are found, return SQLITE_OK. +*/ +int btreeBcDetectIntkeyConflict( + BtShared *pBt, + BtWriteIntkey *aWrite, + int nWrite +){ + BtReadIntkey *aRead = pBt->conc.aReadIntkey; + int nRead = pBt->conc.nReadIntkey; + int iRead = 0; + int iWrite = 0; + + while( iRead iRootRead ){ + iRead++; + } + else{ + /* Same root page */ + i64 iKey = aWrite[iWrite].iKey; + + if( iKey < aRead[iRead].iMin ){ + iWrite++; + } + else if( iKey > aRead[iRead].iMax ){ + iRead++; + } + else{ + /* A conflict. Before returning, issue a log message. */ + const char *zTab = 0; + + zTab = btreeBcRootToObject(pBt, iRootRead, 0); + sqlite3_log(SQLITE_OK, + "cannot commit CONCURRENT transaction - conflict in table %s - " + "range (%lld,%lld) conflicts with write to rowid %lld", + zTab, aRead[iRead].iMin, aRead[iRead].iMax, iKey + ); + return SQLITE_BUSY_SNAPSHOT; + } + } + } + + return SQLITE_OK; /* No conflict */ +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Test for conflicts between the index ranges accumulated in pBtConc +** and the write-keys in the aWriteIndex[] array. Return +** SQLITE_BUSY_SNAPSHOT if there is a conflict, or SQLITE_OK otherwise. +*/ +int btreeBcDetectIndexConflict( + BtShared *pBt, + BtWriteIndex *aWriteIndex, + int nWriteIndex +){ + BtConcurrent *pBtConc = &pBt->conc; + BtReadIndex *aReadIndex = pBtConc->aReadIndex; + int nReadIndex = pBtConc->nReadIndex; + int iRead = 0; + int iWrite = 0; + + while( iReadiRoot < pRead->iRoot ){ + iWrite++; + }else if( pWrite->iRoot > pRead->iRoot ){ + iRead++; + }else{ + /* Most index scans are not really scans, but exact lookups. In + ** this case pRead->aRecMin==pRead->aRecMax. */ + assert( (pRead->aRecMin!=pRead->aRecMax) || pRead->drc_min>=0 ); + + int cmp = btreeBcRecordCompare( + pBtConc->pUnpacked, + pRead->pKeyInfo, + pWrite->aRec, pWrite->nRec, 0, + pRead->aRecMin, pRead->nRecMin, + (pRead->drc_min>0 ? 0 : pRead->drc_min) + ); + + if( cmp < 0 ){ + /* Write key is less than aRecMin */ + iWrite++; + }else{ + /* Write key is greater than aRecMin */ + iRead++; + + /* If cmp==0, then this is definitely a conflict. Or, if cmp>0 and this + ** is a true range scan, not an exact lookup, compare the write-key to + ** BtReadIndex.aRedMax. If it is less than or equal, this is also a + ** conflict. */ + if( cmp>0 && (pRead->aRecMin!=pRead->aRecMax) ){ + cmp = btreeBcRecordCompare( + pBtConc->pUnpacked, + pRead->pKeyInfo, + pWrite->aRec, pWrite->nRec, 0, + pRead->aRecMax, pRead->nRecMax, pRead->drc_max + ); + } + + if( cmp<=0 ){ + /* Write key is between aRecMin and aRecMax - conflict! */ + const char *zIdx = 0; + const char *zTab = btreeBcRootToObject(pBt, pRead->iRoot, &zIdx); + sqlite3BcLogIndexConflict(zTab, zIdx, pWrite, pRead); + return SQLITE_BUSY_SNAPSHOT; + } + } + } + } + + return SQLITE_OK; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +*/ +static int btreeBcSharedLogEntry(BtShared *pBt, BtSharedLogEntry **ppOut){ + BtSharedLogEntry *pRet = 0; + int ii; + int nIntkey = 0; + int nIndex = 0; + i64 nByte = 0; + BtWrite *aWrite = pBt->conc.aWrite; + int rc = SQLITE_OK; + + /* Count the two different types of writes. */ + for(ii=0; iiconc.nWrite; ii++){ + if( aWrite[ii].pKeyInfo ) nIndex++; + } + nIntkey = pBt->conc.nWrite - nIndex; + + /* Allocate for the BtSharedLogEntry, and the two arrays. */ + nByte = sizeof(BtSharedLogEntry) + + nIntkey * sizeof(BtWriteIntkey) + + nIndex * sizeof(BtWriteIndex); + + pRet = (BtSharedLogEntry*)sqlite3MallocZero(nByte); + if( pRet==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + + pRet->nByte = nByte; + + /* Populate the aIntkey[] and aIndex[] arrays */ + pRet->aIntkey = (BtWriteIntkey*)&pRet[1]; + pRet->aIndex = (BtWriteIndex*)&pRet->aIntkey[nIntkey]; + for(ii=0; iiconc.nWrite; ii++){ + if( aWrite[ii].pKeyInfo ){ + BtWriteIndex *p = &pRet->aIndex[pRet->nIndex++]; + p->iRoot = ii; + p->nRec = aWrite[ii].nRec; + p->aRec = aWrite[ii].aRec; + aWrite[ii].aRec = 0; + pRet->nByte += (p->nRec + 8+9); + }else{ + BtWriteIntkey *p = &pRet->aIntkey[pRet->nIntkey++]; + p->iRoot = aWrite[ii].iRoot; + p->iKey = aWrite[ii].iKey; + } + } + + /* Sort the aIntkey[] array */ + rc = btreeBcWriteIntkeySort(pRet->aIntkey, pRet->nIntkey); + + /* Sort the aIndex[] array */ + if( rc==SQLITE_OK ){ + rc = btreeBcWriteIndexSort(pRet->aIndex, pRet->nIndex, &pBt->conc); + } + + if( rc!=SQLITE_OK ){ + btreeBcSharedLogEntryDelete(pRet); + pRet = 0; + } + } + + *ppOut = pRet; + return rc; +} + + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** A transaction has just been committed. If BtConcurrent info was collected +** for the transaction, add it to the BtSharedLog object. +*/ +static int btreeBcUpdateSharedLog( + BtShared *pBt, + u64 iBaseId, + u32 iFirstFrame, + u32 nFrame +){ + int rc = SQLITE_OK; + if( pBt->conc.eState==BTCONC_STATE_INUSE ){ + BtSharedLogEntry *pNew = 0; + BtSharedLog *pBtLog = pBt->conc.pBtLog; + + rc = btreeBcSharedLogEntry(pBt, &pNew); + if( rc==SQLITE_OK ){ + BtSharedLogEntry *pLast = 0; + + /* Set the two versions in the new log-entry object. */ + pNew->iBaseId = iBaseId; + pNew->iThisId = sqlite3PagerWalCommitId(pBt->pPager); + pNew->iFirstFrame = iFirstFrame; + pNew->nFrame = nFrame; + + /* Take the BtSharedLog mutex */ + sqlite3_mutex_enter(pBtLog->mutex); + + pLast = pBtLog->pLast; + if( pLast && pLast->iThisId!=iBaseId ){ + /* This new log entry does not immediately follow the previous + ** entry in the BtSharedLog object. This means that an external + ** process wrote to the db, or some connection in this process + ** wrote to the db but did not update the BtSharedLog for some + ** reason. Either way, discard the entire contents of the + ** BtSharedLog before adding pNew as the first and only entry. */ + while( pBtLog->pFirst ){ + btreeBcRemoveOldest(pBtLog); + } + } + + if( (iFirstFrame & 0x7FFFFFFF)==1 ){ + /* This was the first entry written into a wal file. *-wal if the + ** 0x80000000 bit of iFirst is clear, or *-wal2 if it is set. + ** Either way, remove all entries corresponding to that wal file + ** from the BtSharedLog. The initial snapshot belonging to each of + ** these entries is no longer available, so there can be no chance + ** of it being required. */ + const u32 m = 0x80000000; + u32 v = (iFirstFrame & m); + while( pBtLog->pFirst && (pBtLog->pFirst->iFirstFrame & m)==v ){ + btreeBcRemoveOldest(pBtLog); + } + } + + /* Link the new entry into the BtSharedLog object. */ + if( pBtLog->pLast ){ + pBtLog->pLast->pLogNext = pNew; + }else{ + pBtLog->pFirst = pNew; + } + pBtLog->pLast = pNew; + pBtLog->nByte += pNew->nByte; + + /* Free any old log-entries no longer required. TODO: Could do + ** this by querying wal.c to see what snapshots are still available + ** or in use. Maybe we can even lock the required BtSharedLogEntry + ** objects in memory when each concurrent transaction is opened. */ + while( pBtLog->nByte>sqlite3GlobalConfig.nSharedLogMaxSize ){ + btreeBcRemoveOldest(pBtLog); + } + + /* Release BtSharedLog mutex */ + sqlite3_mutex_leave(pBtLog->mutex); + } + } + + return rc; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Clear the state of pBt->conc. +*/ +static void btreeBcEndTransaction(sqlite3 *db, BtShared *pBt){ + if( pBt->conc.eState!=BTCONC_STATE_NONE ){ + + { + BtReadIndex *aRead = pBt->conc.aReadIndex; + int ii; + for(ii=0; iiconc.nReadIndex; ii++){ + BtReadIndex *p = &aRead[ii]; + if( p->aRecMin!=p->aRecMax ) sqlite3_free(p->aRecMin); + sqlite3_free(p->aRecMax); + sqlite3KeyInfoUnref(p->pKeyInfo); + } + sqlite3_free(aRead); + + sqlite3_free(pBt->conc.aReadIntkey); + + pBt->conc.aReadIndex = 0; + pBt->conc.nReadIndex = 0; + pBt->conc.nReadIndexAlloc = 0; + pBt->conc.aReadIntkey = 0; + pBt->conc.nReadIntkey = 0; + pBt->conc.nReadIntkeyAlloc = 0; + } + + { + btreeBcFreeWriteArray(pBt->conc.aWrite, pBt->conc.nWrite); + sqlite3_free(pBt->conc.aWrite); + pBt->conc.aWrite = 0; + pBt->conc.nWrite = 0; + pBt->conc.nWriteAlloc = 0; + } + + sqlite3DbFree(db, pBt->conc.pUnpacked); + pBt->conc.pUnpacked = 0; + pBt->conc.nUnpackedField = 0; + pBt->conc.eState = BTCONC_STATE_NONE; + } +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +*/ +static void btreeBcDisconnect(BtShared *pBt){ + if( pBt->conc.pBtLog ){ + BtSharedLog *pFree = pBt->conc.pBtLog; + pBt->conc.pBtLog = 0; + + sqlite3_mutex_enter( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MAIN) ); + pFree->nRef--; + if( pFree->nRef==0 ){ + BtSharedLog **pp = &pGlobalBtSharedLog; + while( *pp!=pFree ){ pp = &(*pp)->pSharedNext; } + *pp = (*pp)->pSharedNext; + }else{ + pFree = 0; + } + sqlite3_mutex_leave( sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MAIN) ); + + if( pFree ){ + while( pFree->pFirst ){ + btreeBcRemoveOldest(pFree); + } + sqlite3_mutex_free(pFree->mutex); + sqlite3_free(pFree); + } + } +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Finish a scan. +*/ +static int btreeBcScanFinish(BtCursor *pCsr){ + int rc = SQLITE_OK; + BtConcurrent *pBtConc = &pCsr->pBt->conc; + if( pBtConc->eState==BTCONC_STATE_INUSE && pCsr->iScanIndex>0 ){ + + if( pCsr->pKeyInfo ){ + BtReadIndex *p = &pBtConc->aReadIndex[pCsr->iScanIndex-1]; + u8 *aRec = 0; + int nRec = 0; + + if( pCsr->eState==CURSOR_VALID || pCsr->eState==CURSOR_SKIPNEXT ){ + nRec = sqlite3BtreePayloadSize(pCsr); + aRec = sqlite3_malloc(nRec + 8+9); + if( !aRec ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + rc = sqlite3BtreePayload(pCsr, 0, nRec, aRec); + } + }else if( pCsr->eState==CURSOR_REQUIRESEEK ){ + nRec = pCsr->nKey; + aRec = sqlite3_malloc(nRec + 8+9); + if( !aRec ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + memcpy(aRec, pCsr->pKey, nRec); + } + } + if( rc!=SQLITE_OK ){ + sqlite3_free(aRec); + return rc; + } + + assert( pCsr->eScanType==BTCONC_READ_MOVETO||(!p->aRecMin&&!p->nRecMin)); + assert( p->aRecMax==0 && p->nRecMax==0 ); + + switch( pCsr->eScanType ){ + case BTCONC_READ_FIRST: { + p->aRecMax = aRec; + p->nRecMax = nRec; + break; + } + + case BTCONC_READ_LAST: { + p->aRecMin = aRec; + p->nRecMin = nRec; + break; + } + + default: { + assert( pCsr->eScanType==BTCONC_READ_MOVETO ); + /* The value used in the seek op is currently stored in p->aRecMin */ + if( pCsr->iScanDir==0 ){ + if( aRec==0 ){ + sqlite3_free(p->aRecMin); + p->aRecMin = 0; + p->nRecMin = 0; + p->drc_min = 0; + }else{ + pCsr->iScanDir = btreeBcRecordCompare( + pBtConc->pUnpacked, p->pKeyInfo, + aRec, nRec, 0, + p->aRecMin, p->nRecMin, p->drc_min + ); + } + } + + if( pCsr->iScanDir<0 ){ + p->aRecMax = p->aRecMin; + p->nRecMax = p->nRecMin; + p->drc_max = p->drc_min; + p->aRecMin = aRec; + p->nRecMin = nRec; + p->drc_min = 0; + }else{ + p->aRecMax = aRec; + p->nRecMax = nRec; + } + + break; + } + } + + if( p->aRecMin==0 ) p->drc_min = +1; + if( p->aRecMax==0 ) p->drc_max = -1; + + }else{ + BtReadIntkey *p = &pBtConc->aReadIntkey[pCsr->iScanIndex-1]; + + int bEof = (pCsr->eState==CURSOR_INVALID || pCsr->eState==CURSOR_FAULT); + i64 iKey = 0; + + if( bEof==0 ){ + if( pCsr->eState==CURSOR_VALID || pCsr->eState==CURSOR_SKIPNEXT ){ + iKey = sqlite3BtreeIntegerKey(pCsr); + }else{ + assert( pCsr->eState==CURSOR_REQUIRESEEK ); + iKey = pCsr->nKey; + } + } + + switch( pCsr->eScanType ){ + case BTCONC_READ_FIRST: { + p->iMin = SMALLEST_INT64; + p->iMax = bEof ? LARGEST_INT64 : iKey; + break; + } + + case BTCONC_READ_LAST: { + p->iMax = LARGEST_INT64; + if( bEof ){ + p->iMin = SMALLEST_INT64; + }else{ + p->iMin = iKey; + } + break; + } + + default: { + assert( pCsr->eScanType==BTCONC_READ_MOVETO ); + /* The value used in the seek op is currently stored in p->iMin */ + if( bEof==0 ){ + p->iMax = iKey; + }else{ + if( pCsr->iScanDir==0 ){ + p->iMin = SMALLEST_INT64; + p->iMax = LARGEST_INT64; + }else if( pCsr->iScanDir>0 ){ + p->iMax = LARGEST_INT64; + }else{ + p->iMax = p->iMin; + p->iMin = SMALLEST_INT64; + } + } + if( p->iMin>p->iMax ){ + SWAP(i64, p->iMin, p->iMax); + } + break; + } + } + + if( p->iMin==SMALLEST_INT64 && p->iMax==LARGEST_INT64 ){ + /* Full table scan. Issue a log message to record this. */ + const char *zTab = btreeBcRootToObject(pCsr->pBt, p->iRoot, 0); + sqlite3_log(SQLITE_OK, + "BEGIN CONCURRENT: logging full scan on table %s " + "(root=%d) (sql=\"%s\")", + zTab, p->iRoot, sqlite3BcCurrentSql(pCsr->pBtree->db) + ); + } + + } + pCsr->iScanIndex = 0; + pCsr->iScanDir = 0; + } + return rc; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Allocate and return a BtReadIntkey object. +*/ +static BtReadIntkey *btreeBcIntkeyRead( + BtConcurrent *pBtConc, + int *piScanIndex, + int *pRc +){ + int rc = btreeBcGrowArray( + (void**)&pBtConc->aReadIntkey, + &pBtConc->nReadIntkey, + &pBtConc->nReadIntkeyAlloc, + sizeof(BtReadIntkey) + ); + if( rc==SQLITE_OK ){ + pBtConc->nReadIntkey++; + *piScanIndex = pBtConc->nReadIntkey; + return &pBtConc->aReadIntkey[pBtConc->nReadIntkey-1]; + } + *pRc = rc; + return 0; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Allocate and return a BtReadIndex object. +*/ +static BtReadIndex *btreeBcIndexRead( + BtConcurrent *pBtConc, + int *piScanIndex, + int *pRc +){ + int rc = btreeBcGrowArray( + (void**)&pBtConc->aReadIndex, + &pBtConc->nReadIndex, + &pBtConc->nReadIndexAlloc, + sizeof(BtReadIndex) + ); + if( rc==SQLITE_OK ){ + pBtConc->nReadIndex++; + *piScanIndex = pBtConc->nReadIndex; + return &pBtConc->aReadIndex[pBtConc->nReadIndex-1]; + } + *pRc = rc; + return 0; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Ensure the UnpackedRecord structure at BtConcurrent.pUnpacked is +** large enough to unpack keys that are compared using pKeyInfo. +** Return SQLITE_NOMEM if an OOM error is encountered, or SQLITE_OK +** otherwise. +*/ +static int btreeBcUpdateUnpacked(BtConcurrent *pBtConc, KeyInfo *pKeyInfo){ + if( pKeyInfo->nKeyField>pBtConc->nUnpackedField ){ + sqlite3 *db = pKeyInfo->db; + sqlite3DbFree(db, pBtConc->pUnpacked ); + pBtConc->pUnpacked = sqlite3VdbeAllocUnpackedRecord(pKeyInfo); + if( pBtConc->pUnpacked ){ + pBtConc->nUnpackedField = pKeyInfo->nKeyField; + pBtConc->pUnpacked->default_rc = 0; + }else{ + pBtConc->nUnpackedField = 0; + return SQLITE_NOMEM; + } + } + return SQLITE_OK; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +*/ +static void btreeBcScanIsExact(BtCursor *pCsr){ + BtReadIndex *p = &pCsr->pBt->conc.aReadIndex[pCsr->iScanIndex-1]; + p->nRecMax = p->nRecMin; + p->aRecMax = p->aRecMin; + if( p->drc_min<0 ){ + p->drc_min = p->drc_min * -1; + } + p->drc_max = p->drc_min * -1; + pCsr->iScanIndex = 0; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Start a scan. +*/ +static int btreeBcScanStart( + BtCursor *pCsr, /* Cursor to start scan on */ + int eRead, /* BTCONC_READ_XXX value */ + i64 iKey, UnpackedRecord *pKey /* Key value for BTCONC_READ_MOVETO */ +){ + int rc = SQLITE_OK; + BtConcurrent *pBtConc = &pCsr->pBt->conc; + + rc = btreeBcScanFinish(pCsr); + if( rc==SQLITE_OK && pBtConc->eState==BTCONC_STATE_INUSE ){ + pCsr->iScanDir = 0; + pCsr->eScanType = eRead; + if( pCsr->pKeyInfo ){ + + /* Reserve a BtReadIndex structure from the array */ + BtReadIndex *pRead = btreeBcIndexRead(pBtConc, &pCsr->iScanIndex, &rc); + if( pRead==0 ) return rc; + memset(pRead, 0, sizeof(*pRead)); + + pRead->iRoot = pCsr->pgnoRoot; + pRead->pKeyInfo = sqlite3KeyInfoRef(pCsr->pKeyInfo); + rc = btreeBcUpdateUnpacked(pBtConc, pRead->pKeyInfo); + if( rc==SQLITE_OK && pKey ){ + int drc = 0; + assert( eRead==BTCONC_READ_MOVETO ); + rc = sqlite3BcSerializeRecord(pKey, &pRead->aRecMin, &pRead->nRecMin); + assert( pKey->default_rc>=-1 && pKey->default_rc<=+1 ); + assert( pCsr->pKeyInfo->nAllField>=pKey->nField ); + drc = pKey->default_rc * (pCsr->pKeyInfo->nAllField+1 - pKey->nField); + pRead->drc_min = (i16)drc; + + /* If the BTREE_SEEK_EQ flag is set, then the caller is only + ** interested in index entries that exactly match the supplied prefix. + ** In this case we can set the first and last keys of the scan + ** directly instead of tracking where the cursor finishes up. + */ + if( (pCsr->hints & BTREE_SEEK_EQ) && rc==SQLITE_OK ){ + btreeBcScanIsExact(pCsr); + } + } + + if( eRead==BTCONC_READ_COUNT ){ + pRead->drc_min = +1; + pRead->drc_max = -1; + } + + }else{ + BtReadIntkey *pRead = btreeBcIntkeyRead(pBtConc, &pCsr->iScanIndex, &rc); + if( pRead==0 ) return rc; + pRead->iRoot = pCsr->pgnoRoot; + + pRead->iMin = iKey; + pRead->iMax = 0; + if( eRead==BTCONC_READ_COUNT ){ + pRead->iMax = LARGEST_INT64; + pRead->iMin = SMALLEST_INT64; + } + } + + if( eRead==BTCONC_READ_COUNT ){ + pCsr->iScanIndex = 0; + pCsr->iScanDir = 0; + } + } + + return rc; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Append an insert to the BtConcurrent object, if it is in use. Return +** SQLITE_OK if successful, or SQLITE_NOMEM if an OOM occurs. +*/ +static int btreeBcInsert( + BtCursor *pCsr, /* Cursor to write to */ + const BtreePayload *pPay /* Payload to write */ +){ + BtConcurrent *pBtConc = &pCsr->pBt->conc; + int rc = SQLITE_OK; + + if( SQLITE_OK==(rc = btreeBcScanFinish(pCsr)) + && pBtConc->eState==BTCONC_STATE_INUSE + && SQLITE_OK==(rc = btreeBcGrowWriteArray(pBtConc)) + ){ + int nByte = (pPay->pKey? pPay->nKey : (pPay->nData + pPay->nZero)); + BtWrite *p = &pBtConc->aWrite[pBtConc->nWrite]; + memset(p, 0, sizeof(BtWrite)); + pBtConc->nWrite++; + p->iRoot = pCsr->pgnoRoot; + p->aRec = sqlite3_malloc(nByte); + if( p->aRec==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else if( pCsr->pKeyInfo ){ + p->pKeyInfo = sqlite3KeyInfoRef(pCsr->pKeyInfo); + p->nRec = pPay->nKey; + memcpy(p->aRec, pPay->pKey, p->nRec); + rc = btreeBcUpdateUnpacked(pBtConc, p->pKeyInfo); + }else{ + p->iKey = pPay->nKey; + p->nRec = nByte; + memcpy(p->aRec, pPay->pData, pPay->nData); + if( pPay->nZero ){ + memset(&p->aRec[pPay->nData], 0, pPay->nZero); + } + } + } + + return rc; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** The entry under pCur is to be deleted. If this is a BEGIN CONCURRENT +** transaction, add an entry for the delete to the BtConcurrent object. +** Return SQLITE_OK if successful, or an SQLite error code (SQLITE_NOMEM) +** if something goes wrong. +*/ +static int btreeBcDelete(BtCursor *pCsr){ + BtConcurrent *pBtConc = &pCsr->pBt->conc; + int rc; + + if( SQLITE_OK==(rc = btreeBcScanFinish(pCsr)) + && pBtConc->eState==BTCONC_STATE_INUSE + && SQLITE_OK==(rc = btreeBcGrowWriteArray(pBtConc)) + ){ + BtWrite *p = &pBtConc->aWrite[pBtConc->nWrite]; + int nRec = 0; + + pBtConc->nWrite++; + memset(p, 0, sizeof(BtWrite)); + p->bDel = 1; + p->iRoot = pCsr->pgnoRoot; + if( pCsr->pKeyInfo ){ + p->pKeyInfo = sqlite3KeyInfoRef(pCsr->pKeyInfo); + p->nRec = sqlite3BtreePayloadSize(pCsr); + p->aRec = sqlite3_malloc(p->nRec + 8+9); + if( p->aRec==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + rc = sqlite3BtreePayload(pCsr, 0, p->nRec, p->aRec); + } + if( rc==SQLITE_OK ){ + rc = btreeBcUpdateUnpacked(pBtConc, p->pKeyInfo); + } + }else{ + p->iKey = sqlite3BtreeIntegerKey(pCsr); + } + } + + return rc; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Close all cursors open on the b-tree object. It is the responsibility +** of the caller to ensure none of the cursors will be used after +** this call. +*/ +static void btreeCloseAllCursors(BtShared *pBt){ + while( pBt->pCursor ){ + BtCursor *pCsr = pBt->pCursor; + sqlite3BtreeCloseCursor(pCsr); + sqlite3_free(pCsr); + } +} + +/* +** Return the size of a BtCursor object in bytes. +** +** This interfaces is needed so that users of cursors can preallocate +** sufficient storage to hold a cursor. The BtCursor object is opaque +** to users so they cannot do the sizeof() themselves - they must call +** this routine. +*/ +SQLITE_PRIVATE int sqlite3BtreeCursorSize(void){ + return ROUND8(sizeof(BtCursor)); +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Allocate memory for and open a cursor. +*/ +static int btreeCursorOpen( + Btree *p, + Pgno iRoot, + int flags, + KeyInfo *pKeyInfo, + BtCursor **ppCsr +){ + BtCursor *pCsr = 0; + int rc = SQLITE_OK; + + pCsr = (BtCursor*)sqlite3MallocZero(sqlite3BtreeCursorSize()); + if( pCsr==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + rc = sqlite3BtreeCursor(p, iRoot, flags, pKeyInfo, pCsr); + if( rc!=SQLITE_OK ){ + sqlite3_free(pCsr); + pCsr = 0; + } + } + *ppCsr = pCsr; + return rc; +} + +/* Used by btreeBcTryLogicalCommit() */ +static int btreeMoveto(BtCursor*, const void*, i64, int, int*); + +/* +** Sort the pBtConc->aReadIntkey[] and aReadIndex[] arrays. +*/ +SQLITE_PRIVATE int sqlite3BtreeSortReadArrays(BtConcurrent *pBtConc){ + int rc = btreeBcReadIntkeySort(pBtConc->aReadIntkey, &pBtConc->nReadIntkey); + if( rc==SQLITE_OK ){ + rc = btreeBcReadIndexSort(pBtConc); + } + return rc; +} + +/* !defined(SQLITE_OMIT_CONCURRENT) +** +** Attempt to validate and write the transaction in BtShared.conc to the +** page cache. Return SQLITE_OK if successful. SQLITE_BUSY_SNAPSHOT if +** the transaction cannot be written because validation failed, or an +** SQLite error code if some other error occurred. +*/ +static int btreeBcTryLogicalCommit(Btree *p, int *pbLog){ + int rc = SQLITE_OK; + BtShared *pBt = p->pBt; + + u64 cksum1 = 0; + u64 cksum2 = 0; + + BtConcurrent *pBtConc = &pBt->conc; + assert( pBtConc->eState==BTCONC_STATE_INUSE ); + pBtConc->eState = BTCONC_STATE_RETIRED; + + /* Sort the read arrays */ + rc = sqlite3BtreeSortReadArrays(pBtConc); + + if( rc==SQLITE_OK ){ + BtSharedLog *pBtLog = pBt->conc.pBtLog; + BtSharedLogEntry *pEntry; + u64 iLiveId = sqlite3PagerWalLiveId(pBt->pPager); + + sqlite3_mutex_enter(pBtLog->mutex); + + /* Skip past log entries corresponding to transactions that were + ** committed before the snapshot on which this transaction is based + ** was created. */ + for(pEntry=pBtLog->pFirst; pEntry; pEntry=pEntry->pLogNext){ + if( pEntry->iBaseId==pBt->conc.iBase ) break; + } + + if( pEntry==0 || pBtLog->pLast->iThisId!=iLiveId ){ + /* The shared-log does not contain all required entries. Either it + ** does not have entries back as far as the snapshot that this + ** snapshot was prepared against (pEntry==0), or the last entry + ** is older than the current live head of the wal file. Either + ** way, logical validation cannot run. */ + rc = SQLITE_BUSY_SNAPSHOT; + }else{ + /* We know the frame that page-level validation failed at. Since + ** page-level validation scans transactions in the order committed, + ** skip over BtSharedLogEntry structures until we come to the + ** one that wrote the conflicting frame. Everything before this + ** in the list is guaranteed not to conflict. */ + u32 iConf = p->db->aCommit[SQLITE_COMMIT_CONFLICT_FRAME]; + while( iConfiFirstFrame + || iConf>=(pEntry->iFirstFrame+pEntry->nFrame) + ){ + pEntry = pEntry->pLogNext; + assert( pEntry ); + } + *pbLog = 1; + } + + /* Loop through all remaining shared-log entries checking for + ** conflicts. If none are found, then the transaction may be + ** committed. This block sets rc to SQLITE_BUSY_SNAPSHOT if conflicts + ** are found. */ + for(; pEntry && rc==SQLITE_OK; pEntry=pEntry->pLogNext){ + rc = btreeBcDetectIntkeyConflict(pBt, pEntry->aIntkey, pEntry->nIntkey); + if( rc==SQLITE_OK ){ + rc = btreeBcDetectIndexConflict(pBt, pEntry->aIndex, pEntry->nIndex); + } + } + sqlite3_mutex_leave(pBtLog->mutex); + } + + if( rc==SQLITE_OK ){ + /* Update the snapshot to the head of the wal file. Drop the contents + ** of the page-cache at the same time. Then ensure that the database + ** size is set correctly at both the btree and pager level. */ + btreePtrmapDelete(pBt); + rc = sqlite3PagerUpgradeSnapshot(pBt->pPager, pBt->pPage1->pDbPage, 1); + if( rc==SQLITE_OK ){ + const u8 *aPg1 = (const u8*)pBt->pPage1->pDbPage->pData; + u32 dbSize = get4byte(&aPg1[28]); + sqlite3PagerSetDbsize(pBt->pPager, dbSize); + pBt->nPage = dbSize; + } + } + + /* If everything still looks ok, proceed with the commit. */ + if( rc==SQLITE_OK ){ + int ii; + + for(ii=0; iinWrite; ii++){ + BtWrite *pWrite = &pBtConc->aWrite[ii]; + BtCursor *pCsr = 0; + for(pCsr=pBt->pCursor; pCsr; pCsr=pCsr->pNext){ + if( pCsr->pgnoRoot==pWrite->iRoot ) break; + } + + if( pCsr==0 ){ + rc = btreeCursorOpen(p, pWrite->iRoot, + BTREE_WRCSR, pWrite->pKeyInfo, &pCsr + ); + } + + if( pCsr ){ + if( pWrite->bDel ){ + int res = 0; + if( pWrite->pKeyInfo ){ + rc = btreeMoveto(pCsr, pWrite->aRec, pWrite->nRec, 0, &res); + }else{ + rc = btreeMoveto(pCsr, 0, pWrite->iKey, 0, &res); + } + if( rc==SQLITE_OK && res==0 ){ + sqlite3BtreeDelete(pCsr, 0); + } + }else{ + BtreePayload pay; + memset(&pay, 0, sizeof(pay)); + if( pWrite->pKeyInfo ){ + UnpackedRecord *pUnpacked = pBtConc->pUnpacked; + pay.pKey = (const void*)pWrite->aRec; + pay.nKey = pWrite->nRec; + + /* For most indexes, this would not be necessary - + ** sqlite3BtreeInsert() would decode pay.pKey/pay.nKey + ** automatically. But that case doesn't work if this is + ** an UPDATE of a WITHOUT ROWID PRIMARY KEY index. In that + ** case sqlite3BtreeInsert() needs the unpacked record - + ** otherwise it inserts a second record instead of correctly + ** clobbering the first. */ + assert( pUnpacked->default_rc==0 ); + pUnpacked->pKeyInfo = pWrite->pKeyInfo; + pUnpacked->nField = pWrite->pKeyInfo->nKeyField + 1; + sqlite3VdbeRecordUnpack(pay.nKey, pay.pKey, pUnpacked); + pay.aMem = pUnpacked->aMem; + pay.nMem = pWrite->pKeyInfo->nKeyField; + + }else{ + pay.nKey = pWrite->iKey; + pay.pData = (const void*)pWrite->aRec; + pay.nData = pWrite->nRec; + } + rc = sqlite3BtreeInsert(pCsr, &pay, 0, 0); + } + } + } + + btreeCloseAllCursors(pBt); + } + + pBtConc->eState = BTCONC_STATE_INUSE; + return rc; +} + + #else /* SQLITE_OMIT_CONCURRENT */ # define btreePtrmapAllocate(x) SQLITE_OK # define btreePtrmapDelete(x) # define btreePtrmapBegin(x,y) SQLITE_OK # define btreePtrmapEnd(x,y,z) # define btreePtrmapCheck(y,z) + +# define btreeBcEndTransaction(db, pBt) +# define btreeBcDisconnect(pBt) +# define btreeBcScanFinish(pCsr) SQLITE_OK +# define btreeBcScanStart(pCsr,eRead,pKey,iKey) SQLITE_OK #endif /* SQLITE_OMIT_CONCURRENT */ static void releasePage(MemPage *pPage); /* Forward reference */ @@ -78708,6 +80564,7 @@ SQLITE_PRIVATE int sqlite3BtreeClose(Btree *p){ ** Clean out and delete the BtShared object. */ assert( !pBt->pCursor ); + btreeBcDisconnect(pBt); sqlite3PagerClose(pBt->pPager, p->db); if( pBt->xFreeSchema && pBt->pSchema ){ pBt->xFreeSchema(pBt->pSchema); @@ -79540,7 +81397,10 @@ static SQLITE_NOINLINE int btreeBeginTrans( trans_begun: #ifndef SQLITE_OMIT_CONCURRENT if( bConcurrent && rc==SQLITE_OK && sqlite3PagerIsWal(pBt->pPager) ){ - rc = sqlite3PagerBeginConcurrent(pBt->pPager); + rc = btreeBcBeginConcurrent(pBt); + if( rc==SQLITE_OK ){ + rc = sqlite3PagerBeginConcurrent(pBt->pPager); + } if( rc==SQLITE_OK && wrflag ){ rc = btreePtrmapAllocate(pBt); } @@ -80122,6 +81982,7 @@ static int btreeRelocateRange( ** (i.e. pgno>=iFirst), then discard it and allocate another. */ do { rc = allocateBtreePage(pBt, &pFree, &iNew, 0, 0); + if( rc!=SQLITE_OK ) break; if( iNew>=iFirst ){ assert( sqlite3PagerPageRefcount(pFree->pDbPage)==1 ); assert( iNew>iPg ); @@ -80166,7 +82027,7 @@ static int btreeFixUnlocked(Btree *p){ u32 nFree = get4byte(&p1[36]); assert( pBt->pMap ); - rc = sqlite3PagerUpgradeSnapshot(pPager, pPage1->pDbPage); + rc = sqlite3PagerUpgradeSnapshot(pPager, pPage1->pDbPage, 0); assert( p1==pPage1->aData ); if( rc==SQLITE_OK ){ @@ -80271,10 +82132,12 @@ static int btreeFixUnlocked(Btree *p){ SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zSuperJrnl){ int rc = SQLITE_OK; if( p->inTrans==TRANS_WRITE ){ + u64 iBaseId = 0; BtShared *pBt = p->pBt; sqlite3BtreeEnter(p); #ifndef SQLITE_OMIT_CONCURRENT + iBaseId = sqlite3PagerWalLiveId(pBt->pPager); memset(p->aCommit, 0, sizeof(p->aCommit)); #endif #ifndef SQLITE_OMIT_AUTOVACUUM @@ -80307,6 +82170,11 @@ SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zSuperJrnl){ p->aCommit[SQLITE_COMMIT_FIRSTFRAME] = iPrev+1; p->aCommit[SQLITE_COMMIT_NFRAME] = iCurrent-iPrev; + + rc = btreeBcUpdateSharedLog(pBt, iBaseId, + p->aCommit[SQLITE_COMMIT_FIRSTFRAME], + p->aCommit[SQLITE_COMMIT_NFRAME] + ); } #endif sqlite3BtreeLeave(p); @@ -80355,6 +82223,7 @@ static void btreeEndTransaction(Btree *p){ ** Also call PagerEndConcurrent() to ensure that the pager has discarded ** the record of all pages read within the transaction. */ btreePtrmapDelete(pBt); + btreeBcEndTransaction(db, pBt); sqlite3PagerEndConcurrent(pBt->pPager); btreeIntegrity(p); } @@ -80770,18 +82639,6 @@ SQLITE_PRIVATE int sqlite3BtreeCursor( } } -/* -** Return the size of a BtCursor object in bytes. -** -** This interfaces is needed so that users of cursors can preallocate -** sufficient storage to hold a cursor. The BtCursor object is opaque -** to users so they cannot do the sizeof() themselves - they must call -** this routine. -*/ -SQLITE_PRIVATE int sqlite3BtreeCursorSize(void){ - return ROUND8(sizeof(BtCursor)); -} - #ifdef SQLITE_DEBUG /* ** Return true if and only if the Btree object will be automatically @@ -80823,6 +82680,17 @@ SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor *pCur){ BtShared *pBt = pCur->pBt; sqlite3BtreeEnter(pBtree); assert( pBt->pCursor!=0 ); + +#ifndef SQLITE_OMIT_CONCURRENT + if( SQLITE_OK!=btreeBcScanFinish(pCur) ){ + /* Allocation failed in btreeBcScanFinish(), but we have no way + ** to return the error to the user. So just disable the BtConcurrent + ** object. */ + assert( pBt->conc.eState==BTCONC_STATE_INUSE ); + pBt->conc.eState = BTCONC_STATE_RETIRED; + } +#endif + if( pBt->pCursor==pCur ){ pBt->pCursor = pCur->pNext; }else{ @@ -81680,6 +83548,10 @@ SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){ assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); + + rc = btreeBcScanStart(pCur, BTCONC_READ_FIRST, 0, 0); + if( rc!=SQLITE_OK ) return rc; + rc = moveToRoot(pCur); if( rc==SQLITE_OK ){ assert( pCur->pPage->nCell>0 ); @@ -81760,6 +83632,13 @@ SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); +#ifndef SQLITE_OMIT_CONCURRENT + { + int rc = btreeBcScanStart(pCur, BTCONC_READ_LAST, 0, 0); + if( rc!=SQLITE_OK ) return rc; + } +#endif + /* If the cursor already points to the last entry, this is a no-op. */ if( CURSOR_VALID==pCur->eState && (pCur->curFlags & BTCF_AtLast)!=0 ){ assert( cursorIsAtLastEntry(pCur) || CORRUPT_DB ); @@ -81806,6 +83685,9 @@ SQLITE_PRIVATE int sqlite3BtreeTableMoveto( assert( pCur->pKeyInfo==0 ); assert( pCur->eState!=CURSOR_VALID || pCur->curIntKey!=0 ); + rc = btreeBcScanStart(pCur, BTCONC_READ_MOVETO, intKey, 0); + if( rc!=SQLITE_OK ) return rc; + /* If the cursor is already positioned at the point we are trying ** to move to, then just return without doing any work */ if( pCur->eState==CURSOR_VALID && (pCur->curFlags & BTCF_ValidNKey)!=0 ){ @@ -81935,6 +83817,33 @@ SQLITE_PRIVATE int sqlite3BtreeTableMoveto( return rc; } +#ifndef SQLITE_OMIT_CONCURRENT +/* !SQLITE_OMIT_CONCURRENT +** +** This is called on a cursor open on an intkey btree immediately after +** an unsuccessful seek. It tells the BC layer that the cursor was only +** used to search for the specific rowid, not the one it landed on. And +** so the OCC range lock is only required on the sought rowid, not on +** the range between the sought rowid and whereever it landed. +** +** e.g. if the table contains rowids 1, 3 and 5, and the user queries +** for rowid 4, the cursor will land on rowid 3. But we want the OCC +** range lock on 4-4, not 3-4. That's what this call is for. +*/ +SQLITE_PRIVATE void sqlite3BtreeCursorNoScan(BtCursor *pCsr){ + if( pCsr->iScanIndex>0 ){ + if( pCsr->pKeyInfo ){ + btreeBcScanIsExact(pCsr); + }else{ + BtReadIntkey *p = &pCsr->pBt->conc.aReadIntkey[pCsr->iScanIndex-1]; + p->iMax = p->iMin; + pCsr->iScanIndex = 0; + } + } +} +#endif + + /* ** Compare the "idx"-th cell on the page pPage against the key ** pointing to by pIdxKey using xRecordCompare. Return negative or @@ -82036,6 +83945,9 @@ SQLITE_PRIVATE int sqlite3BtreeIndexMoveto( assert( pRes ); assert( pCur->pKeyInfo!=0 ); + rc = btreeBcScanStart(pCur, BTCONC_READ_MOVETO, 0, pIdxKey); + if( rc!=SQLITE_OK ) return rc; + #ifdef SQLITE_DEBUG pCur->pBtree->nSeek++; /* Performance measurement during testing */ #endif @@ -82374,6 +84286,9 @@ SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor *pCur, int flags){ UNUSED_PARAMETER( flags ); /* Used in COMDB2 but not native SQLite */ assert( cursorOwnsBtShared(pCur) ); assert( flags==0 || flags==1 ); +#ifndef SQLITE_OMIT_CONCURRENT + pCur->iScanDir = +1; +#endif pCur->info.nSize = 0; pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); if( pCur->eState!=CURSOR_VALID ) return btreeNext(pCur); @@ -82465,6 +84380,9 @@ SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor *pCur, int flags){ assert( cursorOwnsBtShared(pCur) ); assert( flags==0 || flags==1 ); UNUSED_PARAMETER( flags ); /* Used in COMDB2 but not native SQLite */ +#ifndef SQLITE_OMIT_CONCURRENT + pCur->iScanDir = -1; +#endif pCur->curFlags &= ~(BTCF_AtLast|BTCF_ValidOvfl|BTCF_ValidNKey); pCur->info.nSize = 0; if( pCur->eState!=CURSOR_VALID @@ -85375,6 +87293,22 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ } } +#ifndef SQLITE_OMIT_CONCURRENT +# define BTCONC_DISABLE(pBtConc) \ + int eConcStateSave = pBtConc->eState; \ + pBtConc->eState = BTCONC_STATE_RETIRED + +# define BTCONC_RESTORE(pBtConc) pBtConc->eState = eConcStateSave + +# define SAVE_BTCONC \ + int eConcStateSave = pCur->pBt->conc.eState; \ + pCur->pBt->conc.eState = BTCONC_STATE_RETIRED + +# define RESTORE_BTCONC pCur->pBt->conc.eState = eConcStateSave +#else +# define SAVE_BTCONC +# define RESTORE_BTCONC +#endif /* ** Insert a new record into the BTree. The content of the new record @@ -85424,6 +87358,11 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND|BTREE_PREFORMAT))==flags ); assert( (flags & BTREE_PREFORMAT)==0 || seekResult || pCur->pKeyInfo==0 ); +#ifndef SQLITE_OMIT_CONCURRENT + rc = btreeBcInsert(pCur, pX); + if( rc!=SQLITE_OK ) return rc; +#endif + /* Save the positions of any other cursors open on this table. ** ** In some cases, the call to btreeMoveto() below is a no-op. For @@ -85472,6 +87411,8 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( assert( (flags & BTREE_PREFORMAT) || (pX->pKey==0)==(pCur->pKeyInfo==0) ); if( pCur->pKeyInfo==0 ){ + SAVE_BTCONC; + assert( pX->pKey==0 ); /* If this is an insert into a table b-tree, invalidate any incrblob ** cursors open on the row being replaced */ @@ -85502,6 +87443,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( && pCur->info.nPayload==(u32)pX->nData+pX->nZero ){ /* New entry is the same size as the old. Do an overwrite */ + RESTORE_BTCONC; return btreeOverwriteCell(pCur, pX); } assert( loc==0 ); @@ -85512,9 +87454,12 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( */ rc = sqlite3BtreeTableMoveto(pCur, pX->nKey, (flags & BTREE_APPEND)!=0, &loc); + RESTORE_BTCONC; if( rc ) return rc; } + RESTORE_BTCONC; }else{ + SAVE_BTCONC; /* This is an index or a WITHOUT ROWID table */ /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing @@ -85540,8 +87485,10 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( rc = btreeMoveto(pCur, pX->pKey, pX->nKey, (flags & BTREE_APPEND)!=0, &loc); } + RESTORE_BTCONC; if( rc ) return rc; } + RESTORE_BTCONC; /* If the cursor is currently pointing to an entry to be overwritten ** and the new content is the same as as the old, then use the @@ -85871,6 +87818,11 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ } assert( pCur->eState==CURSOR_VALID ); +#ifndef SQLITE_OMIT_CONCURRENT + rc = btreeBcDelete(pCur); + if( rc!=SQLITE_OK ) return rc; +#endif + iCellDepth = pCur->iPage; iCellIdx = pCur->ix; pPage = pCur->pPage; @@ -86513,6 +88465,9 @@ SQLITE_PRIVATE int sqlite3BtreeCount(sqlite3 *db, BtCursor *pCur, i64 *pnEntry){ i64 nEntry = 0; /* Value to return in *pnEntry */ int rc; /* Return code */ + rc = btreeBcScanStart(pCur, BTCONC_READ_COUNT, 0, 0); + if( rc!=SQLITE_OK ) return rc; + rc = moveToRoot(pCur); if( rc==SQLITE_EMPTY ){ *pnEntry = 0; @@ -87501,6 +89456,11 @@ SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void ** Mark this cursor as an incremental blob cursor. */ SQLITE_PRIVATE void sqlite3BtreeIncrblobCursor(BtCursor *pCur){ +#ifndef SQLITE_OMIT_CONCURRENT + if( pCur->curFlags & BTCF_WriteFlag ){ + pCur->pBt->conc.eState = BTCONC_STATE_RETIRED; + } +#endif pCur->curFlags |= BTCF_Incrblob; pCur->pBtree->hasIncrblobCur = 1; } @@ -87582,19 +89542,34 @@ SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); SQLITE_PRIVATE int sqlite3BtreeExclusiveLock(Btree *p){ sqlite3 *db = p->db; int rc; + int bLog = 0; Pgno pgno = 0; BtShared *pBt = p->pBt; assert( p->inTrans==TRANS_WRITE && pBt->pPage1 ); - memset(db->aCommit, 0, sizeof(db->aCommit)); sqlite3BtreeEnter(p); + +#ifdef SQLITE_OMIT_CONCURRENT + rc = sqlite3PagerExclusiveLock(pBt->pPager, 0, 0); +#else + memset(db->aCommit, 0, sizeof(db->aCommit)); rc = sqlite3PagerExclusiveLock(pBt->pPager, (db->eConcurrent==CONCURRENT_SCHEMA) ? 0 : pBt->pPage1->pDbPage, db->aCommit ); -#ifdef SQLITE_OMIT_CONCURRENT - assert( db->aCommit[SQLITE_COMMIT_CONFLICT_PGNO]==0 ); -#else - if( (rc==SQLITE_BUSY_SNAPSHOT) + + if( rc==SQLITE_BUSY_SNAPSHOT + && db->aCommit[SQLITE_COMMIT_CONFLICT_PGNO]>1 + && pBt->conc.eState==BTCONC_STATE_INUSE + && pBt->pCursor==0 + ){ + /* Page-level locking has detected a conflict. But it is not a + ** schema conflict (SQLITE_COMMIT_CONFLICT_PGNO>1) and the BtConcurrent + ** object is populated. So attempt a logical commit. */ + rc = btreeBcTryLogicalCommit(p, &bLog); + } + + if( bLog==0 + && (rc==SQLITE_BUSY_SNAPSHOT) && (pgno = db->aCommit[SQLITE_COMMIT_CONFLICT_PGNO]) ){ int iDb; @@ -87604,7 +89579,7 @@ SQLITE_PRIVATE int sqlite3BtreeExclusiveLock(Btree *p){ (void)sqlite3PagerGet(pBt->pPager, pgno, &pPg, 0); if( pPg ){ int bWrite = -1; - const char *zObj = 0; + const char *zIdx = 0; const char *zTab = 0; char zContent[17]; @@ -87628,24 +89603,7 @@ SQLITE_PRIVATE int sqlite3BtreeExclusiveLock(Btree *p){ bWrite = sqlite3PagerIswriteable(pPg); sqlite3PagerUnref(pPg); - pSchema = sqlite3SchemaGet(p->db, p); - if( pSchema ){ - for(pE=sqliteHashFirst(&pSchema->tblHash); pE; pE=sqliteHashNext(pE)){ - Table *pTab = (Table *)sqliteHashData(pE); - if( pTab->tnum==pgnoRoot ){ - zObj = pTab->zName; - zTab = 0; - }else{ - Index *pIdx; - for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->tnum==pgnoRoot ){ - zObj = pIdx->zName; - zTab = pTab->zName; - } - } - } - } - } + zTab = btreeBcRootToObject(pBt, pgnoRoot, &zIdx); } sqlite3_log(SQLITE_OK, @@ -87654,8 +89612,8 @@ SQLITE_PRIVATE int sqlite3BtreeExclusiveLock(Btree *p){ "(%s page; part of db %s %s%s%s; content=%s...)", (int)pgno, (bWrite==0?"read-only":(bWrite>0?"read/write":"unknown")), - (zTab ? "index" : "table"), - (zTab ? zTab : ""), (zTab ? "." : ""), (zObj ? zObj : "UNKNOWN"), + (zIdx ? "index" : "table"), + (zTab ? zTab : "UNKNOWN"), (zIdx ? "." : ""), (zIdx ? zIdx : ""), zContent ); } @@ -87766,6 +89724,7 @@ SQLITE_API int sqlite3_commit_status( #undef sqlite3BtreeCount #undef sqlite3BtreeOffset #undef sqlite3BtreeIsEmpty +#undef sqlite3BtreeCursorNoScan #undef sqlite3BtreeCursor #ifdef SQLITE_DEBUG #undef sqlite3BtreeSeekCount @@ -88711,6 +90670,7 @@ struct BtCursorMethods { int(*xBtreeCount)(sqlite3*, BtCursor*, i64*); i64(*xBtreeOffset)(BtCursor*); int(*xBtreeIsEmpty)(BtCursor*, int*); + void(*xBtreeCursorNoScan)(BtCursor*); }; struct BtreeMethods { BtCursorMethods const *pCsrMethods; @@ -88858,6 +90818,9 @@ SQLITE_PRIVATE i64 sqlite3BtreeOffset(BtCursor *p){ SQLITE_PRIVATE int sqlite3BtreeIsEmpty(BtCursor *p, int *a){ return p->pMethods->xBtreeIsEmpty(p, a); } +SQLITE_PRIVATE void sqlite3BtreeCursorNoScan(BtCursor *p){ + p->pMethods->xBtreeCursorNoScan(p); +} SQLITE_PRIVATE Pgno sqlite3BtreeLastPage(Btree *p){ return p->pMethods->xBtreeLastPage(p); } @@ -89026,6 +90989,7 @@ static const BtCursorMethods hct_btcursor_methods = { .xBtreeCount = sqlite3HctBtreeCount, .xBtreeOffset = sqlite3HctBtreeOffset, .xBtreeIsEmpty = sqlite3HctBtreeIsEmpty, + .xBtreeCursorNoScan = sqlite3HctBtreeCursorNoScan, }; static const BtreeMethods hct_btree_methods = { .pCsrMethods = &hct_btcursor_methods, @@ -89111,6 +91075,7 @@ static const BtCursorMethods stock_btcursor_methods = { .xBtreeCount = sqlite3StockBtreeCount, .xBtreeOffset = sqlite3StockBtreeOffset, .xBtreeIsEmpty = sqlite3StockBtreeIsEmpty, + .xBtreeCursorNoScan = sqlite3StockBtreeCursorNoScan, }; static const BtreeMethods stock_btree_methods = { .pCsrMethods = &stock_btcursor_methods, @@ -94392,6 +96357,12 @@ SQLITE_PRIVATE int sqlite3HctBtreeIsReadonly(Btree *p){ return 0; } +/* +** A no-scan cursor. +*/ +SQLITE_PRIVATE void sqlite3HctBtreeCursorNoScan(BtCursor *pCsr){ +} + #if !defined(SQLITE_OMIT_SHARED_CACHE) /* ** Return true if the Btree passed as the only argument is sharable. @@ -110869,9 +112840,14 @@ case OP_Found: { /* jump, in3, ncycle */ rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, pIdxKey, &pC->seekResult); sqlite3DbFreeNN(db, pIdxKey); } +#ifndef SQLITE_OMIT_CONCURRENT if( rc!=SQLITE_OK ){ goto abort_due_to_error; } + if( pOp->opcode==OP_NoConflict ){ + sqlite3BtreeCursorNoScan(pC->uc.pCursor); + } +#endif alreadyExists = (pC->seekResult==0); pC->nullRow = 1-alreadyExists; pC->deferredMoveto = 0; @@ -111001,6 +112977,9 @@ case OP_NotExists: /* jump, in3, ncycle */ VdbeBranchTaken(res!=0,2); pC->seekResult = res; if( res!=0 ){ +#ifndef SQLITE_OMIT_CONCURRENT + sqlite3BtreeCursorNoScan(pCrsr); +#endif assert( rc==SQLITE_OK ); if( pOp->p2==0 ){ rc = SQLITE_CORRUPT_BKPT; @@ -196598,6 +198577,9 @@ static int (*const sqlite3BuiltinExtensions[])(sqlite3*) = { #ifdef SQLITE_ENABLE_HCT sqlite3HctVtabInit, #endif +#ifndef SQLITE_OMIT_CONCURRENT + sqlite3ConcurrentRegister, +#endif }; #ifndef SQLITE_AMALGAMATION @@ -197264,6 +199246,18 @@ SQLITE_API int sqlite3_config(int op, ...){ break; } + case SQLITE_CONFIG_SHAREDLOG_MAXSIZE: { +#ifndef SQLITE_OMIT_CONCURRENT + i64 iVal = va_arg(ap, sqlite3_int64); + if( iVal<0 ){ + sqlite3GlobalConfig.nSharedLogMaxSize =SQLITE_DEFAULT_SHAREDLOG_MAXSIZE; + }else{ + sqlite3GlobalConfig.nSharedLogMaxSize = iVal; + } +#endif + break; + } + default: { rc = SQLITE_ERROR; break; @@ -272750,7 +274744,7 @@ static void fts5SourceIdFunc( ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); - sqlite3_result_text(pCtx, "fts5: 2026-05-12 17:42:11 012a04edd0ab7001f2635eeb766089201cbe372d54e5008e7138a5dbe522bf06", -1, SQLITE_TRANSIENT); + sqlite3_result_text(pCtx, "fts5: 2026-05-27 18:37:40 4b66dc55fe9a13d543e85ac7a592165bbb2a15eed717a4604ae6a09c0bac5840", -1, SQLITE_TRANSIENT); } /* @@ -296692,6 +298686,750 @@ SQLITE_PRIVATE void sqlite3HctLogFree(void *pLogList, int bUnlink){ /************** End of hct_log.c *********************************************/ +/************** Begin file btrecord.c ****************************************/ +/* +** 2026 February 13 +** +** 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 utility routines used by btree.c when compiled to +** support BEGIN CONCURRENT. +*/ + +/* #include "sqliteInt.h" */ +/* #include "vdbeInt.h" */ +/* #include "btreeInt.h" */ + +/* #include */ +/* #include */ + +#ifndef SQLITE_OMIT_CONCURRENT + +/* !SQLITE_OMIT_CONCURRENT +** +** Write the serialized data blob for the value stored in pMem into +** buf. It is assumed that the caller has allocated sufficient space. +** Return the number of bytes written. +*/ +static u32 bcRecordSerialPut(u8 *buf, Mem *pMem, u32 serial_type){ + u32 len; + + /* Integer and Real */ + if( serial_type<=7 && serial_type>0 ){ + u64 v; + u32 i; + if( serial_type==7 ){ + assert( sizeof(v)==sizeof(pMem->u.r) ); + memcpy(&v, &pMem->u.r, sizeof(v)); + swapMixedEndianFloat(v); + }else{ + v = pMem->u.i; + } + len = i = sqlite3SmallTypeSizes[serial_type]; + assert( i>0 ); + do{ + buf[--i] = (u8)(v&0xFF); + v >>= 8; + }while( i ); + return len; + } + + /* String or blob */ + if( serial_type>=12 ){ + assert( pMem->n + ((pMem->flags & MEM_Zero)?pMem->u.nZero:0) + == (int)sqlite3VdbeSerialTypeLen(serial_type) ); + len = pMem->n; + if( len>0 ) memcpy(buf, pMem->z, len); + if( (pMem->flags & MEM_Zero) && pMem->u.nZero>0 ){ + memset(&buf[len], 0, pMem->u.nZero); + len += pMem->u.nZero; + } + return len; + } + + /* NULL or constants 0 or 1 */ + return 0; +} + +/* !SQLITE_OMIT_CONCURRENT +** +** Return the serial-type for the value stored in pMem. Before returning, +** set (*pLen) to the size in bytes of the serialized form of the value. +** +** This routine might convert a large MEM_IntReal value into MEM_Real. +*/ +static u32 bcRecordSerialType(Mem *pMem, u32 *pLen){ + int flags = pMem->flags; + u32 n; + + assert( pLen!=0 ); + if( flags&MEM_Null ){ + *pLen = 0; + return 0; + } + if( flags&(MEM_Int|MEM_IntReal) ){ + /* Figure out whether to use 1, 2, 4, 6 or 8 bytes. */ +# define MAX_6BYTE ((((i64)0x00008000)<<32)-1) + i64 i = pMem->u.i; + u64 u; + if( i<0 ){ + u = ~i; + }else{ + u = i; + } + if( u<=127 ){ + if( (i&1)==i ){ + *pLen = 0; + return 8+(u32)u; + }else{ + *pLen = 1; + return 1; + } + } + if( u<=32767 ){ *pLen = 2; return 2; } + if( u<=8388607 ){ *pLen = 3; return 3; } + if( u<=2147483647 ){ *pLen = 4; return 4; } + if( u<=MAX_6BYTE ){ *pLen = 6; return 5; } + *pLen = 8; + return 6; + } + if( flags&MEM_Real ){ + *pLen = 8; + return 7; + } + assert( pMem->db->mallocFailed || flags&(MEM_Str|MEM_Blob) ); + assert( pMem->n>=0 ); + n = (u32)pMem->n; + if( flags & MEM_Zero ){ + n += pMem->u.nZero; + } + *pLen = n; + return ((n*2) + 12 + ((flags&MEM_Str)!=0)); +} + + +/* !SQLITE_OMIT_CONCURRENT +** +** Serialize the unpacked record in pRec into a buffer obtained from +** sqlite3_malloc(). If successful, (*ppRec) is set to point to the +** buffer and (*pnRec) to its size in bytes before returning SQLITE_OK. +** Or, if an OOM error occurs, return SQLITE_NOMEM. The final values +** of (*ppRec) and (*pnRec) are undefined in this case. +*/ +SQLITE_PRIVATE int sqlite3BcSerializeRecord( + UnpackedRecord *pRec, /* Record to serialize */ + u8 **ppRec, /* OUT: buffer containing serialization */ + int *pnRec /* OUT: size of (*ppRec) in bytes */ +){ + int ii; + int nData = 0; + int nHdr = 0; + u8 *pOut = 0; + int iOffHdr = 0; + int iOffData = 0; + + for(ii=0; iinField; ii++){ + u32 n; + u32 stype = bcRecordSerialType(&pRec->aMem[ii], &n); + assert( sqlite3VdbeSerialTypeLen(stype)==n ); + nData += n; + nHdr += sqlite3VarintLen(stype); + pRec->aMem[ii].uTemp = stype; + } + + if( nHdr<=126 ){ + /* The common case */ + nHdr += 1; + }else{ + /* Rare case of a really large header */ + int nVarint = sqlite3VarintLen(nHdr); + nHdr += nVarint; + if( nVarintnField; ii++){ + u32 stype = pRec->aMem[ii].uTemp; + iOffHdr += putVarint32(&pOut[iOffHdr], stype); + iOffData += bcRecordSerialPut(&pOut[iOffData], &pRec->aMem[ii], stype); + } + assert( iOffData==(nHdr+nData) ); + + *ppRec = pOut; + *pnRec = iOffData; + + return SQLITE_OK; +} + +/* !SQLITE_OMIT_CONCURRENT +** +** Helper function for bcRecordToText(). Return a buffer obtained from +** sqlite3_malloc() containing a nul-terminated string containing the hex +** form of blob aIn[], size nIn bytes. It is the responsibility of the +** caller to eventually free the buffer using sqlite3_free(). If an OOM +** occurs, NULL may be returned. +*/ +static char *bcHexEncode(const u8 *aIn, int nIn){ + char *zRet = sqlite3_malloc(nIn*2+1); + if( zRet ){ + static const char aDigit[] = "0123456789ABCDEF"; + int i; + for(i=0; i> 4) ]; + zRet[i*2+1] = aDigit[ (aIn[i] & 0xF) ]; + } + zRet[i*2] = '\0'; + } + return zRet; +} + + +/* !SQLITE_OMIT_CONCURRENT +** +** Buffer aRec[], which is nRec bytes in size, contains a serialized SQLite +** record. This function decodes the record and returns a nul-terminated +** string containing a human-readable version of the record. +** +** The value returned points to a buffer obtained from sqlite3_malloc(). It +** is the responsibility of the caller to eventually free this buffer using +** sqlite3_free(). If an OOM error occurs, NULL may be returned. +** +** If parameter delta is -ve, a "+" is appended to the text record. If it +** is +ve, a "-" is appended. +*/ +static char *bcRecordToText(const u8 *aRec, int nRec, int delta){ + char *zRet = 0; + const char *zSep = ""; + const u8 *pEndHdr; /* Points to one byte past record header */ + const u8 *pHdr; /* Current point in record header */ + const u8 *pBody; /* Current point in record data */ + u64 nHdr; /* Bytes in record header */ + const char *zDelta = 0; + + if( nRec>0 ){ + + pHdr = aRec + sqlite3GetVarint(aRec, &nHdr); + pBody = pEndHdr = &aRec[nHdr]; + while( pHdr0 ) zDelta = "-"; + return sqlite3_mprintf("(%z)%s", zRet, zDelta); +} + +/* !SQLITE_OMIT_CONCURRENT +** +** There has just been a conflict between pWrite and pRead on index zIdx, which +** is attached to table zTab. Issue a log message. +*/ +SQLITE_PRIVATE void sqlite3BcLogIndexConflict( + const char *zTab, + const char *zIdx, + BtWriteIndex *pWrite, + BtReadIndex *pRead +){ + sqlite3BeginBenignMalloc(); + { + char *zMin = bcRecordToText(pRead->aRecMin, pRead->nRecMin, pRead->drc_min); + char *zMax = bcRecordToText(pRead->aRecMax, pRead->nRecMax, pRead->drc_max); + char *zKey = bcRecordToText(pWrite->aRec, pWrite->nRec, 0); + sqlite3_log(SQLITE_OK, + "cannot commit CONCURRENT transaction - conflict in index %s.%s - " + "range (%s,%s) conflicts with write to key %s", + zTab, (zIdx ? zIdx : "pk"), + zMin, zMax, zKey + ); + sqlite3_free(zMin); + sqlite3_free(zMax); + sqlite3_free(zKey); + } + sqlite3EndBenignMalloc(); +} + +/* !SQLITE_OMIT_CONCURRENT +** +** If possible, return a pointer to a buffer containing the SQL statement +** currently being executed by handle db. +*/ +SQLITE_PRIVATE const char *sqlite3BcCurrentSql(sqlite3 *db){ + const char *zRet = "?"; + if( db->nVdbeRead==1 ){ + Vdbe *pIter; + for(pIter=db->pVdbe; pIter; pIter=pIter->pVNext){ + if( pIter->eVdbeState==VDBE_RUN_STATE && pIter->bIsReader ){ + zRet = pIter->zSql; + break; + } + } + } + return zRet; +} + +/************************************************************************* +** Start of virtual table "sqlite_concurrent" implementation. +*/ +#define CONC_SCHEMA "CREATE TABLE x(root, op, k1, k2, sortem HIDDEN)" +#define CONCURRENT_SORTEM 4 + +typedef struct ConcTable ConcTable; +typedef struct ConcCursor ConcCursor; +typedef struct ConcRow ConcRow; + +/* +** Each row returned from the sqlite_concurrent table is represented by +** an instance of this structure. Each call to the xFilter() method +** constructs a linked-list of these objects representing all the rows +** that will be returned by the virtual table scan. Then the xNext() +** method walks through the list. +*/ +struct ConcRow { + Pgno root; + const char *zOp; + char *zK1; + char *zK2; + ConcRow *pRowNext; +}; + +/* +** Cursor type for the sqlite_concurrent virtual table. +*/ +struct ConcCursor { + sqlite3_vtab_cursor base; /* Base class. Must be first */ + ConcRow *pRow; +}; + +/* +** Table type for the sqlite_concurrent virtual table. +*/ +struct ConcTable { + sqlite3_vtab base; /* Base class. Must be first */ + sqlite3 *db; /* The database */ +}; + +/* !SQLITE_OMIT_CONCURRENT +** +** Connect to the sqlite_concurrent eponymous table. +*/ +static int concConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + ConcTable *pTab = 0; + int rc = SQLITE_OK; + (void)pAux; + (void)argc; + (void)argv; + (void)pzErr; + + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + sqlite3_vtab_config(db, SQLITE_VTAB_USES_ALL_SCHEMAS); + rc = sqlite3_declare_vtab(db, CONC_SCHEMA); + if( rc==SQLITE_OK ){ + pTab = (ConcTable *)sqlite3_malloc64(sizeof(ConcTable)); + if( pTab==0 ) rc = SQLITE_NOMEM_BKPT; + } + + assert( rc==SQLITE_OK || pTab==0 ); + if( rc==SQLITE_OK ){ + memset(pTab, 0, sizeof(ConcTable)); + pTab->db = db; + } + + *ppVtab = (sqlite3_vtab*)pTab; + return rc; +} + +/* !SQLITE_OMIT_CONCURRENT +** +** Disconnect from sqlite_concurrent. +*/ +static int concDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* !SQLITE_OMIT_CONCURRENT +** +** xBestIndex method for sqlite_concurrent. There are two possible plans: +** +** idxNum==0 - full table scan. +** idxNum==1 - full table scan after possibly sorting and merging +** read ranges. +** +** idxNum==1 is used if there is an equality constraint on hidden column +** "sortem". In this case the RHS of the = is passed to xFilter(). xFilter() +** will sort and merge the read ranges if this parameter is non-zero. The +** SQL syntax is usually: +** +** SELECT * FROM sqlite_concurrent; -- full table scan +** SELECT * FROM sqlite_concurrent(1); -- sort + merge reads first +*/ +static int concBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + int ii; + for(ii=0; iinConstraint; ii++){ + struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii]; + if( p->iColumn!=CONCURRENT_SORTEM ) continue; + if( p->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + if( !p->usable ) return SQLITE_CONSTRAINT; + pIdxInfo->idxNum = 1; + pIdxInfo->aConstraintUsage[ii].argvIndex = 1; + pIdxInfo->aConstraintUsage[ii].omit = 1; + break; + } + return SQLITE_OK; +} + + +/* !SQLITE_OMIT_CONCURRENT +** +** Open a new cursor for sqlite_concurrent. +*/ +static int concOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ + ConcCursor *pCsr; + + pCsr = (ConcCursor *)sqlite3_malloc64(sizeof(ConcCursor)); + if( pCsr==0 ){ + return SQLITE_NOMEM_BKPT; + }else{ + memset(pCsr, 0, sizeof(ConcCursor)); + } + + *ppCursor = (sqlite3_vtab_cursor *)pCsr; + return SQLITE_OK; +} + +/* !SQLITE_OMIT_CONCURRENT +** +** Close an sqlite_concurrent cursor. +*/ +static int concClose(sqlite3_vtab_cursor *pCursor){ + ConcCursor *pCsr = (ConcCursor *)pCursor; + ConcRow *pRow = 0; + ConcRow *pNext = 0; + for(pRow=pCsr->pRow; pRow; pRow=pNext){ + pNext = pRow->pRowNext; + sqlite3_free(pRow->zK1); + sqlite3_free(pRow->zK2); + sqlite3_free(pRow); + } + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* !SQLITE_OMIT_CONCURRENT +** +** Advance the sqlite_concurrent cursor to the next row. +*/ +static int concNext(sqlite3_vtab_cursor *pCursor){ + ConcCursor *pCsr = (ConcCursor *)pCursor; + ConcRow *pFree = pCsr->pRow; + assert( pFree ); + pCsr->pRow = pFree->pRowNext; + sqlite3_free(pFree->zK1); + sqlite3_free(pFree->zK2); + sqlite3_free(pFree); + return SQLITE_OK; +} + +/* !SQLITE_OMIT_CONCURRENT +** +** Return true if the sqlite_concurrent cursor is at EOF. +*/ +static int concEof(sqlite3_vtab_cursor *pCursor){ + ConcCursor *pCsr = (ConcCursor *)pCursor; + return pCsr->pRow==0; +} + +/* !SQLITE_OMIT_CONCURRENT +** +** xFilter method for sqlite_concurrent. +** +** In all cases this function populates the cursor with rows for each +** read and write currently accumulated by the datbase connection. +** +** idxNum may be either 0 or 1. If it is 1, then there is a single +** argument passed. If this is a non-zero integer, the reads are +** sorted before any rows are returned. +*/ +static int concFilter( + sqlite3_vtab_cursor *pCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + ConcCursor *pCsr = (ConcCursor *)pCursor; + ConcTable *pTab = (ConcTable *)pCursor->pVtab; + sqlite3 *db = pTab->db; + BtConcurrent *pConc = &db->aDb[0].pBt->pBt->conc; + int rc = SQLITE_OK; + + assert( idxNum==0 || idxNum==1 ); + assert( idxNum==argc ); + + if( pConc->eState==BTCONC_STATE_INUSE ){ + if( idxNum==1 ){ + int bSort = sqlite3_value_int(argv[0]); + if( bSort ){ + rc = sqlite3BtreeSortReadArrays(pConc); + } + } + + if( rc==SQLITE_OK ){ + int ii; + + for(ii=0; rc==SQLITE_OK && iinWrite; ii++){ + BtWrite *pWrite = &pConc->aWrite[ii]; + ConcRow *pRow = (ConcRow*)sqlite3MallocZero(sizeof(ConcRow)); + if( pRow==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + pRow->root = pWrite->iRoot; + pRow->zOp = pWrite->bDel ? "delete" : "insert"; + if( pWrite->pKeyInfo ){ + pRow->zK1 = bcRecordToText(pWrite->aRec, pWrite->nRec, 0); + if( pRow->zK1==0 ) rc = SQLITE_NOMEM_BKPT; + }else{ + pRow->zK1 = sqlite3_mprintf("%lld", pWrite->iKey); + if( pRow->zK1==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else if( pWrite->bDel==0 ){ + pRow->zK2 = bcRecordToText(pWrite->aRec, pWrite->nRec, 0); + if( pRow->zK2==0 ) rc = SQLITE_NOMEM_BKPT; + } + } + pRow->pRowNext = pCsr->pRow; + pCsr->pRow = pRow; + } + } + + for(ii=pConc->nReadIndex-1; rc==SQLITE_OK && ii>=0; ii--){ + BtReadIndex *p = &pConc->aReadIndex[ii]; + ConcRow *pRow = (ConcRow*)sqlite3MallocZero(sizeof(ConcRow)); + if( pRow==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + pRow->root = p->iRoot; + pRow->zOp = "read"; + pRow->zK1 = bcRecordToText(p->aRecMin, p->nRecMin, p->drc_min); + pRow->zK2 = bcRecordToText(p->aRecMax, p->nRecMax, p->drc_max); + pRow->pRowNext = pCsr->pRow; + pCsr->pRow = pRow; + if( pRow->zK1==0 || pRow->zK2==0 ){ + rc = SQLITE_NOMEM_BKPT; + } + } + } + + for(ii=pConc->nReadIntkey-1; rc==SQLITE_OK && ii>=0; ii--){ + BtReadIntkey *p = &pConc->aReadIntkey[ii]; + ConcRow *pRow = (ConcRow*)sqlite3MallocZero(sizeof(ConcRow)); + if( pRow==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + pRow->root = p->iRoot; + pRow->zOp = "read"; + pRow->zK1 = sqlite3_mprintf("%lld", p->iMin); + pRow->zK2 = sqlite3_mprintf("%lld", p->iMax); + pRow->pRowNext = pCsr->pRow; + pCsr->pRow = pRow; + if( pRow->zK1==0 || pRow->zK2==0 ){ + rc = SQLITE_NOMEM_BKPT; + } + } + } + } + } + + return rc; +} + +/* !SQLITE_OMIT_CONCURRENT +** +** xColumn method for sqlite_concurrent. +*/ +static int concColumn( + sqlite3_vtab_cursor *pCursor, + sqlite3_context *ctx, + int i +){ + ConcCursor *pCsr = (ConcCursor *)pCursor; + int rc = SQLITE_OK; + ConcRow *pRow = pCsr->pRow; + assert( pRow ); + switch( i ){ + case 0: { /* root */ + sqlite3_result_int64(ctx, (sqlite3_int64)pRow->root); + break; + } + case 1: { /* op */ + sqlite3_result_text(ctx, pRow->zOp, -1, SQLITE_TRANSIENT); + break; + } + case 2: { /* k1 */ + sqlite3_result_text(ctx, pRow->zK1, -1, SQLITE_TRANSIENT); + break; + } + case 3: { /* k2 */ + sqlite3_result_text(ctx, pRow->zK2, -1, SQLITE_TRANSIENT); + break; + } + } + return rc; +} + +/* !SQLITE_OMIT_CONCURRENT +** +** xRowid method for sqlite_concurrent. +*/ +static int concRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ + *pRowid = 0; + return SQLITE_OK; +} + +/* !SQLITE_OMIT_CONCURRENT +** +** Register the sqlite_concurrent eponymous virtual table with database +** connection db. Return SQLITE_OK if successful, or an SQLite error code +** if an error occurs. +*/ +SQLITE_PRIVATE int sqlite3ConcurrentRegister(sqlite3 *db){ + static sqlite3_module conc_module = { + 2, /* iVersion */ + concConnect, /* xCreate */ + concConnect, /* xConnect */ + concBestIndex, /* xBestIndex */ + concDisconnect, /* xDisconnect */ + concDisconnect, /* xDestroy */ + concOpen, /* xOpen - open a cursor */ + concClose, /* xClose - close a cursor */ + concFilter, /* xFilter - configure scan constraints */ + concNext, /* xNext - advance a cursor */ + concEof, /* xEof - check for end of scan */ + concColumn, /* xColumn - read data */ + concRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ + }; + return sqlite3_create_module(db, "sqlite_concurrent", &conc_module, 0); +} + +#endif /* !SQLITE_OMIT_CONCURRENT */ + +/************** End of btrecord.c ********************************************/ /* Return the source-id for this library */ SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; } #endif /* SQLITE_AMALGAMATION */ diff --git a/libstuff/sqlite3.h b/libstuff/sqlite3.h index 56f555862..baa3dc350 100644 --- a/libstuff/sqlite3.h +++ b/libstuff/sqlite3.h @@ -148,10 +148,10 @@ extern "C" { */ #define SQLITE_VERSION "3.54.0" #define SQLITE_VERSION_NUMBER 3054000 -#define SQLITE_SOURCE_ID "2026-05-12 17:42:11 012a04edd0ab7001f2635eeb766089201cbe372d54e5008e7138a5dbe522bf06" -#define SQLITE_SCM_BRANCH "hctree-bedrock" +#define SQLITE_SOURCE_ID "2026-05-27 18:37:40 4b66dc55fe9a13d543e85ac7a592165bbb2a15eed717a4604ae6a09c0bac5840" +#define SQLITE_SCM_BRANCH "hctree-bedrock-lcd-ex" #define SQLITE_SCM_TAGS "" -#define SQLITE_SCM_DATETIME "2026-05-12T17:42:11.503Z" +#define SQLITE_SCM_DATETIME "2026-05-27T18:37:40.605Z" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -2210,6 +2210,19 @@ struct sqlite3_mem_methods { ** recommended case) then the integer is always filled with zero, regardless ** if its initial value. ** +** +** [[SQLITE_CONFIG_SHAREDLOG_MAXSIZE]] +**
SQLITE_CONFIG_SHAREDLOG_MAXSIZE +**
This option is used to set the maximum size of a BEGIN CONCURRENT +** shared-log in bytes. The default value is 1GiB (1*1024*1024*1024). The +** argument to this configuration option must be a 64-bit signed integer +** (type sqlite3_int64) to use as the new limit. A negative parameter +** restores the default value, a value of zero disabled shared-logs +** altogether. There is no way to configure unlimited memory usage, but +** applications may instead configure a very large value (e.g. 1TiB). +** Shared-log entries are automatically discarded when their associated +** transactions are checkpointed, which prevents the shared-log from growing +** indefinitely in this case. */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ #define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ @@ -2241,6 +2254,7 @@ struct sqlite3_mem_methods { #define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */ #define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */ #define SQLITE_CONFIG_ROWID_IN_VIEW 30 /* int* */ +#define SQLITE_CONFIG_SHAREDLOG_MAXSIZE 31 /* sqlite3_int64 */ /* ** CAPI3REF: Database Connection Configuration Options diff --git a/libstuff/sqlite3ext.h b/libstuff/sqlite3ext.h index 36c35b7b1..6c12ec88b 100644 --- a/libstuff/sqlite3ext.h +++ b/libstuff/sqlite3ext.h @@ -375,6 +375,9 @@ struct sqlite3_api_routines { void (*str_truncate)(sqlite3_str*,int); void (*str_free)(sqlite3_str*); int (*carray_bind)(sqlite3_stmt*,int,void*,int,int,void(*)(void*)); + int (*carray_bind_v2)(sqlite3_stmt*,int,void*,int,int,void(*)(void*),void*); + /* Version 3.54.0 and later */ + sqlite3_int64 (*incomplete)(const char*); }; /* @@ -717,6 +720,9 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_str_truncate sqlite3_api->str_truncate #define sqlite3_str_free sqlite3_api->str_free #define sqlite3_carray_bind sqlite3_api->carray_bind +#define sqlite3_carray_bind_v2 sqlite3_api->carray_bind_v2 +/* Version 3.54.0 and later */ +#define sqlite3_incomplete sqlite3_api->incomplete #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)