/ Check-in [dd03a280]
Login

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

Overview
Comment:When a transaction or savepoint rollback occurs, save the positions of all open read-cursors so that they can be restored following the rollback operation.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: dd03a2802f3f276525f3cef9a93f825dd8606626
User & Date: dan 2014-11-12 14:56:02
References
2014-11-13
13:42
When a transaction or savepoint rollback occurs, save the positions of all open read-cursors so that they can be restored following the rollback operation. Cherry-pick of check-in [dd03a2802f3f27] check-in: 402780a9 user: drh tags: branch-3.8.7
Context
2014-11-13
13:42
When a transaction or savepoint rollback occurs, save the positions of all open read-cursors so that they can be restored following the rollback operation. Cherry-pick of check-in [dd03a2802f3f27] check-in: 402780a9 user: drh tags: branch-3.8.7
2014-11-12
17:45
Add further tests for rollback operations in the presence of ongoing selects. check-in: eaf3aae0 user: dan tags: trunk
14:56
When a transaction or savepoint rollback occurs, save the positions of all open read-cursors so that they can be restored following the rollback operation. check-in: dd03a280 user: dan tags: trunk
2014-11-11
19:07
Remove some calls to the 'breakpoint' test command. check-in: 1412fcc4 user: mistachkin tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/btree.c.

  3510   3510   ** references.  Or if the writeOnly flag is set to 1, then only
  3511   3511   ** trip write cursors and leave read cursors unchanged.
  3512   3512   **
  3513   3513   ** Every cursor is a candidate to be tripped, including cursors
  3514   3514   ** that belong to other database connections that happen to be
  3515   3515   ** sharing the cache with pBtree.
  3516   3516   **
  3517         -** This routine gets called when a rollback occurs.  The writeOnly
  3518         -** flag is set to 1 if the transaction did not make any schema
  3519         -** changes, in which case the read cursors can continue operating.
  3520         -** If schema changes did occur in the transaction, then both read
  3521         -** and write cursors must both be tripped.
         3517  +** This routine gets called when a rollback occurs. If the writeOnly
         3518  +** flag is true, then only write-cursors need be tripped - read-only
         3519  +** cursors save their current positions so that they may continue 
         3520  +** following the rollback. Or, if writeOnly is false, all cursors are 
         3521  +** tripped. In general, writeOnly is false if the transaction being
         3522  +** rolled back modified the database schema. In this case b-tree root
         3523  +** pages may be moved or deleted from the database altogether, making
         3524  +** it unsafe for read cursors to continue.
         3525  +**
         3526  +** If the writeOnly flag is true and an error is encountered while 
         3527  +** saving the current position of a read-only cursor, all cursors, 
         3528  +** including all read-cursors are tripped.
         3529  +**
         3530  +** SQLITE_OK is returned if successful, or if an error occurs while
         3531  +** saving a cursor position, an SQLite error code.
  3522   3532   */
  3523         -void sqlite3BtreeTripAllCursors(Btree *pBtree, int errCode, int writeOnly){
         3533  +int sqlite3BtreeTripAllCursors(Btree *pBtree, int errCode, int writeOnly){
  3524   3534     BtCursor *p;
         3535  +  int rc = SQLITE_OK;
         3536  +
  3525   3537     assert( (writeOnly==0 || writeOnly==1) && BTCF_WriteFlag==1 );
  3526         -  if( pBtree==0 ) return;
  3527         -  sqlite3BtreeEnter(pBtree);
  3528         -  for(p=pBtree->pBt->pCursor; p; p=p->pNext){
  3529         -    int i;
  3530         -    if( writeOnly && (p->curFlags & BTCF_WriteFlag)==0 ) continue;
  3531         -    sqlite3BtreeClearCursor(p);
  3532         -    p->eState = CURSOR_FAULT;
  3533         -    p->skipNext = errCode;
  3534         -    for(i=0; i<=p->iPage; i++){
  3535         -      releasePage(p->apPage[i]);
  3536         -      p->apPage[i] = 0;
         3538  +  if( pBtree ){
         3539  +    sqlite3BtreeEnter(pBtree);
         3540  +    for(p=pBtree->pBt->pCursor; p; p=p->pNext){
         3541  +      int i;
         3542  +      if( writeOnly && (p->curFlags & BTCF_WriteFlag)==0 ){
         3543  +        if( p->eState==CURSOR_VALID ){
         3544  +          int rc = saveCursorPosition(p);
         3545  +          if( rc!=SQLITE_OK ){
         3546  +            (void)sqlite3BtreeTripAllCursors(pBtree, rc, 0);
         3547  +            break;
         3548  +          }
         3549  +        }
         3550  +      }else{
         3551  +        sqlite3BtreeClearCursor(p);
         3552  +        p->eState = CURSOR_FAULT;
         3553  +        p->skipNext = errCode;
         3554  +      }
         3555  +      for(i=0; i<=p->iPage; i++){
         3556  +        releasePage(p->apPage[i]);
         3557  +        p->apPage[i] = 0;
         3558  +      }
  3537   3559       }
         3560  +    sqlite3BtreeLeave(pBtree);
  3538   3561     }
  3539         -  sqlite3BtreeLeave(pBtree);
         3562  +  return rc;
  3540   3563   }
  3541   3564   
  3542   3565   /*
  3543   3566   ** Rollback the transaction in progress.
  3544   3567   **
  3545   3568   ** If tripCode is not SQLITE_OK then cursors will be invalidated (tripped).
  3546   3569   ** Only write cursors are tripped if writeOnly is true but all cursors are
................................................................................
  3561   3584     if( tripCode==SQLITE_OK ){
  3562   3585       rc = tripCode = saveAllCursors(pBt, 0, 0);
  3563   3586       if( rc ) writeOnly = 0;
  3564   3587     }else{
  3565   3588       rc = SQLITE_OK;
  3566   3589     }
  3567   3590     if( tripCode ){
  3568         -    sqlite3BtreeTripAllCursors(p, tripCode, writeOnly);
         3591  +    int rc2 = sqlite3BtreeTripAllCursors(p, tripCode, writeOnly);
         3592  +    assert( rc==SQLITE_OK || (writeOnly==0 && rc2==SQLITE_OK) );
         3593  +    if( rc2!=SQLITE_OK ) rc = rc2;
  3569   3594     }
  3570   3595     btreeIntegrity(p);
  3571   3596   
  3572   3597     if( p->inTrans==TRANS_WRITE ){
  3573   3598       int rc2;
  3574   3599   
  3575   3600       assert( TRANS_WRITE==pBt->inTransaction );

Changes to src/btree.h.

   112    112   */
   113    113   #define BTREE_INTKEY     1    /* Table has only 64-bit signed integer keys */
   114    114   #define BTREE_BLOBKEY    2    /* Table has keys only - no data */
   115    115   
   116    116   int sqlite3BtreeDropTable(Btree*, int, int*);
   117    117   int sqlite3BtreeClearTable(Btree*, int, int*);
   118    118   int sqlite3BtreeClearTableOfCursor(BtCursor*);
   119         -void sqlite3BtreeTripAllCursors(Btree*, int, int);
          119  +int sqlite3BtreeTripAllCursors(Btree*, int, int);
   120    120   
   121    121   void sqlite3BtreeGetMeta(Btree *pBtree, int idx, u32 *pValue);
   122    122   int sqlite3BtreeUpdateMeta(Btree*, int idx, u32 value);
   123    123   
   124    124   int sqlite3BtreeNewDb(Btree *p);
   125    125   
   126    126   /*

Changes to src/vdbe.c.

  2826   2826           rc = p->rc;
  2827   2827         }else{
  2828   2828           int isSchemaChange;
  2829   2829           iSavepoint = db->nSavepoint - iSavepoint - 1;
  2830   2830           if( p1==SAVEPOINT_ROLLBACK ){
  2831   2831             isSchemaChange = (db->flags & SQLITE_InternChanges)!=0;
  2832   2832             for(ii=0; ii<db->nDb; ii++){
  2833         -            sqlite3BtreeTripAllCursors(db->aDb[ii].pBt, SQLITE_ABORT,
         2833  +            rc = sqlite3BtreeTripAllCursors(db->aDb[ii].pBt, SQLITE_ABORT,
  2834   2834                                          isSchemaChange==0);
         2835  +            if( rc!=SQLITE_OK ) goto abort_due_to_error;
  2835   2836             }
  2836   2837           }else{
  2837   2838             isSchemaChange = 0;
  2838   2839           }
  2839   2840           for(ii=0; ii<db->nDb; ii++){
  2840   2841             rc = sqlite3BtreeSavepoint(db->aDb[ii].pBt, p1, iSavepoint);
  2841   2842             if( rc!=SQLITE_OK ){

Added test/rollback2.test.

            1  +# 2014 November 12
            2  +#
            3  +# The author disclaims copyright to this source code.  In place of
            4  +# a legal notice, here is a blessing:
            5  +#
            6  +#    May you do good and not evil.
            7  +#    May you find forgiveness for yourself and forgive others.
            8  +#    May you share freely, never taking more than you give.
            9  +#
           10  +#***********************************************************************
           11  +#
           12  +
           13  +set testdir [file dirname $argv0]
           14  +source $testdir/tester.tcl
           15  +set ::testprefix rollback2
           16  +
           17  +proc int2hex {i} { format %.2X $i }
           18  +db func int2hex int2hex
           19  +
           20  +do_execsql_test 1.0 {
           21  +  SELECT int2hex(0), int2hex(100), int2hex(255)
           22  +} {00 64 FF}
           23  +do_execsql_test 1.1 {
           24  +  CREATE TABLE t1(i, h);
           25  +  CREATE INDEX i1 ON t1(h);
           26  +  WITH data(a, b) AS (
           27  +    SELECT 1, int2hex(1)
           28  +      UNION ALL
           29  +    SELECT a+1, int2hex(a+1) FROM data WHERE a<40
           30  +  )
           31  +  INSERT INTO t1 SELECT * FROM data;
           32  +} {}
           33  +
           34  +
           35  +proc do_rollback_test {tn args} {
           36  +  set A(-setup)    ""
           37  +  set A(-select)   ""
           38  +  set A(-result)   ""
           39  +  set A(-rollback) ROLLBACK
           40  +
           41  +  array set O $args
           42  +  foreach k [array names O] {
           43  +    if {[info exists A($k)]==0} { error "unknown option: $k" }
           44  +    set A($k) $O($k)
           45  +  }
           46  +
           47  +  for {set iRollback 0} 1 {incr iRollback} {
           48  +    catch { db eval ROLLBACK }
           49  +    set res [list]
           50  +    db eval $A(-setup)
           51  +
           52  +    set i 0
           53  +    db eval $A(-select) x {
           54  +      if {$i==$iRollback} { db eval $A(-rollback) }
           55  +      foreach k $x(*) { lappend res $x($k) }
           56  +      incr i
           57  +    }
           58  +
           59  +    do_test $tn.$iRollback [list set {} $res] [list {*}$A(-result)]
           60  +    if {$i < $iRollback} break
           61  +  }
           62  +}
           63  +
           64  +do_rollback_test 2 -setup {
           65  +  BEGIN;
           66  +    DELETE FROM t1 WHERE (i%2)==1;
           67  +} -select {
           68  +  SELECT i FROM t1 WHERE (i%2)==0
           69  +} -result {
           70  +  2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40
           71  +}
           72  +
           73  +finish_test
           74  +