]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
First attempt at a sharding VFS to split large DBs.
authorshaneh <shaneh@noemail.net>
Thu, 4 Nov 2010 20:50:27 +0000 (20:50 +0000)
committershaneh <shaneh@noemail.net>
Thu, 4 Nov 2010 20:50:27 +0000 (20:50 +0000)
FossilOrigin-Name: dd4dc8a4269e23ffe0e18438690da6077e17cdad

manifest
manifest.uuid
src/tclsqlite.c
src/test_multiplex.c [new file with mode: 0644]
test/multiplex.test [new file with mode: 0644]

index 9c6262987b1ef5132a0b6a476615000ec1b2834b..7081b634156ecc64e4f46e08f3cad75f5fc43e3a 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\ssize\sso\sit's\sreally\s32x32.
-D 2010-11-04T20:47:44
+C First\sattempt\sat\sa\ssharding\sVFS\sto\ssplit\slarge\sDBs.
+D 2010-11-04T20:50:27
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 2c8cefd962eca0147132c7cf9eaa4bb24c656f3f
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -182,7 +182,7 @@ F src/sqliteInt.h c63b0340dfdfde18ff255ddccf004edd2d073288
 F src/sqliteLimit.h a17dcd3fb775d63b64a43a55c54cb282f9726f44
 F src/status.c 496913d4e8441195f6f2a75b1c95993a45b9b30b
 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
-F src/tclsqlite.c dc71e2bd0f16dbb9106c1f5e257a64b783574f26
+F src/tclsqlite.c e1c485fa323e3ef02e5b10fe6a016e7638013eb9
 F src/test1.c 9e1fe842f72aa41af48ab7b21c3a12975b4c5b37
 F src/test2.c 80d323d11e909cf0eb1b6fbb4ac22276483bcf31
 F src/test3.c 056093cfef69ff4227a6bdb9108564dc7f45e4bc
@@ -207,6 +207,7 @@ F src/test_intarray.h 489edb9068bb926583445cb02589344961054207
 F src/test_journal.c 785edd54f963aefb3c1628124170a56697c68c70
 F src/test_loadext.c df586c27176e3c2cb2e099c78da67bf14379a56e
 F src/test_malloc.c 09a88f0c111201dc4f8c20470aa1b5f611d59200
+F src/test_multiplex.c be9c1445dca81ab9c77278b85b827cd446c656a8
 F src/test_mutex.c ce06b59aca168cd8c520b77159a24352a7469bd3
 F src/test_onefile.c 40cf9e212a377a6511469384a64b01e6e34b2eec
 F src/test_osinst.c f408c6a181f2fb04c56273afd5c3e1e82f60392c
@@ -558,6 +559,7 @@ F test/misc5.test 45b2e3ed5f79af2b4f38ae362eaf4c49674575bd
 F test/misc6.test 953cc693924d88e6117aeba16f46f0bf5abede91
 F test/misc7.test c5f4e6a82e04e71820c0f9f64f6733f04c8ae0ae
 F test/misuse.test 30b3a458e5a70c31e74c291937b6c82204c59f33
+F test/multiplex.test 257af7decc32a50814adffbb6865e1a7eb9f42c3
 F test/mutex1.test 5b71777fc127509cd257910c8db799de557a02de
 F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660
 F test/nan.test a44e04df1486fcfb02d32468cbcd3c8e1e433723
@@ -883,7 +885,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P cc9d9a12c3b19320b0ecfa78a6cee3d668004988
-R 0b84c10fec00628f99d964887ecf071d
+P 62c494f6947e53808404081c8f48cc327ef814e7
+R 081ff33d1b2f0f16499724ab46c0e8b9
 U shaneh
