/ Check-in [31215969]
Login

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

Overview
Comment:Fix bugs in WAL mode rollback.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | wal
Files: files | file ages | folders
SHA1: 31215969f59be536fe87431bb9fbfa7d13027e35
User & Date: dan 2010-04-24 18:44:05
Context
2010-04-24
19:07
Add comment explaining checksum mechanism. check-in: 3e9ef515 user: dan tags: wal
18:44
Fix bugs in WAL mode rollback. check-in: 31215969 user: dan tags: wal
14:33
Merge with [0291ed974d]. Merge with [0291ed974d]. Merge with [0291ed974d]. check-in: a352f628 user: dan tags: wal
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/log.c.

1606
1607
1608
1609
1610
1611
1612



























1613
1614
1615
1616
1617
1618
1619
  }else if( pLog->isWriteLocked ){
    logLockRegion(pLog, LOG_REGION_C|LOG_REGION_D, LOG_UNLOCK);
    memcpy(&pLog->hdr, pLog->pSummary->aData, sizeof(pLog->hdr));
    pLog->isWriteLocked = 0;
  }
  return SQLITE_OK;
}




























/* 
** Return true if data has been written but not committed to the log file. 
*/
int sqlite3LogDirty(Log *pLog){
  assert( pLog->isWriteLocked );
  return( pLog->hdr.iLastPg!=((LogSummaryHdr*)pLog->pSummary->aData)->iLastPg );







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
  }else if( pLog->isWriteLocked ){
    logLockRegion(pLog, LOG_REGION_C|LOG_REGION_D, LOG_UNLOCK);
    memcpy(&pLog->hdr, pLog->pSummary->aData, sizeof(pLog->hdr));
    pLog->isWriteLocked = 0;
  }
  return SQLITE_OK;
}

/*
** The log handle passed to this function must be holding the write-lock.
**
** If any data has been written (but not committed) to the log file, this
** function moves the write-pointer back to the start of the transaction.
**
** Additionally, the callback function is invoked for each frame written
** to the log since the start of the transaction. If the callback returns
** other than SQLITE_OK, it is not invoked again and the error code is
** returned to the caller.
**
** Otherwise, if the callback function does not return an error, this
** function returns SQLITE_OK.
*/
int sqlite3LogUndo(Log *pLog, int (*xUndo)(void *, Pgno), void *pUndoCtx){
  int rc = SQLITE_OK;
  Pgno iMax = pLog->hdr.iLastPg;
  Pgno iFrame;

  assert( pLog->isWriteLocked );
  logSummaryReadHdr(pLog, 0);
  for(iFrame=pLog->hdr.iLastPg+1; iFrame<=iMax && rc==SQLITE_OK; iFrame++){
    rc = xUndo(pUndoCtx, pLog->pSummary->aData[logSummaryEntry(iFrame)]);
  }
  return rc;
}

/* 
** Return true if data has been written but not committed to the log file. 
*/
int sqlite3LogDirty(Log *pLog){
  assert( pLog->isWriteLocked );
  return( pLog->hdr.iLastPg!=((LogSummaryHdr*)pLog->pSummary->aData)->iLastPg );

Changes to src/log.h.

33
34
35
36
37
38
39



40
41
42
43
44
45
46
/* Read a page from the log, if it is present. */
int sqlite3LogRead(Log *pLog, Pgno pgno, int *pInLog, u8 *pOut);
void sqlite3LogDbsize(Log *pLog, Pgno *pPgno);

/* Obtain or release the WRITER lock. */
int sqlite3LogWriteLock(Log *pLog, int op);




/* Return true if data has been written but not committed to the log file. */
int sqlite3LogDirty(Log *pLog);

/* Write a frame or frames to the log. */
int sqlite3LogFrames(Log *pLog, int, PgHdr *, Pgno, int, int);

/* Copy pages from the log to the database file */ 







>
>
>







33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/* Read a page from the log, if it is present. */
int sqlite3LogRead(Log *pLog, Pgno pgno, int *pInLog, u8 *pOut);
void sqlite3LogDbsize(Log *pLog, Pgno *pPgno);

/* Obtain or release the WRITER lock. */
int sqlite3LogWriteLock(Log *pLog, int op);

/* Undo any frames written (but not committed) to the log */
int sqlite3LogUndo(Log *pLog, int (*xUndo)(void *, Pgno), void *pUndoCtx);

/* Return true if data has been written but not committed to the log file. */
int sqlite3LogDirty(Log *pLog);

/* Write a frame or frames to the log. */
int sqlite3LogFrames(Log *pLog, int, PgHdr *, Pgno, int, int);

/* Copy pages from the log to the database file */ 

Changes to src/pager.c.

2231
2232
2233
2234
2235
2236
2237
2238
2239


2240
2241
2242



2243

2244
2245

2246
2247

























2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260







2261


2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274

2275
2276
2277
2278
2279
2280
2281
  PAGERTRACE(("FETCH %d page %d hash(%08x)\n",
               PAGERID(pPager), pgno, pager_pagehash(pPg)));

  return rc;
}

