]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add part of the btree layer of the shared-cache feature. (CVS 2848)
authordanielk1977 <danielk1977@noemail.net>
Fri, 30 Dec 2005 16:28:01 +0000 (16:28 +0000)
committerdanielk1977 <danielk1977@noemail.net>
Fri, 30 Dec 2005 16:28:01 +0000 (16:28 +0000)
FossilOrigin-Name: 2afcad990190af97d1ad0010f211a5ca8f0fd745

19 files changed:
manifest
manifest.uuid
src/btree.c
src/btree.h
src/main.c
src/pager.c
src/sqlite.h.in
src/sqliteInt.h
src/tclsqlite.c
src/test1.c
src/test3.c
src/vdbeaux.c
test/alter3.test
test/attach2.test
test/malloc5.test
test/quick.test
test/shared.test [new file with mode: 0644]
test/tester.tcl
test/trans.test

index 6b7f12ada9627902e28733667fb08d3932bb439f..a68fc8f0ccfe960c74d80672dee42891cbb76f08 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\ssupport\sfor\sCREATE\sTABLE\sIF\sNOT\sEXISTS.\s(CVS\s2847)
-D 2005-12-29T23:33:54
+C Add\spart\sof\sthe\sbtree\slayer\sof\sthe\sshared-cache\sfeature.\s(CVS\s2848)
+D 2005-12-30T16:28:02
 F Makefile.in e3c6b3a38d734d41574c04f2fc90d18de2b87102
 F Makefile.linux-gcc aee18d8a05546dcf1888bd4547e442008a49a092
 F README 9c4e2d6706bdcc3efdd773ce752a8cdab4f90028
@@ -34,8 +34,8 @@ F src/alter.c 5905a3372379daa4f860199452b4de5a836e53f3
 F src/analyze.c ea42005eed52c382fcc7ef66969e7f1858597633
 F src/attach.c 07822dbd2dcf6de548aba6cb24142aec800fa3b6
 F src/auth.c 31e2304bef67f44d635655f44234387ea7d21454
-F src/btree.c 2b2651e0f6f9f8c5976b662fbfab7fc8f54f02c9
-F src/btree.h 8ff86378bb5af0cde282614c16bf0c0190b6d216
+F src/btree.c ccb912b1f27b6de4ed9f8ab5289a5491c9be5bc8
+F src/btree.h d6481f9253f0b5fa40b35da4b93a54d0f9c5f9f2
 F src/build.c bb4c9df2583246728167659d401fd75aa3d9997f
 F src/callback.c 62066afd516f220575e81b1a1239ab92a2eae252
 F src/complete.c df1681cef40dec33a286006981845f87b194e7a4
@@ -48,7 +48,7 @@ F src/hash.c 8747cf51d12de46512880dfcf1b68b4e24072863
 F src/hash.h 1b0c445e1c89ff2aaad9b4605ba61375af001e84
 F src/insert.c a5629e462560d6aa9be7e0dafb6ed1518bb623bb
 F src/legacy.c 59757d857ab95fcbb0ac27692d3201e35f093dd7
-F src/main.c ec04b37605752a4294ae98f2b7d78ba62950ba2f
+F src/main.c c93f80d1fcaf3ed508d62a163819b10a730c2fb7
 F src/md5.c c5fdfa5c2593eaee2e32a5ce6c6927c986eaf217
 F src/os.c 7b4a002d9c9421580276db55d2329636a604e8ef
 F src/os.h e941992043b127fdb1bd114f0b4319ae1c4562a7
@@ -59,7 +59,7 @@ F src/os_unix.c 6394d2fa3a8bfbceb227579b44b4b343b5b54a8f
 F src/os_unix.h 5768d56d28240d3fe4537fac08cc85e4fb52279e
 F src/os_win.c 9feb97f49b93d451f8ef7c5dd388e05a44647dc6
 F src/os_win.h 41a946bea10f61c158ce8645e7646b29d44f122b
-F src/pager.c 5227ea29acbea4b6a9c6f1dfd3e8493de5fc2a93
+F src/pager.c 681b4e39d581ead8fd54283176138bec924a4bae
 F src/pager.h e0acb095b3ad0bca48f2ab00c87346665643f64f
 F src/parse.y fea607bdc0f4440e001ca277a49f507b5a3fb1e5
 F src/pragma.c 8883b4d34796efa315bdd0ec1b03f580ef1575b9
@@ -68,13 +68,13 @@ F src/printf.c f47a2f4b5387cd2ebb12e9117a1a5d6bd9a2b812
 F src/random.c ff5e9a8cad790e2a51cd4d2e7737dc8540e09d1d
 F src/select.c 2292b065bc6be61e01aad39a2e1b93e332fb7e57
 F src/shell.c 66b073375efbdee19045e7e0cd38b85f9aff71da
-F src/sqlite.h.in 015e02efa9e8bafa31b6c270116369ddff4e9803
-F src/sqliteInt.h 3541350fd0617225a61c6ff4af5e6e9d33bc3794
+F src/sqlite.h.in ba3a29daa6a16e054191ccb384a981964e882a1d
+F src/sqliteInt.h bb648c5274d67060cb13f0cd0da141aff1205358
 F src/table.c 486dcfce532685b53b5a2b5da8bba0ded6fb2316
-F src/tclsqlite.c ce481c0a21a20641cdfe87c9cbbb328cfb3a58b9
-F src/test1.c 4691cc7235683324791a0be560308ca338de36de
+F src/tclsqlite.c 230a367c50d84f15e92545f3d468acf192726696
+F src/test1.c 7374e6576e15a85f11d6032ea8a69d99643695b3
 F src/test2.c 36390cdfc70c08e5ee0b466d0654a117f398bbff
-F src/test3.c 7c97833e33496c2b69f4fe6b9882ac60a481da97
+F src/test3.c 9742aa146eb750cab81c1d5605286c3a0eb88054
 F src/test4.c a8fd681e139e1c61f22a77d07fc3a99cb28fff3f
 F src/test5.c 7162f8526affb771c4ed256826eee7bb9eca265f
 F src/test6.c cb811391ec0b7c75f29e545d4820a9cf19f3637e
@@ -88,7 +88,7 @@ F src/vdbe.c 8b0d676bcd8d8b12606f684ea2d7934147b40101
 F src/vdbe.h 8729a4ee16ff9aeab2af9667df3cf300ff978e13
 F src/vdbeInt.h 9b78ba00cc006bff17e04a54ba3ded9fc7810a10
 F src/vdbeapi.c b270b680cbc5d20b5a1abfdb08339667985df94e
-F src/vdbeaux.c 9dc10a70f0f697b9ede43237522e93ef863763e1
+F src/vdbeaux.c e7c116d4b14868d5fe570e361c6872839b6ab07a
 F src/vdbefifo.c 9efb94c8c3f4c979ebd0028219483f88e57584f5
 F src/vdbemem.c deba8d6e3727643924b210a8c531a496c2b8d386
 F src/where.c 0296a20c2e2a39c0cb785efe471fd1958a5259f3
@@ -96,11 +96,11 @@ F tclinstaller.tcl 046e3624671962dc50f0481d7c25b38ef803eb42
 F test/all.test 55706917a12cd616440d50c35323747b4a9f03c3
 F test/alter.test 9d6837a3d946b73df692b7cef2a7644d2e2f6bc6
 F test/alter2.test 60ba0a7057dc71ad630a1cc7c487104346849d50
-F test/alter3.test d4eecd8dbd008d0e66f1c201fa6dc2edca853c38
+F test/alter3.test 6e144ea3dcc395afcc28e794bb532be83dc8fdcb
 F test/altermalloc.test 6e1f404ec021eb2ba6582e3c77b0a35cf206b7af
 F test/analyze.test 2f55535aa335785db1a2f97d3f3831c16c09f8b0
 F test/attach.test dae07fa1554b618b9cc4c7bc349b3bc1a532180e
-F test/attach2.test 4c31484096fd24b7b98487f9c6d04d9f3f156c6c
+F test/attach2.test 609604393708c3d3227f7c316d15b25d015aa4dd
 F test/attach3.test 63013383adc4380af69779f34f4af19bd49f7cbe
 F test/attachmalloc.test cdb26c42850f04698377ccec05f5fa89d987837c
 F test/auth.test 973ae7274eae32c4453fbbcbd0ec2b80c5b1eeb3
@@ -184,7 +184,7 @@ F test/malloc.test a5ed721cf7d1b12602ede4f98c11b65ab1582cc0
 F test/malloc2.test e6e321db96d6c94cb18bf82ad7215070c41e624e
 F test/malloc3.test 9797d39eca7087a1022bcc0eb0b22215475c9698
 F test/malloc4.test 2e29d155eb4b4808019ef47eeedfcbe9e09e0f05
-F test/malloc5.test 8dd58affc441a726d16448a7188ab175bc9e7280
+F test/malloc5.test cd31d7be49690abc6845baa1f83bfd7b789ee187
 F test/manydb.test d81debbf5871242e3b5df1d3bb5e14c50431b6f8
 F test/memdb.test 1860e060be810bf0775bc57408a5b7c4954bcaea
 F test/memleak.test df2b2b96e77f8ba159a332299535b1e5f18e49ac
@@ -204,7 +204,7 @@ F test/pagesize.test cbc6a312b6f6c0f02619b189985df2a14164b690
 F test/pragma.test 95ea907adf68459e1be6f310c9ae94d1d59c465b
 F test/printf.test 9e10c74e16bf889f8495ddb3d6f5f891e75ff1b7
 F test/progress.test 16496001da445e6534afb94562c286708316d82f x
-F test/quick.test 4be1733beae790f4a61fb7c7827b396c95375d7e
+F test/quick.test 24ef81e4e9c1784e7f081e7ce4576c15659b97a7
 F test/quote.test 5891f2338980916cf7415484b4ce785294044adb
 F test/reindex.test 38b138abe36bf9a08c791ed44d9f76cd6b97b78b
 F test/rollback.test 94cd981ee3a627d9f6466f69dcf1f7dbfe695d7a
@@ -218,6 +218,7 @@ F test/select4.test c239f516aa31f42f2ef7c6d7cd01105f08f934ca
 F test/select5.test 07a90ab3c7e3f0a241a9cdea1d997b2c8a89ff0b
 F test/select6.test f459a19bdac0501c4d3eb1a4df4b7a76f1bb8ad4
 F test/select7.test 1bf795b948c133a15a2a5e99d3270e652ec58ce6
+F test/shared.test 1f68f8aecf5064d1da2fb2f316bbb6e70054f08e
 F test/sort.test 0e4456e729e5a92a625907c63dcdedfbe72c5dc5
 F test/subquery.test e6de53332c0301b3cfa34edc3f3cd5fa1e859efd
 F test/subselect.test 2d13fb7f450db3595adcdd24079a0dd1d2d6abc2
@@ -226,7 +227,7 @@ F test/table.test 149b76a28bbe2a1cd799232e4ae5133881e1902a
 F test/tableapi.test 6a66d58b37d46dc0f2b3c7d4bd2617d209399bd1
 F test/tclsqlite.test 19578d32a7692311918caf0ae3521d19525bcb62
 F test/temptable.test 7927261befdbc7b0a7ffebb85ecc70a74fa7b15b
-F test/tester.tcl a06c798a653daefb5bce2c85fc3a7d06450a1875
+F test/tester.tcl fae9e5b9ca4cfda10a6a52e95f7ebf731e52f3e5
 F test/thread1.test 776c9e459b75ba905193b351926ac4019b049f35
 F test/threadtest1.c 6029d9c5567db28e6dc908a0c63099c3ba6c383b
 F test/threadtest2.c 97a830d53c24c42290501fdfba4a6e5bdd34748b
@@ -242,7 +243,7 @@ F test/tkt1536.test 83ff7a7b6e248016f8d682d4f7a4ae114070d466
 F test/tkt1537.test 8e6c8399b5be8abeaac18ca17133990806b175fa
 F test/tkt1567.test 18023cc3626a365f0118e17b66decedec93b1a6f
 F test/trace.test 9fd28695c463b90c2d32c387a432e01eb26e8ccf
-F test/trans.test 10506dc30305cfb8c4098359f7f6f64786f69c5e
+F test/trans.test abd2f74c5685b156d79438e6e812db5bf984eea4
 F test/trigger1.test 152aed5a1fa90709fe171f2ca501a6b7f7901479
 F test/trigger2.test dea71f4b05e22896e72527278bc8ef71b7475bf2
 F test/trigger3.test 9102fd3933db294dc654b5aee9edfe9e94f2b9e2
@@ -331,7 +332,7 @@ F www/tclsqlite.tcl ddcf912ea48695603c8ed7efb29f0812ef8d1b49
 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0
 F www/version3.tcl a99cf5f6d8bd4d5537584a2b342f0fb9fa601d8b
 F www/whentouse.tcl 97e2b5cd296f7d8057e11f44427dea8a4c2db513
-P a88580bce045ee1c11cc6fd986ee7bab043ded4f
-R 2caf522c84042dde1b412f517fefbe54
-U drh
-Z a798ff6c2214d388933326d22672d220
+P 0bd9e35fd22946640f4fb1c1874922ae096916f7
+R 4baca77991845360aad68bb03e406c8e
+U danielk1977
+Z 2dfbc30edccb7b747e04832859ba5105
index a6b4bc00ab8349cdcab1262227ab4585e15a9e22..a15a1d6ddf5acc1f67c5300834413a6378ab3292 100644 (file)
@@ -1 +1 @@
-0bd9e35fd22946640f4fb1c1874922ae096916f7
\ No newline at end of file
+2afcad990190af97d1ad0010f211a5ca8f0fd745
\ No newline at end of file
index 6b8f962bdd3f89d08fac6223af000721d3bc99ff..e3febe981855fabe00b88ed9ea9224728ef273ad 100644 (file)
@@ -9,7 +9,7 @@
 **    May you share freely, never taking more than you give.
 **
 *************************************************************************
-** $Id: btree.c,v 1.276 2005/12/21 18:36:46 drh Exp $
+** $Id: btree.c,v 1.277 2005/12/30 16:28:02 danielk1977 Exp $
 **
 ** This file implements a external (disk-based) database using BTrees.
 ** For a detailed discussion of BTrees, refer to
 
 /* Forward declarations */
 typedef struct MemPage MemPage;
+typedef struct BtLock BtLock;
 
 /*
 ** This is a magic string that appears at the beginning of every
@@ -285,13 +286,13 @@ struct MemPage {
   u16 nFree;           /* Number of free bytes on the page */
   u16 nCell;           /* Number of cells on this page, local and ovfl */
   struct _OvflCell {   /* Cells that will not fit on aData[] */
-    u8 *pCell;           /* Pointers to the body of the overflow cell */
-    u16 idx;             /* Insert this cell before idx-th non-overflow cell */
+    u8 *pCell;          /* Pointers to the body of the overflow cell */
+    u16 idx;            /* Insert this cell before idx-th non-overflow cell */
   } aOvfl[5];
