/ Check-in [0b971842]
Login
SQLite training in Houston TX on 2019-11-05 (details)
Part of the 2019 Tcl Conference

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Only allow UNLOCKED transactions to commit if none of the pages read by the transaction have been modified since it was opened.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | begin-concurrent
Files: files | file ages | folders
SHA1: 0b9718426e44df092850c5d095ce1b84a1e567cf
User & Date: dan 2015-07-29 12:14:28
Wiki:begin-concurrent
Context
2015-08-15
18:16
Handle writes to auto-vacuum databases within UNLOCKED transactions in the same way as for non-UNLOCKED transactions. check-in: de1ea450 user: dan tags: begin-concurrent
2015-07-29
12:14
Only allow UNLOCKED transactions to commit if none of the pages read by the transaction have been modified since it was opened. check-in: 0b971842 user: dan tags: begin-concurrent
2015-07-28
16:46
Add some test cases and fix some small problems with BEGIN UNLOCKED transactions. check-in: 6da0e962 user: dan tags: begin-concurrent
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/btree.c.

9586
9587
9588
9589
9590
9591
9592
9593
9594
9595
9596
9597
9598
9599
9600
9601
9602
9603
9604
  return (p->pBt->btsFlags & BTS_READ_ONLY)!=0;
}

/*
** Return the size of the header added to each page by this module.
*/
int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); }

int sqlite3BtreeExclusiveLock(Btree *p){
  int rc;
  BtShared *pBt = p->pBt;
  sqlite3BtreeEnter(p);
  rc = sqlite3PagerExclusiveLock(pBt->pPager, pBt->pPage1->pDbPage);
  sqlite3BtreeLeave(p);
  return rc;
}










<
<
<
<
<
<
<
<
<
<
<
<
9586
9587
9588
9589
9590
9591
9592












  return (p->pBt->btsFlags & BTS_READ_ONLY)!=0;
}

/*
** Return the size of the header added to each page by this module.
*/
int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); }












Changes to src/btree.h.

266
267
268
269
270
271
272
273
274
275
# define sqlite3BtreeLeaveAll(X)

# define sqlite3BtreeHoldsMutex(X) 1
# define sqlite3BtreeHoldsAllMutexes(X) 1
# define sqlite3SchemaMutexHeld(X,Y,Z) 1
#endif

int sqlite3BtreeExclusiveLock(Btree*);

#endif /* _BTREE_H_ */







<
<

266
267
268
269
270
271
272


273
# define sqlite3BtreeLeaveAll(X)

# define sqlite3BtreeHoldsMutex(X) 1
# define sqlite3BtreeHoldsAllMutexes(X) 1
# define sqlite3SchemaMutexHeld(X,Y,Z) 1
#endif



#endif /* _BTREE_H_ */

Changes to src/pager.c.

653
654
655
656
657
658
659

660
661
662
663
664
665
666
....
1733
1734
1735
1736
1737
1738
1739










1740
1741
1742
1743
1744
1745
1746
....
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
....
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
....
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
....
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
....
5269
5270
5271
5272
5273
5274
5275








5276
5277
5278
5279
5280
5281
5282
....
5574
5575
5576
5577
5578
5579
5580

5581
5582
5583
5584
5585
5586
5587
....
5594
5595
5596
5597
5598
5599
5600







5601
5602
5603
5604
5605
5606
5607
....
6062
6063
6064
6065
6066
6067
6068
6069
6070
6071
6072
6073
6074
6075
6076
6077
6078
6079
6080
6081
6082
6083
6084
6085
6086
6087
6088
6089
6090
6091
6092
6093
6094
6095
6096
6097
6098
6099
6100
  Pgno dbFileSize;            /* Number of pages in the database file */
  Pgno dbHintSize;            /* Value passed to FCNTL_SIZE_HINT call */
  int errCode;                /* One of several kinds of errors */
  int nRec;                   /* Pages journalled since last j-header written */
  u32 cksumInit;              /* Quasi-random value added to every checksum */
  u32 nSubRec;                /* Number of records written to sub-journal */
  Bitvec *pInJournal;         /* One bit for each page in the database file */

  sqlite3_file *fd;           /* File descriptor for database */
  sqlite3_file *jfd;          /* File descriptor for main journal */
  sqlite3_file *sjfd;         /* File descriptor for sub-journal */
  i64 journalOff;             /* Current write offset in the journal file */
  i64 journalHdr;             /* Byte offset to previous journal header */
  sqlite3_backup *pBackup;    /* Pointer to list of ongoing backup processes */
  PagerSavepoint *aSavepoint; /* Array of active savepoints */