-Z 2c70e7d440c064ec216e31a9c4e6aaa8
+Z fa2a353cd6df65e68d308d8dfcf83710
index 193df921312e6247a6c3d0adfb1a6a89e11bf57f..6328c230beb2f03e11bf366122f1e3127b295777 100644 (file)
@@ -1 +1 @@
-62c494f6947e53808404081c8f48cc327ef814e7
\ No newline at end of file
+dd4dc8a4269e23ffe0e18438690da6077e17cdad
\ No newline at end of file
index 471b0d3fb11cb902ec89af03412d3b0f1bbff6ec..13796bed2fb6b0aa49a7a7618e43311219a4395a 100644 (file)
@@ -3579,6 +3579,7 @@ static void init_all(Tcl_Interp *interp){
     extern int SqlitetestStat_Init(Tcl_Interp*);
     extern int Sqlitetestrtree_Init(Tcl_Interp*);
     extern int Sqlitequota_Init(Tcl_Interp*);
+    extern int Sqlitemultiplex_Init(Tcl_Interp*);
 
     Sqliteconfig_Init(interp);
     Sqlitetest1_Init(interp);
@@ -3609,6 +3610,7 @@ static void init_all(Tcl_Interp *interp){
     SqlitetestStat_Init(interp);
     Sqlitetestrtree_Init(interp);
     Sqlitequota_Init(interp);
+    Sqlitemultiplex_Init(interp);
 
     Tcl_CreateObjCommand(interp,"load_testfixture_extensions",init_all_cmd,0,0);
 
diff --git a/src/test_multiplex.c b/src/test_multiplex.c
new file mode 100644 (file)
index 0000000..d8c0473
--- /dev/null
@@ -0,0 +1,816 @@
+/*\r
+** 2010 October 28\r
+**\r
+** The author disclaims copyright to this source code.  In place of\r
+** a legal notice, here is a blessing:\r
+**\r
+**    May you do good and not evil.\r
+**    May you find forgiveness for yourself and forgive others.\r
+**    May you share freely, never taking more than you give.\r
+**\r
+*************************************************************************\r
+**\r
+** This file contains a VFS "shim" - a layer that sits in between the\r
+** pager and the real VFS.\r
+**\r
+** This particular shim enforces a multiplex system on DB files.  \r
+** This shim shards/partitions a single DB file into smaller \r
+** "chunks" such that the total DB file size may exceed the maximum\r
+** file size of the underlying file system.\r
+**\r
+*/\r
+#include "sqlite3.h"\r
+#include <string.h>\r
+#include <assert.h>\r
+#include "sqliteInt.h"\r
+\r
+/************************ Shim Definitions ******************************/\r
+\r
+#define SQLITE_MULTIPLEX_CHUNK_SIZE 0x80000000\r
+#define SQLITE_MULTIPLEX_MAX_CHUNKS 32\r
+\r
+/************************ Object Definitions ******************************/\r
+\r
+/* Forward declaration of all object types */\r
+typedef struct multiplexGroup multiplexGroup;\r
+typedef struct multiplexConn multiplexConn;\r
+\r
+/*\r
+** A "multiplex group" is a collection of files that collectively\r
+** makeup a single SQLite DB file.  This allows the size of the DB\r
+** to exceed the limits imposed by the file system.\r
+**\r
+** There is an instance of the following object for each defined multiplex\r
+** group.\r
+*/\r
+struct multiplexGroup {\r
+  sqlite3_file *pReal[SQLITE_MULTIPLEX_MAX_CHUNKS]; /* Handles to each chunk */\r
+  char bOpen[SQLITE_MULTIPLEX_MAX_CHUNKS];          /* 0 if chunk not opened */\r
+  char *zName;                                      /* Base filename of this group */\r
+  int nName;                                        /* Length of base filename */\r
+  int flags;                                        /* Flags used for original opening */\r
+  multiplexGroup *pNext, *pPrev;   /* Doubly linked list of all group objects */\r
+};\r
+\r
+/*\r
+** An instance of the following object represents each open connection\r
+** to a file that is multiplex'ed.  This object is a \r
+** subclass of sqlite3_file.  The sqlite3_file object for the underlying\r
+** VFS is appended to this structure.\r
+*/\r
+struct multiplexConn {\r
+  sqlite3_file base;              /* Base class - must be first */\r
+  multiplexGroup *pGroup;         /* The underlying group of files */\r
+};\r
+\r
+/************************* Global Variables **********************************/\r
+/*\r
+** All global variables used by this file are containing within the following\r
+** gMultiplex structure.\r
+*/\r
+static struct {\r
+  /* The pOrigVfs is the real, original underlying VFS implementation.\r
+  ** Most operations pass-through to the real VFS.  This value is read-only\r
+  ** during operation.  It is only modified at start-time and thus does not\r
+  ** require a mutex.\r
+  */\r
+  sqlite3_vfs *pOrigVfs;\r
+\r
+  /* The sThisVfs is the VFS structure used by this shim.  It is initialized\r
+  ** at start-time and thus does not require a mutex\r
+  */\r
+  sqlite3_vfs sThisVfs;\r
+\r
+  /* The sIoMethods defines the methods used by sqlite3_file objects \r
+  ** associated with this shim.  It is initialized at start-time and does\r
+  ** not require a mutex.\r
+  **\r
+  ** When the underlying VFS is called to open a file, it might return \r
+  ** either a version 1 or a version 2 sqlite3_file object.  This shim\r
+  ** has to create a wrapper sqlite3_file of the same version.  Hence\r
+  ** there are two I/O method structures, one for version 1 and the other\r
+  ** for version 2.\r
+  */\r
+  sqlite3_io_methods sIoMethodsV1;\r
+  sqlite3_io_methods sIoMethodsV2;\r
+\r
+  /* True when this shim as been initialized.\r
+  */\r
+  int isInitialized;\r
+\r
+  /* For run-time access any of the other global data structures in this\r
+  ** shim, the following mutex must be held.\r
+  */\r
+  sqlite3_mutex *pMutex;\r
+\r
+  /* List of multiplexGroup objects.\r
+  */\r
+  multiplexGroup *pGroups;\r
+\r
+  /* Chunk params\r
+  */\r
+  int nChunkSize;\r
+  int nMaxChunks;\r
+\r
+} gMultiplex;\r
+\r
+/************************* Utility Routines *********************************/\r
+/*\r
+** Acquire and release the mutex used to serialize access to the\r
+** list of multiplexGroups.\r
+*/\r
+static void multiplexEnter(void){ sqlite3_mutex_enter(gMultiplex.pMutex); }\r
+static void multiplexLeave(void){ sqlite3_mutex_leave(gMultiplex.pMutex); }\r
+\r
+/* Translate an sqlite3_file* that is really a multiplexGroup* into\r
+** the sqlite3_file* for the underlying original VFS.\r
+*/\r
+static sqlite3_file *multiplexSubOpen(multiplexConn *pConn, int iChunk, int *rc, int *pOutFlags){\r
+  multiplexGroup *pGroup = pConn->pGroup;\r
+  sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;        /* Real VFS */\r
+  if( iChunk<gMultiplex.nMaxChunks ){\r
+    sqlite3_file *pSubOpen = pGroup->pReal[iChunk];    /* Real file descriptor */\r
+    if( !pGroup->bOpen[iChunk] ){\r
+      pGroup->zName[pGroup->nName] = '\0';\r
+      if( iChunk ) sqlite3_snprintf(pGroup->nName+6, pGroup->zName+pGroup->nName, "-%04d", iChunk);\r
+      *rc = pOrigVfs->xOpen(pOrigVfs, pGroup->zName, pSubOpen, pGroup->flags, pOutFlags);\r
+      if( *rc==SQLITE_OK ){\r
+        pGroup->bOpen[iChunk] = -1;\r
+        return pSubOpen;\r
+      }\r
+      return NULL;\r
+    }\r
+    *rc = SQLITE_OK;\r
+    return pSubOpen;\r
+  }\r
+  *rc = SQLITE_ERROR;\r
+  return NULL;\r
+}\r
+\r
+/************************* VFS Method Wrappers *****************************/\r
+/*\r
+** This is the xOpen method used for the "multiplex" VFS.\r
+**\r
+** Most of the work is done by the underlying original VFS.  This method\r
+** simply links the new file into the appropriate multiplex group if it is a\r
+** file that needs to be tracked.\r
+*/\r
+static int multiplexOpen(\r
+  sqlite3_vfs *pVfs,         /* The multiplex VFS */\r
+  const char *zName,         /* Name of file to be opened */\r
+  sqlite3_file *pConn,       /* Fill in this file descriptor */\r
+  int flags,                 /* Flags to control the opening */\r
+  int *pOutFlags             /* Flags showing results of opening */\r
+){\r
+  int rc;                                        /* Result code */\r
+  multiplexConn *pMultiplexOpen;                 /* The new multiplex file descriptor */\r
+  multiplexGroup *pGroup;                        /* Corresponding multiplexGroup object */\r
+  sqlite3_file *pSubOpen;                        /* Real file descriptor */\r
+  sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */\r
+  int nName = sqlite3Strlen30(zName);\r
+  int i;\r
+\r
+  UNUSED_PARAMETER(pVfs);\r
+\r
+  /* If the file is not a main database file or a WAL, then use the\r
+  ** normal xOpen method.\r
+  */\r
+  if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_WAL|SQLITE_OPEN_MAIN_JOURNAL))==0 ){\r
+    return pOrigVfs->xOpen(pOrigVfs, zName, pConn, flags, pOutFlags);\r
+  }\r
+\r
+  /* We need to create a group structure and manage\r
+  ** access to this group of files.\r
+  */\r
+  multiplexEnter();\r
+  pMultiplexOpen = (multiplexConn*)pConn;\r
+  /* -0000\0 */\r
+  pGroup = sqlite3_malloc( sizeof(multiplexGroup) + (pOrigVfs->szOsFile*gMultiplex.nMaxChunks) + nName + 6 );\r
+  if( pGroup==0 ){\r
+    rc=SQLITE_NOMEM;\r
+  }else{\r
+    pMultiplexOpen->pGroup = pGroup;\r
+    memset(pGroup, 0, sizeof(multiplexGroup) + (pOrigVfs->szOsFile*gMultiplex.nMaxChunks) + nName + 6);\r
+    for(i=0; i<gMultiplex.nMaxChunks; i++){\r
+      pGroup->pReal[i] = (sqlite3_file *)((char *)&pGroup[1] + (pOrigVfs->szOsFile*i));\r
+    }\r
+    pGroup->zName = (char *)&pGroup[1] + (pOrigVfs->szOsFile*gMultiplex.nMaxChunks);\r
+    memcpy(pGroup->zName, zName, nName+1);\r
+    pGroup->nName = nName;\r
+    pGroup->flags = flags;\r
+    pSubOpen = multiplexSubOpen(pMultiplexOpen, 0, &rc, pOutFlags);\r
+    if( pSubOpen ){\r
+      if( pSubOpen->pMethods->iVersion==1 ){\r
+        pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV1;\r
+      }else{\r
+        pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV2;\r
+      }\r
+      /* place this group at the head of our list */\r
+      pGroup->pNext = gMultiplex.pGroups;\r
+      if( gMultiplex.pGroups ) gMultiplex.pGroups->pPrev = pGroup;\r
+      gMultiplex.pGroups = pGroup;\r
+    }else{\r
+      sqlite3_free(pGroup);\r
+    }\r
+  }\r
+  multiplexLeave();\r
+  return rc;\r
+}\r
+\r
+/************************ I/O Method Wrappers *******************************/\r
+\r
+/* xClose requests get passed through to the original VFS.\r
+** We loop over all open chunk handles and close them.\r
+** The group structure for this file is unlinked from \r
+** our list of groups and freed.\r
+*/\r
+static int multiplexClose(sqlite3_file *pConn){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  multiplexGroup *pGroup = p->pGroup;\r
+  int rc = SQLITE_OK;\r
+  int i;\r
+  multiplexEnter();\r
+  for(i=0; i<gMultiplex.nMaxChunks; i++){\r
+    if( pGroup->bOpen[i] ){\r
+      sqlite3_file *pSubOpen = pGroup->pReal[i];\r
+      int rc2 = pSubOpen->pMethods->xClose(pSubOpen);\r
+      if( rc2!=SQLITE_OK ) rc = rc2;\r
+      pGroup->bOpen[i] = 0;\r
+    }\r
+  }\r
+  if( pGroup->pNext ) pGroup->pNext->pPrev = pGroup->pPrev;\r
+  if( pGroup->pPrev ){\r
+    pGroup->pPrev->pNext = pGroup->pNext;\r
+  }else{\r
+    gMultiplex.pGroups = pGroup->pNext;\r
+  }\r
+  sqlite3_free(pGroup);\r
+  multiplexLeave();\r
+  return rc;\r
+}\r
+\r
+/* Pass xRead requests thru to the original VFS after\r
+** determining the correct chunk to operate on.\r
+*/\r
+static int multiplexRead(\r
+  sqlite3_file *pConn,\r
+  void *pBuf,\r
+  int iAmt,\r
+  sqlite3_int64 iOfst\r
+){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  int rc = SQLITE_OK;\r
+  multiplexEnter();\r
+  while( iAmt > 0 ){\r
+    int i = (int)(iOfst/gMultiplex.nChunkSize);\r
+    sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL);\r
+    if( pSubOpen ){\r
+      int extra = ((int)(iOfst % gMultiplex.nChunkSize) + iAmt) - gMultiplex.nChunkSize;\r
+      if( extra<0 ) extra = 0;\r
+      iAmt -= extra;\r
+      rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst%gMultiplex.nChunkSize);\r
+      if( rc!=SQLITE_OK ) break;\r
+      pBuf = (char *)pBuf + iAmt;\r
+      iOfst += iAmt;\r
+      iAmt = extra;\r
+    }else{\r
+      rc = SQLITE_IOERR_READ;\r
+      break;\r
+    }\r
+  }\r
+  multiplexLeave();\r
+  return rc;\r
+}\r
+\r
+/* Pass xWrite requests thru to the original VFS after\r
+** determining the correct chunk to operate on.\r
+*/\r
+static int multiplexWrite(\r
+  sqlite3_file *pConn,\r
+  const void *pBuf,\r
+  int iAmt,\r
+  sqlite3_int64 iOfst\r
+){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  int rc = SQLITE_OK;\r
+  multiplexEnter();\r
+  while( iAmt > 0 ){\r
+    int i = (int)(iOfst/gMultiplex.nChunkSize);\r
+    sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL);\r
+    if( pSubOpen ){\r
+      int extra = ((int)(iOfst % gMultiplex.nChunkSize) + iAmt) - gMultiplex.nChunkSize;\r
+      if( extra<0 ) extra = 0;\r
+      iAmt -= extra;\r
+      rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst%gMultiplex.nChunkSize);\r
+      if( rc!=SQLITE_OK ) break;\r
+      pBuf = (char *)pBuf + iAmt;\r
+      iOfst += iAmt;\r
+      iAmt = extra;\r
+    }else{\r
+      rc = SQLITE_IOERR_WRITE;\r
+      break;\r
+    }\r
+  }\r
+  multiplexLeave();\r
+  return rc;\r
+}\r
+\r
+/* Pass xTruncate requests thru to the original VFS after\r
+** determining the correct chunk to operate on.  Delete any\r
+** chunks above the truncate mark.\r
+*/\r
+static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  multiplexGroup *pGroup = p->pGroup;\r
+  int rc = SQLITE_OK;\r
+  int rc2;\r
+  int i;\r
+  sqlite3_file *pSubOpen;\r
+  sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */\r
+  multiplexEnter();\r
+  /* delete the chunks above the truncate limit */\r
+  for(i=(int)(size/gMultiplex.nChunkSize)+1; i<gMultiplex.nMaxChunks; i++){\r
+    if( pGroup->bOpen[i] ){\r
+      pSubOpen = pGroup->pReal[i];\r
+      rc2 = pSubOpen->pMethods->xClose(pSubOpen);\r
+      if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE;\r
+    }\r
+    pGroup->zName[pGroup->nName] = '\0';\r
+    if( i ) sqlite3_snprintf(pGroup->nName+6, pGroup->zName+pGroup->nName, "-%04d", i);\r
+    rc2 = pOrigVfs->xDelete(pOrigVfs, pGroup->zName, 0);\r
+    if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE;\r
+  }\r
+  pSubOpen = multiplexSubOpen(p, (int)(size/gMultiplex.nChunkSize), &rc2, NULL);\r
+  if( pSubOpen ){\r
+    rc2 = pSubOpen->pMethods->xTruncate(pSubOpen, size%gMultiplex.nChunkSize);\r
+    if( rc2!=SQLITE_OK ) rc = rc2;\r
+  }else{\r
+    rc = SQLITE_IOERR_TRUNCATE;\r
+  }\r
+  multiplexLeave();\r
+  return rc;\r
+}\r
+\r
+/* Pass xSync requests through to the original VFS without change\r
+*/\r
+static int multiplexSync(sqlite3_file *pConn, int flags){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  multiplexGroup *pGroup = p->pGroup;\r
+  int rc = SQLITE_OK;\r
+  int i;\r
+  multiplexEnter();\r
+  for(i=0; i<gMultiplex.nMaxChunks; i++){\r
+    /* if we don't have it open, we don't need to sync it */\r
+    if( pGroup->bOpen[i] ){\r
+      sqlite3_file *pSubOpen = pGroup->pReal[i];\r
+      int rc2 = pSubOpen->pMethods->xSync(pSubOpen, flags);\r
+      if( rc2!=SQLITE_OK ) rc = rc2;\r
+    }\r
+  }\r
+  multiplexLeave();\r
+  return rc;\r
+}\r
+\r
+/* Pass xFileSize requests through to the original VFS.\r
+** Aggregate the size of all the chunks before returning.\r
+*/\r
+static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  multiplexGroup *pGroup = p->pGroup;\r
+  int rc = SQLITE_OK;\r
+  int rc2;\r
+  int i;\r
+  multiplexEnter();\r
+  *pSize = 0;\r
+  for(i=0; i<gMultiplex.nMaxChunks; i++){\r
+    sqlite3_file *pSubOpen = NULL;\r
+    sqlite3_int64 sz;\r
+    /* if not opened already, check to see if the chunk exists */\r
+    if( pGroup->bOpen[i] ){\r
+      pSubOpen = pGroup->pReal[i];\r
+    }else{\r
+      sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */\r
+      int exists = 0;\r
+      pGroup->zName[pGroup->nName] = '\0';\r
+      if( i ) sqlite3_snprintf(pGroup->nName+6, pGroup->zName+pGroup->nName, "-%04d", i);\r
+      rc2 = pOrigVfs->xAccess(pOrigVfs, pGroup->zName, SQLITE_ACCESS_EXISTS, &exists);\r
+      if( rc2==SQLITE_OK && exists){\r
+        /* if it exists, open it */\r
+        pSubOpen = multiplexSubOpen(p, i, &rc, NULL);\r
+      }else{\r
+        /* stop at first "gap" */\r
+        break;\r
+      }\r
+    }\r
+    if( pSubOpen ){\r
+      rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);\r
+      if( rc2!=SQLITE_OK ){\r
+        rc = rc2;\r
+      }else{\r
+        *pSize += sz;\r
+      }\r
+    }else{\r
+      break;\r
+    }\r
+  }\r
+  multiplexLeave();\r
+  return rc;\r
+}\r
+\r
+/* Pass xLock requests through to the original VFS unchanged.\r
+*/\r
+static int multiplexLock(sqlite3_file *pConn, int lock){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  int rc;\r
+  sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);\r
+  if( pSubOpen ){\r
+    return pSubOpen->pMethods->xLock(pSubOpen, lock);\r
+  }\r
+  return SQLITE_BUSY;\r
+}\r
+\r
+/* Pass xUnlock requests through to the original VFS unchanged.\r
+*/\r
+static int multiplexUnlock(sqlite3_file *pConn, int lock){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  int rc;\r
+  sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);\r
+  if( pSubOpen ){\r
+    return pSubOpen->pMethods->xUnlock(pSubOpen, lock);\r
+  }\r
+  return SQLITE_IOERR_UNLOCK;\r
+}\r
+\r
+/* Pass xCheckReservedLock requests through to the original VFS unchanged.\r
+*/\r
+static int multiplexCheckReservedLock(sqlite3_file *pConn, int *pResOut){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  int rc;\r
+  sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);\r
+  if( pSubOpen ){\r
+    return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);\r
+  }\r
+  return SQLITE_IOERR_CHECKRESERVEDLOCK;\r
+}\r
+\r
+/* Pass xFileControl requests through to the original VFS unchanged.\r
+*/\r
+static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  int rc;\r
+  sqlite3_file *pSubOpen;
+  if ( op==SQLITE_FCNTL_SIZE_HINT || op==SQLITE_FCNTL_CHUNK_SIZE ) return SQLITE_OK;
+  pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);\r
+  if( pSubOpen ){\r
+    return pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);\r
+  }\r
+  return SQLITE_ERROR;\r
+}\r
+\r
+/* Pass xSectorSize requests through to the original VFS unchanged.\r
+*/\r
+static int multiplexSectorSize(sqlite3_file *pConn){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  int rc;\r
+  sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);\r
+  if( pSubOpen ){\r
+    return pSubOpen->pMethods->xSectorSize(pSubOpen);\r
+  }\r
+  return SQLITE_DEFAULT_SECTOR_SIZE;\r
+}\r
+\r
+/* Pass xDeviceCharacteristics requests through to the original VFS unchanged.\r
+*/\r
+static int multiplexDeviceCharacteristics(sqlite3_file *pConn){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  int rc;\r
+  sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);\r
+  if( pSubOpen ){\r
+    return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen);\r
+  }\r
+  return 0;\r
+}\r
+\r
+/* Pass xShmMap requests through to the original VFS unchanged.\r
+*/\r
+static int multiplexShmMap(\r
+  sqlite3_file *pConn,            /* Handle open on database file */\r
+  int iRegion,                    /* Region to retrieve */\r
+  int szRegion,                   /* Size of regions */\r
+  int bExtend,                    /* True to extend file if necessary */\r
+  void volatile **pp              /* OUT: Mapped memory */\r
+){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  int rc;\r
+  sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);\r
+  if( pSubOpen ){\r
+    return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend, pp);\r
+  }\r
+  return SQLITE_IOERR;\r
+}\r
+\r
+/* Pass xShmLock requests through to the original VFS unchanged.\r
+*/\r
+static int multiplexShmLock(\r
+  sqlite3_file *pConn,       /* Database file holding the shared memory */\r
+  int ofst,                  /* First lock to acquire or release */\r
+  int n,                     /* Number of locks to acquire or release */\r
+  int flags                  /* What to do with the lock */\r
+){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  int rc;\r
+  sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);\r
+  if( pSubOpen ){\r
+    return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags);\r
+  }\r
+  return SQLITE_BUSY;\r
+}\r
+\r
+/* Pass xShmBarrier requests through to the original VFS unchanged.\r
+*/\r
+static void multiplexShmBarrier(sqlite3_file *pConn){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  int rc;\r
+  sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);\r
+  if( pSubOpen ){\r
+    pSubOpen->pMethods->xShmBarrier(pSubOpen);\r
+  }\r
+}\r
+\r
+/* Pass xShmUnmap requests through to the original VFS unchanged.\r
+*/\r
+static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){\r
+  multiplexConn *p = (multiplexConn*)pConn;\r
+  int rc;\r
+  sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);\r
+  if( pSubOpen ){\r
+    return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag);\r
+  }\r
+  return SQLITE_OK;\r
+}\r
+\r
+/************************** Public Interfaces *****************************/\r
+/*\r
+** Initialize the multiplex VFS shim.  Use the VFS named zOrigVfsName\r
+** as the VFS that does the actual work.  Use the default if\r
+** zOrigVfsName==NULL.  \r
+**\r
+** The multiplex VFS shim is named "multiplex".  It will become the default\r
+** VFS if makeDefault is non-zero.\r
+**\r
+** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once\r
+** during start-up.\r
+*/\r
+int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){\r
+  sqlite3_vfs *pOrigVfs;\r
+  if( gMultiplex.isInitialized ) return SQLITE_MISUSE;\r
+  pOrigVfs = sqlite3_vfs_find(zOrigVfsName);\r
+  if( pOrigVfs==0 ) return SQLITE_ERROR;\r
+  assert( pOrigVfs!=&gMultiplex.sThisVfs );\r
+  gMultiplex.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);\r
+  if( !gMultiplex.pMutex ){\r
+    return SQLITE_NOMEM;\r
+  }\r
+  gMultiplex.nChunkSize = SQLITE_MULTIPLEX_CHUNK_SIZE;\r
+  gMultiplex.nMaxChunks = SQLITE_MULTIPLEX_MAX_CHUNKS;\r
+  gMultiplex.pGroups = NULL;\r
+  gMultiplex.isInitialized = 1;\r
+  gMultiplex.pOrigVfs = pOrigVfs;\r
+  gMultiplex.sThisVfs = *pOrigVfs;\r
+  gMultiplex.sThisVfs.szOsFile += sizeof(multiplexConn);
+  gMultiplex.sThisVfs.zName = "multiplex";\r
+  gMultiplex.sThisVfs.xOpen = multiplexOpen;\r
+  gMultiplex.sIoMethodsV1.iVersion = 1;\r
+  gMultiplex.sIoMethodsV1.xClose = multiplexClose;\r
+  gMultiplex.sIoMethodsV1.xRead = multiplexRead;\r
+  gMultiplex.sIoMethodsV1.xWrite = multiplexWrite;\r
+  gMultiplex.sIoMethodsV1.xTruncate = multiplexTruncate;\r
+  gMultiplex.sIoMethodsV1.xSync = multiplexSync;\r
+  gMultiplex.sIoMethodsV1.xFileSize = multiplexFileSize;\r
+  gMultiplex.sIoMethodsV1.xLock = multiplexLock;\r
+  gMultiplex.sIoMethodsV1.xUnlock = multiplexUnlock;\r
+  gMultiplex.sIoMethodsV1.xCheckReservedLock = multiplexCheckReservedLock;\r
+  gMultiplex.sIoMethodsV1.xFileControl = multiplexFileControl;\r
+  gMultiplex.sIoMethodsV1.xSectorSize = multiplexSectorSize;\r
+  gMultiplex.sIoMethodsV1.xDeviceCharacteristics = multiplexDeviceCharacteristics;\r
+  gMultiplex.sIoMethodsV2 = gMultiplex.sIoMethodsV1;\r
+  gMultiplex.sIoMethodsV2.iVersion = 2;\r
+  gMultiplex.sIoMethodsV2.xShmMap = multiplexShmMap;\r
+  gMultiplex.sIoMethodsV2.xShmLock = multiplexShmLock;\r
+  gMultiplex.sIoMethodsV2.xShmBarrier = multiplexShmBarrier;\r
+  gMultiplex.sIoMethodsV2.xShmUnmap = multiplexShmUnmap;\r
+  sqlite3_vfs_register(&gMultiplex.sThisVfs, makeDefault);\r
+  return SQLITE_OK;\r
+}\r
+\r
+/*\r
+** Shutdown the multiplex system.\r
+**\r
+** All SQLite database connections must be closed before calling this\r
+** routine.\r
+**\r
+** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once while\r
+** shutting down in order to free all remaining multiplex groups.\r
+*/\r
+int sqlite3_multiplex_shutdown(void){\r
+  if( gMultiplex.isInitialized==0 ) return SQLITE_MISUSE;\r
+  if( gMultiplex.pGroups ) return SQLITE_MISUSE;\r
+  gMultiplex.isInitialized = 0;\r
+  sqlite3_mutex_free(gMultiplex.pMutex);\r
+  sqlite3_vfs_unregister(&gMultiplex.sThisVfs);\r
+  memset(&gMultiplex, 0, sizeof(gMultiplex));\r
+  return SQLITE_OK;\r
+}\r
+\r
+/*\r
+** Adjust chunking params.  VFS should be initialized first.\r
+** No files should be open.  Re-intializing will reset these\r
+** to the default.\r
+*/
+int sqlite3_multiplex_set(
+  int nChunkSize,                 /* Max chunk size */\r
+  int nMaxChunks                  /* Max number of chunks */\r
+){
+  if( !gMultiplex.isInitialized ) return SQLITE_MISUSE;\r
+  if( gMultiplex.pGroups ) return SQLITE_MISUSE;\r
+  if( nMaxChunks>SQLITE_MULTIPLEX_MAX_CHUNKS ) return SQLITE_MISUSE;\r
+  multiplexEnter();
+  gMultiplex.nChunkSize = nChunkSize;\r
+  gMultiplex.nMaxChunks = nMaxChunks;\r
+  multiplexLeave();
+  return SQLITE_OK;
+}
+\r
+/***************************** Test Code ***********************************/\r
+#ifdef SQLITE_TEST\r
+#include <tcl.h>\r
+\r
+extern const char *sqlite3TestErrorName(int);\r
+\r
+\r
+/*\r
+** tclcmd: sqlite3_multiplex_initialize NAME MAKEDEFAULT\r
+*/\r
+static int test_multiplex_initialize(\r
+  void * clientData,\r
+  Tcl_Interp *interp,\r
+  int objc,\r
+  Tcl_Obj *CONST objv[]\r
+){\r
+  const char *zName;              /* Name of new multiplex VFS */\r
+  int makeDefault;                /* True to make the new VFS the default */\r
+  int rc;                         /* Value returned by multiplex_initialize() */\r
+\r
+  UNUSED_PARAMETER(clientData);\r
+\r
+  /* Process arguments */\r
+  if( objc!=3 ){\r
+    Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT");\r
+    return TCL_ERROR;\r
+  }\r
+  zName = Tcl_GetString(objv[1]);\r
+  if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR;\r
+  if( zName[0]=='\0' ) zName = 0;\r
+\r
+  /* Call sqlite3_multiplex_initialize() */\r
+  rc = sqlite3_multiplex_initialize(zName, makeDefault);\r
+  Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);\r
+\r
+  return TCL_OK;\r
+}\r
+\r
+/*\r
+** tclcmd: sqlite3_multiplex_shutdown\r
+*/\r
+static int test_multiplex_shutdown(\r
+  void * clientData,\r
+  Tcl_Interp *interp,\r
+  int objc,\r
+  Tcl_Obj *CONST objv[]\r
+){\r
+  int rc;                         /* Value returned by multiplex_shutdown() */\r
+\r
+  UNUSED_PARAMETER(clientData);\r
+\r
+  if( objc!=1 ){\r
+    Tcl_WrongNumArgs(interp, 1, objv, "");\r
+    return TCL_ERROR;\r
+  }\r
+\r
+  /* Call sqlite3_multiplex_shutdown() */\r
+  rc = sqlite3_multiplex_shutdown();\r
+  Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);\r
+\r
+  return TCL_OK;\r
+}\r
+\r
+/*
+** tclcmd: sqlite3_multiplex_set CHUNK_SIZE MAX_CHUNKS
+*/
+static int test_multiplex_set(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  int nChunkSize;                 /* Max chunk size */\r
+  int nMaxChunks;                 /* Max number of chunks */\r
+  int rc;                         /* Value returned by sqlite3_multiplex_set() */
+
+  UNUSED_PARAMETER(clientData);\r
+
+  /* Process arguments */
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "CHUNK_SIZE MAX_CHUNKS");
+    return TCL_ERROR;
+  }
+  if( Tcl_GetIntFromObj(interp, objv[1], &nChunkSize) ) return TCL_ERROR;
+  if( Tcl_GetIntFromObj(interp, objv[2], &nMaxChunks) ) return TCL_ERROR;
+
+  if( nMaxChunks>SQLITE_MULTIPLEX_MAX_CHUNKS ){
+    Tcl_WrongNumArgs(interp, 1, objv, "MAX_CHUNKS > SQLITE_MULTIPLEX_MAX_CHUNKS");
+    return TCL_ERROR;
+  }
+
+  /* Invoke sqlite3_multiplex_set() */
+  rc = sqlite3_multiplex_set(nChunkSize, nMaxChunks);
+
+  Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
+  return TCL_OK;
+}
+
+/*\r
+** tclcmd:  sqlite3_multiplex_dump\r
+*/\r
+static int test_multiplex_dump(\r
+  void * clientData,\r
+  Tcl_Interp *interp,\r
+  int objc,\r
+  Tcl_Obj *CONST objv[]\r
+){\r
+  Tcl_Obj *pResult;\r
+  Tcl_Obj *pGroupTerm;\r
+  multiplexGroup *pGroup;\r
+  int i;\r
+  int nChunks = 0;\r
+\r
+  UNUSED_PARAMETER(clientData);\r
+  UNUSED_PARAMETER(objc);\r
+  UNUSED_PARAMETER(objv);\r
+\r
+  pResult = Tcl_NewObj();\r
+  multiplexEnter();\r
+  for(pGroup=gMultiplex.pGroups; pGroup; pGroup=pGroup->pNext){\r
+    pGroupTerm = Tcl_NewObj();\r
+\r
+    pGroup->zName[pGroup->nName] = '\0';\r
+    Tcl_ListObjAppendElement(interp, pGroupTerm,\r
+          Tcl_NewStringObj(pGroup->zName, -1));\r
+    Tcl_ListObjAppendElement(interp, pGroupTerm,\r
+          Tcl_NewIntObj(pGroup->nName));\r
+    Tcl_ListObjAppendElement(interp, pGroupTerm,\r
+          Tcl_NewIntObj(pGroup->flags));\r
+\r
+    /* count number of chunks with open handles */\r
+    for(i=0; i<gMultiplex.nMaxChunks; i++){\r
+      if( pGroup->bOpen[i] ) nChunks++;\r
+    }\r
+    Tcl_ListObjAppendElement(interp, pGroupTerm,\r
+          Tcl_NewIntObj(nChunks));\r
+\r
+    Tcl_ListObjAppendElement(interp, pGroupTerm,\r
+          Tcl_NewIntObj(gMultiplex.nChunkSize));\r
+    Tcl_ListObjAppendElement(interp, pGroupTerm,\r
+          Tcl_NewIntObj(gMultiplex.nMaxChunks));\r
+\r
+    Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);\r
+  }\r
+  multiplexLeave();\r
+  Tcl_SetObjResult(interp, pResult);\r
+  return TCL_OK;\r
+}\r
+\r
+/*\r
+** This routine registers the custom TCL commands defined in this\r
+** module.  This should be the only procedure visible from outside\r
+** of this module.\r
+*/\r
+int Sqlitemultiplex_Init(Tcl_Interp *interp){\r
+  static struct {\r
+     char *zName;\r
+     Tcl_ObjCmdProc *xProc;\r
+  } aCmd[] = {\r
+    { "sqlite3_multiplex_initialize", test_multiplex_initialize },\r
+    { "sqlite3_multiplex_shutdown", test_multiplex_shutdown },\r
+    { "sqlite3_multiplex_set", test_multiplex_set },
+    { "sqlite3_multiplex_dump", test_multiplex_dump },\r
+  };\r
+  int i;\r
+\r
+  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){\r
+    Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);\r
+  }\r
+\r
+  return TCL_OK;\r
+}\r
+#endif\r
diff --git a/test/multiplex.test b/test/multiplex.test
new file mode 100644 (file)
index 0000000..92c1a9d
--- /dev/null
@@ -0,0 +1,306 @@
+# 2010 October 29
+#
+# 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.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+source $testdir/malloc_common.tcl
+
+proc multiplex_delete {name} {
+  file delete -force $name
+  file delete -force $name-journal
+  file delete -force $name-wal
+  for {set i 1} {$i<=15} {incr i} {
+    file delete -force $name-000$i
+    file delete -force $name-00$i
+  }
+}
+
+db close
+
+do_test multiplex-1.1 { sqlite3_multiplex_initialize nosuchvfs 1 } {SQLITE_ERROR}
+do_test multiplex-1.2 { sqlite3_multiplex_initialize "" 1 }        {SQLITE_OK}
+do_test multiplex-1.3 { sqlite3_multiplex_initialize "" 1 }        {SQLITE_MISUSE}
+do_test multiplex-1.4 { sqlite3_multiplex_shutdown }               {SQLITE_OK}
+
+do_test multiplex-1.5 { sqlite3_multiplex_initialize "" 0 }        {SQLITE_OK}
+do_test multiplex-1.6 { sqlite3_multiplex_shutdown }               {SQLITE_OK}
+do_test multiplex-1.7 { sqlite3_multiplex_initialize "" 1 }        {SQLITE_OK}
+do_test multiplex-1.8 { sqlite3_multiplex_shutdown }               {SQLITE_OK}
+
+
+#-------------------------------------------------------------------------
+# Some simple warm-body tests with a single database file in rollback 
+# mode:
+#
+#   multiplex-2.1.*: Test simple writing to a multiplex file.
+#
+#   multiplex-2.2.*: More writing.
+#
+#   multiplex-2.3.*: Open and close a second db.
+#
+#   multiplex-2.4.*: Try to shutdown the multiplex system before closing the db
+#                file. Check that this fails and the multiplex system still works
+#                afterwards. Then close the database and successfully shut
+#                down the multiplex system.
+#
+
+sqlite3_multiplex_initialize "" 1
+sqlite3_multiplex_set 0x8000 16
+
+do_test multiplex-2.1.2 {
+  sqlite3 db test.db
+  execsql {
+    PRAGMA page_size=1024;
+    PRAGMA auto_vacuum=OFF;
+    PRAGMA journal_mode=DELETE;
+  }
+  execsql {
+    CREATE TABLE t1(a, b);
+    INSERT INTO t1 VALUES(1, randomblob(1100));
+    INSERT INTO t1 VALUES(2, randomblob(1100));
+  }
+} {}
+do_test multiplex-2.1.3 { file size test.db } {4096}
+do_test multiplex-2.1.4 {
+  execsql { INSERT INTO t1 VALUES(3, randomblob(1100)) }
+} {}
+
+do_test multiplex-2.2.1 {
+  execsql { INSERT INTO t1 VALUES(3, randomblob(1100)) }
+} {}
+do_test multiplex-2.2.3 { file size test.db } {6144}
+
+do_test multiplex-2.3.1 {
+  sqlite3 db2 bak.db
+  db2 close
+} {}
+
+do_test multiplex-2.4.1 {
+  sqlite3_multiplex_shutdown
+} {SQLITE_MISUSE}
+do_test multiplex-2.4.2 {
+  execsql { INSERT INTO t1 VALUES(3, randomblob(1100)) }
+} {}
+do_test multiplex-2.4.4 { file size test.db } {7168}
+do_test multiplex-2.4.99 {
+  db close
+  sqlite3_multiplex_shutdown
+} {SQLITE_OK}
+
+#-------------------------------------------------------------------------
+# Try some tests with more than one connection to a database file. Still
+# in rollback mode.
+#
+#   multiplex-3.1.*: Two connections to a single database file.
+#
+#   multiplex-3.2.*: Two connections to each of several database files (that
+#                are in the same multiplex group).
+#
+do_test multiplex-3.1.1 {
+  multiplex_delete test.db
+  sqlite3_multiplex_initialize "" 1
+  sqlite3_multiplex_set 0x8000 16
+} {SQLITE_OK}
+do_test multiplex-3.1.2 {
+  sqlite3 db test.db
+  execsql {
+    PRAGMA page_size = 1024;
+    PRAGMA journal_mode = delete;
+    PRAGMA auto_vacuum = off;
+    CREATE TABLE t1(a PRIMARY KEY, b);
+    INSERT INTO t1 VALUES(1, 'one');
+  }
+  file size test.db
+} {3072}
+do_test multiplex-3.1.3 {
+  sqlite3 db2 test.db
+  execsql { CREATE TABLE t2(a, b) } db2
+} {}
+do_test multiplex-3.1.4 {
+  execsql { CREATE TABLE t3(a, b) }
+} {}
+do_test multiplex-3.1.5 {
+  catchsql { CREATE TABLE t3(a, b) }
+} {1 {table t3 already exists}}
+do_test multiplex-3.1.6 {
+  db close
+  db2 close
+} {}
+
+do_test multiplex-3.2.1a {
+
+  multiplex_delete test.db
+  multiplex_delete test2.db
+
+  sqlite3 db1a test.db
+  sqlite3 db2a test2.db
+
+  foreach db {db1a db2a} {
+    execsql {
+      PRAGMA page_size = 1024;
+      PRAGMA journal_mode = delete;
+      PRAGMA auto_vacuum = off;
+      CREATE TABLE t1(a, b);
+    } $db
+  }
+
+  list [file size test.db] [file size test2.db]
+} {2048 2048}
+
+do_test multiplex-3.2.1b {
+  sqlite3 db1b test.db
+  sqlite3 db2b test2.db
+} {}
+
+do_test multiplex-3.2.2 { execsql { INSERT INTO t1 VALUES('x', 'y') } db1a } {}
+do_test multiplex-3.2.3 { execsql { INSERT INTO t1 VALUES('v', 'w') } db1b } {}
+do_test multiplex-3.2.4 { execsql { INSERT INTO t1 VALUES('t', 'u') } db2a } {}
+do_test multiplex-3.2.5 { execsql { INSERT INTO t1 VALUES('r', 's') } db2b } {}
+
+do_test multiplex-3.2.6 { 
+  execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db1a
+} {}
+do_test multiplex-3.2.7 { 
+  execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db1b
+} {}
+do_test multiplex-3.2.8 { 
+  execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db2a
+} {}
+do_test multiplex-3.2.9 { 
+  execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db2b
+} {}
+
+do_test multiplex-3.3.1 { 
+  execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db1a
+  execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db1b
+  execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db2a
+  execsql { INSERT INTO t1 VALUES(randomblob(500), randomblob(500)) } db2b
+} {}
+
+do_test multiplex-3.2.X {
+  foreach db {db1a db2a db2b db1b} { catch { $db close } }
+} {}
+
+#-------------------------------------------------------------------------
+#
+
+sqlite3_multiplex_initialize "" 1
+sqlite3_multiplex_set 0x8000 16
+
+# Return a list of all currently defined multiplexs.
+proc multiplex_list {} {
+  set allq {}
+  foreach q [sqlite3_multiplex_dump] {
+    lappend allq [lindex $q 0]
+  }
+  return [lsort $allq]
+}
+
+do_test multiplex-4.1.6 {
+  multiplex_delete test2.db
+  sqlite3 db test2.db
+  db eval {CREATE TABLE t2(x); INSERT INTO t2 VALUES('tab-t2');}
+  set res [multiplex_list]
+  list [regexp {test2.db} $res]
+} {1}
+do_test multiplex-4.1.6a {
+  sqlite3 db2 test2.db
+  db2 eval {SELECT * FROM t2}
+} {tab-t2}
+do_test multiplex-4.1.7 {
+  execsql {INSERT INTO t2 VALUES(zeroblob(200000))}
+} {}
+do_test multiplex-4.1.8 {
+  sqlite3 db2 test2.db
+  db2 eval {SELECT count(*) FROM t2}
+} {2}
+do_test multiplex-4.1.8a {
+   db2 eval { DELETE FROM t2 WHERE x = 'tab-t2' }
+} {}
+do_test multiplex-4.1.8b {
+  sqlite3 db2 test2.db
+  db2 eval {SELECT count(*) FROM t2}
+} {1}
+
+
+do_test multiplex-4.1.9 {
+  execsql {INSERT INTO t2 VALUES(zeroblob(200000))}
+} {}
+do_test multiplex-4.1.10 {
+  set res [multiplex_list]
+  list [regexp {test2.db} $res]
+} {1}
+do_test multiplex-4.1.11 {
+  db2 close
+  set res [multiplex_list]
+  list [regexp {test2.db} $res]
+} {1}
+do_test multiplex-4.1.12 {
+  db close
+  multiplex_list
+} {}
+
+
+#-------------------------------------------------------------------------
+# The following tests test that the multiplex VFS handles malloc and IO 
+# errors.
+#
+
+sqlite3_multiplex_initialize "" 1
+sqlite3_multiplex_set 0x8000 16
+
+do_faultsim_test multiplex-5.1 -prep {
+  catch {db close}
+} -body {
+  sqlite3 db test2.db
+}
+do_faultsim_test multiplex-5.2 -prep {
+  catch {db close}
+} -body {
+  sqlite3 db test.db
+}
+
+catch { db close }
+multiplex_delete test.db
+
+do_test multiplex-5.3.prep {
+  sqlite3 db test.db
+  execsql {
+    PRAGMA auto_vacuum = 1;
+    PRAGMA page_size = 1024;
+    CREATE TABLE t1(a, b);
+    INSERT INTO t1 VALUES(10, zeroblob(1200));
+  }
+  faultsim_save_and_close
+} {}
+do_faultsim_test multiplex-5.3 -prep {
+  faultsim_restore_and_reopen
+} -body {
+  execsql { DELETE FROM t1 }
+}
+
+do_test multiplex-5.4.1 {
+  catch { db close }
+  multiplex_delete test.db
+  file mkdir test.db
+  list [catch { sqlite3 db test.db } msg] $msg
+} {1 {unable to open database file}}
+
+do_faultsim_test multiplex-5.5 -prep {
+  catch { sqlite3_multiplex_shutdown }
+} -body {
+  sqlite3_multiplex_initialize "" 1
+  sqlite3_multiplex_set 0x8000 16
+}
+
+catch { sqlite3_multiplex_shutdown }
+finish_test