Add the savepoint feature. This feature is largely untested at this point. (CVS 6036)

FossilOrigin-Name: 34b56600ec0c5cd7b5faab265750252bc9850e3e
This commit is contained in:
danielk1977 2008-12-17 17:30:26 +00:00
parent f0f9f75443
commit fd7f045225
14 changed files with 820 additions and 251 deletions

View File

@ -1,5 +1,5 @@
C Fix\ssome\sstrict-aliasing\sproblems\sin\sfts3_expr.c.\s(CVS\s6035)
D 2008-12-17T15:49:52
C Add\sthe\ssavepoint\sfeature.\sThis\sfeature\sis\slargely\suntested\sat\sthis\spoint.\s(CVS\s6036)
D 2008-12-17T17:30:26
F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0
F Makefile.in f7e4c81c347b04f7b0f1c1b081a168645d7b8af7
F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
@ -102,10 +102,10 @@ F src/attach.c 1c35f95da3c62d19de75b44cfefd12c81c1791b3
F src/auth.c c8b2ab5c8bad4bd90ed7c294694f48269162c627
F src/bitvec.c 4300d311b17fb3c1476623fd895a8feac02a0b08
F src/btmutex.c 63c5cc4ad5715690767ffcb741e185d7bc35ec1a
F src/btree.c fb64a2afba6d417b78d6727c4de34a821495ed1e
F src/btree.h 179c3ea813780df78a289a8f5130db18e6d4616e
F src/btree.c c402a9a15fe62508b332517b162f6fdbcf1bfb47
F src/btree.h 4f141cf748d2ee7c6d7fc175f64f87a45cd44113
F src/btreeInt.h 7ef2c872371d7508657f8d7a4efe651c741d6ee6
F src/build.c ae4359475f82acbd947db6d957e2ff39d66de26e
F src/build.c f3e8377cbc0d007b01aab1e7d4fc1d5b296c422e
F src/callback.c bee8949d619b1b7b1e4dfac8a19c5116ae1dd12a
F src/complete.c cb14e06dbe79dee031031f0d9e686ff306afe07c
F src/date.c 3dbac3eac2848af416786b6e1e3150f7c740dac6
@ -121,7 +121,7 @@ F src/insert.c f6db1e6f43aae337e64a755208abb6ff124edc19
F src/journal.c cffd2cd214e58c0e99c3ff632b3bee6c7cbb260e
F src/legacy.c 4f7410b29598d991628ca40b150aa089649f17d8
F src/loadext.c 2f53996c693a347edc2d773e9217dde49d96ae64
F src/main.c 64857582ae00cc638973cbc47997d25fdbf26cf6
F src/main.c 1d2b56821327321af1d6275603c22f86d55b3438
F src/malloc.c e2b4e6d7033372bd43adb0192bf5f64c0aa03c91
F src/mem0.c f2f84062d1f35814d6535c9f9e33de3bfb3b132c
F src/mem1.c 3bfb39e4f60b0179713a7c087b2d4f0dc205735f
@ -141,9 +141,9 @@ F src/os_common.h 24525d8b7bce66c374dfc1810a6c9043f3359b60
F src/os_os2.c bed77dc26e3a95ce4a204936b9a1ca6fe612fcc5
F src/os_unix.c 96b4a6e87335ba943455740f311b4dfb63f26756
F src/os_win.c 496e3ceb499aedc63622a89ef76f7af2dd902709
F src/pager.c 7e8c2b7b7131031cfa88ab0fdbb2de047f5be934
F src/pager.h 37f5173612b7803f44656c16e80df3280234bb18
F src/parse.y 3dfd941533cdc6ce0b09b905b25c4eb73858400b
F src/pager.c dd1aba4a1dc246b72d15fa9ffcf59902cea51d54
F src/pager.h 7191294438881eb4d13eedade97891e8dc993905
F src/parse.y 4d0e33a702dc3ea7b69d8ae1914b3fbd32e46057
F src/pcache.c 16dc8da6e6ba6250f8dfd9ee46036db1cbceedc6
F src/pcache.h f20c3e82dd6da622c3fe296170cb1801f9a2d75a
F src/pcache1.c 533b18aa2456b0f135e376289443e0a342e0c456
@ -157,13 +157,13 @@ F src/select.c a4316c5e8a417687e159b3d3ae689363d1dec5df
F src/shell.c 60638e2fdfe97f1eb9c18caf87d3744d8269d012
F src/sqlite.h.in 065a828e299960316aa34f05b9f0f10f33afe4c8
F src/sqlite3ext.h 1db7d63ab5de4b3e6b83dd03d1a4e64fef6d2a17
F src/sqliteInt.h a1478d1ec40bca0b511c06e2b4c93dc932aa3426
F src/sqliteInt.h e26694bae99940ab603f30d15bf493d301d4e249
F src/sqliteLimit.h f435e728c6b620ef7312814d660a81f9356eb5c8
F src/status.c 237b193efae0cf6ac3f0817a208de6c6c6ef6d76
F src/table.c 23db1e5f27c03160987c122a078b4bb51ef0b2f8
F src/tclsqlite.c 23afb60549af943e135ded441a631f4745be6040
F src/test1.c b193b8b80617bdb8297b25a87d00ee8d5a125d0d
F src/test2.c 897528183edf2839c2a3c991d415905db56f1240
F src/test2.c 4e0ea288e1cf237f8ff26c8817f177f45486f4a6
F src/test3.c 88a246b56b824275300e6c899634fbac1dc94b14
F src/test4.c f79ab52d27ff49b784b631a42e2ccd52cfd5c84c
F src/test5.c 162a1cea2105a2c460a3f39fa6919617b562a288
@ -196,7 +196,7 @@ F src/update.c 080889d241e4dcd1c545c8051eb6de86f4939295
F src/utf.c 1da9c832dba0fa8f865b5b902d93f420a1ee4245
F src/util.c ea62608f66f33a7e8322de83024ae37c415c0c7f
F src/vacuum.c 383d6297bddc011ab04a9eed110db6eaf523e8e9
F src/vdbe.c 3fd1fe6408598121213e19e1808e5de882c7b636
F src/vdbe.c 79d3ec97b28e2a95ad2c43ecf2d757de312cb989
F src/vdbe.h 03516f28bf5aca00a53c4dccd6c313f96adb94f6
F src/vdbeInt.h e6e80a99ce634983b7cc2498843b4d2e5540900a
F src/vdbeapi.c 85c33cfbfa56249cbe627831610afafba754477d
@ -490,6 +490,7 @@ F test/rollback.test 1f70ab4301d8d105d41438a436cad1fc8897f5e5
F test/rowid.test 1c8fc43c60d273e6ea44dfb992db587f3164312c
F test/rtree.test b85fd4f0861a40ca366ac195e363be2528dcfadf
F test/safety.test b69e2b2dd5d52a3f78e216967086884bbc1a09c6
F test/savepoint.test fdad3b61f4a00a96cd773ca0c758cf2f53918ae3
F test/schema.test a8b000723375fd42c68d310091bdbd744fde647c
F test/schema2.test 35e1c9696443d6694c8980c411497c2b5190d32e
F test/select1.test d0a4cad954fd41c030ec16ffbd2d08a4c0548742
@ -660,7 +661,7 @@ F tool/lempar.c c9151d2a4adf418fd9c3970dbb923b7a188b37c2
F tool/memleak.awk 4e7690a51bf3ed757e611273d43fe3f65b510133
F tool/memleak2.awk 9cc20c8e8f3c675efac71ea0721ee6874a1566e8
F tool/memleak3.tcl 7707006ee908cffff210c98158788d85bb3fcdbf
F tool/mkkeywordhash.c b7f85b700627becf998304a0a98aa4f0dfe30269
F tool/mkkeywordhash.c 698ea044ca0b49bde8a9f3ce6429e8dc5a259d25
F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e x
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
F tool/mksqlite3c.tcl c259bcf64ae8fce346e3ae302c3fd6db977f89a8
@ -678,7 +679,7 @@ F tool/speedtest16.c c8a9c793df96db7e4933f0852abb7a03d48f2e81
F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
P 7389b9ecb80294569845c40a23e0c832d07f7a45
R b5a5267b6551ee3859c69dff94406f49
P 20a4ca5d361ecbb982129171f10cccac4f5ad093
R 149ae663dd4ddd00dcd03e3c7806764e
U danielk1977
Z 6c8b8d45848acab7be1cd4bfcb7320ce
Z d50bab95338b938b6f59c39d60f1fcf0