................................................................................
      rc |= sqlite3BitvecSet(p->pInSavepoint, pgno);
      testcase( rc==SQLITE_NOMEM );
      assert( rc==SQLITE_OK || rc==SQLITE_NOMEM );
    }
  }
  return rc;
}











/*
** This function is a no-op if the pager is in exclusive mode and not
** in the ERROR state. Otherwise, it switches the pager to PAGER_OPEN
** state.
**
** If the pager is not in exclusive-access mode, the database file is
................................................................................
static void pager_unlock(Pager *pPager){

  assert( pPager->eState==PAGER_READER 
       || pPager->eState==PAGER_OPEN 
       || pPager->eState==PAGER_ERROR 
  );

  sqlite3BitvecDestroy(pPager->pInJournal);
  pPager->pInJournal = 0;
  releaseAllSavepoints(pPager);

  if( pagerUseWal(pPager) ){
    assert( !isOpen(pPager->jfd) );
    sqlite3WalEndReadTransaction(pPager->pWal);
    pPager->eState = PAGER_OPEN;
  }else if( !pPager->exclusiveMode ){
................................................................................
    if( p ){
      p->pageHash = 0;
      sqlite3PagerUnrefNotNull(p);
    }
  }
#endif

  sqlite3BitvecDestroy(pPager->pInJournal);
  pPager->pInJournal = 0;
  pPager->nRec = 0;
  sqlite3PcacheCleanAll(pPager->pPCache);
  sqlite3PcacheTruncate(pPager->pPCache, pPager->dbSize);

  if( pagerUseWal(pPager) ){
    /* Drop the WAL write-lock, if any. Also, if the connection was in 
    ** locking_mode=exclusive mode but is no longer, drop the EXCLUSIVE 
................................................................................

  assert( pPager->eState==PAGER_WRITER_CACHEMOD
       || pPager->eState==PAGER_WRITER_DBMOD
  );
  assert( assert_pager_state(pPager) );
  assert( !pagerUseWal(pPager) );

  rc = sqlite3PagerExclusiveLock(pPager, 0);
  if( rc!=SQLITE_OK ) return rc;

  if( !pPager->noSync ){
    assert( !pPager->tempFile );
    if( isOpen(pPager->jfd) && pPager->journalMode!=PAGER_JOURNALMODE_MEMORY ){
      const int iDc = sqlite3OsDeviceCharacteristics(pPager->fd);
      assert( isOpen(pPager->jfd) );
................................................................................
    return SQLITE_OK;
  }

  pPg->pDirty = 0;
  if( pagerUseWal(pPager) ){
    /* If the transaction is a "BEGIN UNLOCKED" transaction, the page 
    ** cannot be flushed to disk. Return early in this case. */
    if( sqlite3WalIsInTrans(pPager->pWal)==0 ) return SQLITE_OK;

    /* Write a single frame for this page to the log. */
    rc = subjournalPageIfRequired(pPg); 
    if( rc==SQLITE_OK ){
      rc = pagerWalFrames(pPager, pPg, 0, 0);
    }
  }else{
................................................................................
  assert( assert_pager_state(pPager) );
  assert( noContent==0 || bMmapOk==0 );

  if( pgno==0 ){
    return SQLITE_CORRUPT_BKPT;
  }
  pPager->hasBeenUsed = 1;









  /* If the pager is in the error state, return an error immediately. 
  ** Otherwise, request the page from the PCache layer. */
  if( pPager->errCode!=SQLITE_OK ){
    rc = pPager->errCode;
  }else{
    if( bMmapOk && pagerUseWal(pPager) ){
................................................................................

  if( pPager->errCode ) return pPager->errCode;
  assert( pPager->eState>=PAGER_READER && pPager->eState<PAGER_ERROR );
  pPager->subjInMemory = (u8)subjInMemory;

  if( ALWAYS(pPager->eState==PAGER_READER) ){
    assert( pPager->pInJournal==0 );


    if( pagerUseWal(pPager) ){
      /* If the pager is configured to use locking_mode=exclusive, and an
      ** exclusive lock on the database is not already held, obtain it now.
      */
      if( pPager->exclusiveMode && sqlite3WalExclusiveMode(pPager->pWal, -1) ){
        rc = pagerLockDb(pPager, EXCLUSIVE_LOCK);
................................................................................
      /* Grab the write lock on the log file. If successful, upgrade to
      ** PAGER_RESERVED state. Otherwise, return an error code to the caller.
      ** The busy-handler is not invoked if another connection already
      ** holds the write-lock. If possible, the upper layer will call it.
      */
      if( exFlag>=0 ){
        rc = sqlite3WalBeginWriteTransaction(pPager->pWal);







      }
    }else{
      /* Obtain a RESERVED lock on the database file. If the exFlag parameter
      ** is true, then immediately upgrade this to an EXCLUSIVE lock. The
      ** busy-handler callback can be used when upgrading to the EXCLUSIVE
      ** lock, but not when obtaining the RESERVED lock.
      */
................................................................................
** the database file, an attempt is made to obtain one.
**
** If the EXCLUSIVE lock is already held or the attempt to obtain it is
** successful, or the connection is in WAL mode, SQLITE_OK is returned.
** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is 
** returned.
*/
int sqlite3PagerExclusiveLock(Pager *pPager, PgHdr *pPage1){
  int rc = SQLITE_OK;
  assert( pPager->eState==PAGER_WRITER_CACHEMOD 
       || pPager->eState==PAGER_WRITER_DBMOD 
       || pPager->eState==PAGER_WRITER_LOCKED 
  );
  assert( assert_pager_state(pPager) );
  if( 0==pagerUseWal(pPager) ){
    rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
  }else{
    Wal *pWal = pPager->pWal;
    if( 0==sqlite3WalIsInTrans(pWal) ){
      /* TODO: There must be an optimization opportunity here, as this call 
      ** to PcacheDirtyList() sorts the list of dirty pages, even though it 
      ** is not really required - and will be sorted again in CommitPhaseOne()
      ** in any case.  */
      PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache);

      /* This is an UNLOCKED transaction. Attempt to lock the wal database
      ** here. If SQLITE_BUSY (but not SQLITE_BUSY_SNAPSHOT) is returned,
      ** invoke the busy-handler and try again for as long as it returns
      ** non-zero.  */
      do {
        /* rc = sqlite3WalBeginWriteTransaction(pWal); */
        rc = sqlite3WalLockForCommit(pWal, pList, pPage1); 
      }while( rc==SQLITE_BUSY 
           && pPager->xBusyHandler(pPager->pBusyHandlerArg) 
      );
    }
  }
  return rc;
}







>







 







>
>
>
>
>
>
>
>
>
>







 







|
<







 







|
<







 







|







 







|







 







>
>
>
>
>
>
>
>







 







>







 







>
>
>
>
>
>
>







 







|









|
<
<
<
<
<
<
<






|







653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
....
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
....
1769
1770
1771
1772
1773
1774
1775
1776

1777
1778
1779
1780
1781
1782
1783
....
2005
2006
2007
2008
2009
2010
2011
2012

2013
2014
2015
2016
2017
2018
2019
....
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
....
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
....
5278
5279
5280
5281
5282
5283
5284
5285
5286
5287
5288
5289
5290
5291
5292
5293
5294
5295
5296
5297
5298
5299
....
5591
5592
5593
5594
5595
5596
5597
5598
5599
5600
5601
5602
5603
5604
5605
....
5612
5613
5614
5615
5616
5617
5618
5619
5620
5621
5622
5623
5624
5625
5626
5627
5628
5629
5630
5631
5632
....
6087
6088
6089
6090
6091
6092
6093
6094
6095
6096
6097
6098
6099
6100
6101
6102
6103
6104







6105
6106
6107
6108
6109
6110
6111
6112
6113
6114
6115
6116
6117
6118
  Pgno dbFileSize;            /* Number of pages in the database file */
  Pgno dbHintSize;            /* Value passed to FCNTL_SIZE_HINT call */
  int errCode;                /* One of several kinds of errors */
  int nRec;                   /* Pages journalled since last j-header written */
  u32 cksumInit;              /* Quasi-random value added to every checksum */
  u32 nSubRec;                /* Number of records written to sub-journal */
  Bitvec *pInJournal;         /* One bit for each page in the database file */
  Bitvec *pAllRead;           /* Pages read within current UNLOCKED trans. */
  sqlite3_file *fd;           /* File descriptor for database */
  sqlite3_file *jfd;          /* File descriptor for main journal */
  sqlite3_file *sjfd;         /* File descriptor for sub-journal */
  i64 journalOff;             /* Current write offset in the journal file */
  i64 journalHdr;             /* Byte offset to previous journal header */
  sqlite3_backup *pBackup;    /* Pointer to list of ongoing backup processes */
  PagerSavepoint *aSavepoint; /* Array of active savepoints */
................................................................................
      rc |= sqlite3BitvecSet(p->pInSavepoint, pgno);
      testcase( rc==SQLITE_NOMEM );
      assert( rc==SQLITE_OK || rc==SQLITE_NOMEM );
    }
  }
  return rc;
}

/*
** Free the Pager.pInJournal and Pager.pAllRead bitvec objects.
*/
static void pagerFreeBitvecs(Pager *pPager){
  sqlite3BitvecDestroy(pPager->pInJournal);
  sqlite3BitvecDestroy(pPager->pAllRead);
  pPager->pInJournal = 0;
  pPager->pAllRead = 0;
}

/*
** This function is a no-op if the pager is in exclusive mode and not
** in the ERROR state. Otherwise, it switches the pager to PAGER_OPEN
** state.
**
** If the pager is not in exclusive-access mode, the database file is
................................................................................
static void pager_unlock(Pager *pPager){

  assert( pPager->eState==PAGER_READER 
       || pPager->eState==PAGER_OPEN 
       || pPager->eState==PAGER_ERROR 
  );

  pagerFreeBitvecs(pPager);

  releaseAllSavepoints(pPager);

  if( pagerUseWal(pPager) ){
    assert( !isOpen(pPager->jfd) );
    sqlite3WalEndReadTransaction(pPager->pWal);
    pPager->eState = PAGER_OPEN;
  }else if( !pPager->exclusiveMode ){
................................................................................
    if( p ){
      p->pageHash = 0;
      sqlite3PagerUnrefNotNull(p);
    }
  }
#endif

  pagerFreeBitvecs(pPager);

  pPager->nRec = 0;
  sqlite3PcacheCleanAll(pPager->pPCache);
  sqlite3PcacheTruncate(pPager->pPCache, pPager->dbSize);

  if( pagerUseWal(pPager) ){
    /* Drop the WAL write-lock, if any. Also, if the connection was in 
    ** locking_mode=exclusive mode but is no longer, drop the EXCLUSIVE 
................................................................................

  assert( pPager->eState==PAGER_WRITER_CACHEMOD
       || pPager->eState==PAGER_WRITER_DBMOD
  );
  assert( assert_pager_state(pPager) );
  assert( !pagerUseWal(pPager) );

  rc = sqlite3PagerExclusiveLock(pPager);
  if( rc!=SQLITE_OK ) return rc;

  if( !pPager->noSync ){
    assert( !pPager->tempFile );
    if( isOpen(pPager->jfd) && pPager->journalMode!=PAGER_JOURNALMODE_MEMORY ){
      const int iDc = sqlite3OsDeviceCharacteristics(pPager->fd);
      assert( isOpen(pPager->jfd) );
................................................................................
    return SQLITE_OK;
  }

  pPg->pDirty = 0;
  if( pagerUseWal(pPager) ){
    /* If the transaction is a "BEGIN UNLOCKED" transaction, the page 
    ** cannot be flushed to disk. Return early in this case. */
    if( pPager->pAllRead ) return SQLITE_OK;

    /* Write a single frame for this page to the log. */
    rc = subjournalPageIfRequired(pPg); 
    if( rc==SQLITE_OK ){
      rc = pagerWalFrames(pPager, pPg, 0, 0);
    }
  }else{
................................................................................
  assert( assert_pager_state(pPager) );
  assert( noContent==0 || bMmapOk==0 );

  if( pgno==0 ){
    return SQLITE_CORRUPT_BKPT;
  }
  pPager->hasBeenUsed = 1;

  /* If this is an UNLOCKED transaction and the page being read was
  ** present in the database file when the transaction was opened,
  ** mark it as read in the pAllRead vector.  */
  if( pPager->pAllRead && pgno<=pPager->dbOrigSize ){
    rc = sqlite3BitvecSet(pPager->pAllRead, pgno);
    if( rc!=SQLITE_OK ) goto pager_acquire_err;
  }

  /* If the pager is in the error state, return an error immediately. 
  ** Otherwise, request the page from the PCache layer. */
  if( pPager->errCode!=SQLITE_OK ){
    rc = pPager->errCode;
  }else{
    if( bMmapOk && pagerUseWal(pPager) ){
................................................................................

  if( pPager->errCode ) return pPager->errCode;
  assert( pPager->eState>=PAGER_READER && pPager->eState<PAGER_ERROR );
  pPager->subjInMemory = (u8)subjInMemory;

  if( ALWAYS(pPager->eState==PAGER_READER) ){
    assert( pPager->pInJournal==0 );
    assert( pPager->pAllRead==0 );

    if( pagerUseWal(pPager) ){
      /* If the pager is configured to use locking_mode=exclusive, and an
      ** exclusive lock on the database is not already held, obtain it now.
      */
      if( pPager->exclusiveMode && sqlite3WalExclusiveMode(pPager->pWal, -1) ){
        rc = pagerLockDb(pPager, EXCLUSIVE_LOCK);
................................................................................
      /* Grab the write lock on the log file. If successful, upgrade to
      ** PAGER_RESERVED state. Otherwise, return an error code to the caller.
      ** The busy-handler is not invoked if another connection already
      ** holds the write-lock. If possible, the upper layer will call it.
      */
      if( exFlag>=0 ){
        rc = sqlite3WalBeginWriteTransaction(pPager->pWal);
      }else{
        pPager->pAllRead = sqlite3BitvecCreate(pPager->dbSize);
        if( pPager->pAllRead==0 ){
          rc = SQLITE_NOMEM;
        }else{
          rc = sqlite3BitvecSet(pPager->pAllRead, 1);
        }
      }
    }else{
      /* Obtain a RESERVED lock on the database file. If the exFlag parameter
      ** is true, then immediately upgrade this to an EXCLUSIVE lock. The
      ** busy-handler callback can be used when upgrading to the EXCLUSIVE
      ** lock, but not when obtaining the RESERVED lock.
      */
................................................................................
** the database file, an attempt is made to obtain one.
**
** If the EXCLUSIVE lock is already held or the attempt to obtain it is
** successful, or the connection is in WAL mode, SQLITE_OK is returned.
** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is 
** returned.
*/
int sqlite3PagerExclusiveLock(Pager *pPager){
  int rc = SQLITE_OK;
  assert( pPager->eState==PAGER_WRITER_CACHEMOD 
       || pPager->eState==PAGER_WRITER_DBMOD 
       || pPager->eState==PAGER_WRITER_LOCKED 
  );
  assert( assert_pager_state(pPager) );
  if( 0==pagerUseWal(pPager) ){
    rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK);
  }else{
    if( pPager->pAllRead ){







      /* This is an UNLOCKED transaction. Attempt to lock the wal database
      ** here. If SQLITE_BUSY (but not SQLITE_BUSY_SNAPSHOT) is returned,
      ** invoke the busy-handler and try again for as long as it returns
      ** non-zero.  */
      do {
        /* rc = sqlite3WalBeginWriteTransaction(pWal); */
        rc = sqlite3WalLockForCommit(pPager->pWal, pPager->pAllRead);
      }while( rc==SQLITE_BUSY 
           && pPager->xBusyHandler(pPager->pBusyHandlerArg) 
      );
    }
  }
  return rc;
}

Changes to src/pager.h.

146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
void *sqlite3PagerGetData(DbPage *); 
void *sqlite3PagerGetExtra(DbPage *); 

/* Functions used to manage pager transactions and savepoints. */
void sqlite3PagerPagecount(Pager*, int*);
int sqlite3PagerBegin(Pager*, int exFlag, int);
int sqlite3PagerCommitPhaseOne(Pager*,const char *zMaster, int);
int sqlite3PagerExclusiveLock(Pager*, DbPage *pPage1);
int sqlite3PagerSync(Pager *pPager, const char *zMaster);
int sqlite3PagerCommitPhaseTwo(Pager*);
int sqlite3PagerRollback(Pager*);
int sqlite3PagerOpenSavepoint(Pager *pPager, int n);
int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint);
int sqlite3PagerSharedLock(Pager *pPager);








|







146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
void *sqlite3PagerGetData(DbPage *); 
void *sqlite3PagerGetExtra(DbPage *); 

/* Functions used to manage pager transactions and savepoints. */
void sqlite3PagerPagecount(Pager*, int*);
int sqlite3PagerBegin(Pager*, int exFlag, int);
int sqlite3PagerCommitPhaseOne(Pager*,const char *zMaster, int);
int sqlite3PagerExclusiveLock(Pager*);
int sqlite3PagerSync(Pager *pPager, const char *zMaster);
int sqlite3PagerCommitPhaseTwo(Pager*);
int sqlite3PagerRollback(Pager*);
int sqlite3PagerOpenSavepoint(Pager *pPager, int n);
int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint);
int sqlite3PagerSharedLock(Pager *pPager);

Changes to src/vdbeaux.c.

2016
2017
2018
2019
2020
2021
2022
2023


2024
2025
2026
2027
2028
2029
2030
  ** file is required for an atomic commit.
  */ 
  for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ 
    Btree *pBt = db->aDb[i].pBt;
    if( sqlite3BtreeIsInTrans(pBt) ){
      needXcommit = 1;
      if( i!=1 ) nTrans++;
      rc = sqlite3BtreeExclusiveLock(pBt);


    }
  }

  if( db->bUnlocked && (rc & 0xFF)==SQLITE_BUSY ){
    /* An SQLITE_BUSY or SQLITE_BUSY_SNAPSHOT was encountered while 
    ** attempting to take the WRITER lock on a wal file. Release the
    ** WRITER locks on all wal files and return early.  */







|
>
>







2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
  ** file is required for an atomic commit.
  */ 
  for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ 
    Btree *pBt = db->aDb[i].pBt;
    if( sqlite3BtreeIsInTrans(pBt) ){
      needXcommit = 1;
      if( i!=1 ) nTrans++;
      sqlite3BtreeEnter(pBt);
      rc = sqlite3PagerExclusiveLock(sqlite3BtreePager(pBt));
      sqlite3BtreeLeave(pBt);
    }
  }

  if( db->bUnlocked && (rc & 0xFF)==SQLITE_BUSY ){
    /* An SQLITE_BUSY or SQLITE_BUSY_SNAPSHOT was encountered while 
    ** attempting to take the WRITER lock on a wal file. Release the
    ** WRITER locks on all wal files and return early.  */

Changes to src/wal.c.

2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
....
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
....
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
/* 
** TODO: Combine some code with BeginWriteTransaction()
**
** This function is only ever called when committing a "BEGIN UNLOCKED"
** transaction. It may be assumed that no frames have been written to
** the wal file.
*/
int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pList, PgHdr *pPage1){
  int rc = walWriteLock(pWal);

  /* If the database has been modified since this transaction was started,
  ** check if it is still possible to commit. The transaction can be 
  ** committed if:
  **
  **   a) None of the pages in pList have been modified since the 
................................................................................
  **   b) The database schema cookie has not been modified since the
  **      transaction was started.
  */
  if( rc==SQLITE_OK ){
    volatile WalIndexHdr *pHead;    /* Head of the wal file */
    pHead = walIndexHdr(pWal);
    if( memcmp(&pWal->hdr, (void*)pHead, sizeof(WalIndexHdr))!=0 ){
      int bSeenPage1 = 0;           /* True if page 1 is in list pList */
  
      /* TODO: Is this safe? Because it holds the WRITER lock this thread
      ** has exclusive access to the live header, but might it be corrupt? */
      PgHdr *pPg;
      u32 iLast = pHead->mxFrame;
      for(pPg=pList; rc==SQLITE_OK && pPg; pPg=pPg->pDirty){
        u32 iSlot = 0;
        rc = walFindFrame(pWal, pPg->pgno, iLast, &iSlot);
        if( iSlot>pWal->hdr.mxFrame ){
          sqlite3_log(SQLITE_OK,
              "cannot commit UNLOCKED transaction (conflict at page %d)",
              (int)pPg->pgno
          );
          rc = SQLITE_BUSY_SNAPSHOT;
        }
        if( pPg->pgno==1 ) bSeenPage1 = 1;
      }
  
      /* If the current transaction does not modify page 1 of the database,
      ** check if page 1 has been modified since the transaction was started.
      ** If it has, check if the schema cookie value (the 4 bytes beginning at
      ** byte offset 40) is the same as it is on pPage1. If not, this indicates
      ** that the current head of the wal file uses a different schema than 
      ** the snapshot against which the current transaction was prepared. Return
      ** SQLITE_BUSY_SNAPSHOT in this case.  */
      if( rc==SQLITE_OK && bSeenPage1==0 ){
        u32 iSlot = 0;
        rc = walFindFrame(pWal, 1, iLast, &iSlot);
        if( rc==SQLITE_OK && iSlot>pWal->hdr.mxFrame ){
          u8 aNew[4];
          u8 *aOld = &((u8*)pPage1->pData)[40];
          int sz;
          i64 iOffset;
          sz = pWal->hdr.szPage;
          sz = (sz&0xfe00) + ((sz&0x0001)<<16);
          iOffset = walFrameOffset(iSlot, sz) + WAL_FRAME_HDRSIZE + 40;
          rc = sqlite3OsRead(pWal->pWalFd, aNew, sizeof(aNew), iOffset);
          if( rc==SQLITE_OK && memcmp(aOld, aNew, sizeof(aNew)) ){
            /* TODO: New error code? SQLITE_BUSY_SCHEMA. */
            rc = SQLITE_BUSY_SNAPSHOT;
          }
        }
      }
    }
  }

  return rc;
}

................................................................................
** heap-memory for the wal-index. Otherwise, if the argument is NULL or the
** WAL module is using shared-memory, return false. 
*/
int sqlite3WalHeapMemory(Wal *pWal){
  return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE );
}

/*
** Return true if in a write transaction, false otherwise.
*/
int sqlite3WalIsInTrans(Wal *pWal){
  return (int)pWal->writeLock;
}

#ifdef SQLITE_ENABLE_ZIPVFS
/*
** If the argument is not NULL, it points to a Wal object that holds a
** read-lock. This function returns the database page-size if it is known,
** or zero if it is not (or if pWal is NULL).
*/
int sqlite3WalFramesize(Wal *pWal){
  assert( pWal==0 || pWal->readLock>=0 );
  return (pWal ? pWal->szPage : 0);
}
#endif

#endif /* #ifndef SQLITE_OMIT_WAL */







|







 







|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







<
<
<
<
<
<
<













2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
....
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621














2622
2623
2624
2625
2626
2627
2628
....
3275
3276
3277
3278
3279
3280
3281







3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
/* 
** TODO: Combine some code with BeginWriteTransaction()
**
** This function is only ever called when committing a "BEGIN UNLOCKED"
** transaction. It may be assumed that no frames have been written to
** the wal file.
*/
int sqlite3WalLockForCommit(Wal *pWal, Bitvec *pAllRead){
  int rc = walWriteLock(pWal);

  /* If the database has been modified since this transaction was started,
  ** check if it is still possible to commit. The transaction can be 
  ** committed if:
  **
  **   a) None of the pages in pList have been modified since the 
................................................................................
  **   b) The database schema cookie has not been modified since the
  **      transaction was started.
  */
  if( rc==SQLITE_OK ){
    volatile WalIndexHdr *pHead;    /* Head of the wal file */
    pHead = walIndexHdr(pWal);
    if( memcmp(&pWal->hdr, (void*)pHead, sizeof(WalIndexHdr))!=0 ){
      /* TODO: Is this safe? Because it holds the WRITER lock this thread
      ** has exclusive access to the live header, but might it be corrupt? 
      ** This code should check that the wal-index-header is Ok, and return
      ** SQLITE_BUSY_SNAPSHOT if it is not. */
      int iHash;
      int iLastHash = walFramePage(pHead->mxFrame);
      for(iHash=walFramePage(pWal->hdr.mxFrame+1); iHash<=iLastHash; iHash++){
        volatile ht_slot *aHash;
        volatile u32 *aPgno;
        u32 iZero;

        rc = walHashGet(pWal, iHash, &aHash, &aPgno, &iZero);
        if( rc==SQLITE_OK ){
          int i;
          int iMin = (pWal->hdr.mxFrame+1 - iZero);
          int iMax = (iHash==0) ? HASHTABLE_NPAGE_ONE : HASHTABLE_NPAGE;
          if( iMin<1 ) iMin = 1;
          if( iMax>pHead->mxFrame ) iMax = pHead->mxFrame;
          for(i=iMin; i<=iMax; i++){
            if( sqlite3BitvecTestNotNull(pAllRead, aPgno[i]) ){
              sqlite3_log(SQLITE_OK,
                  "cannot commit UNLOCKED transaction (conflict at page %d)",
                  (int)aPgno[i]
              );
              rc = SQLITE_BUSY_SNAPSHOT;
            }
          }
        }
        if( rc!=SQLITE_OK ) break;














      }
    }
  }

  return rc;
}

................................................................................
** heap-memory for the wal-index. Otherwise, if the argument is NULL or the
** WAL module is using shared-memory, return false. 
*/
int sqlite3WalHeapMemory(Wal *pWal){
  return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE );
}








#ifdef SQLITE_ENABLE_ZIPVFS
/*
** If the argument is not NULL, it points to a Wal object that holds a
** read-lock. This function returns the database page-size if it is known,
** or zero if it is not (or if pWal is NULL).
*/
int sqlite3WalFramesize(Wal *pWal){
  assert( pWal==0 || pWal->readLock>=0 );
  return (pWal ? pWal->szPage : 0);
}
#endif

#endif /* #ifndef SQLITE_OMIT_WAL */

Changes to src/wal.h.

123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/* Return true if the argument is non-NULL and the WAL module is using
** heap-memory for the wal-index. Otherwise, if the argument is NULL or the
** WAL module is using shared-memory, return false. 
*/
int sqlite3WalHeapMemory(Wal *pWal);

/* Return true if the WRITER lock is held. False otherwise. */
int sqlite3WalIsInTrans(Wal *pWal);
int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pDirtyList, PgHdr *pPage1); 
int sqlite3WalCommitRequiresUpgrade(Wal *pWal);