-  struct Btree *pBt;   /* Pointer back to BTree structure */
-  u8 *aData;           /* Pointer back to the start of the page */
-  Pgno pgno;           /* Page number for this page */
-  MemPage *pParent;    /* The parent of this page.  NULL for root */
+  BtShared *pBt;        /* Pointer back to BTree structure */
+  u8 *aData;            /* Pointer back to the start of the page */
+  Pgno pgno;            /* Page number for this page */
+  MemPage *pParent;     /* The parent of this page.  NULL for root */
 };
 
 /*
@@ -301,14 +302,32 @@ struct MemPage {
 */
 #define EXTRA_SIZE sizeof(MemPage)
 
+/* Btree handle */
+struct Btree {
+  sqlite3 *pSqlite;
+  BtShared *pBt;
+  u8 inTrans;            /* TRANS_NONE, TRANS_READ or TRANS_WRITE */
+};
+
+/*
+** Btree.inTrans may take one of the following values.
+**
+** If the shared-data extension is enabled, there may be multiple users
+** of the Btree structure. At most one of these may open a write transaction,
+** but any number may have active read transactions. Variable Btree.pDb 
+** points to the handle that owns any current write-transaction.
+*/
+#define TRANS_NONE  0
+#define TRANS_READ  1
+#define TRANS_WRITE 2
+
 /*
 ** Everything we need to know about an open database
 */