View File

@ -1 +1 @@
20a4ca5d361ecbb982129171f10cccac4f5ad093
34b56600ec0c5cd7b5faab265750252bc9850e3e

View File

@ -9,7 +9,7 @@
** May you share freely, never taking more than you give.
**
*************************************************************************
** $Id: btree.c,v 1.548 2008/12/16 13:46:30 drh Exp $
** $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.
@ -2059,6 +2059,9 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
trans_begun:
if( rc==SQLITE_OK && wrflag ){
rc = sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint);
}
btreeIntegrity(p);
sqlite3BtreeLeave(p);
return rc;
@ -2729,14 +2732,23 @@ int sqlite3BtreeBeginStmt(Btree *p){
rc = pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
}else{
assert( pBt->inTransaction==TRANS_WRITE );
rc = pBt->readOnly ? SQLITE_OK : sqlite3PagerStmtBegin(pBt->pPager);
if( pBt->readOnly ){
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;
}
/*
** Commit the statment subtransaction currently in progress. If no
** subtransaction is active, this is a no-op.
@ -2747,7 +2759,8 @@ int sqlite3BtreeCommitStmt(Btree *p){
sqlite3BtreeEnter(p);
pBt->db = p->db;
if( pBt->inStmt && !pBt->readOnly ){
rc = sqlite3PagerStmtCommit(pBt->pPager);
int iStmtpoint = p->db->nSavepoint;
rc = sqlite3PagerSavepoint(pBt->pPager, SAVEPOINT_RELEASE, iStmtpoint);
}else{
rc = SQLITE_OK;
}
@ -2770,13 +2783,38 @@ int sqlite3BtreeRollbackStmt(Btree *p){
sqlite3BtreeEnter(p);
pBt->db = p->db;
if( pBt->inStmt && !pBt->readOnly ){
rc = sqlite3PagerStmtRollback(pBt->pPager);
int iStmtpoint = p->db->nSavepoint;
rc = sqlite3PagerSavepoint(pBt->pPager, SAVEPOINT_ROLLBACK, iStmtpoint);
if( rc==SQLITE_OK ){
rc = sqlite3PagerSavepoint(pBt->pPager, SAVEPOINT_RELEASE, iStmtpoint);
}
pBt->inStmt = 0;
}
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 );
assert( iSavepoint>=0 || (iSavepoint==-1 && op==SAVEPOINT_ROLLBACK) );
sqlite3BtreeEnter(p);
pBt->db = p->db;
rc = sqlite3PagerSavepoint(pBt->pPager, op, iSavepoint);
sqlite3BtreeLeave(p);
}
return rc;
}
/*
** Create a new cursor for the BTree whose root is on the page
** iTable. The act of acquiring a cursor gets a read lock on

View File

@ -13,7 +13,7 @@
** subsystem. See comments in the source code for a detailed description
** of what each interface routine does.
**
** @(#) $Id: btree.h,v 1.105 2008/10/27 13:59:34 danielk1977 Exp $
** @(#) $Id: btree.h,v 1.106 2008/12/17 17:30:26 danielk1977 Exp $
*/
#ifndef _BTREE_H_
#define _BTREE_H_
@ -101,6 +101,7 @@ int sqlite3BtreeIsInReadTrans(Btree*);
void *sqlite3BtreeSchema(Btree *, int, void(*)(void *));
int sqlite3BtreeSchemaLocked(Btree *);
int sqlite3BtreeLockTable(Btree *, int, u8);
int sqlite3BtreeSavepoint(Btree *, int, int);
const char *sqlite3BtreeGetFilename(Btree *);
const char *sqlite3BtreeGetDirname(Btree *);

View File

@ -22,7 +22,7 @@
** COMMIT
** ROLLBACK
**
** $Id: build.c,v 1.508 2008/12/10 22:30:25 shane Exp $
** $Id: build.c,v 1.509 2008/12/17 17:30:26 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
@ -3311,6 +3311,23 @@ void sqlite3RollbackTransaction(Parse *pParse){
}
}
/*
** This function is called by the parser when it parses a command to create,
** release or rollback an SQL savepoint.
*/
void sqlite3Savepoint(Parse *pParse, int op, Token *pName){
Vdbe *v;
if( pParse->nErr || pParse->db->mallocFailed ) return;
/* TODO: Invoke the authorization callback */
v = sqlite3GetVdbe(pParse);
if( v ){
const char *zName = (const char *)pName->z;
sqlite3VdbeAddOp4(v, OP_Savepoint, op, 0, 0, zName, pName->n);
}
}
/*
** Make sure the TEMP database is open and available for use. Return
** the number of errors. Leave any error messages in the pParse structure.

View File

@ -14,7 +14,7 @@
** other files are for internal use by SQLite and should not be
** accessed by users of the library.
**
** $Id: main.c,v 1.519 2008/12/10 23:04:13 drh Exp $
** $Id: main.c,v 1.520 2008/12/17 17:30:26 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
@ -545,6 +545,21 @@ int sqlite3_total_changes(sqlite3 *db){
return db->nTotalChange;
}
/*
** Close all open savepoints. This function only manipulates fields of the
** database handle object, it does not close any savepoints that may be open
** at the b-tree/pager level.
*/
void sqlite3CloseSavepoints(sqlite3 *db){
while( db->pSavepoint ){
Savepoint *pTmp = db->pSavepoint;
db->pSavepoint = pTmp->pNext;
sqlite3DbFree(db, pTmp);
}
db->nSavepoint = 0;
db->isTransactionSavepoint = 0;
}
/*
** Close an existing SQLite database
*/
@ -587,6 +602,9 @@ int sqlite3_close(sqlite3 *db){
}
assert( sqlite3SafetyCheckSickOrOk(db) );
/* Free any outstanding Savepoint structures. */
sqlite3CloseSavepoints(db);
for(j=0; j<db->nDb; j++){
struct Db *pDb = &db->aDb[j];
if( pDb->pBt ){

View File

@ -18,7 +18,7 @@
** file simultaneously, or one process from reading the database while
** another is writing.
**
** @(#) $Id: pager.c,v 1.514 2008/12/10 22:15:00 drh Exp $
** @(#) $Id: pager.c,v 1.515 2008/12/17 17:30:26 danielk1977 Exp $
*/
#ifndef SQLITE_OMIT_DISKIO
#include "sqliteInt.h"
@ -140,6 +140,28 @@
# define CODEC2(P,D,N,X) ((char*)D)
#endif
/*
** An instance of the following structure is allocated for each active
** savepoint and statement transaction in the system. All such structures
** are stored in the Pager.aSavepoint[] array, which is allocated and
** resized using sqlite3Realloc().
**
** When a savepoint is created, the PagerSavepoint.iHdrOffset field is
** set to 0. If a journal-header is written into the main journal while
** the savepoint is active, then iHdrOffset is set to the byte offset
** immediately following the last journal record written into the main
** journal before the journal-header. This is required during savepoint
** rollback (see pagerPlaybackSavepoint()).
*/
typedef struct PagerSavepoint PagerSavepoint;
struct PagerSavepoint {
i64 iOffset; /* Starting offset in main journal */
i64 iHdrOffset; /* See above */
Bitvec *pInSavepoint; /* Set of pages in this savepoint */
Pgno nOrig; /* Original number of pages in file */
Pgno iSubRec; /* Index of first record in sub-journal */
};
/*
** A open page cache is an instance of the following structure.
**
@ -157,9 +179,6 @@ struct Pager {
u8 journalStarted; /* True if header of journal is synced */
u8 useJournal; /* Use a rollback journal on this file */
u8 noReadlock; /* Do not bother to obtain readlocks */
u8 stmtOpen; /* True if the statement subjournal is open */
u8 stmtInUse; /* True we are in a statement subtransaction */
u8 stmtAutoopen; /* Open stmt journal when main journal is opened*/
u8 noSync; /* Do not sync the journal if true */
u8 fullSync; /* Do extra syncs of the journal for robustness */
u8 sync_flags; /* One of SYNC_NORMAL or SYNC_FULL */
@ -181,7 +200,6 @@ struct Pager {
int errCode; /* One of several kinds of errors */
Pgno dbSize; /* Number of pages in the file */
Pgno origDbSize; /* dbSize before the current change */
Pgno stmtSize; /* Size of database (in pages) at stmt_begin() */
int nRec; /* Number of pages written to the journal */
u32 cksumInit; /* Quasi-random value added to every checksum */
int stmtNRec; /* Number of records in stmt subjournal */
@ -191,20 +209,16 @@ struct Pager {
int mxPage; /* Maximum number of pages to hold in cache */
Pgno mxPgno; /* Maximum allowed size of the database */
Bitvec *pInJournal; /* One bit for each page in the database file */
Bitvec *pInStmt; /* One bit for each page in the database */
Bitvec *pAlwaysRollback; /* One bit for each page marked always-rollback */
char *zFilename; /* Name of the database file */
char *zJournal; /* Name of the journal file */
char *zDirectory; /* Directory hold database and journal files */
sqlite3_file *fd, *jfd; /* File descriptors for database and journal */
sqlite3_file *stfd; /* File descriptor for the statement subjournal*/
sqlite3_file *sjfd; /* File descriptor for the sub-journal*/
int (*xBusyHandler)(void*); /* Function to call when busy */
void *pBusyHandlerArg; /* Context argument for xBusyHandler */
i64 journalOff; /* Current byte offset in the journal file */
i64 journalHdr; /* Byte offset to previous journal header */
i64 stmtHdrOff; /* First journal header written this statement */
i64 stmtCksum; /* cksumInit when statement was started */
i64 stmtJSize; /* Size of journal at stmt_begin() */
u32 sectorSize; /* Assumed sector size during rollback */
#ifdef SQLITE_TEST
int nHit, nMiss; /* Cache hits and missing */
@ -219,6 +233,9 @@ struct Pager {
char dbFileVers[16]; /* Changes whenever database file changes */
i64 journalSizeLimit; /* Size limit for persistent journal files */
PCache *pPCache; /* Pointer to page cache object */
PagerSavepoint *aSavepoint;
int nSavepoint;
};
/*
@ -305,13 +322,26 @@ static const unsigned char aJournalMagic[] = {
#define PAGER_MAX_PGNO 2147483647
/*
** Return true if page *pPg has already been written to the statement
** journal (or statement snapshot has been created, if *pPg is part
** of an in-memory database).
** Return false if it is necessary to write page *pPg into the sub-journal.
** More accurately, true is returned if either:
**
** * No savepoints are open, or
** * The page has been saved to the sub-journal since the most recent
** savepoint was opened.
**
** TODO: There's a bug here. To do with PagerSavepoint.nOrig. Also consider
** the idea that the page may not be required by the outermost savepoint
** but may be required by some earlier savepoint, due to an incremental
** vacuum operation.
*/
static int pageInStatement(PgHdr *pPg){
static int pageInSavepoint(PgHdr *pPg){
Pager *pPager = pPg->pPager;
return sqlite3BitvecTest(pPager->pInStmt, pPg->pgno);
if( pPager->nSavepoint==0 ){
return 1;
}
return sqlite3BitvecTest(
pPager->aSavepoint[pPager->nSavepoint-1].pInSavepoint, pPg->pgno
);
}
static int pageInJournal(PgHdr *pPg){
@ -631,13 +661,20 @@ static int writeJournalHdr(Pager *pPager){
char *zHeader = pPager->pTmpSpace;
u32 nHeader = pPager->pageSize;
u32 nWrite;
int ii;
if( nHeader>JOURNAL_HDR_SZ(pPager) ){
nHeader = JOURNAL_HDR_SZ(pPager);
}
if( pPager->stmtHdrOff==0 ){
pPager->stmtHdrOff = pPager->journalOff;
/* If there are active savepoints and any of them were created since the
** most recent journal header was written, update the PagerSavepoint.iHdrOff
** fields now.
*/
for(ii=0; ii<pPager->nSavepoint; ii++){
if( pPager->aSavepoint[ii].iHdrOffset==0 ){
pPager->aSavepoint[ii].iHdrOffset = pPager->journalOff;
}
}
seekJournalHdr(pPager);
@ -885,6 +922,31 @@ static void pager_reset(Pager *pPager){
sqlite3PcacheClear(pPager->pPCache);
}
static void releaseAllSavepoint(Pager *pPager){
int ii;
for(ii=0; ii<pPager->nSavepoint; ii++){
sqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint);
}
if( !pPager->exclusiveMode ){
sqlite3OsClose(pPager->sjfd);
}
sqlite3_free(pPager->aSavepoint);
pPager->aSavepoint = 0;
pPager->nSavepoint = 0;
}
static int addToSavepointBitvecs(Pager *pPager, Pgno pgno){
int ii;
for(ii=0; ii<pPager->nSavepoint; ii++){
PagerSavepoint *p = &pPager->aSavepoint[ii];
if( pgno<=p->nOrig ){
/* TODO: malloc() failure handling */
sqlite3BitvecSet(p->pInSavepoint, pgno);
}
}
return SQLITE_OK;
}
/*
** Unlock the database file.
**
@ -921,16 +983,9 @@ static void pager_unlock(Pager *pPager){
if( pPager->errCode ){
if( rc==SQLITE_OK ) pPager->errCode = SQLITE_OK;
pager_reset(pPager);
if( pPager->stmtOpen ){
sqlite3OsClose(pPager->stfd);
sqlite3BitvecDestroy(pPager->pInStmt);
pPager->pInStmt = 0;
}
pPager->stmtOpen = 0;
pPager->stmtInUse = 0;
releaseAllSavepoint(pPager);
pPager->journalOff = 0;
pPager->journalStarted = 0;
pPager->stmtAutoopen = 0;
pPager->origDbSize = 0;
}
@ -976,11 +1031,7 @@ static int pager_end_transaction(Pager *pPager, int hasMaster){
if( pPager->state<PAGER_RESERVED ){
return SQLITE_OK;
}
sqlite3PagerStmtCommit(pPager);
if( pPager->stmtOpen && !pPager->exclusiveMode ){
sqlite3OsClose(pPager->stfd);
pPager->stmtOpen = 0;
}
releaseAllSavepoint(pPager);
if( pPager->journalOpen ){
if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ){
int isMemoryJournal = sqlite3IsMemJournal(pPager->jfd);
@ -1079,21 +1130,19 @@ static u32 pager_cksum(Pager *pPager, const u8 *aData){
** checksums - the statement journal does not.
*/
static int pager_playback_one_page(
Pager *pPager, /* The pager being played back */
sqlite3_file *jfd, /* The file that is the journal being rolled back */
i64 offset, /* Offset of the page within the journal */
int isMainJrnl /* True for main rollback journal. False for Stmt jrnl */
Pager *pPager, /* The pager being played back */
int isMainJrnl, /* 1 -> main journal. 0 -> sub-journal. */
i64 offset, /* Offset of record to playback */
Bitvec *pDone /* Bitvec of pages already played back */
){
int rc;
PgHdr *pPg; /* An existing page in the cache */
Pgno pgno; /* The page number of a page in journal */
u32 cksum; /* Checksum used for sanity checking */
u8 *aData = (u8 *)pPager->pTmpSpace; /* Temp storage for a page */
sqlite3_file *jfd = (isMainJrnl ? pPager->jfd : pPager->sjfd);
/* isMainJrnl should be true for the main journal and false for
** statement journals. Verify that this is always the case
*/
assert( jfd == (isMainJrnl ? pPager->jfd : pPager->stfd) );
/* The temp storage must be allocated at this point */
assert( aData );
rc = read32bits(jfd, offset, &pgno);
@ -1110,17 +1159,20 @@ static int pager_playback_one_page(
if( pgno==0 || pgno==PAGER_MJ_PGNO(pPager) ){
return SQLITE_DONE;
}
if( pgno>(unsigned)pPager->dbSize ){
if( pgno>(Pgno)pPager->dbSize || sqlite3BitvecTest(pDone, pgno) ){
return SQLITE_OK;
}
if( isMainJrnl ){
rc = read32bits(jfd, offset+pPager->pageSize+4, &cksum);
if( rc ) return rc;
pPager->journalOff += 4;
if( pager_cksum(pPager, aData)!=cksum ){
if( !pDone && pager_cksum(pPager, aData)!=cksum ){
return SQLITE_DONE;
}
}
if( pDone && (rc = sqlite3BitvecSet(pDone, pgno)) ){
return rc;
}
assert( pPager->state==PAGER_RESERVED || pPager->state>=PAGER_EXCLUSIVE );
@ -1505,7 +1557,7 @@ static int pager_playback(Pager *pPager, int isHot){
/* Copy original pages out of the journal and back into the database file.
*/
for(u=0; u<nRec; u++){
rc = pager_playback_one_page(pPager, pPager->jfd, pPager->journalOff, 1);
rc = pager_playback_one_page(pPager, 1, pPager->journalOff, 0);
if( rc!=SQLITE_OK ){
if( rc==SQLITE_DONE ){
rc = SQLITE_OK;
@ -1549,101 +1601,73 @@ end_playback:
}
/*
** Playback the statement journal.
**
** This is similar to playing back the transaction journal but with
** a few extra twists.
**
** (1) The number of pages in the database file at the start of
** the statement is stored in pPager->stmtSize, not in the
** journal file itself.
**
** (2) In addition to playing back the statement journal, also
** playback all pages of the transaction journal beginning
** at offset pPager->stmtJSize.
** Playback a savepoint.
*/
static int pager_stmt_playback(Pager *pPager){
static int pagerPlaybackSavepoint(Pager *pPager, PagerSavepoint *pSavepoint){
i64 szJ; /* Size of the full journal */
i64 hdrOff;
int nRec; /* Number of Records */
int i; /* Loop counter */
int rc;
i64 iHdrOff; /* End of first segment of main-journal records */
Pgno ii; /* Loop counter */
int rc; /* Return code */
Bitvec *pDone = 0; /* Bitvec to ensure pages played back only once */
szJ = pPager->journalOff;
/* Set hdrOff to be the offset just after the end of the last journal
** page written before the first journal-header for this statement
** transaction was written, or the end of the file if no journal
** header was written.
*/
hdrOff = pPager->stmtHdrOff;
assert( pPager->fullSync || !hdrOff );
if( !hdrOff ){
hdrOff = szJ;
/* Allocate a bitvec to use to store the set of pages rolled back */
if( pSavepoint ){
pDone = sqlite3BitvecCreate(pSavepoint->nOrig);
if( !pDone ){
return SQLITE_NOMEM;
}
}
/* Truncate the database back to its original size.
/* Truncate the database back to the size it was before the
** savepoint being reverted was opened.
*/
rc = pager_truncate(pPager, pPager->stmtSize);
rc = pager_truncate(pPager, pSavepoint?pSavepoint->nOrig:pPager->origDbSize);
assert( pPager->state>=PAGER_SHARED );
/* Figure out how many records are in the statement journal.
/* Now roll back all main journal file records that occur after byte
** byte offset PagerSavepoint.iOffset that have a page number less than
** or equal to PagerSavepoint.nOrig. As each record is played back,
** the corresponding bit in bitvec PagerSavepoint.pInSavepoint is
** cleared.
*/
assert( pPager->stmtInUse && pPager->journalOpen );
nRec = pPager->stmtNRec;
/* Copy original pages out of the statement journal and back into the
** database file. Note that the statement journal omits checksums from
** each record since power-failure recovery is not important to statement
** journals.
*/
for(i=0; i<nRec; i++){
i64 offset = i*(4+pPager->pageSize);
rc = pager_playback_one_page(pPager, pPager->stfd, offset, 0);
assert( rc!=SQLITE_DONE );
if( rc!=SQLITE_OK ) goto end_stmt_playback;
szJ = pPager->journalOff;
if( pSavepoint ){
iHdrOff = pSavepoint->iHdrOffset ? pSavepoint->iHdrOffset : szJ;
pPager->journalOff = pSavepoint->iOffset;
while( rc==SQLITE_OK && pPager->journalOff<iHdrOff ){
rc = pager_playback_one_page(pPager, 1, pPager->journalOff, pDone);
assert( rc!=SQLITE_DONE );
}
}else{
pPager->journalOff = 0;
}
/* Now roll some pages back from the transaction journal. Pager.stmtJSize
** was the size of the journal file when this statement was started, so
** everything after that needs to be rolled back, either into the
** database, the memory cache, or both.
**
** If it is not zero, then Pager.stmtHdrOff is the offset to the start
** of the first journal header written during this statement transaction.
*/
pPager->journalOff = pPager->stmtJSize;
pPager->cksumInit = (int)(pPager->stmtCksum & 0xffffffff);
while( pPager->journalOff < hdrOff ){
rc = pager_playback_one_page(pPager, pPager->jfd, pPager->journalOff, 1);
assert( rc!=SQLITE_DONE );
if( rc!=SQLITE_OK ) goto end_stmt_playback;
}
while( pPager->journalOff < szJ ){
while( rc==SQLITE_OK && pPager->journalOff<szJ ){
u32 nJRec; /* Number of Journal Records */
u32 dummy;
rc = readJournalHdr(pPager, szJ, &nJRec, &dummy);
if( rc!=SQLITE_OK ){
assert( rc!=SQLITE_DONE );
goto end_stmt_playback;
}
assert( rc!=SQLITE_DONE );
if( nJRec==0 ){
nJRec = (int)((szJ - pPager->journalOff) / (pPager->pageSize+8));
nJRec = (szJ - pPager->journalOff) / (pPager->pageSize+8);
}
for(i=nJRec-1; i>=0 && pPager->journalOff < szJ; i--){
rc = pager_playback_one_page(pPager, pPager->jfd, pPager->journalOff, 1);
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. */
if( pSavepoint ){
for(ii=pSavepoint->iSubRec; rc==SQLITE_OK && ii<pPager->stmtNRec; ii++){
i64 offset = ii*(4+pPager->pageSize);
rc = pager_playback_one_page(pPager, 0, offset, pDone);
assert( rc!=SQLITE_DONE );
if( rc!=SQLITE_OK ) goto end_stmt_playback;
}
}
pPager->journalOff = szJ;
end_stmt_playback:
if( rc==SQLITE_OK) {
sqlite3BitvecDestroy(pDone);
if( rc==SQLITE_OK ){
pPager->journalOff = szJ;
/* pager_reload_cache(pPager); */
}
return rc;
}
@ -1815,7 +1839,7 @@ int sqlite3PagerOpen(
pPtr = ((u8 *)&pPager[1]) + pcacheSize;
pPager->vfsFlags = vfsFlags;
pPager->fd = (sqlite3_file*)&pPtr[pVfs->szOsFile*0];
pPager->stfd = (sqlite3_file*)&pPtr[pVfs->szOsFile];
pPager->sjfd = (sqlite3_file*)&pPtr[pVfs->szOsFile];
pPager->jfd = (sqlite3_file*)&pPtr[pVfs->szOsFile+journalFileSize];
pPager->zFilename = (char*)&pPtr[pVfs->szOsFile+2*journalFileSize];
pPager->zDirectory = &pPager->zFilename[nPathname+1];
@ -2247,9 +2271,7 @@ int sqlite3PagerClose(Pager *pPager){
}
sqlite3BitvecDestroy(pPager->pInJournal);
sqlite3BitvecDestroy(pPager->pAlwaysRollback);
if( pPager->stmtOpen ){
sqlite3OsClose(pPager->stfd);
}
releaseAllSavepoint(pPager);
sqlite3OsClose(pPager->fd);
/* Temp files are automatically deleted by the OS
** if( pPager->tempFile ){
@ -2949,6 +2971,18 @@ int sqlite3PagerUnref(DbPage *pPg){
return SQLITE_OK;
}
static int openSubJournal(Pager *pPager){
int rc = SQLITE_OK;
if( pPager->journalOpen && !pPager->sjfd->pMethods ){
if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ){
sqlite3MemJournalOpen(pPager->sjfd);
}else{
rc = sqlite3PagerOpentemp(pPager, pPager->sjfd, SQLITE_OPEN_SUBJOURNAL);
}
}
return rc;
}
/*
** Create a journal file for pPager. There should already be a RESERVED
** or EXCLUSIVE lock on the database file when this routine is called.
@ -3012,8 +3046,8 @@ static int pager_open_journal(Pager *pPager){
rc = writeJournalHdr(pPager);
if( pPager->stmtAutoopen && rc==SQLITE_OK ){
rc = sqlite3PagerStmtBegin(pPager);
if( pPager->nSavepoint && rc==SQLITE_OK ){
rc = openSubJournal(pPager);
}
if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM && rc!=SQLITE_IOERR_NOMEM ){
rc = pager_end_transaction(pPager, 0);
@ -3155,7 +3189,7 @@ static int pager_write(PgHdr *pPg){
** to the journal then we can return right away.
*/
sqlite3PcacheMakeDirty(pPg);
if( pageInJournal(pPg) && (pageInStatement(pPg) || pPager->stmtInUse==0) ){
if( pageInJournal(pPg) && pageInSavepoint(pPg) ){
pPager->dirtyCache = 1;
pPager->dbModified = 1;
}else{
@ -3226,9 +3260,7 @@ static int pager_write(PgHdr *pPg){
if( !pPager->noSync ){
pPg->flags |= PGHDR_NEED_SYNC;
}
if( pPager->stmtInUse ){
sqlite3BitvecSet(pPager->pInStmt, pPg->pgno);
}
addToSavepointBitvecs(pPager, pPg->pgno);
}else{
if( !pPager->journalStarted && !pPager->noSync ){
pPg->flags |= PGHDR_NEED_SYNC;
@ -3247,24 +3279,21 @@ static int pager_write(PgHdr *pPg){
** the statement journal format differs from the standard journal format
** in that it omits the checksums and the header.
*/
if( pPager->stmtInUse
&& !pageInStatement(pPg)
&& pPg->pgno<=pPager->stmtSize
){
if( !pageInSavepoint(pPg) ){
i64 offset = pPager->stmtNRec*(4+pPager->pageSize);
char *pData2 = CODEC2(pPager, pData, pPg->pgno, 7);
assert( pageInJournal(pPg) || pPg->pgno>pPager->origDbSize );
rc = write32bits(pPager->stfd, offset, pPg->pgno);
rc = write32bits(pPager->sjfd, offset, pPg->pgno);
if( rc==SQLITE_OK ){
rc = sqlite3OsWrite(pPager->stfd, pData2, pPager->pageSize, offset+4);
rc = sqlite3OsWrite(pPager->sjfd, pData2, pPager->pageSize, offset+4);
}
PAGERTRACE3("STMT-JOURNAL %d page %d\n", PAGERID(pPager), pPg->pgno);
if( rc!=SQLITE_OK ){
return rc;
}
pPager->stmtNRec++;
assert( pPager->pInStmt!=0 );
sqlite3BitvecSet(pPager->pInStmt, pPg->pgno);
assert( pPager->nSavepoint>0 );
addToSavepointBitvecs(pPager, pPg->pgno);
}
}
@ -3429,7 +3458,7 @@ int sqlite3PagerDontWrite(DbPage *pDbPage){
}
rc = sqlite3BitvecSet(pPager->pAlwaysRollback, pPg->pgno);
if( rc==SQLITE_OK && (pPg->flags&PGHDR_DIRTY) && !pPager->stmtInUse ){
if( rc==SQLITE_OK && (pPg->flags&PGHDR_DIRTY) && pPager->nSavepoint==0 ){
assert( pPager->state>=PAGER_SHARED );
if( pPager->dbSize==pPg->pgno && pPager->origDbSize<pPager->dbSize ){
/* If this pages is the last page in the file and the file has grown
@ -3501,10 +3530,7 @@ void sqlite3PagerDontRollback(DbPage *pPg){
assert( pPager->pInJournal!=0 );
sqlite3BitvecSet(pPager->pInJournal, pPg->pgno);
pPg->flags &= ~PGHDR_NEED_READ;
if( pPager->stmtInUse ){
assert( pPager->stmtSize >= pPager->origDbSize );
sqlite3BitvecSet(pPager->pInStmt, pPg->pgno);
}
addToSavepointBitvecs(pPager, pPg->pgno);
PAGERTRACE3("DONT_ROLLBACK page %d of %d\n", pPg->pgno, PAGERID(pPager));
IOTRACE(("GARBAGE %p %d\n", pPager, pPg->pgno))
}
@ -3869,92 +3895,92 @@ int sqlite3PagerIsMemdb(Pager *pPager){
#endif
/*
** Set the statement rollback point.
**
** This routine should be called with the transaction journal already
** open. A new statement journal is created that can be used to rollback
** changes of a single SQL command within a larger transaction.
** Ensure that there are at least nSavepoint savepoints open.
*/
static int pagerStmtBegin(Pager *pPager){
int rc;
assert( !pPager->stmtInUse );
assert( pPager->state>=PAGER_SHARED );
assert( pPager->dbSizeValid );
PAGERTRACE2("STMT-BEGIN %d\n", PAGERID(pPager));
if( !pPager->journalOpen ){
pPager->stmtAutoopen = 1;
return SQLITE_OK;
}
assert( pPager->journalOpen );
assert( pPager->pInStmt==0 );
pPager->pInStmt = sqlite3BitvecCreate(pPager->dbSize);
if( pPager->pInStmt==0 ){
/* sqlite3OsLock(pPager->fd, SHARED_LOCK); */
return SQLITE_NOMEM;
}
pPager->stmtJSize = pPager->journalOff;
pPager->stmtSize = pPager->dbSize;
pPager->stmtHdrOff = 0;
pPager->stmtCksum = pPager->cksumInit;
if( !pPager->stmtOpen ){
if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ){
sqlite3MemJournalOpen(pPager->stfd);
}else{
rc = sqlite3PagerOpentemp(pPager, pPager->stfd, SQLITE_OPEN_SUBJOURNAL);
if( rc ){
goto stmt_begin_failed;
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
** malloc failure occurs while populating it in the for(...) loop below.
*/
PagerSavepoint *aNew = (PagerSavepoint *)sqlite3Realloc(
pPager->aSavepoint, sizeof(PagerSavepoint)*nSavepoint
);
if( !aNew ){
return SQLITE_NOMEM;
}
memset(&aNew[pPager->nSavepoint], 0,
(nSavepoint - pPager->nSavepoint) * sizeof(PagerSavepoint)
);
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;
}
}
pPager->stmtOpen = 1;
pPager->stmtNRec = 0;
/* Open the sub-journal, if it is not already opened. */
rc = openSubJournal(pPager);
}
pPager->stmtInUse = 1;
return SQLITE_OK;
stmt_begin_failed:
if( pPager->pInStmt ){
sqlite3BitvecDestroy(pPager->pInStmt);
pPager->pInStmt = 0;
}
return rc;
}
int sqlite3PagerStmtBegin(Pager *pPager){
int rc;
rc = pagerStmtBegin(pPager);
return rc;
}
/*
** Commit a statement.
*/
int sqlite3PagerStmtCommit(Pager *pPager){
if( pPager->stmtInUse ){
PAGERTRACE2("STMT-COMMIT %d\n", PAGERID(pPager));
sqlite3BitvecDestroy(pPager->pInStmt);
pPager->pInStmt = 0;
pPager->stmtNRec = 0;
pPager->stmtInUse = 0;
if( sqlite3IsMemJournal(pPager->stfd) ){
sqlite3OsTruncate(pPager->stfd, 0);
** Parameter op is always either SAVEPOINT_ROLLBACK or SAVEPOINT_RELEASE.
** If it is SAVEPOINT_RELEASE, then release and destroy the savepoint with
** index iSavepoint. If it is SAVEPOINT_ROLLBACK, then rollback all changes
** that have occured since savepoint iSavepoint was created.
**
** In either case, all savepoints with an index greater than iSavepoint
** are destroyed.
**
** If there are less than (iSavepoint+1) active savepoints when this
** function is called it is a no-op.
*/
int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){
int rc = SQLITE_OK;
assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK );
if( iSavepoint<pPager->nSavepoint ){
int ii;
int nNew = iSavepoint + (op==SAVEPOINT_ROLLBACK);
for(ii=nNew; ii<pPager->nSavepoint; ii++){
sqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint);
}
pPager->nSavepoint = nNew;
if( op==SAVEPOINT_ROLLBACK ){
PagerSavepoint *pSavepoint = (nNew==0) ? 0 : &pPager->aSavepoint[nNew-1];
rc = pagerPlaybackSavepoint(pPager, pSavepoint);
assert(rc!=SQLITE_DONE);
}
/* If this is a release of the outermost savepoint, truncate
** the sub-journal. */
if( nNew==0 && op==SAVEPOINT_RELEASE && pPager->sjfd->pMethods ){
assert( rc==SQLITE_OK );
rc = sqlite3OsTruncate(pPager->sjfd, 0);
pPager->stmtNRec = 0;
}
}
pPager->stmtAutoopen = 0;
return SQLITE_OK;
}
/*
** Rollback a statement.
*/
int sqlite3PagerStmtRollback(Pager *pPager){
int rc;
if( pPager->stmtInUse ){
PAGERTRACE2("STMT-ROLLBACK %d\n", PAGERID(pPager));
rc = pager_stmt_playback(pPager);
sqlite3PagerStmtCommit(pPager);
}else{
rc = SQLITE_OK;
}
pPager->stmtAutoopen = 0;
return rc;
}

View File

@ -13,7 +13,7 @@
** subsystem. The page cache subsystem reads and writes a file a page
** at a time and provides a journal for rollback.
**
** @(#) $Id: pager.h,v 1.88 2008/12/10 16:45:51 drh Exp $
** @(#) $Id: pager.h,v 1.89 2008/12/17 17:30:26 danielk1977 Exp $
*/
#ifndef _PAGER_H_
@ -116,6 +116,9 @@ i64 sqlite3PagerJournalSizeLimit(Pager *, i64);
void *sqlite3PagerTempSpace(Pager*);
int sqlite3PagerSync(Pager *pPager);
int sqlite3PagerOpenSavepoint(Pager *pPager, int n);
int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint);
#ifdef SQLITE_HAS_CODEC
void sqlite3PagerSetCodec(Pager*,void*(*)(void*,void*,Pgno,int),void*);
#endif

View File

@ -14,7 +14,7 @@
** the parser. Lemon will also generate a header file containing
** numeric codes for all of the tokens.
**
** @(#) $Id: parse.y,v 1.265 2008/12/10 18:03:46 drh Exp $
** @(#) $Id: parse.y,v 1.266 2008/12/17 17:30:26 danielk1977 Exp $
*/
// All token codes are small integers with #defines that begin with "TK_"
@ -118,6 +118,18 @@ cmd ::= COMMIT trans_opt. {sqlite3CommitTransaction(pParse);}
cmd ::= END trans_opt. {sqlite3CommitTransaction(pParse);}
cmd ::= ROLLBACK trans_opt. {sqlite3RollbackTransaction(pParse);}
savepoint_opt ::= SAVEPOINT.
savepoint_opt ::= .
cmd ::= SAVEPOINT nm(X). {
sqlite3Savepoint(pParse, SAVEPOINT_BEGIN, &X);
}
cmd ::= RELEASE savepoint_opt nm(X). {
sqlite3Savepoint(pParse, SAVEPOINT_RELEASE, &X);
}
cmd ::= ROLLBACK trans_opt TO savepoint_opt nm(X). {
sqlite3Savepoint(pParse, SAVEPOINT_ROLLBACK, &X);
}
///////////////////// The CREATE TABLE statement ////////////////////////////
//
cmd ::= create_table create_table_args.

View File

@ -11,7 +11,7 @@
*************************************************************************
** Internal interface definitions for SQLite.
**
** @(#) $Id: sqliteInt.h,v 1.809 2008/12/10 21:19:57 drh Exp $
** @(#) $Id: sqliteInt.h,v 1.810 2008/12/17 17:30:26 danielk1977 Exp $
*/
#ifndef _SQLITEINT_H_
#define _SQLITEINT_H_
@ -522,6 +522,7 @@ typedef struct LookasideSlot LookasideSlot;
typedef struct Module Module;
typedef struct NameContext NameContext;
typedef struct Parse Parse;
typedef struct Savepoint Savepoint;
typedef struct Select Select;
typedef struct SrcList SrcList;
typedef struct StrAccum StrAccum;
@ -765,6 +766,9 @@ struct sqlite3 {
#ifdef SQLITE_SSE
sqlite3_stmt *pFetch; /* Used by SSE to fetch stored statements */
#endif
Savepoint *pSavepoint; /* List of active savepoints */
int nSavepoint; /* Number of non-transaction savepoints */
u8 isTransactionSavepoint; /* True if the outermost savepoint is a TS */
};
/*
@ -876,6 +880,25 @@ struct FuncDef {
#define AGGREGATE(zName, nArg, arg, nc, xStep, xFinal) \
{nArg, SQLITE_UTF8, nc*8, SQLITE_INT_TO_PTR(arg), 0, 0, xStep,xFinal,#zName,0}
/*
** All current savepoints are stored in a linked list starting at
** sqlite3.pSavepoint. The first element in the list is the most recently
** opened savepoint. Savepoints are added to the list by the vdbe
** OP_Savepoint instruction.
*/
struct Savepoint {
char *zName; /* Savepoint name (nul-terminated) */
Savepoint *pNext; /* Parent savepoint (if any) */
};
/*
** The following are used as the second parameter to sqlite3Savepoint(),
** and as the P1 argument to the OP_Savepoint instruction.
*/
#define SAVEPOINT_BEGIN 0
#define SAVEPOINT_RELEASE 1
#define SAVEPOINT_ROLLBACK 2
/*
** Each SQLite module (virtual table definition) is defined by an
@ -2249,6 +2272,8 @@ void sqlite3CodeVerifySchema(Parse*, int);
void sqlite3BeginTransaction(Parse*, int);
void sqlite3CommitTransaction(Parse*);
void sqlite3RollbackTransaction(Parse*);
void sqlite3Savepoint(Parse*, int, Token*);
void sqlite3CloseSavepoints(sqlite3 *);
int sqlite3ExprIsConstant(Expr*);
int sqlite3ExprIsConstantNotJoin(Expr*);
int sqlite3ExprIsConstantOrFunction(Expr*);

View File

@ -13,7 +13,7 @@
** is not included in the SQLite library. It is used for automated
** testing of the SQLite library.
**
** $Id: test2.c,v 1.62 2008/09/29 11:49:48 danielk1977 Exp $
** $Id: test2.c,v 1.63 2008/12/17 17:30:26 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "tcl.h"
@ -197,7 +197,7 @@ static int pager_stmt_begin(
return TCL_ERROR;
}
pPager = sqlite3TestTextToPtr(argv[1]);
rc = sqlite3PagerStmtBegin(pPager);
rc = sqlite3PagerOpenSavepoint(pPager, 1);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, errorName(rc), 0);
return TCL_ERROR;
@ -224,7 +224,8 @@ static int pager_stmt_rollback(
return TCL_ERROR;
}
pPager = sqlite3TestTextToPtr(argv[1]);
rc = sqlite3PagerStmtRollback(pPager);
rc = sqlite3PagerSavepoint(pPager, SAVEPOINT_ROLLBACK, 0);
sqlite3PagerSavepoint(pPager, SAVEPOINT_RELEASE, 0);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, errorName(rc), 0);
return TCL_ERROR;
@ -251,7 +252,7 @@ static int pager_stmt_commit(
return TCL_ERROR;
}
pPager = sqlite3TestTextToPtr(argv[1]);
rc = sqlite3PagerStmtCommit(pPager);
rc = sqlite3PagerSavepoint(pPager, SAVEPOINT_RELEASE, 0);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, errorName(rc), 0);
return TCL_ERROR;

View File

@ -43,7 +43,7 @@
** in this file for details. If in doubt, do not deviate from existing
** commenting and indentation practices when changing or adding code.
**
** $Id: vdbe.c,v 1.803 2008/12/15 15:27:52 drh Exp $
** $Id: vdbe.c,v 1.804 2008/12/17 17:30:26 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
@ -499,6 +499,26 @@ static int fileExists(sqlite3 *db, const char *zFile){
}
#endif
#ifndef NDEBUG
/*
** This function is only called from within an assert() expression. It
** checks that the sqlite3.nTransaction variable is correctly set to
** the number of non-transaction savepoints currently in the
** linked list starting at sqlite3.pSavepoint.
**
** Usage:
**
** assert( checkSavepointCount(db) );
*/
static int checkSavepointCount(sqlite3 *db){
int n = 0;
Savepoint *p;
for(p=db->pSavepoint; p; p=p->pNext) n++;
assert( n==(db->nSavepoint + db->isTransactionSavepoint) );
return 1;
}
#endif
/*
** Execute as much of a VDBE program as we can then return.
**
@ -2356,6 +2376,141 @@ case OP_Statement: {
break;
}
/* Opcode: Savepoint P1 * * P4 *
**
** Open, release or rollback the savepoint named by parameter P4, depending
** on the value of P1. To open a new savepoint, P1==0. To release (commit) an
** existing savepoint, P1==1, or to rollback an existing savepoint P1==2.
*/
case OP_Savepoint: {
int p1 = pOp->p1;
char *zName = pOp->p4.z; /* Name of savepoint */
/* Assert that the p1 parameter is valid. Also that if there is no open
** transaction, then there cannot be any savepoints.
*/
assert( db->pSavepoint==0 || db->autoCommit==0 );
assert( p1==SAVEPOINT_BEGIN||p1==SAVEPOINT_RELEASE||p1==SAVEPOINT_ROLLBACK );
assert( db->pSavepoint || db->isTransactionSavepoint==0 );
assert( checkSavepointCount(db) );
if( p1==SAVEPOINT_BEGIN ){
if( db->writeVdbeCnt>1 ){
/* A new savepoint cannot be created if there are active write
** statements (i.e. open read/write incremental blob handles).
*/
sqlite3SetString(&p->zErrMsg, db, "cannot open savepoint - "
"SQL statements in progress");
rc = SQLITE_BUSY;
}else{
int nName = sqlite3Strlen30(zName);
Savepoint *pNew;
/* Create a new savepoint structure. */
pNew = sqlite3DbMallocRaw(db, sizeof(Savepoint)+nName+1);
if( pNew ){
pNew->zName = (char *)&pNew[1];
memcpy(pNew->zName, zName, nName+1);
/* If there is no open transaction, then mark this as a special
** "transaction savepoint". */
if( db->autoCommit ){
db->autoCommit = 0;
db->isTransactionSavepoint = 1;
}else{
db->nSavepoint++;
}
/* Link the new savepoint into the database handle's list. */
pNew->pNext = db->pSavepoint;
db->pSavepoint = pNew;
}
}
}else{
Savepoint *pSavepoint;
int iSavepoint = 0;
/* Find the named savepoint. If there is no such savepoint, then an
** an error is returned to the user. */
for(
pSavepoint=db->pSavepoint;
pSavepoint && sqlite3StrICmp(pSavepoint->zName, zName);
pSavepoint=pSavepoint->pNext
){
iSavepoint++;
}
if( !pSavepoint ){
sqlite3SetString(&p->zErrMsg, db, "no such savepoint: %s", zName);
rc = SQLITE_ERROR;
}else if(
db->writeVdbeCnt>0 || (p1==SAVEPOINT_ROLLBACK && db->activeVdbeCnt>1)
){
/* It is not possible to release (commit) a savepoint if there are
** active write statements. It is not possible to rollback a savepoint
** if there are any active statements at all.
*/
sqlite3SetString(&p->zErrMsg, db,
"cannot %s savepoint - SQL statements in progress",
(p1==SAVEPOINT_ROLLBACK ? "rollback": "release")
);
rc = SQLITE_BUSY;
}else{
/* Determine whether or not this is a transaction savepoint. If so,
** operate on the currently open transaction. If this is a RELEASE
** command, then the transaction is committed. If it is a ROLLBACK
** command, then all changes made by the current transaction are
** reverted, but the transaction is not actually closed.
*/
int isTransaction = pSavepoint->pNext==0 && db->isTransactionSavepoint;
if( isTransaction && p1==SAVEPOINT_RELEASE ){
db->isTransactionSavepoint = 0;
db->autoCommit = 1;
if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){
p->pc = pc;
db->autoCommit = 0;
p->rc = rc = SQLITE_BUSY;
goto vdbe_return;
}
}else{
int ii;
iSavepoint = db->nSavepoint - iSavepoint - 1;
for(ii=0; ii<db->nDb; ii++){
rc = sqlite3BtreeSavepoint(db->aDb[ii].pBt, p1, iSavepoint);
if( rc!=SQLITE_OK ){
goto abort_due_to_error;
}
}
if( p1==SAVEPOINT_ROLLBACK && db->flags&SQLITE_InternChanges ){
sqlite3ExpirePreparedStatements(db);
sqlite3ResetInternalSchema(db, 0);
}
}
/* Regardless of whether this is a RELEASE or ROLLBACK, destroy all
** savepoints nested inside of the savepoint being operated on. */
while( db->pSavepoint!=pSavepoint ){
Savepoint *pTmp = db->pSavepoint;
db->pSavepoint = pTmp->pNext;
sqlite3DbFree(db, pTmp);
db->nSavepoint--;
}
/* If it is a RELEASE, then destroy the savepoint being operated on too */
if( p1==SAVEPOINT_RELEASE ){
assert( pSavepoint==db->pSavepoint );
db->pSavepoint = pSavepoint->pNext;
sqlite3DbFree(db, pSavepoint);
if( !isTransaction ){
db->nSavepoint--;
}
}
}
}
break;
}
/* Opcode: AutoCommit P1 P2 * * *
**
** Set the database auto-commit flag to P1 (1 or 0). If P2 is true, roll
@ -2390,7 +2545,7 @@ case OP_AutoCommit: {
"SQL statements in progress");
rc = SQLITE_BUSY;
}else if( desiredAutoCommit!=db->autoCommit ){
if( pOp->p2 ){
if( rollback ){
assert( desiredAutoCommit==1 );
sqlite3RollbackAll(db);
db->autoCommit = 1;
@ -2403,6 +2558,7 @@ case OP_AutoCommit: {
goto vdbe_return;
}
}
sqlite3CloseSavepoints(db);
if( p->rc==SQLITE_OK ){
rc = SQLITE_DONE;
}else{

269
test/savepoint.test Normal file
View File

@ -0,0 +1,269 @@
# 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: savepoint.test,v 1.1 2008/12/17 17:30:26 danielk1977 Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
#----------------------------------------------------------------------
# The following tests - savepoint-1.* - test that the SAVEPOINT, RELEASE
# and ROLLBACK TO comands are correctly parsed, and that the auto-commit
# flag is correctly set and unset as a result.
#
do_test savepoint-1.1 {
execsql {
SAVEPOINT sp1;
RELEASE sp1;
}
} {}
do_test savepoint-1.2 {
execsql {
SAVEPOINT sp1;
ROLLBACK TO sp1;
}
} {}
do_test savepoint-1.3 {
execsql { SAVEPOINT sp1 }
db close
} {}
sqlite3 db test.db
do_test savepoint-1.4.1 {
execsql {
SAVEPOINT sp1;
SAVEPOINT sp2;
RELEASE sp1;
}
sqlite3_get_autocommit db
} {1}
do_test savepoint-1.4.2 {
execsql {
SAVEPOINT sp1;
SAVEPOINT sp2;
RELEASE sp2;
}
sqlite3_get_autocommit db
} {0}
do_test savepoint-1.4.3 {
execsql { RELEASE sp1 }
sqlite3_get_autocommit db
} {1}
do_test savepoint-1.4.4 {
execsql {
SAVEPOINT sp1;
SAVEPOINT sp2;
ROLLBACK TO sp1;
}
sqlite3_get_autocommit db
} {0}
do_test savepoint-1.4.5 {
execsql { RELEASE SAVEPOINT sp1 }
sqlite3_get_autocommit db
} {1}
do_test savepoint-1.4.6 {
execsql {
SAVEPOINT sp1;
SAVEPOINT sp2;
SAVEPOINT sp3;
ROLLBACK TO SAVEPOINT sp3;
ROLLBACK TRANSACTION TO sp2;
ROLLBACK TRANSACTION TO SAVEPOINT sp1;
}
sqlite3_get_autocommit db
} {0}
do_test savepoint-1.4.7 {
execsql { RELEASE SAVEPOINT SP1 }
sqlite3_get_autocommit db
} {1}
do_test savepoint-1.5 {
execsql {
SAVEPOINT sp1;
ROLLBACK TO sp1;
}
} {}
do_test savepoint-1.6 {
execsql COMMIT
} {}
#------------------------------------------------------------------------
# These tests - savepoint-2.* - test rollbacks and releases of savepoints
# with a very simple data set.
#
do_test savepoint-2.1 {
execsql {
CREATE TABLE t1(a, b, c);
BEGIN;
INSERT INTO t1 VALUES(1, 2, 3);
SAVEPOINT one;
UPDATE t1 SET a = 2, b = 3, c = 4;
}
execsql { SELECT * FROM t1 }
} {2 3 4}
do_test savepoint-2.2 {
execsql {
ROLLBACK TO one;
}
execsql { SELECT * FROM t1 }
} {1 2 3}
do_test savepoint-2.3 {
execsql {
INSERT INTO t1 VALUES(4, 5, 6);
}
execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6}
do_test savepoint-2.4 {
execsql {
ROLLBACK TO one;
}
execsql { SELECT * FROM t1 }
} {1 2 3}
do_test savepoint-2.5 {
execsql {
INSERT INTO t1 VALUES(7, 8, 9);
SAVEPOINT two;
INSERT INTO t1 VALUES(10, 11, 12);
}
execsql { SELECT * FROM t1 }
} {1 2 3 7 8 9 10 11 12}
do_test savepoint-2.6 {
execsql {
ROLLBACK TO two;
}
execsql { SELECT * FROM t1 }
} {1 2 3 7 8 9}
do_test savepoint-2.7 {
execsql {
INSERT INTO t1 VALUES(10, 11, 12);
}
execsql { SELECT * FROM t1 }
} {1 2 3 7 8 9 10 11 12}
do_test savepoint-2.8 {
execsql {
ROLLBACK TO one;
}
execsql { SELECT * FROM t1 }
} {1 2 3}
do_test savepoint-2.9 {
execsql {
INSERT INTO t1 VALUES('a', 'b', 'c');
SAVEPOINT two;
INSERT INTO t1 VALUES('d', 'e', 'f');
}
execsql { SELECT * FROM t1 }
} {1 2 3 a b c d e f}
do_test savepoint-2.10 {
execsql {
RELEASE two;
}
execsql { SELECT * FROM t1 }
} {1 2 3 a b c d e f}
do_test savepoint-2.11 {
execsql {
ROLLBACK;
}
execsql { SELECT * FROM t1 }
} {}
#------------------------------------------------------------------------
# This block of tests - savepoint-3.* - test that when a transaction
# savepoint is rolled back, locks are not released from database files.
# And that when a transaction savepoint is released, they are released.
#
do_test savepoint-3.1 {
execsql { SAVEPOINT "transaction" }
execsql { PRAGMA lock_status }
} {main unlocked temp closed}
do_test savepoint-3.2 {
execsql { INSERT INTO t1 VALUES(1, 2, 3) }
execsql { PRAGMA lock_status }
} {main reserved temp closed}
do_test savepoint-3.3 {
execsql { ROLLBACK TO "transaction" }
execsql { PRAGMA lock_status }
} {main reserved temp closed}
do_test savepoint-3.4 {
execsql { INSERT INTO t1 VALUES(1, 2, 3) }
execsql { PRAGMA lock_status }
} {main reserved temp closed}
do_test savepoint-3.5 {
execsql { RELEASE "transaction" }
execsql { PRAGMA lock_status }
} {main unlocked temp closed}
#------------------------------------------------------------------------
# Test that savepoints that include schema modifications are handled
# correctly. Test cases savepoint-4.*.
#
do_test savepoint-4.1 {
execsql {
CREATE TABLE t2(d, e, f);
SELECT sql FROM sqlite_master;
}
} {{CREATE TABLE t1(a, b, c)} {CREATE TABLE t2(d, e, f)}}
do_test savepoint-4.2 {
execsql {
BEGIN;
CREATE TABLE t3(g,h);
INSERT INTO t3 VALUES('I', 'II');
SAVEPOINT one;
DROP TABLE t3;
}
} {}
do_test savepoint-4.3 {
execsql {
CREATE TABLE t3(g, h, i);
INSERT INTO t3 VALUES('III', 'IV', 'V');
}
execsql {SELECT * FROM t3}
} {III IV V}
do_test savepoint-4.4 {
execsql { ROLLBACK TO one; }
execsql {SELECT * FROM t3}
} {I II}
do_test savepoint-4.5 {
execsql {
ROLLBACK;
SELECT sql FROM sqlite_master;
}
} {{CREATE TABLE t1(a, b, c)} {CREATE TABLE t2(d, e, f)}}
do_test savepoint-4.6 {
execsql {
BEGIN;
INSERT INTO t1 VALUES('o', 't', 't');
SAVEPOINT sp1;
CREATE TABLE t3(a, b, c);
INSERT INTO t3 VALUES('z', 'y', 'x');
}
execsql {SELECT * FROM t3}
} {z y x}
do_test savepoint-4.7 {
execsql {
ROLLBACK TO sp1;
CREATE TABLE t3(a);
INSERT INTO t3 VALUES('value');
}
execsql {SELECT * FROM t3}
} {value}
do_test savepoint-4.8 {
execsql COMMIT
} {}
finish_test

View File

@ -15,7 +15,7 @@ static const char zHdr[] =
"**\n"
"** The code in this file has been automatically generated by\n"
"**\n"
"** $Header: /home/drh/sqlite/trans/cvs/sqlite/sqlite/tool/mkkeywordhash.c,v 1.34 2008/12/10 20:11:01 shane Exp $\n"
"** $Header: /home/drh/sqlite/trans/cvs/sqlite/sqlite/tool/mkkeywordhash.c,v 1.35 2008/12/17 17:30:26 danielk1977 Exp $\n"
"**\n"
"** The code in this file implements a function that determines whether\n"
"** or not a given identifier is really an SQL keyword. The same thing\n"
@ -233,12 +233,14 @@ static Keyword aKeywordTable[] = {
{ "REFERENCES", "TK_REFERENCES", FKEY },
{ "REGEXP", "TK_LIKE_KW", ALWAYS },
{ "REINDEX", "TK_REINDEX", REINDEX },
{ "RELEASE", "TK_RELEASE", ALWAYS },
{ "RENAME", "TK_RENAME", ALTER },
{ "REPLACE", "TK_REPLACE", CONFLICT },
{ "RESTRICT", "TK_RESTRICT", FKEY },
{ "RIGHT", "TK_JOIN_KW", ALWAYS },
{ "ROLLBACK", "TK_ROLLBACK", ALWAYS },
{ "ROW", "TK_ROW", TRIGGER },
{ "SAVEPOINT", "TK_SAVEPOINT", ALWAYS },
{ "SELECT", "TK_SELECT", ALWAYS },
{ "SET", "TK_SET", ALWAYS },
{ "TABLE", "TK_TABLE", ALWAYS },