#ifdef SQLITE_ENABLE_ZIPVFS
/* If the WAL file is not empty, return the number of bytes of content
** stored in each frame (i.e. the db page-size when the WAL was created).
*/
int sqlite3WalFramesize(Wal *pWal);
#endif

#endif /* ifndef SQLITE_OMIT_WAL */
#endif /* _WAL_H_ */







<
|











123
124
125
126
127
128
129

130
131
132
133
134
135
136
137
138
139
140
141
/* Return true if the argument is non-NULL and the WAL module is using
** heap-memory for the wal-index. Otherwise, if the argument is NULL or the
** WAL module is using shared-memory, return false. 
*/
int sqlite3WalHeapMemory(Wal *pWal);

/* Return true if the WRITER lock is held. False otherwise. */

int sqlite3WalLockForCommit(Wal *pWal, Bitvec *pRead);
int sqlite3WalCommitRequiresUpgrade(Wal *pWal);

#ifdef SQLITE_ENABLE_ZIPVFS
/* If the WAL file is not empty, return the number of bytes of content
** stored in each frame (i.e. the db page-size when the WAL was created).
*/
int sqlite3WalFramesize(Wal *pWal);
#endif

#endif /* ifndef SQLITE_OMIT_WAL */
#endif /* _WAL_H_ */