-struct Btree {
+struct BtShared {
   Pager *pPager;        /* The page cache */
   BtCursor *pCursor;    /* A list of all open cursors */
   MemPage *pPage1;      /* First page of the database */
-  u8 inTrans;           /* True if a transaction is in progress */
   u8 inStmt;            /* True if we are in a statement subtransaction */
   u8 readOnly;          /* True if the underlying file is readonly */
   u8 maxEmbedFrac;      /* Maximum payload as % of total page size */
@@ -325,15 +344,12 @@ struct Btree {
   int maxLeaf;          /* Maximum local payload in a LEAFDATA table */
   int minLeaf;          /* Minimum local payload in a LEAFDATA table */
   BusyHandler *pBusyHandler;   /* Callback for when there is lock contention */
+  u8 inTransaction;     /* Transaction state */
+  BtShared *pNext;      /* Next in SqliteTsd.pBtree linked list */
+  int nRef;             /* Number of references to this structure */
+  int nTransaction;     /* Number of open transactions (read + write) */
+  BtLock *pLock;        /* List of locks held on this shared-btree struct */
 };
-typedef Btree Bt;
-
-/*
-** Btree.inTrans may take one of the following values.
-*/
-#define TRANS_NONE  0
-#define TRANS_READ  1
-#define TRANS_WRITE 2
 
 /*
 ** An instance of the following structure is used to hold information
@@ -357,7 +373,7 @@ struct CellInfo {
 ** MemPage.aCell[] of the entry.
 */
 struct BtCursor {
-  Btree *pBt;               /* The Btree to which this cursor belongs */
+  Btree *pBtree;            /* The Btree to which this cursor belongs */
   BtCursor *pNext, *pPrev;  /* Forms a linked list of all cursors */
   int (*xCompare)(void*,int,const void*,int,const void*); /* Key comp func */
   void *pArg;               /* First arg to xCompare() */
@@ -385,7 +401,7 @@ int sqlite3_btree_trace=0;  /* True to enable tracing */
 /*
 ** Forward declaration
 */
-static int checkReadLocks(Btree*,Pgno,BtCursor*);
+static int checkReadLocks(BtShared*,Pgno,BtCursor*);
 
 /*
 ** Read or write a two- and four-byte big-endian integer values.
@@ -422,6 +438,122 @@ static void put4byte(unsigned char *p, u32 v){
 */
 #define PENDING_BYTE_PAGE(pBt) ((PENDING_BYTE/(pBt)->pageSize)+1)
 
+/*
+** A linked list of the following structures is stored at BtShared.pLock.
+** Locks are added (or upgraded from READ_LOCK to WRITE_LOCK) when a cursor 
+** is opened on the table with root page BtShared.iTable. Locks are removed
+** from this list when a transaction is committed or rolled back, or when
+** a btree handle is closed.
+*/
+struct BtLock {
+  Btree *pBtree;        /* Btree handle holding this lock */
+  Pgno iTable;          /* Root page of table */
+  u8 eLock;             /* READ_LOCK or WRITE_LOCK */
+  BtLock *pNext;        /* Next in BtShared.pLock list */
+};
+
+/* Candidate values for BtLock.eLock */
+#define READ_LOCK     1
+#define WRITE_LOCK    2
+
+#ifdef SQLITE_OMIT_SHARED_CACHE
+  /*
+  ** The functions queryTableLock(), lockTable() and unlockAllTables()
+  ** manipulate entries in the BtShared.pLock linked list used to store
+  ** shared-cache table level locks. If the library is compiled with the
+  ** shared-cache feature disabled, then there is only ever one user
+  ** of each BtShared structure and so this locking is not required. 
+  ** So define the three interface functions as no-ops.
+  */
+  #define queryTableLock(a,b,c) SQLITE_OK
+  #define lockTable(a,b,c) SQLITE_OK
+  #define unlockAllTables(a,b,c)
+#else
+
+/*
+** Query to see if btree handle p may obtain a lock of type eLock 
+** (READ_LOCK or WRITE_LOCK) on the table with root-page iTab. Return
+** SQLITE_OK if the lock may be obtained (by calling lockTable()), or
+** SQLITE_BUSY if not.
+*/
+static int queryTableLock(Btree *p, Pgno iTab, u8 eLock){
+  BtShared *pBt = p->pBt;
+  BtLock *pIter;
+
+  for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){
+    if( pIter->pBtree!=p && pIter->iTable==iTab && 
+        (pIter->eLock!=READ_LOCK || eLock!=READ_LOCK) ){
+      return SQLITE_BUSY;
+    }
+  }
+  return SQLITE_OK;
+}
+
+/*
+** Add a lock on the table with root-page iTable to the shared-btree used
+** by Btree handle p. Parameter eLock must be either READ_LOCK or 
+** WRITE_LOCK.
+**
+** SQLITE_OK is returned if the lock is added successfully. SQLITE_BUSY and
+** SQLITE_NOMEM may also be returned.
+*/
+static int lockTable(Btree *p, Pgno iTable, u8 eLock){
+  BtShared *pBt = p->pBt;
+  BtLock *pLock = 0;
+  BtLock *pIter;
+
+  assert( SQLITE_OK==queryTableLock(p, iTable, eLock) );
+
+  /* First search the list for an existing lock on this table. */
+  for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){
+    if( pIter->iTable==iTable && pIter->pBtree==p ){
+      pLock = pIter;
+      break;
+    }
+  }
+
+  /* If the above search did not find a BtLock struct associating Btree p
+  ** with table iTable, allocate one and link it into the list.
+  */
+  if( !pLock ){
+    pLock = (BtLock *)sqliteMalloc(sizeof(BtLock));
+    if( !pLock ){
+      return SQLITE_NOMEM;
+    }
+    pLock->iTable = iTable;
+    pLock->pBtree = p;
+    pLock->pNext = pBt->pLock;
+    pBt->pLock = pLock;
+  }
+
+  /* Set the BtLock.eLock variable to the maximum of the current lock
+  ** and the requested lock. This means if a write-lock was already held
+  ** and a read-lock requested, we don't incorrectly downgrade the lock.
+  */
+  assert( WRITE_LOCK>READ_LOCK );
+  pLock->eLock = MAX(pLock->eLock, eLock);
+
+  return SQLITE_OK;
+}
+
+/*
+** Release all the table locks (locks obtained via calls to the lockTable()
+** procedure) held by Btree handle p.
+*/
+static void unlockAllTables(Btree *p){
+  BtLock **ppIter = &p->pBt->pLock;
+  while( *ppIter ){
+    BtLock *pLock = *ppIter;
+    if( pLock->pBtree==p ){
+      *ppIter = pLock->pNext;
+      sqliteFree(pLock);
+    }else{
+      ppIter = &pLock->pNext;
+    }
+  }
+}
+#endif /* SQLITE_OMIT_SHARED_CACHE */
+
 #ifndef SQLITE_OMIT_AUTOVACUUM
 /*
 ** These macros define the location of the pointer-map entry for a 
@@ -486,7 +618,7 @@ static void put4byte(unsigned char *p, u32 v){
 ** so that it maps to type 'eType' and parent page number 'pgno'.
 ** An error code is returned if something goes wrong, otherwise SQLITE_OK.
 */
-static int ptrmapPut(Btree *pBt, Pgno key, u8 eType, Pgno parent){
+static int ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent){
   u8 *pPtrmap;    /* The pointer map page */
   Pgno iPtrmap;   /* The pointer map page number */
   int offset;     /* Offset in pointer map page */
@@ -523,7 +655,7 @@ static int ptrmapPut(Btree *pBt, Pgno key, u8 eType, Pgno parent){
 ** the type and parent page number to *pEType and *pPgno respectively.
 ** An error code is returned if something goes wrong, otherwise SQLITE_OK.
 */
-static int ptrmapGet(Btree *pBt, Pgno key, u8 *pEType, Pgno *pPgno){
+static int ptrmapGet(BtShared *pBt, Pgno key, u8 *pEType, Pgno *pPgno){
   int iPtrmap;       /* Pointer map page index */
   u8 *pPtrmap;       /* Pointer map page data */
   int offset;        /* Offset of entry in pointer map */
@@ -546,6 +678,27 @@ static int ptrmapGet(Btree *pBt, Pgno key, u8 *pEType, Pgno *pPgno){
 
 #endif /* SQLITE_OMIT_AUTOVACUUM */
 
+/*
+** Return a pointer to the Btree structure associated with btree pBt
+** and connection handle pSqlite.
+*/
+#if 0
+static Btree *btree_findref(BtShared *pBt, sqlite3 *pSqlite){
+#ifndef SQLITE_OMIT_SHARED_DATA
+  if( pBt->aRef ){
+    int i;
+    for(i=0; i<pBt->nRef; i++){
+      if( pBt->aRef[i].pSqlite==pSqlite ){
+        return &pBt->aRef[i];
+      }
+    }
+    assert(0);
+  }
+#endif
+  return &pBt->ref;
+}
+#endif
+
 /*
 ** Given a btree page and a cell index (0 means the first cell on
 ** the page, 1 means the second cell, and so forth) return a pointer
@@ -790,6 +943,15 @@ static void _pageIntegrity(MemPage *pPage){
 # define pageIntegrity(X)
 #endif
 
+/* A bunch of assert() statements to check the transaction state variables
+** of handle p (type Btree*) are internally consistent.
+*/
+#define btreeIntegrity(p) \
+  assert( p->inTrans!=TRANS_NONE || p->pBt->nTransaction<p->pBt->nRef ); \
+  assert( p->pBt->nTransaction<=p->pBt->nRef ); \
+  assert( p->pBt->inTransaction!=TRANS_NONE || p->pBt->nTransaction==0 ); \
+  assert( p->pBt->inTransaction>=p->inTrans ); 
+
 /*
 ** Defragment the page given.  All Cells are moved to the
 ** end of the page and all free space is collected into one
@@ -978,7 +1140,7 @@ static void freeSpace(MemPage *pPage, int start, int size){
 ** and initialize fields of the MemPage structure accordingly.
 */
 static void decodeFlags(MemPage *pPage, int flagByte){
-  Btree *pBt;     /* A copy of pPage->pBt */
+  BtShared *pBt;     /* A copy of pPage->pBt */
 
   assert( pPage->hdrOffset==(pPage->pgno==1 ? 100 : 0) );
   pPage->intKey = (flagByte & (PTF_INTKEY|PTF_LEAFDATA))!=0;
@@ -1018,7 +1180,7 @@ static int initPage(
   int pc;            /* Address of a freeblock within pPage->aData[] */
   int hdr;           /* Offset to beginning of page header */
   u8 *data;          /* Equal to pPage->aData */
-  Btree *pBt;        /* The main btree structure */
+  BtShared *pBt;        /* The main btree structure */
   int usableSize;    /* Amount of usable space on each page */
   int cellOffset;    /* Offset from start of page to first cell pointer */
   int nFree;         /* Number of unused bytes on the page */
@@ -1091,7 +1253,7 @@ static int initPage(
 */
 static void zeroPage(MemPage *pPage, int flags){
   unsigned char *data = pPage->aData;
-  Btree *pBt = pPage->pBt;
+  BtShared *pBt = pPage->pBt;
   int hdr = pPage->hdrOffset;
   int first;
 
@@ -1119,7 +1281,7 @@ static void zeroPage(MemPage *pPage, int flags){
 ** Get a page from the pager.  Initialize the MemPage.pBt and
 ** MemPage.aData elements if needed.
 */
-static int getPage(Btree *pBt, Pgno pgno, MemPage **ppPage){
+static int getPage(BtShared *pBt, Pgno pgno, MemPage **ppPage){
   int rc;
   unsigned char *aData;
   MemPage *pPage;
@@ -1140,7 +1302,7 @@ static int getPage(Btree *pBt, Pgno pgno, MemPage **ppPage){
 ** getPage() and initPage().
 */
 static int getAndInitPage(
-  Btree *pBt,          /* The database file */
+  BtShared *pBt,          /* The database file */
   Pgno pgno,           /* Number of the page to get */
   MemPage **ppPage,    /* Write the page pointer here */
   MemPage *pParent     /* Parent of the page */
@@ -1213,13 +1375,57 @@ static void pageReinit(void *pData, int pageSize){
 */
 int sqlite3BtreeOpen(
   const char *zFilename,  /* Name of the file containing the BTree database */
+  sqlite3 *pSqlite,       /* Associated database handle */
   Btree **ppBtree,        /* Pointer to new Btree object written here */
   int flags               /* Options */
 ){
-  Btree *pBt;
+  BtShared *pBt;          /* Shared part of btree structure */
+  Btree *p;               /* Handle to return */
   int rc;
   int nReserve;
   unsigned char zDbHeader[100];
+  SqliteTsd *pTsd = sqlite3Tsd();
+
+  /* Set the variable isMemdb to true for an in-memory database, or 
+  ** false for a file-based database. This symbol is only required if
+  ** either of the shared-data or autovacuum features are compiled 
+  ** into the library.
+  */
+#if !defined(SQLITE_OMIT_SHARED_CACHE) || !defined(SQLITE_OMIT_AUTOVACUUM)
+  #ifdef SQLITE_OMIT_MEMORYDB
+  const int isMemdb = !zFilename;
+  #else
+  const int isMemdb = !zFilename || (strcmp(zFilename, ":memory:")?0:1);
+  #endif
+#endif
+
+  p = sqliteMalloc(sizeof(Btree));
+  if( !p ){
+    return SQLITE_NOMEM;
+  }
+  p->inTrans = TRANS_NONE;
+  p->pSqlite = pSqlite;
+
+  /* Try to find an existing Btree structure opened on zFilename. */
+#ifndef SQLITE_OMIT_SHARED_CACHE
+  if( pTsd->useSharedData && zFilename && !isMemdb ){
+    char *zFullPathname = sqlite3Os.xFullPathname(zFilename);
+    if( !zFullPathname ){
+      sqliteFree(p);
+      return SQLITE_NOMEM;
+    }
+    for(pBt=pTsd->pBtree; pBt; pBt=pBt->pNext){
+      if( 0==strcmp(zFullPathname, sqlite3pager_filename(pBt->pPager)) ){
+        p->pBt = pBt;
+        *ppBtree = p;
+        pBt->nRef++;
+        sqliteFree(zFullPathname);
+        return SQLITE_OK;
+      }
+    }
+    sqliteFree(zFullPathname);
+  }
+#endif
 
   /*
   ** The following asserts make sure that structures used by the btree are
@@ -1235,15 +1441,19 @@ int sqlite3BtreeOpen(
   pBt = sqliteMalloc( sizeof(*pBt) );
   if( pBt==0 ){
     *ppBtree = 0;
+    sqliteFree(p);
     return SQLITE_NOMEM;
   }
   rc = sqlite3pager_open(&pBt->pPager, zFilename, EXTRA_SIZE, flags);
   if( rc!=SQLITE_OK ){
     if( pBt->pPager ) sqlite3pager_close(pBt->pPager);
     sqliteFree(pBt);
+    sqliteFree(p);
     *ppBtree = 0;
     return rc;
   }
+  p->pBt = pBt;
+
   sqlite3pager_set_destructor(pBt->pPager, pageDestructor);
   sqlite3pager_set_reiniter(pBt->pPager, pageReinit);
   pBt->pCursor = 0;
@@ -1264,11 +1474,7 @@ int sqlite3BtreeOpen(
     ** then ":memory:" is just a regular file-name. Respect the auto-vacuum
     ** default in this case.
     */
-#ifndef SQLITE_OMIT_MEMORYDB
-    if( zFilename && strcmp(zFilename,":memory:") ){
-#else
-    if( zFilename ){
-#endif
+    if( zFilename && !isMemdb ){
       pBt->autoVacuum = SQLITE_DEFAULT_AUTOVACUUM;
     }
 #endif
@@ -1286,17 +1492,67 @@ int sqlite3BtreeOpen(
   pBt->usableSize = pBt->pageSize - nReserve;
   assert( (pBt->pageSize & 7)==0 );  /* 8-byte alignment of pageSize */
   sqlite3pager_set_pagesize(pBt->pPager, pBt->pageSize);
-  *ppBtree = pBt;
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+  /* Add the new btree to the linked list starting at SqliteTsd.pBtree */
+  if( pTsd->useSharedData && zFilename && !isMemdb ){
+    pBt->pNext = pTsd->pBtree;
+    pTsd->pBtree = pBt;
+  }
+  pBt->nRef = 1;
+#endif
+  *ppBtree = p;
   return SQLITE_OK;
 }
 
 /*
 ** Close an open database and invalidate all cursors.
 */
-int sqlite3BtreeClose(Btree *pBt){
-  while( pBt->pCursor ){
-    sqlite3BtreeCloseCursor(pBt->pCursor);
+int sqlite3BtreeClose(Btree *p){
+  SqliteTsd *pTsd = sqlite3Tsd();
+  BtShared *pBt = p->pBt;
+  BtCursor *pCur;
+
+  /* Drop any table-locks */
+  unlockAllTables(p);
+
+  /* Close all cursors opened via this handle.  */
+  pCur = pBt->pCursor;
+  while( pCur ){
+    BtCursor *pTmp = pCur;
+    pCur = pCur->pNext;
+    if( pTmp->pBtree==p ){
+      sqlite3BtreeCloseCursor(pTmp);
+    }
+  }
+
+  sqliteFree(p);
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+  /* If there are still other outstanding references to the shared-btree
+  ** structure, return now. The remainder of this procedure cleans 
+  ** up the shared-btree.
+  */
+  assert( pBt->nRef>0 );
+  pBt->nRef--;
+  if( pBt->nRef ){
+    return SQLITE_OK;
+  }
+
+  /* Remove the shared-btree from the thread wide list */
+  if( pTsd->pBtree==pBt ){
+    pTsd->pBtree = pBt->pNext;
+  }else{
+    BtShared *pPrev;
+    for(pPrev=pTsd->pBtree; pPrev && pPrev->pNext!=pBt; pPrev=pPrev->pNext);
+    if( pPrev ){
+      pPrev->pNext = pBt->pNext;
+    }
   }
+#endif
+
+  /* Close the pager and free the shared-btree structure */
+  assert( !pBt->pCursor );
   sqlite3pager_close(pBt->pPager);
   sqliteFree(pBt);
   return SQLITE_OK;
@@ -1305,7 +1561,8 @@ int sqlite3BtreeClose(Btree *pBt){
 /*
 ** Change the busy handler callback function.
 */
-int sqlite3BtreeSetBusyHandler(Btree *pBt, BusyHandler *pHandler){
+int sqlite3BtreeSetBusyHandler(Btree *p, BusyHandler *pHandler){
+  BtShared *pBt = p->pBt;
   pBt->pBusyHandler = pHandler;
   sqlite3pager_set_busyhandler(pBt->pPager, pHandler);
   return SQLITE_OK;
@@ -1326,7 +1583,8 @@ int sqlite3BtreeSetBusyHandler(Btree *pBt, BusyHandler *pHandler){
 ** Synchronous is on by default so database corruption is not
 ** normally a worry.
 */
-int sqlite3BtreeSetCacheSize(Btree *pBt, int mxPage){
+int sqlite3BtreeSetCacheSize(Btree *p, int mxPage){
+  BtShared *pBt = p->pBt;
   sqlite3pager_set_cachesize(pBt->pPager, mxPage);
   return SQLITE_OK;
 }
@@ -1340,7 +1598,8 @@ int sqlite3BtreeSetCacheSize(Btree *pBt, int mxPage){
 ** probability of damage to near zero but with a write performance reduction.
 */
 #ifndef SQLITE_OMIT_PAGER_PRAGMAS
-int sqlite3BtreeSetSafetyLevel(Btree *pBt, int level){
+int sqlite3BtreeSetSafetyLevel(Btree *p, int level){
+  BtShared *pBt = p->pBt;
   sqlite3pager_set_safety_level(pBt->pPager, level);
   return SQLITE_OK;
 }
@@ -1350,7 +1609,8 @@ int sqlite3BtreeSetSafetyLevel(Btree *pBt, int level){
 ** Return TRUE if the given btree is set to safety level 1.  In other
 ** words, return TRUE if no sync() occurs on the disk files.
 */
-int sqlite3BtreeSyncDisabled(Btree *pBt){
+int sqlite3BtreeSyncDisabled(Btree *p){
+  BtShared *pBt = p->pBt;
   assert( pBt && pBt->pPager );
   return sqlite3pager_nosync(pBt->pPager);
 }
@@ -1371,7 +1631,8 @@ int sqlite3BtreeSyncDisabled(Btree *pBt){
 ** If parameter nReserve is less than zero, then the number of reserved
 ** bytes per page is left unchanged.
 */
-int sqlite3BtreeSetPageSize(Btree *pBt, int pageSize, int nReserve){
+int sqlite3BtreeSetPageSize(Btree *p, int pageSize, int nReserve){
+  BtShared *pBt = p->pBt;
   if( pBt->pageSizeFixed ){
     return SQLITE_READONLY;
   }
@@ -1381,6 +1642,7 @@ int sqlite3BtreeSetPageSize(Btree *pBt, int pageSize, int nReserve){
   if( pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE &&
         ((pageSize-1)&pageSize)==0 ){
     assert( (pageSize & 7)==0 );
+    assert( !pBt->pPage1 && !pBt->pCursor );
     pBt->pageSize = sqlite3pager_set_pagesize(pBt->pPager, pageSize);
   }
   pBt->usableSize = pBt->pageSize - nReserve;
@@ -1390,11 +1652,11 @@ int sqlite3BtreeSetPageSize(Btree *pBt, int pageSize, int nReserve){
 /*
 ** Return the currently defined page size
 */
-int sqlite3BtreeGetPageSize(Btree *pBt){
-  return pBt->pageSize;
+int sqlite3BtreeGetPageSize(Btree *p){
+  return p->pBt->pageSize;
 }
-int sqlite3BtreeGetReserve(Btree *pBt){
-  return pBt->pageSize - pBt->usableSize;
+int sqlite3BtreeGetReserve(Btree *p){
+  return p->pBt->pageSize - p->pBt->usableSize;
 }
 #endif /* !defined(SQLITE_OMIT_PAGER_PRAGMAS) || !defined(SQLITE_OMIT_VACUUM) */
 
@@ -1404,7 +1666,8 @@ int sqlite3BtreeGetReserve(Btree *pBt){
 ** is disabled. The default value for the auto-vacuum property is 
 ** determined by the SQLITE_DEFAULT_AUTOVACUUM macro.
 */
-int sqlite3BtreeSetAutoVacuum(Btree *pBt, int autoVacuum){
+int sqlite3BtreeSetAutoVacuum(Btree *p, int autoVacuum){
+  BtShared *pBt = p->pBt;;
 #ifdef SQLITE_OMIT_AUTOVACUUM
   return SQLITE_READONLY;
 #else
@@ -1420,11 +1683,11 @@ int sqlite3BtreeSetAutoVacuum(Btree *pBt, int autoVacuum){
 ** Return the value of the 'auto-vacuum' property. If auto-vacuum is 
 ** enabled 1 is returned. Otherwise 0.
 */
-int sqlite3BtreeGetAutoVacuum(Btree *pBt){
+int sqlite3BtreeGetAutoVacuum(Btree *p){
 #ifdef SQLITE_OMIT_AUTOVACUUM
   return 0;
 #else
-  return pBt->autoVacuum;
+  return p->pBt->autoVacuum;
 #endif
 }
 
@@ -1439,7 +1702,7 @@ int sqlite3BtreeGetAutoVacuum(Btree *pBt){
 ** is returned if we run out of memory.  SQLITE_PROTOCOL is returned
 ** if there is a locking protocol violation.
 */
-static int lockBtree(Btree *pBt){
+static int lockBtree(BtShared *pBt){
   int rc, pageSize;
   MemPage *pPage1;
   if( pBt->pPage1 ) return SQLITE_OK;
@@ -1511,11 +1774,18 @@ page1_init_failed:
 ** This routine works like lockBtree() except that it also invokes the
 ** busy callback if there is lock contention.
 */
-static int lockBtreeWithRetry(Btree *pBt){
+static int lockBtreeWithRetry(Btree *pRef){
   int rc = SQLITE_OK;
-  if( pBt->inTrans==TRANS_NONE ){
-    rc = sqlite3BtreeBeginTrans(pBt, 0);
-    pBt->inTrans = TRANS_NONE;
+  if( pRef->inTrans==TRANS_NONE ){
+    u8 inTransaction = pRef->pBt->inTransaction;
+    btreeIntegrity(pRef);
+    rc = sqlite3BtreeBeginTrans(pRef, 0);
+    pRef->pBt->inTransaction = inTransaction;
+    pRef->inTrans = TRANS_NONE;
+    if( rc==SQLITE_OK ){
+      pRef->pBt->nTransaction--;
+    }
+    btreeIntegrity(pRef);
   }
   return rc;
 }
@@ -1531,8 +1801,8 @@ static int lockBtreeWithRetry(Btree *pBt){
 **
 ** If there is a transaction in progress, this routine is a no-op.
 */
-static void unlockBtreeIfUnused(Btree *pBt){
-  if( pBt->inTrans==TRANS_NONE && pBt->pCursor==0 && pBt->pPage1!=0 ){
+static void unlockBtreeIfUnused(BtShared *pBt){
+  if( pBt->inTransaction==TRANS_NONE && pBt->pCursor==0 && pBt->pPage1!=0 ){
     if( pBt->pPage1->aData==0 ){
       MemPage *pPage = pBt->pPage1;
       pPage->aData = &((u8*)pPage)[-pBt->pageSize];
@@ -1549,7 +1819,7 @@ static void unlockBtreeIfUnused(Btree *pBt){
 ** Create a new database by initializing the first page of the
 ** file.
 */
-static int newDatabase(Btree *pBt){
+static int newDatabase(BtShared *pBt){
   MemPage *pP1;
   unsigned char *data;
   int rc;
@@ -1614,14 +1884,17 @@ static int newDatabase(Btree *pBt){
 ** when A already has a read lock, we encourage A to give up and let B
 ** proceed.
 */
-int sqlite3BtreeBeginTrans(Btree *pBt, int wrflag){
+int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
+  BtShared *pBt = p->pBt;
   int rc = SQLITE_OK;
 
+  btreeIntegrity(p);
+
   /* If the btree is already in a write-transaction, or it
   ** is already in a read-transaction and a read-transaction
   ** is requested, this is a no-op.
   */
-  if( pBt->inTrans==TRANS_WRITE || (pBt->inTrans==TRANS_READ && !wrflag) ){
+  if( p->inTrans==TRANS_WRITE || (p->inTrans==TRANS_READ && !wrflag) ){
     return SQLITE_OK;
   }
 
@@ -1630,6 +1903,14 @@ int sqlite3BtreeBeginTrans(Btree *pBt, int wrflag){
     return SQLITE_READONLY;
   }
 
+  /* If another database handle has already opened a write transaction 
+  ** on this shared-btree structure and a second write transaction is
+  ** requested, return SQLITE_BUSY.
+  */
+  if( pBt->inTransaction==TRANS_WRITE && wrflag ){
+    return SQLITE_BUSY;
+  }
+
   do {
     if( pBt->pPage1==0 ){
       rc = lockBtree(pBt);
@@ -1643,13 +1924,24 @@ int sqlite3BtreeBeginTrans(Btree *pBt, int wrflag){
     }
   
     if( rc==SQLITE_OK ){
-      pBt->inTrans = (wrflag?TRANS_WRITE:TRANS_READ);
       if( wrflag ) pBt->inStmt = 0;
     }else{
       unlockBtreeIfUnused(pBt);
     }
-  }while( rc==SQLITE_BUSY && pBt->inTrans==TRANS_NONE &&
+  }while( rc==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE &&
           sqlite3InvokeBusyHandler(pBt->pBusyHandler) );
+
+  if( rc==SQLITE_OK ){
+    if( p->inTrans==TRANS_NONE ){
+      pBt->nTransaction++;
+    }
+    p->inTrans = (wrflag?TRANS_WRITE:TRANS_READ);
+    if( p->inTrans>pBt->inTransaction ){
+      pBt->inTransaction = p->inTrans;
+    }
+  }
+
+  btreeIntegrity(p);
   return rc;
 }
 
@@ -1664,7 +1956,7 @@ static int setChildPtrmaps(MemPage *pPage){
   int i;                             /* Counter variable */
   int nCell;                         /* Number of cells in page pPage */
   int rc = SQLITE_OK;                /* Return code */
-  Btree *pBt = pPage->pBt;
+  BtShared *pBt = pPage->pBt;
   int isInitOrig = pPage->isInit;
   Pgno pgno = pPage->pgno;
 
@@ -1764,7 +2056,7 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){
 ** database. The pDbPage reference remains valid.
 */
 static int relocatePage(
-  Btree *pBt,              /* Btree */
+  BtShared *pBt,           /* Btree */
   MemPage *pDbPage,        /* Open page to move */
   u8 eType,                /* Pointer map 'type' entry for pDbPage */
   Pgno iPtrPage,           /* Pointer map 'page-no' entry for pDbPage */
@@ -1834,13 +2126,13 @@ static int relocatePage(
 }
 
 /* Forward declaration required by autoVacuumCommit(). */
-static int allocatePage(Btree *, MemPage **, Pgno *, Pgno, u8);
+static int allocatePage(BtShared *, MemPage **, Pgno *, Pgno, u8);
 
 /*
 ** This routine is called prior to sqlite3pager_commit when a transaction
 ** is commited for an auto-vacuum database.
 */
-static int autoVacuumCommit(Btree *pBt, Pgno *nTrunc){
+static int autoVacuumCommit(BtShared *pBt, Pgno *nTrunc){
   Pager *pPager = pBt->pPager;
   Pgno nFreeList;   /* Number of pages remaining on the free-list. */
   int nPtrMap;      /* Number of pointer-map pages deallocated */
@@ -1960,14 +2252,43 @@ autovacuum_out:
 ** This will release the write lock on the database file.  If there
 ** are no active cursors, it also releases the read lock.
 */
-int sqlite3BtreeCommit(Btree *pBt){
+int sqlite3BtreeCommit(Btree *p){
   int rc = SQLITE_OK;
-  if( pBt->inTrans==TRANS_WRITE ){
+  BtShared *pBt = p->pBt;
+
+  btreeIntegrity(p);
+  unlockAllTables(p);
+
+  /* If the handle has a write-transaction open, commit the shared-btrees 
+  ** transaction and set the shared state to TRANS_READ.
+  */
+  if( p->inTrans==TRANS_WRITE ){
+    assert( pBt->inTransaction==TRANS_WRITE );
+    assert( pBt->nTransaction>0 );
     rc = sqlite3pager_commit(pBt->pPager);
+    pBt->inTransaction = TRANS_READ;
+    pBt->inStmt = 0;
   }
-  pBt->inTrans = TRANS_NONE;
-  pBt->inStmt = 0;
+
+  /* If the handle has any kind of transaction open, decrement the transaction
+  ** count of the shared btree. If the transaction count reaches 0, set
+  ** the shared state to TRANS_NONE. The unlockBtreeIfUnused() call below
+  ** will unlock the pager.
+  */
+  if( p->inTrans!=TRANS_NONE ){
+    pBt->nTransaction--;
+    if( 0==pBt->nTransaction ){
+      pBt->inTransaction = TRANS_NONE;
+    }
+  }
+
+  /* Set the handles current transaction state to TRANS_NONE and unlock
+  ** the pager if this call closed the only read or write transaction.
+  */
+  p->inTrans = TRANS_NONE;
   unlockBtreeIfUnused(pBt);
+
+  btreeIntegrity(p);
   return rc;
 }
 
@@ -1977,11 +2298,11 @@ int sqlite3BtreeCommit(Btree *pBt){
 ** in assert() expressions, so it is only compiled if NDEBUG is not
 ** defined.
 */
-static int countWriteCursors(Btree *pBt){
+static int countWriteCursors(BtShared *pBt){
   BtCursor *pCur;
   int r = 0;
   for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
-    if( pCur->wrFlag ) r++;
+    if( pCur->wrFlag ) r++; 
   }
   return r;
 }
@@ -1991,8 +2312,9 @@ static int countWriteCursors(Btree *pBt){
 /*
 ** Print debugging information about all cursors to standard output.
 */
-void sqlite3BtreeCursorList(Btree *pBt){
+void sqlite3BtreeCursorList(Btree *p){
   BtCursor *pCur;
+  BtShared *pBt = p->pBt;
   for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
     MemPage *pPage = pCur->pPage;
     char *zMode = pCur->wrFlag ? "rw" : "ro";
@@ -2014,10 +2336,17 @@ void sqlite3BtreeCursorList(Btree *pBt){
 ** This will release the write lock on the database file.  If there
 ** are no active cursors, it also releases the read lock.
 */
-int sqlite3BtreeRollback(Btree *pBt){
+int sqlite3BtreeRollback(Btree *p){
   int rc = SQLITE_OK;
+  BtShared *pBt = p->pBt;
   MemPage *pPage1;
-  if( pBt->inTrans==TRANS_WRITE ){
+
+  btreeIntegrity(p);
+  unlockAllTables(p);
+
+  if( p->inTrans==TRANS_WRITE ){
+    assert( TRANS_WRITE==pBt->inTransaction );
+
     rc = sqlite3pager_rollback(pBt->pPager);
     /* The rollback may have destroyed the pPage1->aData value.  So
     ** call getPage() on page 1 again to make sure pPage1->aData is
@@ -2026,10 +2355,22 @@ int sqlite3BtreeRollback(Btree *pBt){
       releasePage(pPage1);
     }
     assert( countWriteCursors(pBt)==0 );
+    pBt->inTransaction = TRANS_READ;
+  }
+
+  if( p->inTrans!=TRANS_NONE ){
+    assert( pBt->nTransaction>0 );
+    pBt->nTransaction--;
+    if( 0==pBt->nTransaction ){
+      pBt->inTransaction = TRANS_NONE;
+    }
   }
-  pBt->inTrans = TRANS_NONE;
+
+  p->inTrans = TRANS_NONE;
   pBt->inStmt = 0;
   unlockBtreeIfUnused(pBt);
+
+  btreeIntegrity(p);
   return rc;
 }
 
@@ -2048,11 +2389,13 @@ int sqlite3BtreeRollback(Btree *pBt){
 ** error occurs within the statement, the effect of that one statement
 ** can be rolled back without having to rollback the entire transaction.
 */
-int sqlite3BtreeBeginStmt(Btree *pBt){
+int sqlite3BtreeBeginStmt(Btree *p){
   int rc;
-  if( (pBt->inTrans!=TRANS_WRITE) || pBt->inStmt ){
+  BtShared *pBt = p->pBt;
+  if( (p->inTrans!=TRANS_WRITE) || pBt->inStmt ){
     return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
   }
+  assert( pBt->inTransaction==TRANS_WRITE );
   rc = pBt->readOnly ? SQLITE_OK : sqlite3pager_stmt_begin(pBt->pPager);
   pBt->inStmt = 1;
   return rc;
@@ -2063,8 +2406,9 @@ int sqlite3BtreeBeginStmt(Btree *pBt){
 ** Commit the statment subtransaction currently in progress.  If no
 ** subtransaction is active, this is a no-op.
 */
-int sqlite3BtreeCommitStmt(Btree *pBt){
+int sqlite3BtreeCommitStmt(Btree *p){
   int rc;
+  BtShared *pBt = p->pBt;
   if( pBt->inStmt && !pBt->readOnly ){
     rc = sqlite3pager_stmt_commit(pBt->pPager);
   }else{
@@ -2082,8 +2426,9 @@ int sqlite3BtreeCommitStmt(Btree *pBt){
 ** to use a cursor that was open at the beginning of this operation
 ** will result in an error.
 */
-int sqlite3BtreeRollbackStmt(Btree *pBt){
+int sqlite3BtreeRollbackStmt(Btree *p){
   int rc;
+  BtShared *pBt = p->pBt;
   if( pBt->inStmt==0 || pBt->readOnly ) return SQLITE_OK;
   rc = sqlite3pager_stmt_rollback(pBt->pPager);
   assert( countWriteCursors(pBt)==0 );
@@ -2151,7 +2496,7 @@ static int dfltCompare(
 ** always ignored for INTKEY tables.
 */
 int sqlite3BtreeCursor(
-  Btree *pBt,                                 /* The btree */
+  Btree *p,                                   /* The btree */
   int iTable,                                 /* Root page of table to open */
   int wrFlag,                                 /* 1 to write. 0 read-only */
   int (*xCmp)(void*,int,const void*,int,const void*), /* Key Comparison func */
@@ -2160,6 +2505,7 @@ int sqlite3BtreeCursor(
 ){
   int rc;
   BtCursor *pCur;
+  BtShared *pBt = p->pBt;
 
   *ppCur = 0;
   if( wrFlag ){
@@ -2170,8 +2516,16 @@ int sqlite3BtreeCursor(
       return SQLITE_LOCKED;
     }
   }
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+  rc = queryTableLock(p, iTable, wrFlag?WRITE_LOCK:READ_LOCK);
+  if( rc!=SQLITE_OK ){
+    return rc;
+  }
+#endif
+
   if( pBt->pPage1==0 ){
-    rc = lockBtreeWithRetry(pBt);
+    rc = lockBtreeWithRetry(p);
     if( rc!=SQLITE_OK ){
       return rc;
     }
@@ -2191,9 +2545,21 @@ int sqlite3BtreeCursor(
   if( rc!=SQLITE_OK ){
     goto create_cursor_exception;
   }
+
+  /* Obtain the table-lock on the shared-btree. */
+  rc = lockTable(p, iTable, wrFlag?WRITE_LOCK:READ_LOCK);
+  if( rc!=SQLITE_OK ){
+    assert( rc==SQLITE_NOMEM );
+    goto create_cursor_exception;
+  }
+
+  /* Now that no other errors can occur, finish filling in the BtCursor
+  ** variables, link the cursor into the BtShared list and set *ppCur (the
+  ** output argument to this function).
+  */
   pCur->xCompare = xCmp ? xCmp : dfltCompare;
   pCur->pArg = pArg;
-  pCur->pBt = pBt;
+  pCur->pBtree = p;
   pCur->wrFlag = wrFlag;
   pCur->idx = 0;
   memset(&pCur->info, 0, sizeof(pCur->info));
@@ -2205,8 +2571,8 @@ int sqlite3BtreeCursor(
   pBt->pCursor = pCur;
   pCur->isValid = 0;
   *ppCur = pCur;
-  return SQLITE_OK;
 
+  return SQLITE_OK;
 create_cursor_exception:
   if( pCur ){
     releasePage(pCur->pPage);
@@ -2235,7 +2601,7 @@ void sqlite3BtreeSetCompare(
 ** when the last cursor is closed.
 */
 int sqlite3BtreeCloseCursor(BtCursor *pCur){
-  Btree *pBt = pCur->pBt;
+  BtShared *pBt = pCur->pBtree->pBt;
   if( pCur->pPrev ){
     pCur->pPrev->pNext = pCur->pNext;
   }else{
@@ -2349,13 +2715,13 @@ static int getPayload(
   Pgno nextPage;
   int rc;
   MemPage *pPage;
-  Btree *pBt;
+  BtShared *pBt;
   int ovflSize;
   u32 nKey;
 
   assert( pCur!=0 && pCur->pPage!=0 );
   assert( pCur->isValid );
-  pBt = pCur->pBt;
+  pBt = pCur->pBtree->pBt;
   pPage = pCur->pPage;
   pageIntegrity(pPage);
   assert( pCur->idx>=0 && pCur->idx<pPage->nCell );
@@ -2539,7 +2905,7 @@ static int moveToChild(BtCursor *pCur, u32 newPgno){
   int rc;
   MemPage *pNewPage;
   MemPage *pOldPage;
-  Btree *pBt = pCur->pBt;
+  BtShared *pBt = pCur->pBtree->pBt;
 
   assert( pCur->isValid );
   rc = getAndInitPage(pBt, newPgno, &pNewPage, pCur->pPage);
@@ -2611,7 +2977,7 @@ static void moveToParent(BtCursor *pCur){
 static int moveToRoot(BtCursor *pCur){
   MemPage *pRoot;
   int rc;
-  Btree *pBt = pCur->pBt;
+  BtShared *pBt = pCur->pBtree->pBt;
 
   rc = getAndInitPage(pBt, pCur->pgnoRoot, &pRoot, 0);
   if( rc ){
@@ -2964,7 +3330,7 @@ int sqlite3BtreePrevious(BtCursor *pCur, int *pRes){
 ** is only used by auto-vacuum databases when allocating a new table.
 */
 static int allocatePage(
-  Btree *pBt, 
+  BtShared *pBt, 
   MemPage **ppPage, 
   Pgno *pPgno, 
   Pgno nearby,
@@ -3182,7 +3548,7 @@ static int allocatePage(
 ** sqlite3pager_unref() is NOT called for pPage.
 */
 static int freePage(MemPage *pPage){
-  Btree *pBt = pPage->pBt;
+  BtShared *pBt = pPage->pBt;
   MemPage *pPage1 = pBt->pPage1;
   int rc, n, k;
 
@@ -3250,7 +3616,7 @@ static int freePage(MemPage *pPage){
 ** Free any overflow pages associated with the given Cell.
 */
 static int clearCell(MemPage *pPage, unsigned char *pCell){
-  Btree *pBt = pPage->pBt;
+  BtShared *pBt = pPage->pBt;
   CellInfo info;
   Pgno ovflPgno;
   int rc;
@@ -3302,7 +3668,7 @@ static int fillInCell(
   MemPage *pToRelease = 0;
   unsigned char *pPrior;
   unsigned char *pPayload;
-  Btree *pBt = pPage->pBt;
+  BtShared *pBt = pPage->pBt;
   Pgno pgnoOvfl = 0;
   int nHeader;
   CellInfo info;
@@ -3391,7 +3757,7 @@ static int fillInCell(
 ** given in the second argument so that MemPage.pParent holds the
 ** pointer in the third argument.
 */
-static int reparentPage(Btree *pBt, Pgno pgno, MemPage *pNewParent, int idx){
+static int reparentPage(BtShared *pBt, Pgno pgno, MemPage *pNewParent, int idx){
   MemPage *pThis;
   unsigned char *aData;
 
@@ -3434,7 +3800,7 @@ static int reparentPage(Btree *pBt, Pgno pgno, MemPage *pNewParent, int idx){
 */
 static int reparentChildPages(MemPage *pPage){
   int i;
-  Btree *pBt = pPage->pBt;
+  BtShared *pBt = pPage->pBt;
   int rc = SQLITE_OK;
 
   if( pPage->leaf ) return SQLITE_OK;
@@ -3667,7 +4033,7 @@ static int balance_quick(MemPage *pPage, MemPage *pParent){
   u8 *pCell;
   int szCell;
   CellInfo info;
-  Btree *pBt = pPage->pBt;
+  BtShared *pBt = pPage->pBt;
   int parentIdx = pParent->nCell;   /* pParent new divider cell index */
   int parentSize;                   /* Size of new divider cell */
   u8 parentCell[64];                /* Space for the new divider cell */
@@ -3776,7 +4142,7 @@ static int balance_quick(MemPage *pPage, MemPage *pParent){
 */
 static int balance_nonroot(MemPage *pPage){
   MemPage *pParent;            /* The parent of pPage */
-  Btree *pBt;                  /* The whole database */
+  BtShared *pBt;                  /* The whole database */
   int nCell = 0;               /* Number of cells in apCell[] */
   int nMaxCells = 0;           /* Allocated size of apCell, szCell, aFrom. */
   int nOld;                    /* Number of pages in apOld[] */
@@ -4325,7 +4691,7 @@ static int balance_shallower(MemPage *pPage){
   MemPage *pChild;             /* The only child page of pPage */
   Pgno pgnoChild;              /* Page number for pChild */
   int rc = SQLITE_OK;          /* Return code from subprocedures */
-  Btree *pBt;                  /* The main BTree structure */
+  BtShared *pBt;                  /* The main BTree structure */
   int mxCellPerPage;           /* Maximum number of cells per page */
   u8 **apCell;                 /* All cells from pages being balanced */
   int *szCell;                 /* Local size of all cells */
@@ -4427,7 +4793,7 @@ static int balance_deeper(MemPage *pPage){
   int rc;             /* Return value from subprocedures */
   MemPage *pChild;    /* Pointer to a new child page */
   Pgno pgnoChild;     /* Page number of the new child page */
-  Btree *pBt;         /* The BTree */
+  BtShared *pBt;         /* The BTree */
   int usableSize;     /* Total usable size of a page */
   u8 *data;           /* Content of the parent page */
   u8 *cdata;          /* Content of the child page */
@@ -4516,7 +4882,7 @@ static int balance(MemPage *pPage, int insert){
 ** a page entirely and we do not want to leave any cursors 
 ** pointing to non-existant pages or cells.
 */
-static int checkReadLocks(Btree *pBt, Pgno pgnoRoot, BtCursor *pExclude){
+static int checkReadLocks(BtShared *pBt, Pgno pgnoRoot, BtCursor *pExclude){
   BtCursor *p;
   for(p=pBt->pCursor; p; p=p->pNext){
     if( p->pgnoRoot!=pgnoRoot || p==pExclude ) continue;
@@ -4546,11 +4912,11 @@ int sqlite3BtreeInsert(
   int loc;
   int szNew;
   MemPage *pPage;
-  Btree *pBt = pCur->pBt;
+  BtShared *pBt = pCur->pBtree->pBt;
   unsigned char *oldCell;
   unsigned char *newCell = 0;
 
-  if( pBt->inTrans!=TRANS_WRITE ){
+  if( pBt->inTransaction!=TRANS_WRITE ){
     /* Must start a transaction before doing an insert */
     return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
   }
@@ -4618,10 +4984,10 @@ int sqlite3BtreeDelete(BtCursor *pCur){
   unsigned char *pCell;
   int rc;
   Pgno pgnoChild = 0;
-  Btree *pBt = pCur->pBt;
+  BtShared *pBt = pCur->pBtree->pBt;
 
   assert( pPage->isInit );
-  if( pBt->inTrans!=TRANS_WRITE ){
+  if( pBt->inTransaction!=TRANS_WRITE ){
     /* Must start a transaction before doing a delete */
     return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
   }
@@ -4721,11 +5087,12 @@ int sqlite3BtreeDelete(BtCursor *pCur){
 **     BTREE_INTKEY|BTREE_LEAFDATA     Used for SQL tables with rowid keys
 **     BTREE_ZERODATA                  Used for SQL indices
 */
-int sqlite3BtreeCreateTable(Btree *pBt, int *piTable, int flags){
+int sqlite3BtreeCreateTable(Btree *p, int *piTable, int flags){
+  BtShared *pBt = p->pBt;
   MemPage *pRoot;
   Pgno pgnoRoot;
   int rc;
-  if( pBt->inTrans!=TRANS_WRITE ){
+  if( pBt->inTransaction!=TRANS_WRITE ){
     /* Must start a transaction first */
     return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
   }
@@ -4752,7 +5119,7 @@ int sqlite3BtreeCreateTable(Btree *pBt, int *piTable, int flags){
     ** root page of the new table should go. meta[3] is the largest root-page
     ** created so far, so the new root-page is (meta[3]+1).
     */
-    rc = sqlite3BtreeGetMeta(pBt, 4, &pgnoRoot);
+    rc = sqlite3BtreeGetMeta(p, 4, &pgnoRoot);
     if( rc!=SQLITE_OK ) return rc;
     pgnoRoot++;
 
@@ -4819,7 +5186,7 @@ int sqlite3BtreeCreateTable(Btree *pBt, int *piTable, int flags){
       releasePage(pRoot);
       return rc;
     }
-    rc = sqlite3BtreeUpdateMeta(pBt, 4, pgnoRoot);
+    rc = sqlite3BtreeUpdateMeta(p, 4, pgnoRoot);
     if( rc ){
       releasePage(pRoot);
       return rc;
@@ -4842,7 +5209,7 @@ int sqlite3BtreeCreateTable(Btree *pBt, int *piTable, int flags){
 ** the page to the freelist.
 */
 static int clearDatabasePage(
-  Btree *pBt,           /* The BTree that contains the table */
+  BtShared *pBt,           /* The BTree that contains the table */
   Pgno pgno,            /* Page number to clear */
   MemPage *pParent,     /* Parent page.  NULL for the root */
   int freePageFlag      /* Deallocate page if true */
@@ -4893,10 +5260,11 @@ cleardatabasepage_out:
 ** read cursors on the table.  Open write cursors are moved to the
 ** root of the table.
 */
-int sqlite3BtreeClearTable(Btree *pBt, int iTable){
+int sqlite3BtreeClearTable(Btree *p, int iTable){
   int rc;
   BtCursor *pCur;
-  if( pBt->inTrans!=TRANS_WRITE ){
+  BtShared *pBt = p->pBt;
+  if( p->inTrans!=TRANS_WRITE ){
     return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
   }
   for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
@@ -4934,11 +5302,12 @@ int sqlite3BtreeClearTable(Btree *pBt, int iTable){
 ** The last root page is recorded in meta[3] and the value of
 ** meta[3] is updated by this procedure.
 */
-int sqlite3BtreeDropTable(Btree *pBt, int iTable, int *piMoved){
+int sqlite3BtreeDropTable(Btree *p, int iTable, int *piMoved){
   int rc;
   MemPage *pPage = 0;
+  BtShared *pBt = p->pBt;
 
-  if( pBt->inTrans!=TRANS_WRITE ){
+  if( p->inTrans!=TRANS_WRITE ){
     return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
   }
 
@@ -4954,7 +5323,7 @@ int sqlite3BtreeDropTable(Btree *pBt, int iTable, int *piMoved){
 
   rc = getPage(pBt, (Pgno)iTable, &pPage);
   if( rc ) return rc;
-  rc = sqlite3BtreeClearTable(pBt, iTable);
+  rc = sqlite3BtreeClearTable(p, iTable);
   if( rc ){
     releasePage(pPage);
     return rc;
@@ -4969,7 +5338,7 @@ int sqlite3BtreeDropTable(Btree *pBt, int iTable, int *piMoved){
 #else
     if( pBt->autoVacuum ){
       Pgno maxRootPgno;
-      rc = sqlite3BtreeGetMeta(pBt, 4, &maxRootPgno);
+      rc = sqlite3BtreeGetMeta(p, 4, &maxRootPgno);
       if( rc!=SQLITE_OK ){
         releasePage(pPage);
         return rc;
@@ -5026,7 +5395,7 @@ int sqlite3BtreeDropTable(Btree *pBt, int iTable, int *piMoved){
       }
       assert( maxRootPgno!=PENDING_BYTE_PAGE(pBt) );
 
-      rc = sqlite3BtreeUpdateMeta(pBt, 4, maxRootPgno);
+      rc = sqlite3BtreeUpdateMeta(p, 4, maxRootPgno);
     }else{
       rc = freePage(pPage);
       releasePage(pPage);
@@ -5051,9 +5420,10 @@ int sqlite3BtreeDropTable(Btree *pBt, int iTable, int *piMoved){
 ** layer (and the SetCookie and ReadCookie opcodes) the number of
 ** free pages is not visible.  So Cookie[0] is the same as Meta[1].
 */
-int sqlite3BtreeGetMeta(Btree *pBt, int idx, u32 *pMeta){
+int sqlite3BtreeGetMeta(Btree *p, int idx, u32 *pMeta){
   int rc;
   unsigned char *pP1;
+  BtShared *pBt = p->pBt;
 
   assert( idx>=0 && idx<=15 );
   rc = sqlite3pager_get(pBt->pPager, 1, (void**)&pP1);
@@ -5075,11 +5445,12 @@ int sqlite3BtreeGetMeta(Btree *pBt, int idx, u32 *pMeta){
 ** Write meta-information back into the database.  Meta[0] is
 ** read-only and may not be written.
 */
-int sqlite3BtreeUpdateMeta(Btree *pBt, int idx, u32 iMeta){
+int sqlite3BtreeUpdateMeta(Btree *p, int idx, u32 iMeta){
+  BtShared *pBt = p->pBt;
   unsigned char *pP1;
   int rc;
   assert( idx>=1 && idx<=15 );
-  if( pBt->inTrans!=TRANS_WRITE ){
+  if( p->inTrans!=TRANS_WRITE ){
     return pBt->readOnly ? SQLITE_READONLY : SQLITE_ERROR;
   }
   assert( pBt->pPage1!=0 );
@@ -5104,7 +5475,7 @@ int sqlite3BtreeFlags(BtCursor *pCur){
 ** Print a disassembly of the given page on standard output.  This routine
 ** is used for debugging and testing only.
 */
-static int btreePageDump(Btree *pBt, int pgno, int recursive, MemPage *pParent){
+static int btreePageDump(BtShared *pBt, int pgno, int recursive, MemPage *pParent){
   int rc;
   MemPage *pPage;
   int i, j, c;
@@ -5200,8 +5571,8 @@ static int btreePageDump(Btree *pBt, int pgno, int recursive, MemPage *pParent){
   fflush(stdout);
   return SQLITE_OK;
 }
-int sqlite3BtreePageDump(Btree *pBt, int pgno, int recursive){
-  return btreePageDump(pBt, pgno, recursive, 0);
+int sqlite3BtreePageDump(Btree *p, int pgno, int recursive){
+  return btreePageDump(p->pBt, pgno, recursive, 0);
 }
 #endif
 
@@ -5274,8 +5645,8 @@ int sqlite3BtreeCursorInfo(BtCursor *pCur, int *aResult, int upCnt){
 ** Return the pager associated with a BTree.  This routine is used for
 ** testing and debugging only.
 */
-Pager *sqlite3BtreePager(Btree *pBt){
-  return pBt->pPager;
+Pager *sqlite3BtreePager(Btree *p){
+  return p->pBt->pPager;
 }
 
 /*
@@ -5284,7 +5655,7 @@ Pager *sqlite3BtreePager(Btree *pBt){
 */
 typedef struct IntegrityCk IntegrityCk;
 struct IntegrityCk {
-  Btree *pBt;    /* The tree being checked out */
+  BtShared *pBt;    /* The tree being checked out */
   Pager *pPager; /* The associated pager.  Also accessible by pBt->pPager */
   int nPage;     /* Number of pages in the database */
   int *anRef;    /* Number of times each page is referenced */
@@ -5475,7 +5846,7 @@ static int checkTreePage(
   int hdr, cellStart;
   int nCell;
   u8 *data;
-  Btree *pBt;
+  BtShared *pBt;
   int usableSize;
   char zContext[100];
   char *hit;
@@ -5617,13 +5988,14 @@ static int checkTreePage(
 ** and a pointer to that error message is returned.  The calling function
 ** is responsible for freeing the error message when it is done.
 */
-char *sqlite3BtreeIntegrityCheck(Btree *pBt, int *aRoot, int nRoot){
+char *sqlite3BtreeIntegrityCheck(Btree *p, int *aRoot, int nRoot){
   int i;
   int nRef;
   IntegrityCk sCheck;
+  BtShared *pBt = p->pBt;
 
   nRef = *sqlite3pager_stats(pBt->pPager);
-  if( lockBtreeWithRetry(pBt)!=SQLITE_OK ){
+  if( lockBtreeWithRetry(p)!=SQLITE_OK ){
     return sqliteStrDup("Unable to acquire a read lock on the database");
   }
   sCheck.pBt = pBt;
@@ -5705,17 +6077,17 @@ char *sqlite3BtreeIntegrityCheck(Btree *pBt, int *aRoot, int nRoot){
 /*
 ** Return the full pathname of the underlying database file.
 */
-const char *sqlite3BtreeGetFilename(Btree *pBt){
-  assert( pBt->pPager!=0 );
-  return sqlite3pager_filename(pBt->pPager);
+const char *sqlite3BtreeGetFilename(Btree *p){
+  assert( p->pBt->pPager!=0 );
+  return sqlite3pager_filename(p->pBt->pPager);
 }
 
 /*
 ** Return the pathname of the directory that contains the database file.
 */
-const char *sqlite3BtreeGetDirname(Btree *pBt){
-  assert( pBt->pPager!=0 );
-  return sqlite3pager_dirname(pBt->pPager);
+const char *sqlite3BtreeGetDirname(Btree *p){
+  assert( p->pBt->pPager!=0 );
+  return sqlite3pager_dirname(p->pBt->pPager);
 }
 
 /*
@@ -5723,9 +6095,9 @@ const char *sqlite3BtreeGetDirname(Btree *pBt){
 ** value of this routine is the same regardless of whether the journal file
 ** has been created or not.
 */
-const char *sqlite3BtreeGetJournalname(Btree *pBt){
-  assert( pBt->pPager!=0 );
-  return sqlite3pager_journalname(pBt->pPager);
+const char *sqlite3BtreeGetJournalname(Btree *p){
+  assert( p->pBt->pPager!=0 );
+  return sqlite3pager_journalname(p->pBt->pPager);
 }
 
 #ifndef SQLITE_OMIT_VACUUM
@@ -5736,11 +6108,14 @@ const char *sqlite3BtreeGetJournalname(Btree *pBt){
 ** The size of file pBtFrom may be reduced by this operation.
 ** If anything goes wrong, the transaction on pBtFrom is rolled back.
 */
-int sqlite3BtreeCopyFile(Btree *pBtTo, Btree *pBtFrom){
+int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
   int rc = SQLITE_OK;
   Pgno i, nPage, nToPage, iSkip;
 
-  if( pBtTo->inTrans!=TRANS_WRITE || pBtFrom->inTrans!=TRANS_WRITE ){
+  BtShared *pBtTo = pTo->pBt;
+  BtShared *pBtFrom = pFrom->pBt;
+
+  if( pTo->inTrans!=TRANS_WRITE || pFrom->inTrans!=TRANS_WRITE ){
     return SQLITE_ERROR;
   }
   if( pBtTo->pCursor ) return SQLITE_BUSY;
@@ -5769,7 +6144,7 @@ int sqlite3BtreeCopyFile(Btree *pBtTo, Btree *pBtFrom){
     rc = sqlite3pager_truncate(pBtTo->pPager, nPage);
   }
   if( rc ){
-    sqlite3BtreeRollback(pBtTo);
+    sqlite3BtreeRollback(pTo);
   }
   return rc;  
 }
@@ -5778,15 +6153,15 @@ int sqlite3BtreeCopyFile(Btree *pBtTo, Btree *pBtFrom){
 /*
 ** Return non-zero if a transaction is active.
 */
-int sqlite3BtreeIsInTrans(Btree *pBt){
-  return (pBt && (pBt->inTrans==TRANS_WRITE));
+int sqlite3BtreeIsInTrans(Btree *p){
+  return (p && (p->inTrans==TRANS_WRITE));
 }
 
 /*
 ** Return non-zero if a statement transaction is active.
 */
-int sqlite3BtreeIsInStmt(Btree *pBt){
-  return (pBt && pBt->inStmt);
+int sqlite3BtreeIsInStmt(Btree *p){
+  return (p->pBt && p->pBt->inStmt);
 }
 
 /*
@@ -5803,8 +6178,9 @@ int sqlite3BtreeIsInStmt(Btree *pBt){
 ** Once this is routine has returned, the only thing required to commit
 ** the write-transaction for this database file is to delete the journal.
 */
-int sqlite3BtreeSync(Btree *pBt, const char *zMaster){
-  if( pBt->inTrans==TRANS_WRITE ){
+int sqlite3BtreeSync(Btree *p, const char *zMaster){
+  if( p->inTrans==TRANS_WRITE ){
+    BtShared *pBt = p->pBt;
 #ifndef SQLITE_OMIT_AUTOVACUUM
     Pgno nTrunc = 0;
     if( pBt->autoVacuum ){
@@ -5817,3 +6193,18 @@ int sqlite3BtreeSync(Btree *pBt, const char *zMaster){
   }
   return SQLITE_OK;
 }
+
+#ifndef SQLITE_OMIT_SHARED_CACHE
+/*
+** Enable the shared pager and schema features.
+*/
+int sqlite3_enable_shared_cache(int enable){
+  SqliteTsd *pTsd = sqlite3Tsd();
+  if( pTsd->pPager ){
+    return SQLITE_MISUSE;
+  }
+  pTsd->useSharedData = enable;
+  return SQLITE_OK;
+}
+#endif
+
index 61c1cc3d57f20acb8651b454fe7847069f09c69c..75bb991a56bdb75bb4c50ee9da0a03efead3769c 100644 (file)
@@ -13,7 +13,7 @@
 ** subsystem.  See comments in the source code for a detailed description
 ** of what each interface routine does.
 **
-** @(#) $Id: btree.h,v 1.65 2005/12/16 15:24:29 danielk1977 Exp $
+** @(#) $Id: btree.h,v 1.66 2005/12/30 16:28:02 danielk1977 Exp $
 */
 #ifndef _BTREE_H_
 #define _BTREE_H_
 */
 typedef struct Btree Btree;
 typedef struct BtCursor BtCursor;
+typedef struct BtShared BtShared;
 
 
 int sqlite3BtreeOpen(
   const char *zFilename,   /* Name of database file to open */
+  sqlite3 *db,             /* Associated database connection */
   Btree **,                /* Return open Btree* here */
   int flags                /* Flags */
 );
index 2315a6df0d90bc6201a938ee847ced7169160ba0..cb03503806fb78686989896b96b7885b6017717c 100644 (file)
@@ -14,7 +14,7 @@
 ** other files are for internal use by SQLite and should not be
 ** accessed by users of the library.
 **
-** $Id: main.c,v 1.312 2005/12/16 15:24:29 danielk1977 Exp $
+** $Id: main.c,v 1.313 2005/12/30 16:28:02 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
 */
 const int sqlite3one = 1;
 
-#ifndef SQLITE_OMIT_GLOBALRECOVER
-/*
-** Linked list of all open database handles. This is used by the 
-** sqlite3_global_recover() function. Entries are added to the list
-** by openDatabase() and removed by sqlite3_close().
-*/
-static sqlite3 *pDbList = 0;
-#endif
-
 /*
 ** The version of the library
 */
@@ -648,7 +639,7 @@ int sqlite3BtreeFactory(
 #endif /* SQLITE_OMIT_MEMORYDB */
   }
 
-  rc = sqlite3BtreeOpen(zFilename, ppBtree, btree_flags);
+  rc = sqlite3BtreeOpen(zFilename, db, ppBtree, btree_flags);
   if( rc==SQLITE_OK ){
     sqlite3BtreeSetBusyHandler(*ppBtree, (void*)&db->busyHandler);
     sqlite3BtreeSetCacheSize(*ppBtree, nCache);
index 88438aea3fa9589073f45732d32e2030592d720f..40df13f2265030b27db5bc564d291850de187da7 100644 (file)
@@ -18,7 +18,7 @@
 ** file simultaneously, or one process from reading the database while
 ** another is writing.
 **
-** @(#) $Id: pager.c,v 1.228 2005/12/20 09:19:37 danielk1977 Exp $
+** @(#) $Id: pager.c,v 1.229 2005/12/30 16:28:02 danielk1977 Exp $
 */
 #ifndef SQLITE_OMIT_DISKIO
 #include "sqliteInt.h"
@@ -301,12 +301,36 @@ struct Pager {
 
 /*
 ** These are bits that can be set in Pager.errMask.
+**
+** TODO: Maybe we just want a variable - Pager.errCode. Can we really 
+**       have two simultaneous error conditions?
+**
+** Recovering from an SQLITE_FULL, SQLITE_LOCK, SQLITE_CORRUPT or 
+** SQLITE_IOERR error is not a simple matter, particularly if the pager 
+** cache is shared between multiple connections.
+**
+** SQLITE_FULL (PAGER_ERR_FULL):
+**     Cleared when the transaction is rolled back.
+**
+** SQLITE_CORRUPT (PAGER_ERR_CORRUPT):
+**     Cannot be cleared. The upper layer must close the current pager 
+**     and open a new one on the same file to continue.
+**
+** SQLITE_PROTOCOL (PAGER_ERR_LOCK):
+**     This error only occurs if an internal error occurs or another process 
+**     is not following the sqlite locking protocol (i.e. someone is 
+**     manipulating the database file using something other than sqlite).
+**     This is handled in the same way as database corruption - the error 
+**     cannot be cleared except by closing the current pager and opening 
+**     a brand new one on the same file.
+**
+** SQLITE_IOERR (PAGER_ERR_DISK):
+**     Cleared when the transaction is rolled back.
 */
 #define PAGER_ERR_FULL     0x01  /* a write() failed */
-#define PAGER_ERR_MEM      0x02  /* malloc() failed */
-#define PAGER_ERR_LOCK     0x04  /* error in the locking protocol */
-#define PAGER_ERR_CORRUPT  0x08  /* database or journal corruption */
-#define PAGER_ERR_DISK     0x10  /* general disk I/O error - bad hard drive? */
+#define PAGER_ERR_LOCK     0x02  /* error in the locking protocol */
+#define PAGER_ERR_CORRUPT  0x04  /* database or journal corruption */
+#define PAGER_ERR_DISK     0x08  /* general disk I/O error - bad hard drive? */
 
 /*
 ** Journal files begin with the following magic string.  The data
@@ -465,11 +489,32 @@ static int pager_errcode(Pager *pPager){
   if( pPager->errMask & PAGER_ERR_LOCK )    rc = SQLITE_PROTOCOL;
   if( pPager->errMask & PAGER_ERR_DISK )    rc = SQLITE_IOERR;
   if( pPager->errMask & PAGER_ERR_FULL )    rc = SQLITE_FULL;
-  if( pPager->errMask & PAGER_ERR_MEM )     rc = SQLITE_NOMEM;
   if( pPager->errMask & PAGER_ERR_CORRUPT ) rc = SQLITE_CORRUPT;
   return rc;
 }
 
+/*
+** This function should be called when an error occurs within the pager
+** code to set the appropriate bits in Pager.errMask.
+*/
+static int pager_error(Pager *pPager, int rc){
+  switch( rc ){
+    case SQLITE_PROTOCOL:
+      pPager->errMask |= PAGER_ERR_LOCK;
+      break;
+    case SQLITE_IOERR:
+      pPager->errMask |= PAGER_ERR_DISK;
+      break;
+    case SQLITE_FULL:
+      pPager->errMask |= PAGER_ERR_FULL;
+      break;
+    case SQLITE_CORRUPT:
+      pPager->errMask |= PAGER_ERR_CORRUPT;
+      break;
+  }
+  return rc;
+}
+
 #ifdef SQLITE_CHECK_PAGES
 /*
 ** Return a 32-bit hash of the page data for pPage.
@@ -739,6 +784,9 @@ static int readJournalHdr(
 **
 ** The master journal page checksum is the sum of the bytes in the master
 ** journal name.
+**
+** If zMaster is a NULL pointer (occurs for a single database transaction), 
+** this call is a no-op.
 */
 static int writeMasterJournal(Pager *pPager, const char *zMaster){
   int rc;
@@ -861,29 +909,6 @@ static void pager_reset(Pager *pPager){
   assert( pPager->journalOpen==0 );
 }
 
-/*
-** This function is used to reset the pager after a malloc() failure. This
-** doesn't work with in-memory databases. If a malloc() fails when an 
-** in-memory database is in use it is not possible to recover.
-**
-** If a transaction or statement transaction is active, it is rolled back.
-**
-** It is an error to call this function if any pages are in use.
-*/
-#ifndef SQLITE_OMIT_GLOBALRECOVER
-int sqlite3pager_reset(Pager *pPager){
-  if( pPager ){
-    if( pPager->nRef || MEMDB ){
-      return SQLITE_ERROR;
-    }
-    pPager->errMask &= ~(PAGER_ERR_MEM);
-    pager_reset(pPager);
-  }
-  return SQLITE_OK;
-}
-#endif
-
-
 /*
 ** When this routine is called, the pager has the journal file open and
 ** a RESERVED or EXCLUSIVE lock on the database.  This routine releases
@@ -1586,7 +1611,7 @@ int sqlite3pager_open(
   int nExtra,              /* Extra bytes append to each in-memory page */
   int flags                /* flags controlling this file */
 ){
-  Pager *pPager;
+  Pager *pPager = 0;
   char *zFullPathname = 0;
   int nameLen;
   OsFile *fd;
@@ -1600,17 +1625,24 @@ int sqlite3pager_open(
   char zTemp[SQLITE_TEMPNAME_SIZE];
   SqliteTsd *pTsd = sqlite3Tsd();
 
+  /* If malloc() has already failed return SQLITE_NOMEM. Before even
+  ** testing for this, set *ppPager to NULL so the caller knows the pager
+  ** structure was never allocated. 
+  */
   *ppPager = 0;
-  memset(&fd, 0, sizeof(fd));
   if( sqlite3Tsd()->mallocFailed ){
     return SQLITE_NOMEM;
   }
+  memset(&fd, 0, sizeof(fd));
+
+  /* Open the pager file and set zFullPathname to point at malloc()ed 
+  ** memory containing the complete filename (i.e. including the directory).
+  */
   if( zFilename && zFilename[0] ){
 #ifndef SQLITE_OMIT_MEMORYDB
     if( strcmp(zFilename,":memory:")==0 ){
       memDb = 1;
       zFullPathname = sqliteStrDup("");
-      rc = SQLITE_OK;
     }else
 #endif
     {
@@ -1627,28 +1659,35 @@ int sqlite3pager_open(
       tempFile = 1;
     }
   }
-  if( !zFullPathname ){
-    sqlite3OsClose(&fd);
-    return SQLITE_NOMEM;
-  }
-  if( rc!=SQLITE_OK ){
-    sqlite3OsClose(&fd);
-    sqliteFree(zFullPathname);
-    return rc;
+
+  /* Allocate the Pager structure. As part of the same allocation, allocate
+  ** space for the full paths of the file, directory and journal 
+  ** (Pager.zFilename, Pager.zDirectory and Pager.zJournal).
+  */
+  if( zFullPathname ){
+    nameLen = strlen(zFullPathname);
+    pPager = sqliteMalloc( sizeof(*pPager) + nameLen*3 + 30 );
   }
-  nameLen = strlen(zFullPathname);
-  pPager = sqliteMalloc( sizeof(*pPager) + nameLen*3 + 30 );
-  if( pPager==0 ){
+
+  /* If an error occured in either of the blocks above, free the memory 
+  ** pointed to by zFullPathname, free the Pager structure and close the 
+  ** file. Since the pager is not allocated there is no need to set 
+  ** any Pager.errMask variables.
+  */
+  if( !pPager || !zFullPathname || rc!=SQLITE_OK ){
     sqlite3OsClose(&fd);
     sqliteFree(zFullPathname);
-    return SQLITE_NOMEM;
+    sqliteFree(pPager);
+    return ((rc==SQLITE_OK)?SQLITE_NOMEM:rc);
   }
+
   TRACE3("OPEN %d %s\n", FILEHANDLEID(fd), zFullPathname);
   pPager->zFilename = (char*)&pPager[1];
   pPager->zDirectory = &pPager->zFilename[nameLen+1];
   pPager->zJournal = &pPager->zDirectory[nameLen+1];
   strcpy(pPager->zFilename, zFullPathname);
   strcpy(pPager->zDirectory, zFullPathname);
+
   for(i=nameLen; i>0 && pPager->zDirectory[i-1]!='/'; i--){}
   if( i>0 ) pPager->zDirectory[i-1] = 0;
   strcpy(pPager->zJournal, zFullPathname);
@@ -1762,7 +1801,13 @@ void enable_simulated_io_errors(void){
 
 /*
 ** Read the first N bytes from the beginning of the file into memory
-** that pDest points to.  No error checking is done.
+** that pDest points to. 
+**
+** No error checking is done. The rational for this is that this function 
+** may be called even if the file does not exist or contain a header. In 
+** these cases sqlite3OsRead() will return an error, to which the correct 
+** response is to zero the memory at pDest and continue.  A real IO error 
+** will presumably recur and be picked up later (Todo: Think about this).
 */
 void sqlite3pager_read_fileheader(Pager *pPager, int N, unsigned char *pDest){
   memset(pDest, 0, N);
@@ -1973,6 +2018,11 @@ int sqlite3pager_truncate(Pager *pPager, Pgno nPage){
 ** and their memory is freed.  Any attempt to use a page associated
 ** with this page cache after this function returns will likely
 ** result in a coredump.
+**
+** This function always succeeds. If a transaction is active an attempt
+** is made to roll it back. If an error occurs during the rollback 
+** a hot journal may be left in the filesystem but no error is returned
+** to the caller.
 */
 int sqlite3pager_close(Pager *pPager){
   PgHdr *pPg, *pNext;
@@ -2037,6 +2087,9 @@ int sqlite3pager_close(Pager *pPager){
   */
 
 #ifndef SQLITE_OMIT_MEMORY_MANAGEMENT
+  /* Remove the pager from the linked list of pagers starting at 
+  ** SqliteTsd.pPager.
+  */
   if( pPager==pTsd->pPager ){
     pTsd->pPager = pPager->pNext;
   }else{
@@ -2310,7 +2363,10 @@ static int hasHotJournal(Pager *pPager){
 }
 
 /*
-** Try to find a page in the cache that can be recycled.
+** Try to find a page in the cache that can be recycled. 
+**
+** This routine may return SQLITE_IOERR, SQLITE_FULL or SQLITE_OK. It 
+** does not set the pPager->errMask variable.
 */
 static int pager_recycle(Pager *pPager, int syncOk, PgHdr **ppPg){
   PgHdr *pPg;
@@ -2329,8 +2385,7 @@ static int pager_recycle(Pager *pPager, int syncOk, PgHdr **ppPg){
   if( pPg==0 && pPager->pFirst && syncOk && !MEMDB){
     int rc = syncJournal(pPager);
     if( rc!=0 ){
-      sqlite3pager_rollback(pPager);
-      return SQLITE_IOERR;
+      return rc;
     }
     if( pPager->fullSync ){
       /* If in full-sync mode, write a new journal header into the
@@ -2343,8 +2398,7 @@ static int pager_recycle(Pager *pPager, int syncOk, PgHdr **ppPg){
       assert( pPager->journalOff > 0 );
       rc = writeJournalHdr(pPager);
       if( rc!=0 ){
-        sqlite3pager_rollback(pPager);
-        return SQLITE_IOERR;
+        return rc;
       }
     }
     pPg = pPager->pFirst;
@@ -2363,8 +2417,7 @@ static int pager_recycle(Pager *pPager, int syncOk, PgHdr **ppPg){
     pPg->pDirty = 0;
     rc = pager_write_pagelist( pPg );
     if( rc!=SQLITE_OK ){
-      sqlite3pager_rollback(pPager);
-      return SQLITE_IOERR;
+      return rc;
     }
   }
   assert( pPg->dirty==0 );
@@ -2425,9 +2478,9 @@ int sqlite3pager_release_memory(int nReq){
       ** loop).
       */
       while( SQLITE_OK==(rc = pager_recycle(p, i, &pPg)) && pPg) {
-       /* We've found a page to free. At this point the page has been 
+        /* We've found a page to free. At this point the page has been 
         ** removed from the page hash-table, free-list and synced-list 
-       ** (pFirstSynced). It is still in the all pages (pAll) list. 
+        ** (pFirstSynced). It is still in the all pages (pAll) list. 
         ** Remove it from this list before freeing.
         **
         ** Todo: Check the Pager.pStmt list to make sure this is Ok. It 
@@ -2435,6 +2488,7 @@ int sqlite3pager_release_memory(int nReq){
         */
         PgHdr *pTmp;
         assert( pPg );
+        page_remove_from_stmt_list(pPg);
         if( pPg==p->pAll ){
            p->pAll = pPg->pNextAll;
         }else{
@@ -2446,23 +2500,22 @@ int sqlite3pager_release_memory(int nReq){
       }
 
       if( rc!=SQLITE_OK ){
-        /* Assert that fsync() was enabled and the error was an io-error
-        ** or a full database. Nothing else should be able to wrong in 
-        ** pager_recycle.
+        /* An error occured whilst writing to the database file or 
+        ** journal in pager_recycle(). The error is not returned to the 
+        ** caller of this function. Instead, set the Pager.errMask variable.
+        ** The error will be returned to the user (or users, in the case 
+        ** of a shared pager cache) of the pager for which the error occured.
         */
-        assert( i && (rc==SQLITE_IOERR || rc==SQLITE_FULL) );
-
-        /* TODO: Figure out what to do about this. The IO-error
-        ** belongs to the connection that is executing a transaction. 
-        */
-        assert(0);
+        assert( rc==SQLITE_IOERR || rc==SQLITE_FULL );
+        assert( p->state>=PAGER_RESERVED );
+        pager_error(p, rc);
       }
     }
   }
-  
+
   return nReleased;
 }
-#endif
+#endif /* SQLITE_OMIT_MEMORY_MANAGEMENT */
 
 /*
 ** Acquire a page.
@@ -2513,7 +2566,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){
     if( !pPager->noReadlock ){
       rc = pager_wait_on_lock(pPager, SHARED_LOCK);
       if( rc!=SQLITE_OK ){
-        return rc;
+        return pager_error(pPager, rc);
       }
     }
 
@@ -2538,7 +2591,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){
        if( rc!=SQLITE_OK ){
          sqlite3OsUnlock(pPager->fd, NO_LOCK);
          pPager->state = PAGER_UNLOCK;
-         return rc;
+         return pager_error(pPager, rc);
        }
        pPager->state = PAGER_EXCLUSIVE;
 
@@ -2567,7 +2620,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){
        */
        rc = pager_playback(pPager);
        if( rc!=SQLITE_OK ){
-         return rc;
+         return pager_error(pPager, rc);
        }
     }
     pPg = 0;
@@ -2588,7 +2641,6 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){
                               + sizeof(u32) + pPager->nExtra
                               + MEMDB*sizeof(PgHistory) );
       if( pPg==0 ){
-        // pPager->errMask |= PAGER_ERR_MEM;
         return SQLITE_NOMEM;
       }
       memset(pPg, 0, sizeof(*pPg));
@@ -2606,7 +2658,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){
     }else{
       rc = pager_recycle(pPager, 1, &pPg);
       if( rc!=SQLITE_OK ){
-        return rc;
+        return pager_error(pPager, rc);
       }
       assert(pPg) ;
     }
@@ -2662,7 +2714,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){
         if( sqlite3OsFileSize(pPager->fd,&fileSize)!=SQLITE_OK
                || fileSize>=pgno*pPager->pageSize ){
           sqlite3pager_unref(PGHDR_TO_DATA(pPg));
-          return rc;
+          return pager_error(pPager, rc);
         }else{
           clear_simulated_io_error();
           memset(PGHDR_TO_DATA(pPg), 0, pPager->pageSize);
index b105b6709c01a59a2faa1832570993120987102c..afcfe9fe69917006d1efab3b4e71aeabfb4e37c3 100644 (file)
@@ -12,7 +12,7 @@
 ** This header file defines the interface that the SQLite library
 ** presents to client programs.
 **
-** @(#) $Id: sqlite.h.in,v 1.151 2005/12/20 09:19:37 danielk1977 Exp $
+** @(#) $Id: sqlite.h.in,v 1.152 2005/12/30 16:28:02 danielk1977 Exp $
 */
 #ifndef _SQLITE3_H_
 #define _SQLITE3_H_
@@ -1338,6 +1338,14 @@ void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
 
 int sqlite3_release_memory(int);
 
+/*
+** This function is only available if the library is compiled without
+** the SQLITE_OMIT_SHARED_CACHE macro defined. It is used to enable or
+** disable (if the argument is true or false, respectively) the 
+** "shared pager" feature.
+*/
+int sqlite3_enable_shared_cache(int);
+
 /*
 ** Undo the hack that converts floating point types to integer for
 ** builds on processors without floating point support.
index 6868e240b7cfd09b6d4b24d5168469424b3ea3ab..f735aa408d7acca06977c3b8d3dfb2d69a49a0dc 100644 (file)
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.445 2005/12/29 23:33:54 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.446 2005/12/30 16:28:02 danielk1977 Exp $
 */
 #ifndef _SQLITEINT_H_
 #define _SQLITEINT_H_
@@ -289,6 +289,11 @@ struct SqliteTsd {
   Pager *pPager;           /* Linked list of all pagers in this thread */
 #endif
 
+#ifndef SQLITE_OMIT_SHARED_CACHE
+  u8 useSharedData;        /* True if shared pagers and schemas are enabled */
+  BtShared *pBtree;
+#endif
+
 #ifdef SQLITE_MEMDEBUG
   i64 nMaxAlloc;           /* High water mark of SqliteTsd.nAlloc */
   int mallocAllowed;       /* assert() in sqlite3Malloc() if not set */
index 7d1dc0348896dcd5684ae319ff4f6a4d8610fe5a..5a97da06946fbcedcd78641305f512a567f5f321 100644 (file)
@@ -11,7 +11,7 @@
 *************************************************************************
 ** A TCL Interface to SQLite
 **
-** $Id: tclsqlite.c,v 1.141 2005/12/19 14:18:11 danielk1977 Exp $
+** $Id: tclsqlite.c,v 1.142 2005/12/30 16:28:02 danielk1977 Exp $
 */
 #ifndef NO_TCL     /* Omit this whole file if TCL is unavailable */
 
@@ -1754,11 +1754,12 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
   **     $db soft_heap_limit N
   **
   ** Set the soft-heap-limit for this thread. Note that the limit is 
-  ** per-thread, not per-database. An empty string is returned.
+  ** per-thread, not per-database. The previous limit is returned.
   */
   case DB_SOFT_HEAP_LIMIT: {
 #ifndef SQLITE_OMIT_MEMORY_MANAGEMENT
     int n;
+    int ret;
     if( objc!=3 ){
       Tcl_WrongNumArgs(interp, 2, objv, "BYTES");
       return TCL_ERROR;
@@ -1766,8 +1767,9 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
     if( Tcl_GetIntFromObj(interp, objv[2], &n) ){
       return TCL_ERROR;
     }
+    ret = sqlite3Tsd()->nSoftHeapLimit;
     sqlite3_soft_heap_limit(n);
-    Tcl_ResetResult(interp);
+    Tcl_SetObjResult(interp, Tcl_NewIntObj(ret));
 #endif
     break;
   }
index b978a96a448f009da3e296640de919b5a0dccb49..af05a2f3576fc7e40fcafcf7e453fc2b9b3d6418 100644 (file)
@@ -13,7 +13,7 @@
 ** is not included in the SQLite library.  It is used for automated
 ** testing of the SQLite library.
 **
-** $Id: test1.c,v 1.177 2005/12/20 09:19:37 danielk1977 Exp $
+** $Id: test1.c,v 1.178 2005/12/30 16:28:02 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include "tcl.h"
@@ -890,6 +890,38 @@ static int sqlite_malloc_outstanding(
 }
 #endif
 
+/*
+** Usage: sqlite3_enable_shared_cache BOOLEAN
+**
+*/
+#ifndef SQLITE_OMIT_SHARED_CACHE
+static int test_enable_shared_cache(
+  ClientData clientData,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  int rc;
+  int enable;
+  SqliteTsd *pTsd = sqlite3Tsd();
+  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(pTsd->useSharedData));
+
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "BOOLEAN");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetBooleanFromObj(interp, objv[1], &enable) ){
+    return TCL_ERROR;
+  }
+  rc = sqlite3_enable_shared_cache(enable);
+  if( rc!=SQLITE_OK ){
+    Tcl_SetResult(interp, (char *)sqlite3ErrStr(rc), TCL_STATIC);
+    return TCL_ERROR;
+  }
+  return TCL_OK;
+}
+#endif
+
 /*
 ** Usage:  sqlite_abort
 **
@@ -3074,6 +3106,12 @@ static void set_options(Tcl_Interp *interp){
   Tcl_SetVar2(interp, "sqlite_options", "schema_version", "1", TCL_GLOBAL_ONLY);
 #endif
 
+#ifdef SQLITE_OMIT_SHARED_CACHE
+  Tcl_SetVar2(interp, "sqlite_options", "shared_cache", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "shared_cache", "1", TCL_GLOBAL_ONLY);
+#endif
+
 #ifdef SQLITE_OMIT_SUBQUERY
   Tcl_SetVar2(interp, "sqlite_options", "subquery", "0", TCL_GLOBAL_ONLY);
 #else
@@ -3250,6 +3288,9 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
 #endif
      { "sqlite3_test_errstr",     test_errstr, 0             },
      { "tcl_variable_type",       tcl_variable_type, 0       },
+#ifndef SQLITE_OMIT_SHARED_CACHE
+     { "sqlite3_enable_shared_cache", test_enable_shared_cache, 0},
+#endif
   };
   static int bitmask_size = sizeof(Bitmask)*8;
   int i;
index 71a124bc37f3e7f2a0f01d9d23427c28534104e5..e93e858e7ff973c693dab4145158f33c8be367be 100644 (file)
@@ -13,7 +13,7 @@
 ** is not included in the SQLite library.  It is used for automated
 ** testing of the SQLite library.
 **
-** $Id: test3.c,v 1.63 2005/12/09 20:21:59 drh Exp $
+** $Id: test3.c,v 1.64 2005/12/30 16:28:02 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include "pager.h"
@@ -69,7 +69,7 @@ static int btree_open(
   }
   if( Tcl_GetInt(interp, argv[2], &nCache) ) return TCL_ERROR;
   if( Tcl_GetInt(interp, argv[3], &flags) ) return TCL_ERROR;
-  rc = sqlite3BtreeOpen(argv[1], &pBt, flags);
+  rc = sqlite3BtreeOpen(argv[1], 0, &pBt, flags);
   if( rc!=SQLITE_OK ){
     Tcl_AppendResult(interp, errorName(rc), 0);
     return TCL_ERROR;
index 33652fbe6d287ec9ca64a9fcc143e1dc8e8f009c..b0d5f6f5e1b40c797aef4cf680c3630230330b2b 100644 (file)
@@ -926,7 +926,6 @@ static int vdbeCommit(sqlite3 *db){
 
   /* If there are any write-transactions at all, invoke the commit hook */
   if( needXcommit && db->xCommitCallback ){
-    int rc;
     sqlite3SafetyOff(db);
     rc = db->xCommitCallback(db->pCommitArg);
     sqlite3SafetyOn(db);
index c08cb17757a60979b8ec86a01bdd02ca032413f5..1ec3eaddef19d4a9158165b969a07091265b78ce 100644 (file)
@@ -13,7 +13,7 @@
 # file format change that may be used in the future to implement
 # "ALTER TABLE ... ADD COLUMN".
 #
-# $Id: alter3.test,v 1.6 2005/03/29 03:11:00 danielk1977 Exp $
+# $Id: alter3.test,v 1.7 2005/12/30 16:28:02 danielk1977 Exp $
 #
 
 set testdir [file dirname $argv0]
@@ -360,6 +360,7 @@ do_test alter3-8.1 {
     CREATE TABLE t4(c1);
   }
 } {}
+set ::sql ""
 do_test alter3-8.2 {
   set cols c1
   for {set i 2} {$i < 100} {incr i} {
index c4ec2aa194a3e97b9a52adaefd14ee9ed151cc24..d9b03401708f963c66991403206c8bcf1f28c9fc 100644 (file)
 # focus of this script is testing the ATTACH and DETACH commands
 # and related functionality.
 #
-# $Id: attach2.test,v 1.33 2005/12/06 17:19:12 danielk1977 Exp $
+# $Id: attach2.test,v 1.34 2005/12/30 16:28:02 danielk1977 Exp $
 #
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
 
-
 # Ticket #354
 #
 # Databases test.db and test2.db contain identical schemas.  Make
@@ -157,7 +156,7 @@ proc lock_status {testnum db expected_result} {
   # If the database was compiled with OMIT_TEMPDB set, then 
   # the lock_status list will not contain an entry for the temp
   # db. But the test code doesn't know this, so it's easiest 
-  # to filter it out here.
+  # to filter it out of the $expected_result list here.
   ifcapable !tempdb {
     set expected_result [concat \
         [lrange $expected_result 0 1] \
index 61f25a862e0c6e0f6c2bcddef52441dcbe46b892..b17a92068bdda806c6dceaf8b4ad0a7108684d11 100644 (file)
@@ -9,7 +9,10 @@
 #
 #***********************************************************************
 #
-# $Id: malloc5.test,v 1.2 2005/12/20 09:19:38 danielk1977 Exp $
+# This file contains test cases focused on the two memory-management APIs, 
+# sqlite3_soft_heap_limit() and sqlite3_release_memory().
+#
+# $Id: malloc5.test,v 1.3 2005/12/30 16:28:02 danielk1977 Exp $
 
 #---------------------------------------------------------------------------
 # NOTES ON EXPECTED BEHAVIOUR
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
 
+# Only run these tests if memory debugging is turned on.
+if {[info command sqlite_malloc_stat]==""} {
+   puts "Skipping malloc tests: not compiled with -DSQLITE_MEMDEBUG..."
+   finish_test
+   return
+}
+
 do_test malloc5-1.1 {
   # Simplest possible test. Call [db release_memory] when there is exactly
   # one unused page in a single pager cache. This test case set's the 
@@ -159,6 +169,11 @@ sqlite_malloc_outstanding -clearmaxbytes
 # and tests to see that this limit is not exceeded at any point during 
 # transaction execution.
 #
+# Before executing malloc5-4.* we save the value of the current soft heap 
+# limit in variable ::soft_limit. The original value is restored after 
+# running the tests.
+#
+set ::soft_limit [db soft_heap_limit -1]
 do_test malloc5-4.1 {
   execsql {BEGIN;}
   execsql {DELETE FROM abc;}
@@ -188,8 +203,8 @@ do_test malloc5-4.3 {
   }
 } [list 20000 [expr int(20000.0 * 4999.5)] [expr int(20000.0 * 4999.5)]]
 
-# Unset the soft-heap-limit value.
-db soft_heap_limit -1
+# Restore the soft heap limit.
+db soft_heap_limit $::soft_limit
 
 finish_test
 
index 6d81d4db1623daa1b83a28f16a0ec4ec001c9fae..b333f1db172cc64f1214885421594675b41fc34c 100644 (file)
@@ -6,7 +6,25 @@
 #***********************************************************************
 # This file runs all tests.
 #
-# $Id: quick.test,v 1.38 2005/12/15 10:11:32 danielk1977 Exp $
+# $Id: quick.test,v 1.39 2005/12/30 16:28:02 danielk1977 Exp $
+
+proc lshift {lvar} {
+  upvar $lvar l
+  set ret [lindex $l 0]
+  set l [lrange $l 1 end]
+  return $ret
+}
+while {[set arg [lshift argv]] != ""} {
+  switch -- $arg {
+    -sharedpagercache {
+      sqlite3_enable_shared_cache 1
+    }
+    default {
+      set argv [linsert $argv 0 $arg]
+      break
+    }
+  }
+}
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
diff --git a/test/shared.test b/test/shared.test
new file mode 100644 (file)
index 0000000..ddbe341
--- /dev/null
@@ -0,0 +1,205 @@
+# 2005 December 30
+#
+# 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.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this file is testing the SELECT statement.
+#
+# $Id: shared.test,v 1.1 2005/12/30 16:28:02 danielk1977 Exp $
+
+set ::enable_shared_cache [sqlite3_enable_shared_cache 1]
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+db close
+
+ifcapable !shared_cache {
+  finish_test
+  return
+}
+
+# Test organization:
+#
+# shared-1.*: Simple test to verify basic sanity of table level locking when
+#             two connections share a pager cache.
+# shared-2.*: Test that a read transaction can co-exist with a 
+#             write-transaction, including a simple test to ensure the 
+#             external locking protocol is still working.
+#
+
+do_test shared-1.1 {
+  # Open a second database on the file test.db. It should use the same pager
+  # cache and schema as the original connection. Verify that only 1 file is 
+  # opened.
+  sqlite3 db2 test.db
+  sqlite3 db  test.db
+  set ::sqlite_open_file_count
+} {1}
+do_test shared-1.2 {
+  # Add a table and a single row of data via the first connection. 
+  # Ensure that the second connection can see them.
+  execsql {
+    CREATE TABLE abc(a, b, c);
+    INSERT INTO abc VALUES(1, 2, 3);
+  } db
+  execsql {
+    SELECT * FROM abc;
+  } db2
+} {1 2 3}
+do_test shared-1.3 {
+  # Have the first connection begin a transaction and obtain a read-lock
+  # on table abc. This should not prevent the second connection from 
+  # querying abc.
+  execsql {
+    BEGIN;
+    SELECT * FROM abc;
+  }
+  execsql {
+    SELECT * FROM abc;
+  } db2
+} {1 2 3}
+do_test shared-1.4 {
+  # Try to insert a row into abc via connection 2. This should fail because
+  # of the read-lock connection 1 is holding on table abc (obtained in the
+  # previous test case).
+  catchsql {
+    INSERT INTO abc VALUES(4, 5, 6);
+  } db2
+} {1 {database is locked}}
+do_test shared-1.5 {
+  # Using connection 2 (the one without the open transaction), create a 
+  # new table and add a row to it. This is permitted as the transaction
+  # started by connection 1 is currently a read transaction.
+  execsql {
+    CREATE TABLE def(d, e, f);
+    INSERT INTO def VALUES('I', 'II', 'III');
+  } db2
+} {}
+do_test shared-1.6 {
+  # Upgrade connection 1's transaction to a write transaction. Insert
+  # a row into table def - the table just created by connection 2.
+  #
+  # Connection 1 is able to see table def, even though it was created 
+  # "after" the connection 1 transaction was started. This is because no
+  # lock was established on the sqlite_master table.
+
+# Todo: Remove this. Because the implementation does not include
+# shared-schemas yet, we need to run some query (that will fail at 
+# OP_VerifyCookie) so that connection 1 picks up the schema change
+# made via connection 2. Otherwise the sqlite3_prepare("INSERT INTO def...")
+# below will fail.
+execsql {
+  SELECT * FROM sqlite_master;
+}
+
+  execsql {
+    INSERT INTO def VALUES('IV', 'V', 'VI');
+  }
+} {}
+do_test shared-1.7 {
+  # Read from the sqlite_master table with connection 1 (inside the 
+  # transaction). Then test that we can no longer create a table 
+  # with connection 2. This is because of the read-lock on sqlite_master.
+  execsql {
+    SELECT * FROM sqlite_master;
+  }
+  catchsql {
+    CREATE TABLE ghi(g, h, i);
+  } db2
+} {1 {database is locked}}
+do_test shared-1.8 {
+  # Check that connection 2 can read the sqlite_master table. Then
+  # create a table using connection 1 (this should write-lock the 
+  # sqlite_master table). Then try to read sqlite_master again using 
+  # connection 2 and verify that the write-lock prevents this.
+  execsql {
+    SELECT * FROM sqlite_master;
+  } db2
+  execsql {
+    CREATE TABLE ghi(g, h, i);
+  } 
+  catchsql {
+    SELECT * FROM sqlite_master;
+  } db2
+} {1 {database is locked}}
+do_test shared-1.9 {
+  # Commit the connection 1 transaction.
+  execsql {
+    COMMIT;
+  }
+} {}
+
+do_test shared-2.1 {
+  # Open connection db3 to the database. Use a different path to the same
+  # file so that db3 does *not* share the same pager cache as db and db2
+  # (there should be two open file handles).
+  sqlite3 db3 ./test.db
+  set ::sqlite_open_file_count
+} {2}
+do_test shared-2.2 {
+  # Start read transactions on db and db2 (the shared pager cache). Ensure
+  # db3 cannot write to the database.
+  execsql {
+    BEGIN;
+    SELECT * FROM abc;
+  }
+  execsql {
+    BEGIN;
+    SELECT * FROM abc;
+  } db2
+  catchsql {
+    INSERT INTO abc VALUES(1, 2, 3);
+  } db2
+} {1 {database is locked}}
+do_test shared-2.3 {
+  # Turn db's transaction into a write-transaction. db3 should still be
+  # able to read from table def (but will not see the new row). Connection
+  # db2 should not be able to read def (because of the write-lock).
+
+# Todo: The failed "INSERT INTO abc ..." statement in the above test
+# has started a write-transaction on db2 (should this be so?). This 
+# would prevent connection db from starting a write-transaction. So roll the
+# db2 transaction back and replace it with a new read transaction.
+  execsql {
+    ROLLBACK;
+    BEGIN;
+    SELECT * FROM abc;
+  } db2
+
+  execsql {
+    INSERT INTO def VALUES('VII', 'VIII', 'IX');
+  }
+  concat [
+    catchsql { SELECT * FROM def; } db3
+  ] [
+    catchsql { SELECT * FROM def; } db2
+  ]
+} {0 {I II III IV V VI} 1 {database is locked}}
+do_test shared-2.4 {
+  # Commit the open transaction on db. db2 still holds a read-transaction.
+  # This should prevent db3 from writing to the database, but not from 
+  # reading.
+  execsql {
+    COMMIT;
+  }
+  concat [
+    catchsql { SELECT * FROM def; } db3
+  ] [
+    catchsql { INSERT INTO def VALUES('X', 'XI', 'XII'); } db3
+  ]
+} {0 {I II III IV V VI VII VIII IX} 1 {database is locked}}
+
+
+catch {db close}
+catch {db2 close}
+catch {db3 close}
+
+finish_test
+sqlite3_enable_shared_cache $::enable_shared_cache
+
index e13c8f46e3ea3d8035378057ee40b69021327c26..4edee941dcde1057977f67cd0579817988c91371 100644 (file)
@@ -11,7 +11,7 @@
 # This file implements some common TCL routines used for regression
 # testing the SQLite library
 #
-# $Id: tester.tcl,v 1.54 2005/12/09 14:39:05 danielk1977 Exp $
+# $Id: tester.tcl,v 1.55 2005/12/30 16:28:02 danielk1977 Exp $
 
 # Make sure tclsqlite3 was compiled correctly.  Abort now with an
 # error message if not.
@@ -440,6 +440,7 @@ proc copy_file {from to} {
 #
 proc check_for_leaks {} {
   set ret [list]
+  set cnt 0
   foreach alloc [sqlite_malloc_outstanding] {
     foreach {nBytes file iLine userstring backtrace} $alloc {}
     set stack [list]
@@ -447,18 +448,21 @@ proc check_for_leaks {} {
 
     # The first command in this block will probably fail on windows. This
     # means there will be no stack dump available.
-    catch {
-      set stuff [eval "exec addr2line -e ./testfixture -f $backtrace"]
-      foreach {func line} $stuff {
-        if {$func != "??" || $line != "??:0"} {
-          regexp {.*/(.*)} $line dummy line
-          lappend stack "${func}() $line"
-        } else {
-          if {[lindex $stack end] != "..."} {
-            lappend stack "..."
+    if {$cnt < 25} {
+      catch {
+        set stuff [eval "exec addr2line -e ./testfixture -f $backtrace"]
+        foreach {func line} $stuff {
+          if {$func != "??" || $line != "??:0"} {
+            regexp {.*/(.*)} $line dummy line
+            lappend stack "${func}() $line"
+          } else {
+            if {[lindex $stack end] != "..."} {
+              lappend stack "..."
+            }
           }
         }
       }
+      incr cnt
     }
 
     if {!$skip} {
index c2eaa7e20eee9f3367d3e108e4ac3efc85cbe6ea..93943d0ef755ac2c29a4b81e07b05c65d463a9ff 100644 (file)
@@ -11,7 +11,7 @@
 # This file implements regression tests for SQLite library.  The
 # focus of this script is database locks.
 #
-# $Id: trans.test,v 1.26 2005/03/29 03:11:00 danielk1977 Exp $
+# $Id: trans.test,v 1.27 2005/12/30 16:28:02 danielk1977 Exp $
 
 
 set testdir [file dirname $argv0]
@@ -97,6 +97,7 @@ do_test trans-3.2 {
     SELECT a FROM two ORDER BY a;
   } altdb
 } {0 {1 5 10}}
+
 do_test trans-3.3 {
   catchsql {
     SELECT a FROM one ORDER BY a;
@@ -135,6 +136,7 @@ do_test trans-3.9 {
 do_test trans-3.10 {
   execsql {END TRANSACTION}
 } {}
+
 do_test trans-3.11 {
   set v [catch {execsql {
     SELECT a FROM two ORDER BY a;