SQLite

Check-in [98a53d91f6]
Login

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

Overview
Comment:Add savepoint2.test, a file containing savepoint tests similar to tests in trans.test and avtrans.test. And a few savepoint bug fixes. (CVS 6039)
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 98a53d91f6c0c2692d3b56687fdaba8eeab0959d
User & Date: danielk1977 2008-12-18 15:45:07.000
Context
2008-12-18
18:31
Increase test coverage of new savepoint code. (CVS 6040) (check-in: d915718d0b user: danielk1977 tags: trunk)
15:45
Add savepoint2.test, a file containing savepoint tests similar to tests in trans.test and avtrans.test. And a few savepoint bug fixes. (CVS 6039) (check-in: 98a53d91f6 user: danielk1977 tags: trunk)
05:30
Fix a bug in icuOpen() in fts2. (CVS 6038) (check-in: b9c722bd96 user: danielk1977 tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/btree.c.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
** 2004 April 6
**
** 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.
**
*************************************************************************
** $Id: btree.c,v 1.549 2008/12/17 17:30:26 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** See the header comment on "btreeInt.h" for additional information.
** Including a description of file format and an overview of operation.
*/
#include "btreeInt.h"












|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
** 2004 April 6
**
** 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.
**
*************************************************************************
** $Id: btree.c,v 1.550 2008/12/18 15:45:07 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** See the header comment on "btreeInt.h" for additional information.
** Including a description of file format and an overview of operation.
*/
#include "btreeInt.h"

2056
2057
2058
2059
2060
2061
2062




2063
2064

2065
2066
2067
2068
2069
2070
2071
    }
#endif
  }


trans_begun:
  if( rc==SQLITE_OK && wrflag ){




    rc = sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint);
  }

  btreeIntegrity(p);
  sqlite3BtreeLeave(p);
  return rc;
}

#ifndef SQLITE_OMIT_AUTOVACUUM








>
>
>
>


>







2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
    }
#endif
  }


trans_begun:
  if( rc==SQLITE_OK && wrflag ){
    /* This call makes sure that the pager has the correct number of
    ** open savepoints. If the second parameter is greater than 0 and
    ** the sub-journal is not already open, then it will be opened here.
    */
    rc = sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint);
  }

  btreeIntegrity(p);
  sqlite3BtreeLeave(p);
  return rc;
}

#ifndef SQLITE_OMIT_AUTOVACUUM

2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
      rc = SQLITE_OK;
    }else{
      /* At the pager level, a statement transaction is a savepoint with
      ** an index greater than all savepoints created explicitly using
      ** SQL statements. It is illegal to open, release or rollback any
      ** such savepoints while the statement transaction savepoint is active.
      */
      int iStmtpoint = p->db->nSavepoint + 1;
      rc = sqlite3PagerOpenSavepoint(pBt->pPager, iStmtpoint);
    }
    pBt->inStmt = 1;
  }
  sqlite3BtreeLeave(p);
  return rc;
}








<
|







2741
2742
2743
2744
2745
2746
2747

2748
2749
2750
2751
2752
2753
2754
2755
      rc = SQLITE_OK;
    }else{
      /* At the pager level, a statement transaction is a savepoint with
      ** an index greater than all savepoints created explicitly using
      ** SQL statements. It is illegal to open, release or rollback any
      ** such savepoints while the statement transaction savepoint is active.
      */

      rc = sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint+1);
    }
    pBt->inStmt = 1;
  }
  sqlite3BtreeLeave(p);
  return rc;
}

2793
2794
2795
2796
2797
2798
2799
2800
2801






2802
2803
2804
2805
2806
2807
2808
  sqlite3BtreeLeave(p);
  return rc;
}

/*
** The second argument to this function, op, is always SAVEPOINT_ROLLBACK
** or SAVEPOINT_RELEASE. This function either releases or rolls back the
** savepoint identified by parameter iSavepoint, depending on the value of
** op.






*/
int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){
  int rc = SQLITE_OK;
  if( p && p->inTrans==TRANS_WRITE ){
    BtShared *pBt = p->pBt;
    assert( pBt->inStmt==0 );
    assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK );







|
|
>
>
>
>
>
>







2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
  sqlite3BtreeLeave(p);
  return rc;
}