Changes to test/unlocked.test.

296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
...
320
321
322
323
324
325
326






327
328
329
330
331
332
333
334
335
336
337
338
339

  do_test 2.$tn.5.4 {
    sql2 { PRAGMA integrity_check }
  } {ok}
  catch { sql1 ROLLBACK }

  #-----------------------------------------------------------------------
  # The "schema cookie" issue.
  #
  # 1. Begin an UNLOCKED write to "t1" using [db]
  #
  # 2. Lots of inserts into t2. Enough to grow the db file.
  #
  # 3. Check that the UNLOCKED transaction can still be committed.
  #
  do_test 2.$tn.6.1 {
    sql1 {
      BEGIN UNLOCKED;
        INSERT INTO t1 VALUES(6, 'six');
    }
  } {}
................................................................................
      WITH src(a,b) AS (
        VALUES(1,1) UNION ALL SELECT a+1,b+1 FROM src WHERE a<10000
      ) INSERT INTO t2 SELECT * FROM src;
    }
  } {}

  do_test 2.$tn.6.3 {






    sql1 {
      SELECT count(*) FROM t2;
      COMMIT;
      SELECT count(*) FROM t2;
    }
  } {1 10001}

  #-----------------------------------------------------------------------
  # 
  # 1. Begin an big UNLOCKED write to "t1" using [db] - large enough to
  #    grow the db file.
  #
  # 2. Lots of inserts into t2. Also enough to grow the db file.







