1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-08-08 14:02:16 +03:00

Add the sqlite3_commit_status() API. For querying a connection for (a) the ranges of wal/wal2 frames written after a successful commit, and (b) the conflicting frame following a failed commit of a BEGIN CONCURRENT transaction.

FossilOrigin-Name: 4b08d4dad6b254a342353e3f765066c85cbc5450fe13501665c648627cca21cd
This commit is contained in:
dan
2023-06-19 18:16:19 +00:00
parent e36b69a347
commit d095b2c3d5
12 changed files with 581 additions and 33 deletions

View File

@@ -1,5 +1,5 @@
C Merge\sall\s3.42.0\srelease\schanges\sinto\sthe\sbedrock\sbranch.
D 2023-05-16T13:04:38.652
C Add\sthe\ssqlite3_commit_status()\sAPI.\sFor\squerying\sa\sconnection\sfor\s(a)\sthe\sranges\sof\swal/wal2\sframes\swritten\safter\sa\ssuccessful\scommit,\sand\s(b)\sthe\sconflicting\sframe\sfollowing\sa\sfailed\scommit\sof\sa\sBEGIN\sCONCURRENT\stransaction.
D 2023-06-19T18:16:19.942
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -583,9 +583,9 @@ F src/auth.c f4fa91b6a90bbc8e0d0f738aa284551739c9543a367071f55574681e0f24f8cf
F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523
F src/bitvec.c 3907fcbe8a0c8c2db58d97087d15cdabbf2842adb9125df9ab9ff87d3db16775
F src/btmutex.c 6ffb0a22c19e2f9110be0964d0731d2ef1c67b5f7fabfbaeb7b9dabc4b7740ca
F src/btree.c b665847a53bc556d663a70231fdaa600f5eb4d7d29684690fd4f63b1262621b3
F src/btree.c a1e13541545e5dd916daadb1a7730cd415ca3c8e25e2bdee73b4ee149000f77c
F src/btree.h 77a092acf63526827e74e88d0480123212d079593a841ff1fe85507adf256ef6
F src/btreeInt.h 757425aeff908b819f2f086eadcc44ca847a672617ced5161c56c60c6b39c226
F src/btreeInt.h a2c8d4894939eb7f31be5813109304e47147b5fedc3ed9e870a34ab534631ea3
F src/build.c 52784bddd510438361a3ab1141db6aaf0aad76096e2e06208e3c23d21b279ba2
F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
@@ -631,8 +631,8 @@ F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d87210
F src/os_unix.c a585801080e5d36365a409221813534216f503b58f1f7a4398f225c4ae0bc424
F src/os_win.c 2b2411279f7b24f927591561303fc5871845732df42641cbf695c23640b16975
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
F src/pager.c 9dd0da3b43166ceb465fce53a0826b0f6c3ea6ea9e294f7ce22a97d2f6d1f8e8
F src/pager.h 3ddab454e313da7c93f92fea35c842ad17ae9f4e96254871ddb0171b2bfb859a
F src/pager.c d41c9e4c5804de4acf00036bb26cd41a798341a051801b08b98dee73fb4999ec
F src/pager.h e055e649d93f1e121ce50b30a3d01a5225e6d2c45d712c676c8477dec19beeb8
F src/parse.y 03d4d7a079481e6fab9f9256971fa87c716af20d46fffba2ecea21583e6f05db
F src/pcache.c 8ee13acccfd9accbf0af94910b7323dd7f7d55300d92ddafcf40e34fcc8e21be
F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5
@@ -646,15 +646,15 @@ F src/resolve.c 3e53e02ce87c9582bd7e7d22f13f4094a271678d9dc72820fa257a2abb5e4032
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
F src/select.c ead092c26ba1c6d525fc6319923581a34a0b6ae28d05bb3196ed0106b69c37d4
F src/shell.c.in 52836b4002a2cad8095b451f0c39a6542c23a231eb0ed5e39387bc8b1f7aaa9e
F src/sqlite.h.in bd6db733c0f8559c5c28e14c406c27a28635d47c09bd9e0c2778ea1d30a291ab
F src/sqlite.h.in 12f208a569d0340814b52e50c3e30a43fc32b5ff24c4b9854edb85fa555f5196
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4
F src/sqliteInt.h 5a6aac5ca272df3dfc3792dafbee86e417c82cfbc59a040349bfcf88504559b8
F src/sqliteInt.h d52ede4c29dd1af380f882f29fc0209dc0f20439e953ac349a417ca0f4ed5661
F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
F src/tclsqlite.c 8522a04fb9c84faa1d80354430ae0ee9349727a3a4b32e3cfe39b9be8324cabd
F src/test1.c fce757d6c5cc7ecf010283fabe674cb021c1ce3f874803b21c978e6c768138e1
F src/test1.c 2b1cee78d45a95b08cf7c7aded6072c65401b73e69debec4ded579a034dbf772
F src/test2.c 827446e259a3b7ab949da1542953edda7b5117982576d3e6f1c24a0dd20a5cef
F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644
F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664
@@ -726,8 +726,8 @@ F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf8
F src/vdbevtab.c aae4bd769410eb7e1d02c42613eec961d514459b1c3c1c63cfc84e92a137daac
F src/vtab.c 4758a96d36c9a120848386ae603b1ab32a4876e0a1faf81bfcfb524455e583dc
F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
F src/wal.c b6c4ad0a5689dbba6f339cff30b8fb029b092db81544821cf05f670368eb5ff8
F src/wal.h 7a733af13b966ecb81872ce397e862116b3575ea53245b90b139a2873ee87825
F src/wal.c e78a2f4af580db963f987761922a79fa40bc466b37c203ceeb30034b89e29df3
F src/wal.h dd2cd9880f308a1bda0a72f36a29c3d4388d47db45f321ebe936a378ac845e32
F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b
F src/where.c b74a83b4c8f65b218c5c1c8d9122433f85ee1300fd9263ba1697d0e1040eeb36
F src/whereInt.h e25203e5bfee149f5f1225ae0166cfb4f1e65490c998a024249e98bb0647377c
@@ -887,6 +887,7 @@ F test/collateB.test 1e68906951b846570f29f20102ed91d29e634854ee47454d725f2151eca
F test/colmeta.test 2c765ea61ee37bc43bbe6d6047f89004e6508eb1
F test/colname.test 87ad5458bb8709312dac0d6755fd30e8e4ca83298d0a9ef6e5c24277a3c3390e
F test/columncount.test 6fe99c2f35738b0129357a1cf3fa483f76140f4cd8a89014c88c33c876d2638f
F test/commitstatus.test d5a871506ce5944a29afb7e65ce47ca7f76cadc1d09775022830258fdd6168a1
F test/concfault.test 500f17c3fcfe7705114422bcc6ddd3c740001a43
F test/concfault2.test 34b3fd258836aa305475d00e804c7450ade92f0de0bf9fa620e701446669bb12
F test/concurrent.test a0248ec6e3e79a5948453649cf86b5b359175cba55ea636b15426d6f0fa6c3da
@@ -2106,8 +2107,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P d55ba8bb85b1796d534d2db196db63312624630738712054553bd01f2ab30df9 137057f95778b3c913854d2182d0fbbfd9dd117db5566dabb5a22d927a59de62
R 12df87a4c2fba63aa28bed03bc214887
U drh
Z 82ddbc998d903aef55929bc948a95756
P 1348c2a59027d6e260d4c7543479093eaf7baccaa3ea61a0d091c44e0dea4b8c
R c8cdea1b9aa49524702d53e388aa9458
U dan
Z 69c8dd77f80c6e6120460e3d71577e65
# Remove this line to create a well-formed Fossil manifest.

