]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the sqlite3_commit_status() API. For querying a connection for (a) the ranges...
authordan <Dan Kennedy>
Mon, 19 Jun 2023 18:16:19 +0000 (18:16 +0000)
committerdan <Dan Kennedy>
Mon, 19 Jun 2023 18:16:19 +0000 (18:16 +0000)
FossilOrigin-Name: 4b08d4dad6b254a342353e3f765066c85cbc5450fe13501665c648627cca21cd

12 files changed:
manifest
manifest.uuid
src/btree.c
src/btreeInt.h
src/pager.c
src/pager.h
src/sqlite.h.in
src/sqliteInt.h
src/test1.c
src/wal.c
src/wal.h
test/commitstatus.test [new file with mode: 0644]

index a268b1ce86348e9f164a41d5e83258bab1e585aa..0d0e067bbe15f02a04b6a543e337175bb31bb40e 100644 (file)
--- a/manifest
+++ b/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.
index 321632d1677568703d6eb635dceb2e1ec244f4a6..6ea029c061230ec71b7def4d8aacf51b6639b986 100644 (file)
@@ -1 +1 @@
-1348c2a59027d6e260d4c7543479093eaf7baccaa3ea61a0d091c44e0dea4b8c
\ No newline at end of file
+4b08d4dad6b254a342353e3f765066c85cbc5450fe13501665c648627cca21cd
\ No newline at end of file
index 30b3ffb65781c5df28ec94c9e96838ea7f6de1a8..2022f4bc31009d3d226cad931188e375eade394e 100644 (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;
+}
+
index e48d69b3db022495a972aac0a4e202d21248e247..194ff72eb7bf83776d4a7cd41b99d9d450e7b0b7 100644 (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
 };
 
 /*
index 6681793d0baf6d7e343ea5b77633ab140b16d5e0..917856670b29e838aa24ddc874354b70abc31eee 100644 (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) 
index 58bfc0fa19bef078639107e61c45002e264199ed..2ec6f1ac41d5c6c821362f98c06fe6001681af74 100644 (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*);
index c6fbaf8d36168fd8015e3d8ae88878fe648d3e82..e49076f84c4a1aab1c0a0ea684f18752810f6c43 100644 (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.
index 5f09cd75673d8f700bbb4caf55eb0620bbe91a3f..af111d51c69c65d7e9d4e971ffc91da600c45c20 100644 (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
 };
 
 /*
index 8b0592b10d0d93709c2a493aa75da4a41bca06bd..720da685ca6cf5f35aa1adbc4da92bd76cc72559 100644 (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);
index c7fe1f77912415cc860e976110e162e91e35682c..c19713ab17d8a4d302c43d3a86919e0c090898f7 100644 (file)
--- a/src/wal.c
+++ b/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;
 }
index 137abcba2b662443f3dcdcbeb1eca3f99464cfd8..6989142d916a91f0428606f1dfa74b8f69370aad 100644 (file)
--- a/src/wal.h
+++ b/src/wal.h
@@ -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 */
diff --git a/test/commitstatus.test b/test/commitstatus.test
new file mode 100644 (file)
index 0000000..92580fe
--- /dev/null
@@ -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