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:
31
manifest
31
manifest
@@ -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.
|
||||
|
@@ -1 +1 @@
|
||||
1348c2a59027d6e260d4c7543479093eaf7baccaa3ea61a0d091c44e0dea4b8c
|
||||
4b08d4dad6b254a342353e3f765066c85cbc5450fe13501665c648627cca21cd
|
70
src/btree.c
70
src/btree.c
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
};
|
||||
|
||||
/*
|
||||
|
@@ -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)
|
||||
|
@@ -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*);
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
};
|
||||
|
||||
/*
|
||||
|
52
src/test1.c
52
src/test1.c
@@ -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);
|
||||
|
50
src/wal.c
50
src/wal.c
@@ -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;
|
||||
}
|
||||
|
@@ -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
297
test/commitstatus.test
Normal 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
|
Reference in New Issue
Block a user