<



|

|







 







>
>
>
>
>
>

|
<


|







296
297
298
299
300
301
302

303
304
305
306
307
308
309
310
311
312
313
314
315
...
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333

334
335
336
337
338
339
340
341
342
343

  do_test 2.$tn.5.4 {
    sql2 { PRAGMA integrity_check }
  } {ok}
  catch { sql1 ROLLBACK }

  #-----------------------------------------------------------------------

  #
  # 1. Begin an UNLOCKED write to "t1" using [db]
  #
  # 2. Lots of inserts into t2. Enough to grow the db file and modify page 1.
  #
  # 3. Check that the UNLOCKED transaction can not be committed.
  #
  do_test 2.$tn.6.1 {
    sql1 {
      BEGIN UNLOCKED;
        INSERT INTO t1 VALUES(6, 'six');
    }
  } {}
................................................................................
      WITH src(a,b) AS (
        VALUES(1,1) UNION ALL SELECT a+1,b+1 FROM src WHERE a<10000
      ) INSERT INTO t2 SELECT * FROM src;
    }
  } {}

  do_test 2.$tn.6.3 {
    sql1 { SELECT count(*) FROM t2 }
    list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db]
  } {1 {database is locked} SQLITE_BUSY_SNAPSHOT}
  sql1 ROLLBACK

  do_test 2.$tn.6.4 {
    sql1 {
      SELECT count(*) FROM t1;

      SELECT count(*) FROM t2;
    }
  } {5 10001}

  #-----------------------------------------------------------------------
  # 
  # 1. Begin an big UNLOCKED write to "t1" using [db] - large enough to
  #    grow the db file.
  #
  # 2. Lots of inserts into t2. Also enough to grow the db file.