View File

@@ -1 +1 @@
1348c2a59027d6e260d4c7543479093eaf7baccaa3ea61a0d091c44e0dea4b8c
4b08d4dad6b254a342353e3f765066c85cbc5450fe13501665c648627cca21cd

View File

@@ -4654,6 +4654,9 @@ int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zSuperJrnl){
BtShared *pBt = p->pBt;
sqlite3BtreeEnter(p);
#ifndef SQLITE_OMIT_CONCURRENT
memset(p->aCommit, 0, sizeof(p->aCommit));
#endif
#ifndef SQLITE_OMIT_AUTOVACUUM
if( pBt->autoVacuum ){
assert( ISCONCURRENT==0 );
@@ -4673,6 +4676,19 @@ int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zSuperJrnl){
if( rc==SQLITE_OK ){
rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zSuperJrnl, 0);
}
#ifndef SQLITE_OMIT_CONCURRENT
if( rc==SQLITE_OK ){
u32 iPrev = 0;
u32 iCurrent = 0;
sqlite3PagerWalInfo(pBt->pPager, &iPrev, &iCurrent);
if( (iPrev&0x80000000)!=(iCurrent&0x80000000) ){
iPrev = (iPrev & 0x7FFFFFFF) | (iCurrent & 0x80000000);
}
p->aCommit[SQLITE_COMMIT_FIRSTFRAME] = iPrev+1;
p->aCommit[SQLITE_COMMIT_NFRAME] = iCurrent-iPrev;
}
#endif
sqlite3BtreeLeave(p);
}
return rc;
@@ -11802,22 +11818,29 @@ int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); }
** is returned.
*/
int sqlite3BtreeExclusiveLock(Btree *p){
sqlite3 *db = p->db;
int rc;
Pgno pgno = 0;
BtShared *pBt = p->pBt;
assert( p->inTrans==TRANS_WRITE && pBt->pPage1 );
memset(db->aCommit, 0, sizeof(db->aCommit));
sqlite3BtreeEnter(p);
rc = sqlite3PagerExclusiveLock(pBt->pPager,
(p->db->eConcurrent==CONCURRENT_SCHEMA) ? 0 : pBt->pPage1->pDbPage,
&pgno
(db->eConcurrent==CONCURRENT_SCHEMA) ? 0 : pBt->pPage1->pDbPage,
db->aCommit
);
#ifdef SQLITE_OMIT_CONCURRENT
assert( pgno==0 );
assert( db->aCommit[SQLITE_COMMIT_CONFLICT_PGNO]==0 );
#else
if( rc==SQLITE_BUSY_SNAPSHOT && pgno ){
if( (rc==SQLITE_BUSY_SNAPSHOT)
&& (pgno = db->aCommit[SQLITE_COMMIT_CONFLICT_PGNO])
){
int iDb;
PgHdr *pPg = 0;
int rc2 = sqlite3PagerGet(pBt->pPager, pgno, &pPg, 0);
if( rc2==SQLITE_OK ){
for(iDb=0; db->aDb[iDb].pBt!=p; iDb++);
db->aCommit[SQLITE_COMMIT_CONFLICT_DB] = (u32)iDb;
(void)sqlite3PagerGet(pBt->pPager, pgno, &pPg, 0);
if( pPg ){
int bWrite = -1;
const char *zObj = 0;
const char *zTab = 0;
@@ -11909,3 +11932,38 @@ int sqlite3BtreeConnectionCount(Btree *p){
return p->pBt->nRef;
}
#endif
/*
** Access details of recent COMMIT commands. This function allows various
** details related to the most recent COMMIT command to be accessed.
** The requested value is always returned via output parameter (*piVal).
** The specific value requested is identified by parameter op (see
** below).
**
** SQLITE_OK is returned if successful, or SQLITE_ERROR if the "op" or
** "zDb" paramters are unrecognized.
*/
int sqlite3_commit_status(
sqlite3 *db, /* Database handle */
const char *zDb, /* Name of database - "main" etc. */
int op, /* SQLITE_COMMIT_XXX constant */
unsigned int *piVal /* OUT: Write requested value here */
){
int rc = SQLITE_OK;
#ifndef SQLITE_OMIT_CONCURRENT
if( op<0 || op>SQLITE_COMMIT_CONFLICT_PGNO ){
rc = SQLITE_ERROR;
}else if( op==SQLITE_COMMIT_FIRSTFRAME || op==SQLITE_COMMIT_NFRAME ){
int iDb = sqlite3FindDbName(db, zDb);
if( iDb<0 ){
rc = SQLITE_ERROR;
}else{
*piVal = db->aDb[iDb].pBt->aCommit[op];
}
}else{
*piVal = db->aCommit[op];
}
#endif
return rc;
}

View File

@@ -364,6 +364,12 @@ struct Btree {
#ifndef SQLITE_OMIT_SHARED_CACHE
BtLock lock; /* Object used to lock page 1 */
#endif
#ifndef SQLITE_OMIT_CONCURRENT
/* Return values for sqlite3_commit_status() requests:
** SQLITE_COMMIT_FIRSTFRAME, COMMIT_NFRAME.
*/
u32 aCommit[2];
#endif
};
/*

View File

@@ -6437,7 +6437,7 @@ int sqlite3PagerSync(Pager *pPager, const char *zSuper){
** is returned. Otherwise, if some other error occurs (IO error, OOM etc.),
** and SQLite error code is returned.
*/
int sqlite3PagerExclusiveLock(Pager *pPager, PgHdr *pPage1, Pgno *piConflict){
int sqlite3PagerExclusiveLock(Pager *pPager, PgHdr *pPage1, u32 *aConflict){
int rc = pPager->errCode;
assert( assert_pager_state(pPager) );
if( rc==SQLITE_OK ){
@@ -6458,7 +6458,7 @@ int sqlite3PagerExclusiveLock(Pager *pPager, PgHdr *pPage1, Pgno *piConflict){
** non-zero. */
do {
rc = sqlite3WalLockForCommit(
pPager->pWal, pPage1, pPager->pAllRead, piConflict
pPager->pWal, pPage1, pPager->pAllRead, aConflict
);
}while( rc==SQLITE_BUSY
&& pPager->xBusyHandler(pPager->pBusyHandlerArg)

View File

@@ -180,7 +180,7 @@ void *sqlite3PagerGetExtra(DbPage *);
void sqlite3PagerPagecount(Pager*, int*);
int sqlite3PagerBegin(Pager*, int exFlag, int);
int sqlite3PagerCommitPhaseOne(Pager*,const char *zSuper, int);
int sqlite3PagerExclusiveLock(Pager*, DbPage *pPage1, Pgno*);
int sqlite3PagerExclusiveLock(Pager*, DbPage *pPage1, u32*);
int sqlite3PagerSync(Pager *pPager, const char *zSuper);
int sqlite3PagerCommitPhaseTwo(Pager*);
int sqlite3PagerRollback(Pager*);

View File

@@ -10619,6 +10619,98 @@ int sqlite3_deserialize(
#define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */
#define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */
/*
** Access details of recent COMMIT commands. This function allows various
** details related to the most recent COMMIT command to be accessed.
** The requested value is always returned via output parameter (*piVal).
** The specific value requested is identified by parameter op (see
** below).
**
** SQLITE_OK is returned if successful, or SQLITE_ERROR if the "op" or
** "zDb" paramters are unrecognized.
*/
int sqlite3_commit_status(
sqlite3 *db, /* Database handle */
const char *zDb, /* Name of database - "main" etc. */
int op, /* SQLITE_COMMIT_XXX constant */
unsigned int *piVal /* OUT: Write requested value here */
);
/*
** The following describes the five requests supported by
** sqlite3_commit_status(), each identified by an SQLITE_COMMIT_XXX
** constant:
**
** SQLITE_COMMIT_FIRSTFRAME:
** In this case argument zDb must be "main", or "temp", or else the name of
** an attached database. If zDb does not correspond to any attached database,
** SQLITE_ERROR is returned.
**
** The final value of (*piVal) for this request is only defined if (a) the
** most recent attempt to write to the database connection was successful,
** (b) the most recent attempt to write to the database did write to database
** zDb, and (c) zDb is a wal mode database.
**
** If the above conditions are true, then output parameter (*piVal) is
** set to the frame number of the first frame written by the recent
** transaction. In wal mode, or in wal2 mode when a transaction is
** written into the *-wal file, the frame number indicates the frame's
** position in the wal file - frames are numbered starting from 1. In
** wal2 mode, when a transaction is written to the *-wal2 file, the frame
** number is the frame's position in the *-wal2 file, plus (1 << 31).
**
** Note: Although the a database may have up to (1<<32) pages, each wal
** file may contain at most (1<<31) frames.
**
** SQLITE_COMMIT_NFRAME:
** zDb is interpreted in the same way as, and the final value of (*piVal)
** is undefined, for SQLITE_COMMIT_FIRSTFRAME.
**
** Otherwise, (*piVal) is set to the number of frames written by the
** recent transaction.
**
** SQLITE_COMMIT_CONFLICT_DB:
** Parameter zDb is ignored for this request. The results of this
** request are only defined if the most recent attempt to write to
** the database handle was a BEGIN CONCURRENT transaction that
** failed with an SQLITE_BUSY_SNAPSHOT error.
**
** In other cases, (*piVal) is set to the index of the database
** on which the SQLITE_BUSY_SNAPSHOT error occurred (0 for main,
** a value of 2 or greater for an attached database). This value
** may be used with the sqlite3_db_name() API to find the name
** of the conflicting database.
**
** SQLITE_COMMIT_CONFLICT_FRAME:
** Parameter zDb is ignored for this request. The results of this
** request are only defined if the most recent attempt to write to
** the database handle was a BEGIN CONCURRENT transaction that
** failed with an SQLITE_BUSY_SNAPSHOT error.
**
** (*piVal) is set to the frame number of the conflicting frame for
** the recent SQLITE_BUSY_SNAPSHOT error. The conflicting transaction may
** be found by comparing this value with the FIRSTFRAME and
** NFRAME values for recent succesfully committed transactions on
** the same db. If the CONFLICT_FRAME value is F, then the conflicting
** transaction is the most recent successful commit for which
** (FIRSTFRAME <= F <= FIRSTFRAME+NFRAME) is true.
**
** SQLITE_COMMIT_CONFLICT_PGNO:
** Parameter zDb is ignored for this request. The results of this
** request are only defined if the previous attempt to write to
** the database using database handle db failed with
** SQLITE_BUSY_SNAPSHOT.
**
** Return the page number of the conflicting page for the most
** recent SQLITE_BUSY_SNAPSHOT error.
*/
#define SQLITE_COMMIT_FIRSTFRAME 0
#define SQLITE_COMMIT_NFRAME 1
#define SQLITE_COMMIT_CONFLICT_DB 2
#define SQLITE_COMMIT_CONFLICT_FRAME 3
#define SQLITE_COMMIT_CONFLICT_PGNO 4
/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.

View File

@@ -1740,6 +1740,12 @@ struct sqlite3 {
#ifdef SQLITE_USER_AUTHENTICATION
sqlite3_userauth auth; /* User authentication information */
#endif
#ifndef SQLITE_OMIT_CONCURRENT
/* Return values for sqlite3_commit_status() requests:
** SQLITE_COMMIT_CONFLICT_DB, CONFLICT_FRAME and CONFLICT_PGNO.
*/
u32 aCommit[5];
#endif
};
/*

View File

@@ -2416,6 +2416,57 @@ static int SQLITE_TCLAPI test_create_null_module(
}
#endif /* SQLITE_OMIT_VIRTUALTABLE */
/*
** tclcmd: sqlite3_commit_status db DBNAME OP
*/
static int SQLITE_TCLAPI test_commit_status(
ClientData clientData, /* Unused */
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int objc, /* Number of arguments */
Tcl_Obj *CONST objv[] /* Command arguments */
){
struct Op {
const char *zOp;
int op;
} aOp[] = {
{ "FIRSTFRAME", SQLITE_COMMIT_FIRSTFRAME },
{ "NFRAME", SQLITE_COMMIT_NFRAME },
{ "CONFLICT_DB", SQLITE_COMMIT_CONFLICT_DB },
{ "CONFLICT_FRAME", SQLITE_COMMIT_CONFLICT_FRAME },
{ "CONFLICT_PGNO", SQLITE_COMMIT_CONFLICT_PGNO },
{ 0, 0 }
};
sqlite3 *db = 0;
const char *zDb = 0;
int op = 0;
int rc = SQLITE_OK;
unsigned int val = 0;
if( objc!=4 ){
Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME OP");
return TCL_ERROR;
}
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
return TCL_ERROR;
}
zDb = Tcl_GetString(objv[2]);
if( Tcl_GetIndexFromObjStruct(
interp, objv[3], aOp, sizeof(aOp[0]), "OP", 0, &op
)){
return TCL_ERROR;
}
op = aOp[op].op;
rc = sqlite3_commit_status(db, zDb, op, &val);
if( rc==SQLITE_OK ){
Tcl_SetObjResult(interp, Tcl_NewWideIntObj((i64)val));
return TCL_OK;
}
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
return TCL_ERROR;
}
#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** Usage: sqlite3_snapshot_get DB DBNAME
@@ -9046,6 +9097,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
#ifndef SQLITE_OMIT_VIRTUALTABLE
{ "create_null_module", test_create_null_module, 0 },
#endif
{ "sqlite3_commit_status", test_commit_status, 0 },
};
static int bitmask_size = sizeof(Bitmask)*8;
static int longdouble_size = sizeof(LONGDOUBLE_TYPE);

View File

@@ -3683,6 +3683,15 @@ int sqlite3WalSnapshotRecover(Wal *pWal){
}
#endif /* SQLITE_ENABLE_SNAPSHOT */
/*
** Return the current last frame in the wal-index - mxFrame for *-wal,
** or mxFrame2 for *-wal2. If the last frame is current in wal2, return
** mxFrame2 without clearing the 0x80000000 bit.
*/
static u32 walGetPriorFrame(WalIndexHdr *pHdr){
return (walidxGetFile(pHdr) ? pHdr->mxFrame2 : pHdr->mxFrame);
}
/*
** Begin a read transaction on the database.
**
@@ -3745,7 +3754,7 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
rc = walOpenWal2(pWal);
}
pWal->nPriorFrame = pWal->hdr.mxFrame;
pWal->nPriorFrame = walGetPriorFrame(&pWal->hdr);
#ifdef SQLITE_ENABLE_SNAPSHOT
if( rc==SQLITE_OK ){
if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){
@@ -4149,6 +4158,24 @@ static int walUpgradeReadlock(Wal *pWal){
#ifndef SQLITE_OMIT_CONCURRENT
/*
** A concurrent transaction has conflicted with external frame iExternal.
** Transform this value to the one required by SQLITE_COMMIT_CONFLICT_FRAME -
** the frame offset within its wal file, with the 0x80000000 bit set for
** wal2, clear for the default wal file.
*/
static u32 walConflictFrame(Wal *pWal, u32 iExternal){
u32 iRet = iExternal;
if( isWalMode2(pWal) ){
int bFile = walExternalDecode(iExternal, &iRet);
iRet = (iRet | (bFile ? 0x80000000 : 0));
}
return iRet;
}
/*
** This function is only ever called when committing a "BEGIN CONCURRENT"
** transaction. It may be assumed that no frames have been written to
@@ -4181,7 +4208,7 @@ int sqlite3WalLockForCommit(
Wal *pWal,
PgHdr *pPg1,
Bitvec *pAllRead,
Pgno *piConflict
u32 *aConflict
){
int rc = walWriteLock(pWal);
@@ -4213,7 +4240,10 @@ int sqlite3WalLockForCommit(
if( pPg1==0 ){
/* If pPg1==0, then the current transaction modified the database
** schema. This means it conflicts with all other transactions. */
*piConflict = 1;
u32 bFile = walidxGetFile(&pWal->hdr);
u32 iFrame = walidxGetMxFrame(&head, bFile) | (bFile << 31);
aConflict[SQLITE_COMMIT_CONFLICT_PGNO] = 1;
aConflict[SQLITE_COMMIT_CONFLICT_FRAME] = iFrame;
rc = SQLITE_BUSY_SNAPSHOT;
}
@@ -4276,15 +4306,20 @@ int sqlite3WalLockForCommit(
if( bWal2 ){
iWal = walExternalDecode(iFrame, &iFrame);
}
sz = pWal->hdr.szPage;
sz = head.szPage;
sz = (sz&0xfe00) + ((sz&0x0001)<<16);
iOff = walFrameOffset(iFrame, sz) + WAL_FRAME_HDRSIZE + 40;
rc = sqlite3OsRead(pWal->apWalFd[iWal],aNew,sizeof(aNew),iOff);
if( rc==SQLITE_OK && memcmp(aOld, aNew, sizeof(aNew)) ){
u32 iFrame = walConflictFrame(pWal, sLoc.iZero+i);
aConflict[SQLITE_COMMIT_CONFLICT_PGNO] = 1;
aConflict[SQLITE_COMMIT_CONFLICT_FRAME] = iFrame;
rc = SQLITE_BUSY_SNAPSHOT;
}
}else if( sqlite3BitvecTestNotNull(pAllRead, sLoc.aPgno[i-1]) ){
*piConflict = sLoc.aPgno[i-1];
u32 iFrame = walConflictFrame(pWal, sLoc.iZero+i);
aConflict[SQLITE_COMMIT_CONFLICT_PGNO] = sLoc.aPgno[i-1];
aConflict[SQLITE_COMMIT_CONFLICT_FRAME] = iFrame;
rc = SQLITE_BUSY_SNAPSHOT;
}else
if( (pPg = sqlite3PagerLookup(pPg1->pPager, sLoc.aPgno[i-1])) ){
@@ -4318,7 +4353,7 @@ int sqlite3WalLockForCommit(
}
}
pWal->nPriorFrame = pWal->hdr.mxFrame;
pWal->nPriorFrame = walGetPriorFrame(&pWal->hdr);
return rc;
}
@@ -4591,6 +4626,7 @@ static int walRestartLog(Wal *pWal){
rc = walUpgradeReadlock(pWal);
}
pWal->nPriorFrame = walGetPriorFrame(&pWal->hdr);
return rc;
}
@@ -5346,7 +5382,7 @@ int sqlite3WalInfo(Wal *pWal, u32 *pnPrior, u32 *pnFrame){
int rc = SQLITE_OK;
if( pWal ){
*pnPrior = pWal->nPriorFrame;
*pnFrame = walidxGetMxFrame(&pWal->hdr, walidxGetFile(&pWal->hdr));
*pnFrame = walGetPriorFrame(&pWal->hdr);
}
return rc;
}

View File

@@ -139,7 +139,7 @@ void sqlite3WalSnapshotUnlock(Wal *pWal);
#ifndef SQLITE_OMIT_CONCURRENT
/* Tell the wal layer that we want to commit a concurrent transaction */
int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pPg, Bitvec *pRead, Pgno*);
int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pPg, Bitvec *pRead, u32*);
/* Upgrade the state of the client to take into account changes written
** by other connections */

297
test/commitstatus.test Normal file
View File

@@ -0,0 +1,297 @@
# 2015 July 26
#
# 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.
#
#***********************************************************************
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/lock_common.tcl
set ::testprefix commitstatus
ifcapable !concurrent {
finish_test
return
}
proc commit_status_frames {db} {
list [sqlite3_commit_status $db main FIRSTFRAME] \
[sqlite3_commit_status $db main NFRAME]
}
proc commit_status_conflict {db} {
list [sqlite3_commit_status $db main CONFLICT_DB] \
[sqlite3_commit_status $db main CONFLICT_FRAME] \
[sqlite3_commit_status $db main CONFLICT_PGNO]
}
do_execsql_test 1.0 {
PRAGMA journal_mode = wal;
} {wal}
do_execsql_test 1.1 {
CREATE TABLE t1(k INTEGER PRIMARY KEY, v);
}
do_test 1.2 { commit_status_frames db } {1 2}
do_execsql_test 1.3 {
INSERT INTO t1 VALUES(2, 'two');
}
do_test 1.4 { commit_status_frames db } {3 1}
do_execsql_test 1.5 {
CREATE INDEX i1 ON t1(v);
}
do_test 1.6 { commit_status_frames db } {4 2}
do_execsql_test 1.7 {
DROP INDEX i1;
}
do_test 1.8 { commit_status_frames db } {6 2}
do_execsql_test 1.9 {
PRAGMA wal_checkpoint;
INSERT INTO t1 VALUES(3, 'three');
} {0 7 7}
do_test 1.10 { commit_status_frames db } {1 1}
do_execsql_test 1.11 {
PRAGMA journal_mode = delete;
PRAGMA journal_mode = wal2;
PRAGMA journal_size_limit = 10000;
} {delete wal2 10000}
for {set ii 1} {$ii < 30} {incr ii} {
do_execsql_test 1.13.$ii.1 {
INSERT INTO t1(v) VALUES('value');
}
if {$ii<=10} {
set expect [list $ii 1]
} elseif {$ii <= 20} {
set expect [list [expr {(1+($ii-1)%10)|0x80000000}] 1]
} else {
set expect [list [expr ($ii%10)] 1]
}
do_test 1.13.$ii.2 { commit_status_frames db } $expect
if {$ii==15} {
execsql { PRAGMA wal_checkpoint }
}
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {
CREATE TABLE t1(a, b);
CREATE TABLE t2(a, b);
PRAGMA journal_mode = wal;
} {wal}
sqlite3 db2 test.db
do_execsql_test -db db2 2.1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(1, 1);
}
do_execsql_test 2.2 {
INSERT INTO t2 VALUES(1, 1);
INSERT INTO t2 VALUES(2, 2);
INSERT INTO t2 VALUES(3, 3);
INSERT INTO t2 VALUES(4, 4);
}
do_execsql_test -db db2 2.3 {
COMMIT
}
do_test 2.4 { commit_status_frames db2 } {5 1}
do_execsql_test 2.5 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES('yes', 'no');
INSERT INTO t2 VALUES('yes', 'no');
COMMIT;
}
do_test 2.6 { commit_status_frames db } {6 2}
db2 close
do_execsql_test 2.7 {
PRAGMA journal_mode = delete;
PRAGMA journal_mode = wal2;
} {delete wal2}
sqlite3 db2 test.db
do_execsql_test 2.8 {
PRAGMA journal_size_limit = 5000;
BEGIN CONCURRENT;
INSERT INTO t1 VALUES('x', 'y');
} {5000}
do_execsql_test -db db2 2.9 {
PRAGMA journal_size_limit = 5000;
INSERT INTO t2 VALUES(1, 1);
INSERT INTO t2 VALUES(2, 2);
INSERT INTO t2 VALUES(3, 3);
INSERT INTO t2 VALUES(4, 4);
INSERT INTO t2 VALUES(5, 5);
INSERT INTO t2 VALUES(6, 6);
INSERT INTO t2 VALUES(7, 7);
INSERT INTO t2 VALUES(8, 8);
} {5000}
do_execsql_test 2.10 {
COMMIT;
}
do_test 2.11 { commit_status_frames db } [list [expr {0x80000000 | 4}] 1]
do_execsql_test 2.12 {
PRAGMA wal_checkpoint;
BEGIN CONCURRENT;
INSERT INTO t1 VALUES('a', 'b');
} {0 9 5}
do_execsql_test -db db2 2.13 {
INSERT INTO t2 VALUES(1, 1);
INSERT INTO t2 VALUES(2, 2);
INSERT INTO t2 VALUES(3, 3);
INSERT INTO t2 VALUES(4, 4);
INSERT INTO t2 VALUES(5, 5);
INSERT INTO t2 VALUES(6, 6);
INSERT INTO t2 VALUES(7, 7);
INSERT INTO t2 VALUES(8, 8);
} {}
do_execsql_test 2.14 {
COMMIT;
}
do_test 2.15 { commit_status_frames db } [list 8 1]
#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
CREATE TABLE t1(x);
CREATE TABLE t2(y);
CREATE TABLE t3(z);
PRAGMA journal_mode = wal2;
PRAGMA journal_size_limit = 8000;
} {wal2 8000}
sqlite3 db2 test.db
do_execsql_test -db db2 3.1 {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES('x');
}
do_execsql_test 3.2.0 {
INSERT INTO t2 VALUES('y'); -- frame 1
INSERT INTO t3 VALUES('y'); -- frame 2
INSERT INTO t2 VALUES('z'); -- frame 3
INSERT INTO t3 VALUES('z'); -- frame 4
INSERT INTO t1 VALUES('a'); -- frame 5
}
do_test 3.2.1 { commit_status_frames db } {5 1}
do_execsql_test 3.2.2 {
INSERT INTO t3 VALUES('a'); -- frame 6
}
do_test 3.3 {
catchsql { COMMIT } db2
} {1 {database is locked}}
execsql ROLLBACK db2
do_test 3.4 {
commit_status_conflict db2
} {0 5 2}
do_execsql_test -db db2 3.5 {
BEGIN CONCURRENT;
INSERT INTO t3 VALUES('x');
}
do_execsql_test 3.6.0 {
INSERT INTO t2 VALUES('y'); -- frame 7
INSERT INTO t1 VALUES('y'); -- frame 8
INSERT INTO t2 VALUES('z'); -- frame 1b
INSERT INTO t1 VALUES('z'); -- frame 2b
INSERT INTO t3 VALUES('a'); -- frame 3b
}
do_test 3.6.1 { commit_status_frames db } [list [expr {0x80000000 | 3}] 1]
do_execsql_test 3.6.2 {
INSERT INTO t1 VALUES('a'); -- frame 4b
}
do_test 3.7 {
catchsql { COMMIT } db2
} {1 {database is locked}}
execsql ROLLBACK db2
do_test 3.8 {
commit_status_conflict db2
} [list 0 [expr {0x80000000 | 3}] 4]
#-------------------------------------------------------------------------
reset_db
do_execsql_test 4.0 {
CREATE TABLE t1(x);
CREATE TABLE t2(y);
PRAGMA journal_mode = wal2;
PRAGMA journal_size_limit = 10000000;
} {wal2 10000000}
sqlite3 db2 test.db
db2 eval {SELECT * FROM sqlite_schema}
foreach {tn s1 s2} {
1 100 100
2 1000 1000
3 1000 1000
4 1000 1000
5 1000 1000
6 1000 1000
7 1000 1000
8 1000 1000
9 1000 1000
10 2000 2000
11 2000 2000
12 2000 2000
13 2000 2000
14 2000 2000
15 2000 2000
16 2000 2000
17 2000 2000
} {
execsql {
BEGIN CONCURRENT;
INSERT INTO t1 VALUES(NULL);
} db2
for {set ii 0} {$ii<$s1} {incr ii} {
execsql { INSERT INTO t2 VALUES(randomblob(30)); }
}
execsql { INSERT INTO t1 VALUES(NULL) }
set frame [sqlite3_commit_status db main FIRSTFRAME]
for {set ii 0} {$ii<$s2} {incr ii} {
execsql { INSERT INTO t2 VALUES(randomblob(30)); }
}
do_test 4.$tn.1 { catchsql "COMMIT" db2 } {1 {database is locked}}
do_test 4.$tn.2 {
commit_status_conflict db2
} [list 0 $frame 2]
execsql { ROLLBACK } db2
}
finish_test