From 663fc63a776083ed68df33d65d11a0d612a2d542 Mon Sep 17 00:00:00 2001 From: drh Date: Sat, 2 Feb 2002 18:49:19 +0000 Subject: [PATCH] Get the ABORT conflict resolution algorithm working. (CVS 362) FossilOrigin-Name: 9be4d4c6f12056782966396dca0b8e2d384d0cf2 --- manifest | 30 +++---- manifest.uuid | 2 +- src/btree.c | 64 ++++++++++++- src/btree.h | 5 +- src/build.c | 36 ++++++-- src/insert.c | 8 +- src/pager.c | 65 ++++++-------- src/sqliteInt.h | 3 +- src/update.c | 4 +- src/vdbe.c | 133 +++++++++++++++++---------- src/vdbe.h | 233 ++++++++++++++++++++++++------------------------ test/pager.test | 159 ++++++++++++++++++++++++++++++++- 12 files changed, 508 insertions(+), 234 deletions(-) diff --git a/manifest b/manifest index 8fe109219c..f37bfe4807 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Checkpoint\scode\sadded\sto\sthe\spager.\s\sRegression\stests\swork\sbut\sthe\snew\sAPIs\nhave\snot\sbeen\stested\syet.\s(CVS\s361) -D 2002-02-02T15:01:16 +C Get\sthe\sABORT\sconflict\sresolution\salgorithm\sworking.\s(CVS\s362) +D 2002-02-02T18:49:20 F Makefile.in 9fa4277413bf1d9cf91365f07d4108d7d87ed2af F Makefile.template 3372d45f8853afdb70bd30cc6fb50a3cd9069834 F README a4c0ba11354ef6ba0776b400d057c59da47a4cc0 @@ -19,19 +19,19 @@ F ltmain.sh e9ed72eb1d690f447c13945eaf69e28af531eda1 F publish.sh 5b59f4aff037aafa0e4a3b6fa599495dbd73f360 F sqlite.1 2e2bb0529ef468ade9e4322bd609d0695fb9ded9 F src/TODO af7f3cab0228e34149cf98e073aa83d45878e7e6 -F src/btree.c c796e387da340cb628dc1e41f684fc20253f561e -F src/btree.h 9ead7f54c270d8a554e59352ca7318fdaf411390 -F src/build.c f725dc396d784f723950cf3d47a10f1a69b2f7b7 +F src/btree.c 94deba286af8e1f665d98378c099da2f7455f291 +F src/btree.h a94bef69f5174461331b6b9ae45a2d84f05af6db +F src/build.c 397d78ce466e3c22d56de85ae23d8a2777d3b9e6 F src/delete.c f8ad71be53cf18656b6573de65395852fe817f0c F src/expr.c a2a87dbd411a508ff89dffa90505ad42dac2f920 F src/hash.c 8f7c740ef2eaaa8decfa8751f2be30680b123e46 F src/hash.h a5f5b3ce2d086a172c5879b0b06a27a82eac9fac -F src/insert.c 42e89cb227ce744802622886db3572f78e72093f +F src/insert.c 051e909cf4c8505aae930dcd773215404e187f23 F src/main.c 300320ba68d3e5b22c2c5b2c07fa884878202181 F src/md5.c 52f677bfc590e09f71d07d7e327bd59da738d07c F src/os.c 1953080d14098cd45e5bde88941567688efb72b1 F src/os.h a17596ecc7f38a228b83ecdb661fb03ce44726d6 -F src/pager.c f7274d47d8c8a38ce363bfc49f8ccf9d9842e951 +F src/pager.c 4059bda97a7e10083b77b7d347fea45426b08589 F src/pager.h b28f004e2f5541dc60cc32db01bf80cf4d056283 F src/parse.y 88856227ae8472d0f4ae8514bc9561a6ca060690 F src/printf.c 300a90554345751f26e1fc0c0333b90a66110a1d @@ -40,17 +40,17 @@ F src/select.c fc11d5a8c2bae1b62d8028ffb111c773ad6bf161 F src/shell.c c102dfe388c7618a668c944ff157c49cb48f28e3 F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e F src/sqlite.h.in f57074c84a2c112a5093ba7a9d9636aa9cacc87c -F src/sqliteInt.h 70fd20107f4953312e76a9630a704c9405161040 +F src/sqliteInt.h 41459cf6cae299f66c3ab16b20b8e24443c06089 F src/table.c c89698bd5bb4b8d14722d6ee7e9be014c383d24a F src/tclsqlite.c b9cf346e95291cb4c4f1bf5ac1d77db6b8ad023d F src/test1.c 33efd350dca27c52c58c553c04fd3a6a51f13c1f F src/test2.c d410dbd8a90faa466c3ab694fa0aa57f5a773aa6 F src/test3.c d6775f95fd91f5b3cf0e2382a28e5aaeb68f745b F src/tokenize.c 01a09db6adf933e941db1b781789a0c175be6504 -F src/update.c 3fb7c1601bbd379e39881d6b731d3223b822188a +F src/update.c 95459f94a061860bf8e5716b3426a5ba85c79103 F src/util.c 8f8973dd55a6ec63be9632fc5de86965c99d6327 -F src/vdbe.c 8e6f1bfff67639b7c3bd07822595fc2ef19f0f34 -F src/vdbe.h 5b1bd518126fc5a30e6ea13fe11de931b32c4b59 +F src/vdbe.c 3e9d9dba06fb7f6fe85ca3c345eedd32bf27f3a7 +F src/vdbe.h 3791edabb212038ae5fbcfa72580204596be01a7 F src/where.c 2dda39367f193194e4c7d2e0dcab31527d9d8aba F test/all.test 2a51e5395ac7c2c539689b123b9782a05e3837fe F test/bigrow.test 8ab252dba108f12ad64e337b0f2ff31a807ac578 @@ -74,7 +74,7 @@ F test/main.test 1626345b5f630c5398eede500d9354813b76b0fd F test/malloc.test 70fdd0812e2a57eb746aaf015350f58bb8eee0b1 F test/misc1.test 50a5ca3481fc1f3cd6b978bcd6ed04c06f26a1e6 F test/notnull.test b1f3e42fc475b0b5827b27b2e9b562081995ff30 -F test/pager.test 59bbc4e3d489529ed33db6e15595789e51056077 +F test/pager.test b0c0d00cd5dce0ce21f16926956b195c0ab5044c F test/printf.test 3cb415073754cb8ff076f26173143c3cd293a9da F test/quick.test 6f023c7a73fc413e6d65b7a1879c79764038dc05 F test/quote.test 286db944717afa9a9bf829dd85e59185c65d5435 @@ -122,7 +122,7 @@ F www/speed.tcl 83457b2bf6bb430900bd48ca3dd98264d9a916a5 F www/sqlite.tcl 8b5884354cb615049aed83039f8dfe1552a44279 F www/tclsqlite.tcl 829b393d1ab187fd7a5e978631b3429318885c49 F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218 -P d0e7cf4a83e6abad7129bed356b7492dddaff474 -R 819c88d7bb6a0c5001739d080373471b +P aaa53e113ef849e34883ead8ae584c722ad967db +R f2763ccecef4a6c8cd264dd83b3c6a22 U drh -Z 5798e92bdcf7b705413adfa7c6a6e6fe +Z fb37183391242f0b96b036b1bf3ee1ea diff --git a/manifest.uuid b/manifest.uuid index e6ed4d77af..50d1d7fd51 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -aaa53e113ef849e34883ead8ae584c722ad967db \ No newline at end of file +9be4d4c6f12056782966396dca0b8e2d384d0cf2 \ No newline at end of file diff --git a/src/btree.c b/src/btree.c index e4b18d53f5..aea7123904 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.46 2002/01/04 03:09:29 drh Exp $ +** $Id: btree.c,v 1.47 2002/02/02 18:49:20 drh Exp $ ** ** This file implements a external (disk-based) database using BTrees. ** For a detailed discussion of BTrees, refer to @@ -302,7 +302,8 @@ struct Btree { Pager *pPager; /* The page cache */ BtCursor *pCursor; /* A list of all open cursors */ PageOne *page1; /* First page of the database */ - int inTrans; /* True if a transaction is in progress */ + u8 inTrans; /* True if a transaction is in progress */ + u8 inCkpt; /* True if there is a checkpoint on the transaction */ Hash locks; /* Key: root page number. Data: lock count */ }; typedef Btree Bt; @@ -691,6 +692,7 @@ static void unlockBtreeIfUnused(Btree *pBt){ sqlitepager_unref(pBt->page1); pBt->page1 = 0; pBt->inTrans = 0; + pBt->inCkpt = 0; } } @@ -753,6 +755,7 @@ int sqliteBtreeBeginTrans(Btree *pBt){ } if( rc==SQLITE_OK ){ pBt->inTrans = 1; + pBt->inCkpt = 0; }else{ unlockBtreeIfUnused(pBt); } @@ -770,6 +773,7 @@ int sqliteBtreeCommit(Btree *pBt){ if( pBt->inTrans==0 ) return SQLITE_ERROR; rc = sqlitepager_commit(pBt->pPager); pBt->inTrans = 0; + pBt->inCkpt = 0; unlockBtreeIfUnused(pBt); return rc; } @@ -788,6 +792,7 @@ int sqliteBtreeRollback(Btree *pBt){ BtCursor *pCur; if( pBt->inTrans==0 ) return SQLITE_OK; pBt->inTrans = 0; + pBt->inCkpt = 0; for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ if( pCur->pPage ){ sqlitepager_unref(pCur->pPage); @@ -799,6 +804,61 @@ int sqliteBtreeRollback(Btree *pBt){ return rc; } +/* +** Set the checkpoint for the current transaction. The checkpoint serves +** as a sub-transaction that can be rolled back independently of the +** main transaction. You must start a transaction before starting a +** checkpoint. The checkpoint is ended automatically if the transaction +** commits or rolls back. +** +** Only one checkpoint may be active at a time. It is an error to try +** to start a new checkpoint if another checkpoint is already active. +*/ +int sqliteBtreeBeginCkpt(Btree *pBt){ + int rc; + if( !pBt->inTrans || pBt->inCkpt ) return SQLITE_ERROR; + rc = sqlitepager_ckpt_begin(pBt->pPager); + pBt->inCkpt = 1; + return rc; +} + + +/* +** Commit a checkpoint to transaction currently in progress. If no +** checkpoint is active, this is a no-op. +*/ +int sqliteBtreeCommitCkpt(Btree *pBt){ + int rc; + if( pBt->inCkpt ){ + rc = sqlitepager_ckpt_commit(pBt->pPager); + }else{ + rc = SQLITE_OK; + } + return rc; +} + +/* +** Rollback the checkpoint to the current transaction. If there +** is no active checkpoint or transaction, this routine is a no-op. +** +** All cursors will be invalided by this operation. Any attempt +** to use a cursor that was open at the beginning of this operation +** will result in an error. +*/ +int sqliteBtreeRollbackCkpt(Btree *pBt){ + int rc; + BtCursor *pCur; + if( pBt->inCkpt==0 ) return SQLITE_OK; + for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ + if( pCur->pPage ){ + sqlitepager_unref(pCur->pPage); + pCur->pPage = 0; + } + } + rc = sqlitepager_ckpt_rollback(pBt->pPager); + 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 diff --git a/src/btree.h b/src/btree.h index a86966136b..eb78f4b725 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.19 2002/01/04 03:09:30 drh Exp $ +** @(#) $Id: btree.h,v 1.20 2002/02/02 18:49:20 drh Exp $ */ #ifndef _BTREE_H_ #define _BTREE_H_ @@ -28,6 +28,9 @@ int sqliteBtreeSetCacheSize(Btree*, int); int sqliteBtreeBeginTrans(Btree*); int sqliteBtreeCommit(Btree*); int sqliteBtreeRollback(Btree*); +int sqliteBtreeBeginCkpt(Btree*); +int sqliteBtreeCommitCkpt(Btree*); +int sqliteBtreeRollbackCkpt(Btree*); int sqliteBtreeCreateTable(Btree*, int*); int sqliteBtreeCreateIndex(Btree*, int*); diff --git a/src/build.c b/src/build.c index fa8d92a0a6..e45008c7fd 100644 --- a/src/build.c +++ b/src/build.c @@ -25,7 +25,7 @@ ** ROLLBACK ** PRAGMA ** -** $Id: build.c,v 1.70 2002/01/31 15:54:22 drh Exp $ +** $Id: build.c,v 1.71 2002/02/02 18:49:20 drh Exp $ */ #include "sqliteInt.h" #include @@ -1338,7 +1338,7 @@ void sqliteCopy( v = sqliteGetVdbe(pParse); if( v ){ int openOp; - sqliteBeginWriteOperation(pParse); + sqliteBeginMultiWriteOperation(pParse); addr = sqliteVdbeAddOp(v, OP_FileOpen, 0, 0); sqliteVdbeChangeP3(v, addr, pFilename->z, pFilename->n); sqliteVdbeDequoteP3(v, addr); @@ -1493,23 +1493,43 @@ void sqliteRollbackTransaction(Parse *pParse){ /* ** Generate VDBE code that prepares for doing an operation that -** might change the database. If we are in the middle of a transaction, -** then this sets a checkpoint. If we are not in a transaction, then -** start a transaction. +** might change the database. The operation will be atomic in the +** sense that it will either do its changes completely or not at +** all. So there is not need to set a checkpoint is a transaction +** is already in effect. */ void sqliteBeginWriteOperation(Parse *pParse){ Vdbe *v; v = sqliteGetVdbe(pParse); if( v==0 ) return; - if( pParse->db->flags & SQLITE_InTrans ){ - /* sqliteVdbeAddOp(v, OP_CheckPoint, 0, 0); */ - }else{ + if( (pParse->db->flags & SQLITE_InTrans)==0 ){ sqliteVdbeAddOp(v, OP_Transaction, 0, 0); sqliteVdbeAddOp(v, OP_VerifyCookie, pParse->db->schema_cookie, 0); pParse->schemaVerified = 1; } } +/* +** Generate VDBE code that prepares for doing an operation that +** might change the database. The operation might not be atomic in +** the sense that an error may be discovered and the operation might +** abort after some changes have been made. If we are in the middle +** of a transaction, then this sets a checkpoint. If we are not in +** a transaction, then start a transaction. +*/ +void sqliteBeginMultiWriteOperation(Parse *pParse){ + Vdbe *v; + v = sqliteGetVdbe(pParse); + if( v==0 ) return; + if( (pParse->db->flags & SQLITE_InTrans)==0 ){ + sqliteVdbeAddOp(v, OP_Transaction, 0, 0); + sqliteVdbeAddOp(v, OP_VerifyCookie, pParse->db->schema_cookie, 0); + pParse->schemaVerified = 1; + }else{ + sqliteVdbeAddOp(v, OP_Checkpoint, 0, 0); + } +} + /* ** Generate code that concludes an operation that may have changed ** the database. This is a companion function to BeginWriteOperation(). diff --git a/src/insert.c b/src/insert.c index 5632c76b47..3e92c26d65 100644 --- a/src/insert.c +++ b/src/insert.c @@ -12,7 +12,7 @@ ** This file contains C code routines that are called by the parser ** to handle INSERT statements in SQLite. ** -** $Id: insert.c,v 1.39 2002/01/31 15:54:22 drh Exp $ +** $Id: insert.c,v 1.40 2002/02/02 18:49:20 drh Exp $ */ #include "sqliteInt.h" @@ -79,7 +79,11 @@ void sqliteInsert( */ v = sqliteGetVdbe(pParse); if( v==0 ) goto insert_cleanup; - sqliteBeginWriteOperation(pParse); + if( pSelect ){ + sqliteBeginMultiWriteOperation(pParse); + }else{ + sqliteBeginWriteOperation(pParse); + } /* Figure out how many columns of data are supplied. If the data ** is coming from a SELECT statement, then this step has to generate diff --git a/src/pager.c b/src/pager.c index a6f112a5c8..d4fe1852de 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.37 2002/02/02 15:01:16 drh Exp $ +** @(#) $Id: pager.c,v 1.38 2002/02/02 18:49:20 drh Exp $ */ #include "sqliteInt.h" #include "pager.h" @@ -45,10 +45,6 @@ ** threads can be reading or writing while one ** process is writing. ** -** SQLITE_CHECKPOINT The page cache is writing to the database and -** preserving its changes so that it can back them -** out later if need be. -** ** The page cache comes up in SQLITE_UNLOCK. The first time a ** sqlite_page_get() occurs, the state transitions to SQLITE_READLOCK. ** After all pages have been released using sqlite_page_unref(), @@ -59,20 +55,10 @@ ** be in SQLITE_READLOCK before it transitions to SQLITE_WRITELOCK.) ** The sqlite_page_rollback() and sqlite_page_commit() functions ** transition the state from SQLITE_WRITELOCK back to SQLITE_READLOCK. -** -** The sqlite_ckpt_begin() function moves the state from SQLITE_WRITELOCK -** to SQLITE_CHECKPOINT. The state transitions back to SQLITE_WRITELOCK -** on calls to sqlite_ckpt_commit() or sqlite_ckpt_rollback(). While -** in SQLITE_CHECKPOINT, calls to sqlite_commit() or sqlite_rollback() -** transition directly back to SQLITE_READLOCK. -** -** The code does unequality comparisons on these constants so the order -** must be preserved. */ #define SQLITE_UNLOCK 0 #define SQLITE_READLOCK 1 #define SQLITE_WRITELOCK 2 -#define SQLITE_CHECKPOINT 3 /* @@ -254,12 +240,12 @@ static int pager_unwritelock(Pager *pPager){ int rc; PgHdr *pPg; if( pPager->statejfd); pPager->journalOpen = 0; sqliteOsDelete(pPager->zJournal); rc = sqliteOsReadLock(&pPager->fd); assert( rc==SQLITE_OK ); - sqliteFree( pPager->aInCkpt ); sqliteFree( pPager->aInJournal ); pPager->aInJournal = 0; for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ @@ -279,7 +265,7 @@ static int pager_playback_one_page(Pager *pPager, OsFile *jfd){ PgHdr *pPg; /* An existing page in the cache */ PageRecord pgRec; - rc = sqliteOsRead(&pPager->jfd, &pgRec, sizeof(pgRec)); + rc = sqliteOsRead(jfd, &pgRec, sizeof(pgRec)); if( rc!=SQLITE_OK ) return rc; /* Sanity checking on the page */ @@ -383,8 +369,9 @@ end_playback: ** This is similar to playing back the transaction journal but with ** a few extra twists. ** -** (1) The original size of the database file is stored in -** pPager->ckptSize, not in the journal file itself. +** (1) The number of pages in the database file at the start of +** the checkpoint is stored in pPager->ckptSize, not in the +** journal file itself. ** ** (2) In addition to playing back the checkpoint journal, also ** playback all pages of the transaction journal beginning @@ -397,7 +384,7 @@ static int pager_ckpt_playback(Pager *pPager){ /* Truncate the database back to its original size. */ - rc = sqliteOsTruncate(&pPager->fd, pPager->ckptSize); + rc = sqliteOsTruncate(&pPager->fd, pPager->ckptSize*SQLITE_PAGE_SIZE); pPager->dbSize = pPager->ckptSize; /* Figure out how many records are in the checkpoint journal. @@ -437,14 +424,9 @@ static int pager_ckpt_playback(Pager *pPager){ end_ckpt_playback: - sqliteOsClose(&pPager->cpfd); - pPager->ckptOpen = 0; if( rc!=SQLITE_OK ){ - pager_unwritelock(pPager); pPager->errMask |= PAGER_ERR_CORRUPT; rc = SQLITE_CORRUPT; - }else{ - rc = pager_unwritelock(pPager); } return rc; } @@ -595,7 +577,6 @@ int sqlitepager_pagecount(Pager *pPager){ int sqlitepager_close(Pager *pPager){ PgHdr *pPg, *pNext; switch( pPager->state ){ - case SQLITE_CHECKPOINT: case SQLITE_WRITELOCK: { sqlitepager_rollback(pPager); sqliteOsUnlock(&pPager->fd); @@ -883,7 +864,7 @@ int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage){ }else{ pPg->inJournal = 0; } - if( pPager->aInCkpt && (int)pgno*SQLITE_PAGE_SIZE<=pPager->ckptSize ){ + if( pPager->aInCkpt && (int)pgno<=pPager->ckptSize ){ pPg->inCkpt = (pPager->aInCkpt[pgno/8] & (1<<(pgno&7)))!=0; }else{ pPg->inCkpt = 0; @@ -1111,8 +1092,7 @@ int sqlitepager_write(void *pData){ /* If the checkpoint journal is open and the page is not in it, ** then write the current page to the checkpoint journal. */ - if( pPager->ckptOpen && !pPg->inCkpt - && (int)pPg->pgno*SQLITE_PAGE_SIZE < pPager->ckptSize ){ + if( pPager->ckptOpen && !pPg->inCkpt && (int)pPg->pgno<=pPager->ckptSize ){ assert( pPg->inJournal ); rc = sqliteOsWrite(&pPager->cpfd, &pPg->pgno, sizeof(Pgno)); if( rc==SQLITE_OK ){ @@ -1268,7 +1248,7 @@ int sqlitepager_ckpt_begin(Pager *pPager){ } rc = sqliteOsFileSize(&pPager->jfd, &pPager->ckptJSize); if( rc ) goto ckpt_begin_failed; - pPager->ckptSize = pPager->dbSize * SQLITE_PAGE_SIZE; + pPager->ckptSize = pPager->dbSize; rc = sqlitepager_opentemp(zTemp, &pPager->cpfd); if( rc ) goto ckpt_begin_failed; pPager->ckptOpen = 1; @@ -1286,10 +1266,16 @@ ckpt_begin_failed: ** Commit a checkpoint. */ int sqlitepager_ckpt_commit(Pager *pPager){ - assert( pPager->ckptOpen ); - sqliteOsClose(&pPager->cpfd); - sqliteFree(pPager->aInCkpt); - pPager->ckptOpen = 0; + if( pPager->ckptOpen ){ + PgHdr *pPg; + sqliteOsClose(&pPager->cpfd); + pPager->ckptOpen = 0; + sqliteFree( pPager->aInCkpt ); + pPager->aInCkpt = 0; + for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ + pPg->inCkpt = 0; + } + } return SQLITE_OK; } @@ -1298,11 +1284,12 @@ int sqlitepager_ckpt_commit(Pager *pPager){ */ int sqlitepager_ckpt_rollback(Pager *pPager){ int rc; - assert( pPager->ckptOpen ); - rc = pager_ckpt_playback(pPager); - sqliteOsClose(&pPager->cpfd); - sqliteFree(pPager->aInCkpt); - pPager->ckptOpen = 0; + if( pPager->ckptOpen ){ + rc = pager_ckpt_playback(pPager); + sqlitepager_ckpt_commit(pPager); + }else{ + rc = SQLITE_OK; + } return rc; } diff --git a/src/sqliteInt.h b/src/sqliteInt.h index eb4630efb5..9a095b33f9 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -11,7 +11,7 @@ ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.83 2002/01/31 15:54:22 drh Exp $ +** @(#) $Id: sqliteInt.h,v 1.84 2002/02/02 18:49:21 drh Exp $ */ #include "sqlite.h" #include "hash.h" @@ -565,4 +565,5 @@ void sqliteGenerateRowIndexDelete(Vdbe*, Table*, int, char*); void sqliteGenerateConstraintChecks(Parse*,Table*,int,char*,int,int,int,int); void sqliteCompleteInsertion(Parse*, Table*, int, char*, int, int); void sqliteBeginWriteOperation(Parse*); +void sqliteBeginMultiWriteOperation(Parse*); void sqliteEndWriteOperation(Parse*); diff --git a/src/update.c b/src/update.c index 4b3a71111e..c7f12888d2 100644 --- a/src/update.c +++ b/src/update.c @@ -12,7 +12,7 @@ ** This file contains C code routines that are called by the parser ** to handle UPDATE statements. ** -** $Id: update.c,v 1.32 2002/01/31 15:54:22 drh Exp $ +** $Id: update.c,v 1.33 2002/02/02 18:49:21 drh Exp $ */ #include "sqliteInt.h" @@ -161,7 +161,7 @@ void sqliteUpdate( */ v = sqliteGetVdbe(pParse); if( v==0 ) goto update_cleanup; - sqliteBeginWriteOperation(pParse); + sqliteBeginMultiWriteOperation(pParse); /* Begin the database scan */ diff --git a/src/vdbe.c b/src/vdbe.c index acc6d33455..59ec69ddb5 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -30,7 +30,7 @@ ** But other routines are also provided to help in building up ** a program instruction by instruction. ** -** $Id: vdbe.c,v 1.113 2002/01/31 15:54:22 drh Exp $ +** $Id: vdbe.c,v 1.114 2002/02/02 18:49:21 drh Exp $ */ #include "sqliteInt.h" #include @@ -860,35 +860,35 @@ void sqliteVdbeDelete(Vdbe *p){ ** this array, then copy and paste it into this file, if you want. */ static char *zOpName[] = { 0, - "Transaction", "Commit", "Rollback", "ReadCookie", - "SetCookie", "VerifyCookie", "Open", "OpenTemp", - "OpenWrite", "OpenAux", "OpenWrAux", "Close", - "MoveTo", "NewRecno", "PutIntKey", "PutStrKey", - "Distinct", "Found", "NotFound", "IsUnique", - "NotExists", "Delete", "Column", "KeyAsData", - "Recno", "FullKey", "Rewind", "Next", - "Destroy", "Clear", "CreateIndex", "CreateTable", - "Reorganize", "IdxPut", "IdxDelete", "IdxRecno", - "IdxGT", "IdxGE", "MemLoad", "MemStore", - "ListWrite", "ListRewind", "ListRead", "ListReset", - "SortPut", "SortMakeRec", "SortMakeKey", "Sort", - "SortNext", "SortCallback", "SortReset", "FileOpen", - "FileRead", "FileColumn", "AggReset", "AggFocus", - "AggIncr", "AggNext", "AggSet", "AggGet", - "SetInsert", "SetFound", "SetNotFound", "MakeRecord", - "MakeKey", "MakeIdxKey", "IncrKey", "Goto", - "If", "Halt", "ColumnCount", "ColumnName", - "Callback", "NullCallback", "Integer", "String", - "Pop", "Dup", "Pull", "Push", - "MustBeInt", "Add", "AddImm", "Subtract", - "Multiply", "Divide", "Remainder", "BitAnd", - "BitOr", "BitNot", "ShiftLeft", "ShiftRight", - "AbsValue", "Precision", "Min", "Max", - "Like", "Glob", "Eq", "Ne", - "Lt", "Le", "Gt", "Ge", - "IsNull", "NotNull", "Negative", "And", - "Or", "Not", "Concat", "Noop", - "Strlen", "Substr", "Limit", + "Transaction", "Checkpoint", "Commit", "Rollback", + "ReadCookie", "SetCookie", "VerifyCookie", "Open", + "OpenTemp", "OpenWrite", "OpenAux", "OpenWrAux", + "Close", "MoveTo", "NewRecno", "PutIntKey", + "PutStrKey", "Distinct", "Found", "NotFound", + "IsUnique", "NotExists", "Delete", "Column", + "KeyAsData", "Recno", "FullKey", "Rewind", + "Next", "Destroy", "Clear", "CreateIndex", + "CreateTable", "Reorganize", "IdxPut", "IdxDelete", + "IdxRecno", "IdxGT", "IdxGE", "MemLoad", + "MemStore", "ListWrite", "ListRewind", "ListRead", + "ListReset", "SortPut", "SortMakeRec", "SortMakeKey", + "Sort", "SortNext", "SortCallback", "SortReset", + "FileOpen", "FileRead", "FileColumn", "AggReset", + "AggFocus", "AggIncr", "AggNext", "AggSet", + "AggGet", "SetInsert", "SetFound", "SetNotFound", + "MakeRecord", "MakeKey", "MakeIdxKey", "IncrKey", + "Goto", "If", "Halt", "ColumnCount", + "ColumnName", "Callback", "NullCallback", "Integer", + "String", "Pop", "Dup", "Pull", + "Push", "MustBeInt", "Add", "AddImm", + "Subtract", "Multiply", "Divide", "Remainder", + "BitAnd", "BitOr", "BitNot", "ShiftLeft", + "ShiftRight", "AbsValue", "Precision", "Min", + "Max", "Like", "Glob", "Eq", + "Ne", "Lt", "Le", "Gt", + "Ge", "IsNull", "NotNull", "Negative", + "And", "Or", "Not", "Concat", + "Noop", "Strlen", "Substr", "Limit", }; /* @@ -1072,8 +1072,9 @@ int sqliteVdbeExec( sqlite *db = p->db; /* The database */ char **zStack; /* Text stack */ Stack *aStack; /* Additional stack information */ - int rollbackOnError = 1; /* Do a ROLLBACK if an error is encountered */ - char zBuf[100]; /* Space to sprintf() an integer */ + int errorAction = OE_Abort; /* Recovery action to do in case of an error */ + int undoTransOnError = 0; /* If error, either ROLLBACK or COMMIT */ + char zBuf[100]; /* Space to sprintf() an integer */ /* No instruction ever pushes more than a single element onto the @@ -1172,7 +1173,7 @@ case OP_Goto: { case OP_Halt: { if( pOp->p1!=SQLITE_OK ){ rc = pOp->p1; - rollbackOnError = pOp->p2!=OE_Fail; + errorAction = pOp->p2; goto abort_due_to_error; }else{ pc = p->nOp-1; @@ -2319,18 +2320,32 @@ case OP_IncrKey: { break; } +/* Opcode: Checkpoint * * * +** +** Begin a checkpoint. A checkpoint is the beginning of a operation that +** is part of a larger transaction but which might need to be rolled back +** itself without effecting the containing transaction. A checkpoint will +** be automatically committed or rollback when the VDBE halts. +*/ +case OP_Checkpoint: { + rc = sqliteBtreeBeginCkpt(pBt); + if( rc==SQLITE_OK && db->pBeTemp ){ + rc = sqliteBtreeBeginCkpt(pBt); + } + break; +} + /* Opcode: Transaction * * * ** ** Begin a transaction. The transaction ends when a Commit or Rollback -** opcode is encountered or whenever there is an execution error that causes -** a script to abort. A transaction is not ended by a Halt. +** opcode is encountered. Depending on the ON CONFLICT setting, the +** transaction might also be rolled back if an error is encountered. ** ** A write lock is obtained on the database file when a transaction is ** started. No other process can read or write the file while the ** transaction is underway. Starting a transaction also creates a -** rollback journal. -** A transaction must be started before any changes can be made to the -** database. +** rollback journal. A transaction must be started before any changes +** can be made to the database. */ case OP_Transaction: { int busy = 0; @@ -2359,6 +2374,7 @@ case OP_Transaction: { } } }while( busy ); + undoTransOnError = 1; break; } @@ -4466,14 +4482,39 @@ default: { cleanup: Cleanup(p); - if( rc!=SQLITE_OK && rollbackOnError ){ - closeAllCursors(p); - sqliteBtreeRollback(pBt); - if( db->pBeTemp ) sqliteBtreeRollback(db->pBeTemp); - sqliteRollbackInternalChanges(db); - db->flags &= ~SQLITE_InTrans; - db->onError = OE_Default; - } + if( rc!=SQLITE_OK ){ + switch( errorAction ){ + case OE_Abort: { + if( !undoTransOnError ){ + sqliteBtreeRollbackCkpt(pBt); + if( db->pBeTemp ) sqliteBtreeRollbackCkpt(db->pBeTemp); + break; + } + /* Fall through to ROLLBACK */ + } + case OE_Rollback: { + sqliteBtreeRollback(pBt); + if( db->pBeTemp ) sqliteBtreeRollback(db->pBeTemp); + sqliteRollbackInternalChanges(db); + db->flags &= ~SQLITE_InTrans; + db->onError = OE_Default; + break; + } + default: { + if( undoTransOnError ){ + sqliteBtreeCommit(pBt); + if( db->pBeTemp ) sqliteBtreeCommit(db->pBeTemp); + sqliteCommitInternalChanges(db); + db->flags &= ~SQLITE_InTrans; + db->onError = OE_Default; + } + break; + } + } + }else{ + sqliteBtreeCommitCkpt(pBt); + if( db->pBeTemp ) sqliteBtreeCommitCkpt(db->pBeTemp); + } return rc; /* Jump to here if a malloc() fails. It's hard to get a malloc() diff --git a/src/vdbe.h b/src/vdbe.h index 2fc688042a..6a63afd5ab 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -15,7 +15,7 @@ ** or VDBE. The VDBE implements an abstract machine that runs a ** simple program to access and modify the underlying database. ** -** $Id: vdbe.h,v 1.39 2002/01/29 18:41:25 drh Exp $ +** $Id: vdbe.h,v 1.40 2002/02/02 18:49:21 drh Exp $ */ #ifndef _SQLITE_VDBE_H_ #define _SQLITE_VDBE_H_ @@ -69,139 +69,140 @@ typedef struct VdbeOp VdbeOp; ** can be used to renumber these opcodes when new opcodes are inserted. */ #define OP_Transaction 1 -#define OP_Commit 2 -#define OP_Rollback 3 +#define OP_Checkpoint 2 +#define OP_Commit 3 +#define OP_Rollback 4 -#define OP_ReadCookie 4 -#define OP_SetCookie 5 -#define OP_VerifyCookie 6 +#define OP_ReadCookie 5 +#define OP_SetCookie 6 +#define OP_VerifyCookie 7 -#define OP_Open 7 -#define OP_OpenTemp 8 -#define OP_OpenWrite 9 -#define OP_OpenAux 10 -#define OP_OpenWrAux 11 -#define OP_Close 12 -#define OP_MoveTo 13 -#define OP_NewRecno 14 -#define OP_PutIntKey 15 -#define OP_PutStrKey 16 -#define OP_Distinct 17 -#define OP_Found 18 -#define OP_NotFound 19 -#define OP_IsUnique 20 -#define OP_NotExists 21 -#define OP_Delete 22 -#define OP_Column 23 -#define OP_KeyAsData 24 -#define OP_Recno 25 -#define OP_FullKey 26 -#define OP_Rewind 27 -#define OP_Next 28 +#define OP_Open 8 +#define OP_OpenTemp 9 +#define OP_OpenWrite 10 +#define OP_OpenAux 11 +#define OP_OpenWrAux 12 +#define OP_Close 13 +#define OP_MoveTo 14 +#define OP_NewRecno 15 +#define OP_PutIntKey 16 +#define OP_PutStrKey 17 +#define OP_Distinct 18 +#define OP_Found 19 +#define OP_NotFound 20 +#define OP_IsUnique 21 +#define OP_NotExists 22 +#define OP_Delete 23 +#define OP_Column 24 +#define OP_KeyAsData 25 +#define OP_Recno 26 +#define OP_FullKey 27 +#define OP_Rewind 28 +#define OP_Next 29 -#define OP_Destroy 29 -#define OP_Clear 30 -#define OP_CreateIndex 31 -#define OP_CreateTable 32 -#define OP_Reorganize 33 +#define OP_Destroy 30 +#define OP_Clear 31 +#define OP_CreateIndex 32 +#define OP_CreateTable 33 +#define OP_Reorganize 34 -#define OP_IdxPut 34 -#define OP_IdxDelete 35 -#define OP_IdxRecno 36 -#define OP_IdxGT 37 -#define OP_IdxGE 38 +#define OP_IdxPut 35 +#define OP_IdxDelete 36 +#define OP_IdxRecno 37 +#define OP_IdxGT 38 +#define OP_IdxGE 39 -#define OP_MemLoad 39 -#define OP_MemStore 40 +#define OP_MemLoad 40 +#define OP_MemStore 41 -#define OP_ListWrite 41 -#define OP_ListRewind 42 -#define OP_ListRead 43 -#define OP_ListReset 44 +#define OP_ListWrite 42 +#define OP_ListRewind 43 +#define OP_ListRead 44 +#define OP_ListReset 45 -#define OP_SortPut 45 -#define OP_SortMakeRec 46 -#define OP_SortMakeKey 47 -#define OP_Sort 48 -#define OP_SortNext 49 -#define OP_SortCallback 50 -#define OP_SortReset 51 +#define OP_SortPut 46 +#define OP_SortMakeRec 47 +#define OP_SortMakeKey 48 +#define OP_Sort 49 +#define OP_SortNext 50 +#define OP_SortCallback 51 +#define OP_SortReset 52 -#define OP_FileOpen 52 -#define OP_FileRead 53 -#define OP_FileColumn 54 +#define OP_FileOpen 53 +#define OP_FileRead 54 +#define OP_FileColumn 55 -#define OP_AggReset 55 -#define OP_AggFocus 56 -#define OP_AggIncr 57 -#define OP_AggNext 58 -#define OP_AggSet 59 -#define OP_AggGet 60 +#define OP_AggReset 56 +#define OP_AggFocus 57 +#define OP_AggIncr 58 +#define OP_AggNext 59 +#define OP_AggSet 60 +#define OP_AggGet 61 -#define OP_SetInsert 61 -#define OP_SetFound 62 -#define OP_SetNotFound 63 +#define OP_SetInsert 62 +#define OP_SetFound 63 +#define OP_SetNotFound 64 -#define OP_MakeRecord 64 -#define OP_MakeKey 65 -#define OP_MakeIdxKey 66 -#define OP_IncrKey 67 +#define OP_MakeRecord 65 +#define OP_MakeKey 66 +#define OP_MakeIdxKey 67 +#define OP_IncrKey 68 -#define OP_Goto 68 -#define OP_If 69 -#define OP_Halt 70 +#define OP_Goto 69 +#define OP_If 70 +#define OP_Halt 71 -#define OP_ColumnCount 71 -#define OP_ColumnName 72 -#define OP_Callback 73 -#define OP_NullCallback 74 +#define OP_ColumnCount 72 +#define OP_ColumnName 73 +#define OP_Callback 74 +#define OP_NullCallback 75 -#define OP_Integer 75 -#define OP_String 76 -#define OP_Pop 77 -#define OP_Dup 78 -#define OP_Pull 79 -#define OP_Push 80 -#define OP_MustBeInt 81 +#define OP_Integer 76 +#define OP_String 77 +#define OP_Pop 78 +#define OP_Dup 79 +#define OP_Pull 80 +#define OP_Push 81 +#define OP_MustBeInt 82 -#define OP_Add 82 -#define OP_AddImm 83 -#define OP_Subtract 84 -#define OP_Multiply 85 -#define OP_Divide 86 -#define OP_Remainder 87 -#define OP_BitAnd 88 -#define OP_BitOr 89 -#define OP_BitNot 90 -#define OP_ShiftLeft 91 -#define OP_ShiftRight 92 -#define OP_AbsValue 93 -#define OP_Precision 94 -#define OP_Min 95 -#define OP_Max 96 -#define OP_Like 97 -#define OP_Glob 98 -#define OP_Eq 99 -#define OP_Ne 100 -#define OP_Lt 101 -#define OP_Le 102 -#define OP_Gt 103 -#define OP_Ge 104 -#define OP_IsNull 105 -#define OP_NotNull 106 -#define OP_Negative 107 -#define OP_And 108 -#define OP_Or 109 -#define OP_Not 110 -#define OP_Concat 111 -#define OP_Noop 112 +#define OP_Add 83 +#define OP_AddImm 84 +#define OP_Subtract 85 +#define OP_Multiply 86 +#define OP_Divide 87 +#define OP_Remainder 88 +#define OP_BitAnd 89 +#define OP_BitOr 90 +#define OP_BitNot 91 +#define OP_ShiftLeft 92 +#define OP_ShiftRight 93 +#define OP_AbsValue 94 +#define OP_Precision 95 +#define OP_Min 96 +#define OP_Max 97 +#define OP_Like 98 +#define OP_Glob 99 +#define OP_Eq 100 +#define OP_Ne 101 +#define OP_Lt 102 +#define OP_Le 103 +#define OP_Gt 104 +#define OP_Ge 105 +#define OP_IsNull 106 +#define OP_NotNull 107 +#define OP_Negative 108 +#define OP_And 109 +#define OP_Or 110 +#define OP_Not 111 +#define OP_Concat 112 +#define OP_Noop 113 -#define OP_Strlen 113 -#define OP_Substr 114 +#define OP_Strlen 114 +#define OP_Substr 115 -#define OP_Limit 115 +#define OP_Limit 116 -#define OP_MAX 115 +#define OP_MAX 116 /* ** Prototypes for the VDBE interface. See comments on the implementation diff --git a/test/pager.test b/test/pager.test index a39806f3a6..727b2d005d 100644 --- a/test/pager.test +++ b/test/pager.test @@ -11,7 +11,7 @@ # This file implements regression tests for SQLite library. The # focus of this script is page cache subsystem. # -# $Id: pager.test,v 1.10 2001/09/16 00:13:28 drh Exp $ +# $Id: pager.test,v 1.11 2002/02/02 18:49:21 drh Exp $ set testdir [file dirname $argv0] @@ -243,6 +243,163 @@ do_test pager-3.99 { pager_close $::p1 } {} +# tests of the checkpoint mechanism and api +# +do_test pager-4.0 { + set v [catch { + file delete -force ptf1.db + set ::p1 [pager_open ptf1.db 15] + } msg] + if {$v} {lappend v $msg} + set v +} {0} +do_test pager-4.1 { + set g1 [page_get $::p1 1] + page_write $g1 "Page-1 v0" + for {set i 2} {$i<=20} {incr i} { + set gx [page_get $::p1 $i] + page_write $gx "Page-$i v0" + page_unref $gx + } + pager_commit $::p1 +} {} +for {set i 1} {$i<=20} {incr i} { + do_test pager-4.2.$i { + set gx [page_get $p1 $i] + set v [page_read $gx] + page_unref $gx + set v + } "Page-$i v0" +} +do_test pager-4.3 { + lrange [pager_stats $::p1] 0 1 +} {ref 1} +do_test pager-4.4 { + lrange [pager_stats $::p1] 8 9 +} {state 1} + +for {set i 1} {$i<20} {incr i} { + do_test pager-4.5.$i.0 { + set res {} + for {set j 2} {$j<=20} {incr j} { + set gx [page_get $p1 $j] + set value [page_read $gx] + page_unref $gx + set shouldbe "Page-$j v[expr {$i-1}]" + if {$value!=$shouldbe} { + lappend res $value $shouldbe + } + } + set res + } {} + do_test pager-4.5.$i.1 { + page_write $g1 "Page-1 v$i" + lrange [pager_stats $p1] 8 9 + } {state 2} + do_test pager-4.5.$i.2 { + for {set j 2} {$j<=20} {incr j} { + set gx [page_get $p1 $j] + page_write $gx "Page-$j v$i" + page_unref $gx + if {$j==$i} { + pager_ckpt_begin $p1 + } + } + } {} + do_test pager-4.5.$i.3 { + set res {} + for {set j 2} {$j<=20} {incr j} { + set gx [page_get $p1 $j] + set value [page_read $gx] + page_unref $gx + set shouldbe "Page-$j v$i" + if {$value!=$shouldbe} { + lappend res $value $shouldbe + } + } + set res + } {} + do_test pager-4.5.$i.4 { + pager_rollback $p1 + set res {} + for {set j 2} {$j<=20} {incr j} { + set gx [page_get $p1 $j] + set value [page_read $gx] + page_unref $gx + set shouldbe "Page-$j v[expr {$i-1}]" + if {$value!=$shouldbe} { + lappend res $value $shouldbe + } + } + set res + } {} + do_test pager-4.5.$i.5 { + page_write $g1 "Page-1 v$i" + lrange [pager_stats $p1] 8 9 + } {state 2} + do_test pager-4.5.$i.6 { + for {set j 2} {$j<=20} {incr j} { + set gx [page_get $p1 $j] + page_write $gx "Page-$j v$i" + page_unref $gx + if {$j==$i} { + pager_ckpt_begin $p1 + } + } + } {} + do_test pager-4.5.$i.7 { + pager_ckpt_rollback $p1 + for {set j 2} {$j<=20} {incr j} { + set gx [page_get $p1 $j] + set value [page_read $gx] + page_unref $gx + if {$j<=$i || $i==1} { + set shouldbe "Page-$j v$i" + } else { + set shouldbe "Page-$j v[expr {$i-1}]" + } + if {$value!=$shouldbe} { + lappend res $value $shouldbe + } + } + set res + } {} + do_test pager-4.5.$i.8 { + for {set j 2} {$j<=20} {incr j} { + set gx [page_get $p1 $j] + page_write $gx "Page-$j v$i" + page_unref $gx + if {$j==$i} { + pager_ckpt_begin $p1 + } + } + } {} + do_test pager-4.5.$i.9 { + pager_ckpt_commit $p1 + for {set j 2} {$j<=20} {incr j} { + set gx [page_get $p1 $j] + set value [page_read $gx] + page_unref $gx + set shouldbe "Page-$j v$i" + if {$value!=$shouldbe} { + lappend res $value $shouldbe + } + } + set res + } {} + do_test pager-4.5.$i.10 { + pager_commit $p1 + lrange [pager_stats $p1] 8 9 + } {state 1} +} + +do_test pager-4.99 { + pager_close $::p1 +} {} + + + + file delete -force ptf1.db } ;# end if( not mem: and has pager_open command );