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

FossilOrigin-Name: 34b56600ec0c5cd7b5faab265750252bc9850e3e
diff --git a/manifest b/manifest
index 35d84c2..d026825 100644
--- a/manifest
+++ b/manifest
@@ -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/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/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_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/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/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/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/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/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
diff --git a/manifest.uuid b/manifest.uuid
index 3c1c5c5..3a1e45d 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-20a4ca5d361ecbb982129171f10cccac4f5ad093
\ No newline at end of file
+34b56600ec0c5cd7b5faab265750252bc9850e3e
\ No newline at end of file
diff --git a/src/btree.c b/src/btree.c
index 2cf890e..34b4ffa 100644
--- a/src/btree.c
+++ b/src/btree.c
@@ -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 @@
 
 
 trans_begun:
+  if( rc==SQLITE_OK && wrflag ){
+    rc = sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint);
+  }
   btreeIntegrity(p);
   sqlite3BtreeLeave(p);
   return rc;
@@ -2729,14 +2732,23 @@
     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 @@
   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,7 +2783,11 @@
   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);
@@ -2778,6 +2795,27 @@
 }
 
 /*
+** 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 
 ** the database file.
diff --git a/src/btree.h b/src/btree.h
index ba16730..070059c 100644
--- a/src/btree.h
+++ b/src/btree.h
@@ -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 @@
 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 *);
diff --git a/src/build.c b/src/build.c
index c6f82e7..d172777 100644
--- a/src/build.c
+++ b/src/build.c
@@ -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>
@@ -3312,6 +3312,23 @@
 }
 
 /*
+** 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.
 */
diff --git a/src/main.c b/src/main.c
index fd6abb6..5cecd7b 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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>
@@ -546,6 +546,21 @@
 }
 
 /*
+** 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
 */
 int sqlite3_close(sqlite3 *db){
@@ -587,6 +602,9 @@
   }
   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 ){
diff --git a/src/pager.c b/src/pager.c
index 63d0d1c..7743a3f 100644
--- a/src/pager.c
+++ b/src/pager.c
@@ -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"
@@ -141,6 +141,28 @@
 #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.
 **
 ** Pager.errCode may be set to SQLITE_IOERR, SQLITE_CORRUPT, or
@@ -157,9 +179,6 @@
   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 @@
   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 @@
   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 @@
   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 @@
 #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 @@
   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 @@
   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 @@
     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 @@
   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 @@
 ** 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 @@
   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 @@
     /* 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 @@
 }
 
 /*
-** 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 );
-      if( rc!=SQLITE_OK ) goto end_stmt_playback;
+    }
+  }
+  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 );
     }
   }
 
-  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 @@
   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 @@
   }
   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 @@
   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 @@
 
   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 @@
   ** 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 @@
         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 @@
     ** 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 @@
   }
   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 @@
   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 @@
 #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;
 }
 
diff --git a/src/pager.h b/src/pager.h
index 76d7067..e69e7df 100644
--- a/src/pager.h
+++ b/src/pager.h
@@ -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 @@
 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
diff --git a/src/parse.y b/src/parse.y
index e6788b3..149281d 100644
--- a/src/parse.y
+++ b/src/parse.y
@@ -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 ::= 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.
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index ae4b8d1..c3177af 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -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 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 @@
 #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 @@
 #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 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*);
diff --git a/src/test2.c b/src/test2.c
index 083cc13..1111ebb 100644
--- a/src/test2.c
+++ b/src/test2.c
@@ -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 @@
     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 @@
     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 @@
     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;
diff --git a/src/vdbe.c b/src/vdbe.c
index 2bc26d3..22cc31f 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -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 @@
 }
 #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 @@
   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 @@
         "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 @@
         goto vdbe_return;
       }
     }
+    sqlite3CloseSavepoints(db);
     if( p->rc==SQLITE_OK ){
       rc = SQLITE_DONE;
     }else{
diff --git a/test/savepoint.test b/test/savepoint.test
new file mode 100644
index 0000000..289709d
--- /dev/null
+++ b/test/savepoint.test
@@ -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
+
diff --git a/tool/mkkeywordhash.c b/tool/mkkeywordhash.c
index d397cb9..3ef12e2 100644
--- a/tool/mkkeywordhash.c
+++ b/tool/mkkeywordhash.c
@@ -15,7 +15,7 @@
   "**\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 @@
   { "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                 },