/*
** This function is called when a transaction on a WAL database is rolled
** back. For each dirty page in the cache, do one of the following:


**
**   * If the page has no outstanding references, simply discard it.
**   * Otherwise, if the page has one or more outstanding references, 



**     reload the original content from the database (or log file).

*/
static int pagerRollbackLog(Pager *pPager){

  int rc = SQLITE_OK;
  PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache);


























  /* Normally, if a transaction is rolled back, any backup processes are
  ** updated as data is copied out of the rollback journal and into the
  ** database. This is not generally possible with a WAL database, as
  ** rollback involves simply truncating the log file. Therefore, if one
  ** or more frames have already been written to the log (and therefore 
  ** also copied into the backup databases) as part of this transaction,
  ** the backups must be restarted.
  */
  if( sqlite3LogDirty(pPager->pLog) ){
    sqlite3BackupRestart(pPager->pBackup);
  }








  pPager->dbSize = pPager->dbOrigSize;


  while( pList && rc==SQLITE_OK ){
    PgHdr *pNext = pList->pDirty;
    if( sqlite3PcachePageRefcount(pList)==0 ){
      sqlite3PagerLookup(pPager, pList->pgno);
      sqlite3PcacheDrop(pList);
    }else{
      rc = readDbPage(pList);
      if( rc==SQLITE_OK ){
        pPager->xReiniter(pList);
      }
    }
    pList = pNext;
  }

  return rc;
}

/*
** Playback savepoint pSavepoint. Or, if pSavepoint==NULL, then playback
** the entire master journal file. The case pSavepoint==NULL occurs when 
** a ROLLBACK TO command is invoked on a SAVEPOINT that is a transaction 







|
|
>
>

<
<
>
>
>
|
>

<
>

<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>













>
>
>
>
>
>
>

>
>


<
|
<
<
<
<
<
<
<


>







2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242


2243
2244
2245
2246
2247
2248

2249
2250

2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300

2301







2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
  PAGERTRACE(("FETCH %d page %d hash(%08x)\n",
               PAGERID(pPager), pgno, pager_pagehash(pPg)));

  return rc;
}

/*
** This function is invoked once for each page that has already been 
** written into the log file when a WAL transaction is rolled back.
** Parameter iPg is the page number of said page. The pCtx argument 
** is actually a pointer to the Pager structure.
**


** If page iPg is present in the cache, and has no outstanding references,
** it is discarded. Otherwise, if there are one or more outstanding
** references, the page content is reloaded from the database. If the
** attempt to reload content from the database is required and fails, 
** return an SQLite error code. Otherwise, SQLITE_OK.
*/

static int pagerUndoCallback(void *pCtx, Pgno iPg){
  int rc = SQLITE_OK;

  Pager *pPager = (Pager *)pCtx;
  PgHdr *pPg;

  pPg = sqlite3PagerLookup(pPager, iPg);
  if( pPg ){
    if( sqlite3PcachePageRefcount(pPg)==1 ){
      sqlite3PcacheDrop(pPg);
    }else{
      rc = readDbPage(pPg);
      if( rc==SQLITE_OK ){
        pPager->xReiniter(pPg);
      }
      sqlite3PagerUnref(pPg);
    }
  }

  return rc;
}

/*
** This function is called to rollback a transaction on a WAL database.
*/
static int pagerRollbackLog(Pager *pPager){
  int rc;                         /* Return Code */
  PgHdr *pList;                   /* List of dirty pages to revert */

  /* Normally, if a transaction is rolled back, any backup processes are
  ** updated as data is copied out of the rollback journal and into the
  ** database. This is not generally possible with a WAL database, as
  ** rollback involves simply truncating the log file. Therefore, if one
  ** or more frames have already been written to the log (and therefore 
  ** also copied into the backup databases) as part of this transaction,
  ** the backups must be restarted.
  */
  if( sqlite3LogDirty(pPager->pLog) ){
    sqlite3BackupRestart(pPager->pBackup);
  }

  /* For all pages in the cache that are currently dirty or have already
  ** been written (but not committed) to the log file, do one of the 
  ** following:
  **
  **   + Discard the cached page (if refcount==0), or
  **   + Reload page content from the database (if refcount>0).
  */
  pPager->dbSize = pPager->dbOrigSize;
  rc = sqlite3LogUndo(pPager->pLog, pagerUndoCallback, (void *)pPager);
  pList = sqlite3PcacheDirtyList(pPager->pPCache);
  while( pList && rc==SQLITE_OK ){
    PgHdr *pNext = pList->pDirty;

    rc = pagerUndoCallback((void *)pPager, pList->pgno);







    pList = pNext;
  }

  return rc;
}