/*
** The second argument to this function, op, is always SAVEPOINT_ROLLBACK
** or SAVEPOINT_RELEASE. This function either releases or rolls back the
** savepoint identified by parameter iSavepoint, depending on the value 
** of op.
**
** Normally, iSavepoint is greater than or equal to zero. However, if op is
** SAVEPOINT_ROLLBACK, then iSavepoint may also be -1. In this case the 
** contents of the entire transaction are rolled back. This is different
** from a normal transaction rollback, as no locks are released and the
** transaction remains open.
*/
int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){
  int rc = SQLITE_OK;
  if( p && p->inTrans==TRANS_WRITE ){
    BtShared *pBt = p->pBt;
    assert( pBt->inStmt==0 );
    assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK );
Changes to src/pager.c.
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
** The pager is used to access a database disk file.  It implements
** atomic commit and rollback through the use of a journal file that
** is separate from the database file.  The pager also implements file
** locking to prevent two processes from writing the same database
** file simultaneously, or one process from reading the database while
** another is writing.
**
** @(#) $Id: pager.c,v 1.515 2008/12/17 17:30:26 danielk1977 Exp $
*/
#ifndef SQLITE_OMIT_DISKIO
#include "sqliteInt.h"

/*
** Macros for troubleshooting.  Normally turned off
*/







|







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
** The pager is used to access a database disk file.  It implements
** atomic commit and rollback through the use of a journal file that
** is separate from the database file.  The pager also implements file
** locking to prevent two processes from writing the same database
** file simultaneously, or one process from reading the database while
** another is writing.
**
** @(#) $Id: pager.c,v 1.516 2008/12/18 15:45:07 danielk1977 Exp $
*/
#ifndef SQLITE_OMIT_DISKIO
#include "sqliteInt.h"

/*
** Macros for troubleshooting.  Normally turned off
*/
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
    u32 nJRec;         /* Number of Journal Records */
    u32 dummy;
    rc = readJournalHdr(pPager, szJ, &nJRec, &dummy);
    assert( rc!=SQLITE_DONE );
    if( nJRec==0 ){
      nJRec = (szJ - pPager->journalOff) / (pPager->pageSize+8);
    }
    for(ii=0; rc==SQLITE_OK && ii<nJRec; ii++){
      rc = pager_playback_one_page(pPager, 1, pPager->journalOff, pDone);
      assert( rc!=SQLITE_DONE );
    }
  }
  assert( rc!=SQLITE_OK || pPager->journalOff==szJ );

  /* Now roll back pages from the sub-journal. */







|







1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
    u32 nJRec;         /* Number of Journal Records */
    u32 dummy;
    rc = readJournalHdr(pPager, szJ, &nJRec, &dummy);
    assert( rc!=SQLITE_DONE );
    if( nJRec==0 ){
      nJRec = (szJ - pPager->journalOff) / (pPager->pageSize+8);
    }
    for(ii=0; rc==SQLITE_OK && ii<nJRec && pPager->journalOff<szJ; ii++){
      rc = pager_playback_one_page(pPager, 1, pPager->journalOff, pDone);
      assert( rc!=SQLITE_DONE );
    }
  }
  assert( rc!=SQLITE_OK || pPager->journalOff==szJ );

  /* Now roll back pages from the sub-journal. */
2433
2434
2435
2436
2437
2438
2439

2440
2441
2442
2443
2444
2445
2446
    ** than Pager.dbSize, this means sqlite3PagerTruncate() was called to
    ** make the file smaller (presumably by auto-vacuum code). Do not write
    ** any such pages to the file.
    */
    if( pList->pgno<=pPager->dbSize && 0==(pList->flags&PGHDR_DONT_WRITE) ){
      i64 offset = (pList->pgno-1)*(i64)pPager->pageSize;
      char *pData = CODEC2(pPager, pList->pData, pList->pgno, 6);

      PAGERTRACE4("STORE %d page %d hash(%08x)\n",
                   PAGERID(pPager), pList->pgno, pager_pagehash(pList));
      IOTRACE(("PGOUT %p %d\n", pPager, pList->pgno));
      rc = sqlite3OsWrite(pPager->fd, pData, pPager->pageSize, offset);
      PAGER_INCR(sqlite3_pager_writedb_count);
      PAGER_INCR(pPager->nWrite);
      if( pList->pgno==1 ){







>







2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
    ** than Pager.dbSize, this means sqlite3PagerTruncate() was called to
    ** make the file smaller (presumably by auto-vacuum code). Do not write
    ** any such pages to the file.
    */
    if( pList->pgno<=pPager->dbSize && 0==(pList->flags&PGHDR_DONT_WRITE) ){
      i64 offset = (pList->pgno-1)*(i64)pPager->pageSize;
      char *pData = CODEC2(pPager, pList->pData, pList->pgno, 6);

      PAGERTRACE4("STORE %d page %d hash(%08x)\n",
                   PAGERID(pPager), pList->pgno, pager_pagehash(pList));
      IOTRACE(("PGOUT %p %d\n", pPager, pList->pgno));
      rc = sqlite3OsWrite(pPager->fd, pData, pPager->pageSize, offset);
      PAGER_INCR(sqlite3_pager_writedb_count);
      PAGER_INCR(pPager->nWrite);
      if( pList->pgno==1 ){
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
  PgHdr *pPgHdr;
  u32 change_counter;
  int rc = SQLITE_OK;

#ifndef SQLITE_ENABLE_ATOMIC_WRITE
  assert( isDirect==0 );  /* isDirect is only true for atomic writes */
#endif
  if( !pPager->changeCountDone ){
    /* Open page 1 of the file for writing. */
    rc = sqlite3PagerGet(pPager, 1, &pPgHdr);
    if( rc!=SQLITE_OK ) return rc;

    if( !isDirect ){
      rc = sqlite3PagerWrite(pPgHdr);
      if( rc!=SQLITE_OK ){







|







3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
  PgHdr *pPgHdr;
  u32 change_counter;
  int rc = SQLITE_OK;

#ifndef SQLITE_ENABLE_ATOMIC_WRITE
  assert( isDirect==0 );  /* isDirect is only true for atomic writes */
#endif
  if( !pPager->changeCountDone && pPager->dbSize>0 ){
    /* Open page 1 of the file for writing. */
    rc = sqlite3PagerGet(pPager, 1, &pPgHdr);
    if( rc!=SQLITE_OK ) return rc;

    if( !isDirect ){
      rc = sqlite3PagerWrite(pPgHdr);
      if( rc!=SQLITE_OK ){
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910

/*
** Ensure that there are at least nSavepoint savepoints open.
*/
int sqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){
  int rc = SQLITE_OK;

  if( nSavepoint>pPager->nSavepoint ){
    int ii;

    /* Either the sub-journal is open or there are no active savepoints. */
    assert( pPager->nSavepoint==0 || pPager->sjfd->pMethods );

    /* Grow the Pager.aSavepoint array using realloc(). Return SQLITE_NOMEM
    ** if the allocation fails. Otherwise, zero the new portion in case a 







|







3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911

/*
** Ensure that there are at least nSavepoint savepoints open.
*/
int sqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){
  int rc = SQLITE_OK;

  if( nSavepoint>pPager->nSavepoint && pPager->useJournal ){
    int ii;

    /* Either the sub-journal is open or there are no active savepoints. */
    assert( pPager->nSavepoint==0 || pPager->sjfd->pMethods );

    /* Grow the Pager.aSavepoint array using realloc(). Return SQLITE_NOMEM
    ** if the allocation fails. Otherwise, zero the new portion in case a 
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
    );
    pPager->aSavepoint = aNew;
    ii = pPager->nSavepoint;
    pPager->nSavepoint = nSavepoint;

    /* Populate the PagerSavepoint structures just allocated. */
    for(/* no-op */; ii<nSavepoint; ii++){
      assert( pPager->dbSize>=0 );
      aNew[ii].nOrig = pPager->dbSize;
      aNew[ii].iOffset = (pPager->journalOpen ? pPager->journalOff : 0);
      aNew[ii].iSubRec = pPager->stmtNRec;
      aNew[ii].pInSavepoint = sqlite3BitvecCreate(pPager->dbSize);
      if( !aNew[ii].pInSavepoint ){
        return SQLITE_NOMEM;
      }







|







3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
    );
    pPager->aSavepoint = aNew;
    ii = pPager->nSavepoint;
    pPager->nSavepoint = nSavepoint;

    /* Populate the PagerSavepoint structures just allocated. */
    for(/* no-op */; ii<nSavepoint; ii++){
      assert( pPager->dbSizeValid );
      aNew[ii].nOrig = pPager->dbSize;
      aNew[ii].iOffset = (pPager->journalOpen ? pPager->journalOff : 0);
      aNew[ii].iSubRec = pPager->stmtNRec;
      aNew[ii].pInSavepoint = sqlite3BitvecCreate(pPager->dbSize);
      if( !aNew[ii].pInSavepoint ){
        return SQLITE_NOMEM;
      }
Added test/savepoint2.test.






















































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# 2008 December 15
#
# 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.
#
#***********************************************************************
#
# $Id: savepoint2.test,v 1.1 2008/12/18 15:45:07 danielk1977 Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl

# Tests in this file are quite similar to those run by trans.test and
# avtrans.test.
#

proc signature {} {
  return [db eval {SELECT count(*), md5sum(x) FROM t3}]
}



do_test savepoint2-1 {
  execsql {
    PRAGMA cache_size=10;
  }
  db close
  sqlite3 db test.db
  execsql {
    BEGIN;
    CREATE TABLE t3(x TEXT);
    INSERT INTO t3 VALUES(randstr(10,400));
    INSERT INTO t3 VALUES(randstr(10,400));
    INSERT INTO t3 SELECT randstr(10,400) FROM t3;
    INSERT INTO t3 SELECT randstr(10,400) FROM t3;
    INSERT INTO t3 SELECT randstr(10,400) FROM t3;
    INSERT INTO t3 SELECT randstr(10,400) FROM t3;
    INSERT INTO t3 SELECT randstr(10,400) FROM t3;
    INSERT INTO t3 SELECT randstr(10,400) FROM t3;
    INSERT INTO t3 SELECT randstr(10,400) FROM t3;
    INSERT INTO t3 SELECT randstr(10,400) FROM t3;
    INSERT INTO t3 SELECT randstr(10,400) FROM t3;
    COMMIT;
    SELECT count(*) FROM t3;
  }
} {1024}

unset -nocomplain ::sig
unset -nocomplain SQL

set iterations 20

set SQL(1) {
  DELETE FROM t3 WHERE random()%10!=0;
  INSERT INTO t3 SELECT randstr(10,10)||x FROM t3;
  INSERT INTO t3 SELECT randstr(10,10)||x FROM t3;
}
set SQL(2) {
  DELETE FROM t3 WHERE random()%10!=0;
  INSERT INTO t3 SELECT randstr(10,10)||x FROM t3;
  DELETE FROM t3 WHERE random()%10!=0;
  INSERT INTO t3 SELECT randstr(10,10)||x FROM t3;
} 
set SQL(3) {
  UPDATE t3 SET x = randstr(10, 400) WHERE random()%10;
  INSERT INTO t3 SELECT x FROM t3 WHERE random()%10;
  DELETE FROM t3 WHERE random()%10;
}
set SQL(4) {
  INSERT INTO t3 SELECT randstr(10,400) FROM t3 WHERE (random()%10 == 0);
}



for {set ii 2} {$ii < ($iterations+2)} {incr ii} {

  # Record the database signature. Optionally (every second run) open a
  # transaction. In all cases open savepoint "one", which may or may
  # not be a transaction savepoint, depending on whether or not a real
  # transaction has been opened.
  #
  do_test savepoint2-$ii.1 {
    if {$ii % 2} { execsql BEGIN }
    set ::sig(one) [signature]
    execsql "SAVEPOINT one"
  } {}

  # Execute some SQL on the database. Then rollback to savepoint "one".
  # Check that the database signature is as it was when "one" was opened.
  # 
  do_test savepoint2-$ii.2 {
    execsql $SQL(1)
    execsql "ROLLBACK to one"
    signature
  } $::sig(one)
  integrity_check savepoint2-$ii.2.1

  # Execute some SQL. Then open savepoint "two". Savepoint "two" is therefore
  # nested in savepoint "one".
  #
  do_test savepoint2-$ii.3 {
    execsql $SQL(1)
    set ::sig(two) [signature]
    execsql "SAVEPOINT two"
  } {}

  # More SQL changes. The rollback to savepoint "two". Check that the 
  # signature is as it was when savepoint "two" was opened.
  #
  do_test savepoint2-$ii.4 {
    execsql $SQL(2)
    execsql "ROLLBACK to two"
    signature
  } $::sig(two)
  integrity_check savepoint2-$ii.4.1

  # More SQL changes. The rollback to savepoint "two". Check that the 
  # signature is as it was when savepoint "two" was opened.
  #
  do_test savepoint2-$ii.5 {
    execsql $SQL(2)
    execsql "SAVEPOINT three"
    execsql $SQL(3)
    execsql "RELEASE three"
    execsql "ROLLBACK to one"
    signature
  } $::sig(one)

  # By this point the database is in the same state as it was at the 
  # top of the for{} loop (everything having been rolled back by the 
  # "ROLLBACK TO one" command above). So make a few changes to the 
  # database and COMMIT the open transaction, so that the next iteration
  # of the for{} loop works on a different dataset.
  # 
  # The transaction being committed here may have been opened normally using
  # "BEGIN", or may have been opened using a transaction savepoint created
  # by the "SAVEPOINT one" statement.
  #
  do_test savepoint2-$ii.6 {
    execsql $SQL(4)
    execsql COMMIT
    sqlite3_get_autocommit db
  } {1}
  integrity_check savepoint2-$ii.6.1
}

unset -nocomplain ::sig
unset -nocomplain SQL

finish_test