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)