/*
** Playback savepoint pSavepoint. Or, if pSavepoint==NULL, then playback
** the entire master journal file. The case pSavepoint==NULL occurs when 
** a ROLLBACK TO command is invoked on a SAVEPOINT that is a transaction 

Changes to test/wal.test.

135
136
137
138
139
140
141




142
143
144
145
146
147
148
...
158
159
160
161
162
163
164



















































165
166
167
168
169
170
171
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14}
do_test wal-3.3 {
  execsql { ROLLBACK }
  execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14}
db2 close





do_test wal-4.1 {
  execsql {
    DELETE FROM t1;
    BEGIN;
      INSERT INTO t1 VALUES('a', 'b');
      SAVEPOINT sp;
        INSERT INTO t1 VALUES('c', 'd');
................................................................................
do_test wal-4.3 {
  execsql {
    COMMIT;
    SELECT * FROM t1;
  }
} {a b}




















































do_test wal-5.1 {
  execsql {
    CREATE TEMP TABLE t2(a, b);
    INSERT INTO t2 VALUES(1, 2);
  }
} {}
do_test wal-5.2 {







>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
...
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14}
do_test wal-3.3 {
  execsql { ROLLBACK }
  execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14}
db2 close

#-------------------------------------------------------------------------
# The following tests, wal-4.*, test that savepoints work with WAL 
# databases.
#
do_test wal-4.1 {
  execsql {
    DELETE FROM t1;
    BEGIN;
      INSERT INTO t1 VALUES('a', 'b');
      SAVEPOINT sp;
        INSERT INTO t1 VALUES('c', 'd');
................................................................................
do_test wal-4.3 {
  execsql {
    COMMIT;
    SELECT * FROM t1;
  }
} {a b}

do_test wal-4.4 {
  db close
  sqlite3 db test.db
  db func blob blob
  list [execsql { SELECT * FROM t1 }] [file size test.db-wal]
} {{a b} 0}
do_test wal-4.5 {
  execsql { PRAGMA cache_size = 10 }
  execsql {
    CREATE TABLE t2(a, b);
    INSERT INTO t2 VALUES(blob(400), blob(400));
    SAVEPOINT tr;
      INSERT INTO t2 SELECT blob(400), blob(400) FROM t2; /*  2 */
      INSERT INTO t2 SELECT blob(400), blob(400) FROM t2; /*  4 */
      INSERT INTO t2 SELECT blob(400), blob(400) FROM t2; /*  8 */
      INSERT INTO t2 SELECT blob(400), blob(400) FROM t2; /* 16 */
      INSERT INTO t2 SELECT blob(400), blob(400) FROM t2; /* 32 */
      INSERT INTO t1 SELECT blob(400), blob(400) FROM t1; /*  2 */
      INSERT INTO t1 SELECT blob(400), blob(400) FROM t1; /*  4 */
      INSERT INTO t1 SELECT blob(400), blob(400) FROM t1; /*  8 */
      INSERT INTO t1 SELECT blob(400), blob(400) FROM t1; /* 16 */
      INSERT INTO t1 SELECT blob(400), blob(400) FROM t1; /* 32 */
      SELECT count(*) FROM t2;
  }
} {32}
do_test wal-4.6 {
  execsql { ROLLBACK TO tr }
} {}
do_test wal-4.7 {
  set logsize [file size test.db-wal]
  execsql {
      INSERT INTO t1 VALUES('x', 'y');
    RELEASE tr;
  }
  expr { $logsize == [file size test.db-wal] }
} {1}
do_test wal-4.8 {
  execsql { SELECT count(*) FROM t2 }
} {1}
do_test wal-4.9 {
  file copy -force test.db test2.db
  file copy -force test.db-wal test2.db-wal
  sqlite3 db2 test2.db
  execsql { SELECT count(*) FROM t2 ; SELECT count(*) FROM t1 } db2
} {1 2}
do_test wal-4.10 {
  execsql { PRAGMA integrity_check } db2
} {ok}
db2 close

reopen_db
do_test wal-5.1 {
  execsql {
    CREATE TEMP TABLE t2(a, b);
    INSERT INTO t2 VALUES(1, 2);
  }
} {}
do_test wal-5.2 {