/ Check-in [402780a9]
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. Cherry-pick of check-in [dd03a2802f3f27]
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | branch-3.8.7
Files: files | file ages | folders
SHA1: 402780a9c8df9e7ea898bdca49c1191042fe387a
User & Date: drh 2014-11-13 13:42:39
Context
2014-11-14
15:42
Do not automatically remove the DISTINCT keyword from "a IN (SELECT DISTINCT ...)" expressions. Fix for [db87229497]. check-in: 98457a57 user: drh tags: branch-3.8.7
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
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
14:12
Fix the %c format character in sqlite3VXPrintf() so that it correctly handles precisions larger than 70. check-in: 839a6df9 user: drh tags: branch-3.8.7
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/btree.c.

  3468   3468   ** references.  Or if the writeOnly flag is set to 1, then only
  3469   3469   ** trip write cursors and leave read cursors unchanged.
  3470   3470   **
  3471   3471   ** Every cursor is a candidate to be tripped, including cursors
  3472   3472   ** that belong to other database connections that happen to be
  3473   3473   ** sharing the cache with pBtree.
  3474   3474   **
  3475         -** This routine gets called when a rollback occurs.  The writeOnly
  3476         -** flag is set to 1 if the transaction did not make any schema
  3477         -** changes, in which case the read cursors can continue operating.
  3478         -** If schema changes did occur in the transaction, then both read
  3479         -** and write cursors must both be tripped.
         3475  +** This routine gets called when a rollback occurs. If the writeOnly
         3476  +** flag is true, then only write-cursors need be tripped - read-only
         3477  +** cursors save their current positions so that they may continue 
         3478  +** following the rollback. Or, if writeOnly is false, all cursors are 
         3479  +** tripped. In general, writeOnly is false if the transaction being
         3480  +** rolled back modified the database schema. In this case b-tree root
         3481  +** pages may be moved or deleted from the database altogether, making
         3482  +** it unsafe for read cursors to continue.
         3483  +**
         3484  +** If the writeOnly flag is true and an error is encountered while 
         3485  +** saving the current position of a read-only cursor, all cursors, 
         3486  +** including all read-cursors are tripped.
         3487  +**
         3488  +** SQLITE_OK is returned if successful, or if an error occurs while
         3489  +** saving a cursor position, an SQLite error code.
  3480   3490   */
  3481         -void sqlite3BtreeTripAllCursors(Btree *pBtree, int errCode, int writeOnly){
         3491  +int sqlite3BtreeTripAllCursors(Btree *pBtree, int errCode, int writeOnly){
  3482   3492     BtCursor *p;
         3493  +  int rc = SQLITE_OK;
         3494  +
  3483   3495     assert( (writeOnly==0 || writeOnly==1) && BTCF_WriteFlag==1 );
  3484         -  if( pBtree==0 ) return;
  3485         -  sqlite3BtreeEnter(pBtree);
  3486         -  for(p=pBtree->pBt->pCursor; p; p=p->pNext){
  3487         -    int i;
  3488         -    if( writeOnly && (p->curFlags & BTCF_WriteFlag)==0 ) continue;
  3489         -    sqlite3BtreeClearCursor(p);
  3490         -    p->eState = CURSOR_FAULT;
  3491         -    p->skipNext = errCode;
  3492         -    for(i=0; i<=p->iPage; i++){
  3493         -      releasePage(p->apPage[i]);
  3494         -      p->apPage[i] = 0;
         3496  +  if( pBtree ){
         3497  +    sqlite3BtreeEnter(pBtree);
         3498  +    for(p=pBtree->pBt->pCursor; p; p=p->pNext){
         3499  +      int i;
         3500  +      if( writeOnly && (p->curFlags & BTCF_WriteFlag)==0 ){
         3501  +        if( p->eState==CURSOR_VALID ){
         3502  +          int rc = saveCursorPosition(p);
         3503  +          if( rc!=SQLITE_OK ){
         3504  +            (void)sqlite3BtreeTripAllCursors(pBtree, rc, 0);
         3505  +            break;
         3506  +          }
         3507  +        }
         3508  +      }else{
         3509  +        sqlite3BtreeClearCursor(p);
         3510  +        p->eState = CURSOR_FAULT;
         3511  +        p->skipNext = errCode;
         3512  +      }
         3513  +      for(i=0; i<=p->iPage; i++){
         3514  +        releasePage(p->apPage[i]);
         3515  +        p->apPage[i] = 0;
         3516  +      }
  3495   3517       }
         3518  +    sqlite3BtreeLeave(pBtree);
  3496   3519     }
  3497         -  sqlite3BtreeLeave(pBtree);
         3520  +  return rc;
  3498   3521   }
  3499   3522   
  3500   3523   /*
  3501   3524   ** Rollback the transaction in progress.
  3502   3525   **
  3503   3526   ** If tripCode is not SQLITE_OK then cursors will be invalidated (tripped).
  3504   3527   ** Only write cursors are tripped if writeOnly is true but all cursors are
................................................................................
  3519   3542     if( tripCode==SQLITE_OK ){
  3520   3543       rc = tripCode = saveAllCursors(pBt, 0, 0);
  3521   3544       if( rc ) writeOnly = 0;
  3522   3545     }else{
  3523   3546       rc = SQLITE_OK;
  3524   3547     }
  3525   3548     if( tripCode ){
  3526         -    sqlite3BtreeTripAllCursors(p, tripCode, writeOnly);
         3549  +    int rc2 = sqlite3BtreeTripAllCursors(p, tripCode, writeOnly);
         3550  +    assert( rc==SQLITE_OK || (writeOnly==0 && rc2==SQLITE_OK) );
         3551  +    if( rc2!=SQLITE_OK ) rc = rc2;
  3527   3552     }
  3528   3553     btreeIntegrity(p);
  3529   3554   
  3530   3555     if( p->inTrans==TRANS_WRITE ){
  3531   3556       int rc2;
  3532   3557   
  3533   3558       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.

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