]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Enhance the "swarmvtab" extension. See header comments in ext/misc/unionvtab.c
authordan <dan@noemail.net>
Fri, 15 Dec 2017 20:21:17 +0000 (20:21 +0000)
committerdan <dan@noemail.net>
Fri, 15 Dec 2017 20:21:17 +0000 (20:21 +0000)
for details.

FossilOrigin-Name: 01c173651ab22b7b0c139eded6f2ad8504efd09088df8ae6a3471230ebf2306f

ext/misc/unionvtab.c
manifest
manifest.uuid
test/swarmvtab.test
test/swarmvtab2.test
test/swarmvtab3.test [new file with mode: 0644]

index fc87915b386b37f581dcf60ab0fd6c18e33aaafa..14cce84510af6f707ee2c07e59007c4ba5d2c5e5 100644 (file)
@@ -56,6 +56,8 @@
 **
 ** SWARMVTAB
 **
+**  LEGACY SYNTAX:
+**
 **   A "swarmvtab" virtual table is created similarly to a unionvtab table:
 **
 **     CREATE VIRTUAL TABLE <name>
 **   the database file containing the source table.  The <callback> option
 **   is optional.  If included, it is the name of an application-defined
 **   SQL function that is invoked with the URI of the file, if the file
-**   does not already exist on disk.
+**   does not already exist on disk when required by swarmvtab.
+**
+**  NEW SYNTAX:
+**
+**   Using the new syntax, a swarmvtab table is created with:
+**
+**      CREATE VIRTUAL TABLE <name> USING swarmvtab(
+**        <sql-statement> [, <options>]
+**      );
+**
+**   where valid <options> are:
+**
+**      missing=<udf-function-name>
+**      openclose=<udf-function-name>
+**      maxopen=<integer>
+**      <sql-parameter>=<text-value>
+**
+**   The <sql-statement> must return the same 4 columns as for a swarmvtab
+**   table in legacy mode. However, it may also return a 5th column - the
+**   "context" column. The text value returned in this column is not used
+**   at all by the swarmvtab implementation, except that it is passed as
+**   an additional argument to the two UDF functions that may be invoked
+**   (see below).
+**
+**   The "missing" option, if present, specifies the name of an SQL UDF
+**   function to be invoked if a database file is not already present on
+**   disk when required by swarmvtab. If the <sql-statement> did not provide
+**   a context column, it is invoked as:
+**
+**     SELECT <missing-udf>(<database filename/uri>);
+**
+**   Or, if there was a context column:
+**
+**     SELECT <missing-udf>(<database filename/uri>, <context>);
+**
+**   The "openclose" option may also specify a UDF function. This function
+**   is invoked right before swarmvtab opens a database, and right after
+**   it closes one. The first argument - or first two arguments, if
+**   <sql-statement> supplied the context column - is the same as for
+**   the "missing" UDF. Following this, the UDF is passed integer value
+**   0 before a db is opened, and 1 right after it is closed. If both
+**   a missing and openclose UDF is supplied, the application should expect
+**   the following sequence of calls (for a single database):
+**
+**      SELECT <openclose-udf>(<db filename>, <context>, 0);
+**      if( db not already on disk ){
+**          SELECT <missing-udf>(<db filename>, <context>);
+**      }
+**      ... swarmvtab uses database ...
+**      SELECT <openclose-udf>(<db filename>, <context>, 1);
+**
+**   The "maxopen" option is used to configure the maximum number of
+**   database files swarmvtab will hold open simultaneously (default 9).
+**
+**   If an option name begins with a ":" character, then it is assumed
+**   to be an SQL parameter. In this case, the specified text value is
+**   bound to the same variable of the <sql-statement> before it is 
+**   executed. It is an error of the named SQL parameter does not exist.
+**   For example:
+**
+**     CREATE VIRTUAL TABLE swarm USING swarmvtab(
+**       'SELECT :path || localfile, tbl, min, max FROM swarmdir',
+**       :path='/home/user/databases/'
+**       missing='missing_func'
+**     );
 */
 
 #include "sqlite3ext.h"
 SQLITE_EXTENSION_INIT1
 #include <assert.h>
 #include <string.h>
+#include <stdlib.h>
 
 #ifndef SQLITE_OMIT_VIRTUALTABLE
 
@@ -128,6 +195,7 @@ struct UnionSrc {
 
   /* Fields used by swarmvtab only */
   char *zFile;                    /* Database file containing table zTab */
+  char *zContext;                 /* Context string, if any */
   int nUser;                      /* Current number of users */
   sqlite3 *db;                    /* Database handle */
   UnionSrc *pNextClosable;        /* Next in list of closable sources */
@@ -145,8 +213,11 @@ struct UnionTab {
   UnionSrc *aSrc;                 /* Array of source tables, sorted by rowid */
 
   /* Used by swarmvtab only */
+  int bHasContext;                /* Has context strings */
   char *zSourceStr;               /* Expected unionSourceToStr() value */
-  char *zNotFoundCallback;        /* UDF to invoke if file not found on open */
+  sqlite3_stmt *pNotFound;        /* UDF to invoke if file not found on open */
+  sqlite3_stmt *pOpenClose;       /* UDF to invoke on open and close */
+
   UnionSrc *pClosable;            /* First in list of closable sources */
   int nOpen;                      /* Current number of open sources */
   int nMaxOpen;                   /* Maximum number of open sources */
@@ -351,6 +422,39 @@ static void unionFinalize(int *pRc, sqlite3_stmt *pStmt, char **pzErr){
   }
 }
 
+/*
+** If an "openclose" UDF was supplied when this virtual table was created,
+** invoke it now. The first argument passed is the name of the database
+** file for source pSrc. The second is integer value bClose.
+**
+** If successful, return SQLITE_OK. Otherwise an SQLite error code. In this
+** case if argument pzErr is not NULL, also set (*pzErr) to an English
+** language error message. The caller is responsible for eventually freeing 
+** any error message using sqlite3_free().
+*/
+static int unionInvokeOpenClose(
+  UnionTab *pTab, 
+  UnionSrc *pSrc, 
+  int bClose,
+  char **pzErr
+){
+  int rc = SQLITE_OK;
+  if( pTab->pOpenClose ){
+    sqlite3_bind_text(pTab->pOpenClose, 1, pSrc->zFile, -1, SQLITE_STATIC);
+    if( pTab->bHasContext ){
+      sqlite3_bind_text(pTab->pOpenClose, 2, pSrc->zContext, -1, SQLITE_STATIC);
+    }
+    sqlite3_bind_int(pTab->pOpenClose, 2+pTab->bHasContext, bClose);
+    sqlite3_step(pTab->pOpenClose);
+    if( SQLITE_OK!=(rc = sqlite3_reset(pTab->pOpenClose)) ){
+      if( pzErr ){
+        *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db));
+      }
+    }
+  }
+  return rc;
+}
+
 /*
 ** This function is a no-op for unionvtab. For swarmvtab, it attempts to
 ** close open database files until at most nMax are open. An SQLite error
@@ -358,13 +462,16 @@ static void unionFinalize(int *pRc, sqlite3_stmt *pStmt, char **pzErr){
 */
 static void unionCloseSources(UnionTab *pTab, int nMax){
   while( pTab->pClosable && pTab->nOpen>nMax ){
+    UnionSrc *p;
     UnionSrc **pp;
     for(pp=&pTab->pClosable; (*pp)->pNextClosable; pp=&(*pp)->pNextClosable);
-    assert( (*pp)->db );
-    sqlite3_close((*pp)->db);
-    (*pp)->db = 0;
+    p = *pp;
+    assert( p->db );
+    sqlite3_close(p->db);
+    p->db = 0;
     *pp = 0;
     pTab->nOpen--;
+    unionInvokeOpenClose(pTab, p, 1, 0);
   }
 }
 
@@ -377,13 +484,18 @@ static int unionDisconnect(sqlite3_vtab *pVtab){
     int i;
     for(i=0; i<pTab->nSrc; i++){
       UnionSrc *pSrc = &pTab->aSrc[i];
+      if( pSrc->db ){
+        unionInvokeOpenClose(pTab, pSrc, 1, 0);
+      }
       sqlite3_free(pSrc->zDb);
       sqlite3_free(pSrc->zTab);
       sqlite3_free(pSrc->zFile);
+      sqlite3_free(pSrc->zContext);
       sqlite3_close(pSrc->db);
     }
+    sqlite3_finalize(pTab->pNotFound);
+    sqlite3_finalize(pTab->pOpenClose);
     sqlite3_free(pTab->zSourceStr);
-    sqlite3_free(pTab->zNotFoundCallback);
     sqlite3_free(pTab->aSrc);
     sqlite3_free(pTab);
   }
@@ -496,29 +608,31 @@ static int unionSourceCheck(UnionTab *pTab, char **pzErr){
   return rc;
 }
 
-
 /*
 ** Try to open the swarmvtab database.  If initially unable, invoke the
 ** not-found callback UDF and then try again.
 */
 static int unionOpenDatabaseInner(UnionTab *pTab, UnionSrc *pSrc, char **pzErr){
-  int rc = SQLITE_OK;
-  static const int openFlags = 
-       SQLITE_OPEN_READONLY | SQLITE_OPEN_URI;
+  static const int openFlags = SQLITE_OPEN_READONLY | SQLITE_OPEN_URI;
+  int rc;
+
+  rc = unionInvokeOpenClose(pTab, pSrc, 0, pzErr);
+  if( rc!=SQLITE_OK ) return rc;
+
   rc = sqlite3_open_v2(pSrc->zFile, &pSrc->db, openFlags, 0);
   if( rc==SQLITE_OK ) return rc;
-  if( pTab->zNotFoundCallback ){
-    char *zSql = sqlite3_mprintf("SELECT \"%w\"(%Q);",
-                    pTab->zNotFoundCallback, pSrc->zFile);
+  if( pTab->pNotFound ){
     sqlite3_close(pSrc->db);
     pSrc->db = 0;
-    if( zSql==0 ){
-      *pzErr = sqlite3_mprintf("out of memory");
-      return SQLITE_NOMEM;
+    sqlite3_bind_text(pTab->pNotFound, 1, pSrc->zFile, -1, SQLITE_STATIC);
+    if( pTab->bHasContext ){
+      sqlite3_bind_text(pTab->pNotFound, 2, pSrc->zContext, -1, SQLITE_STATIC);
+    }
+    sqlite3_step(pTab->pNotFound);
+    if( SQLITE_OK!=(rc = sqlite3_reset(pTab->pNotFound)) ){
+      *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db));
+      return rc;
     }
-    rc = sqlite3_exec(pTab->db, zSql, 0, 0, pzErr);
-    sqlite3_free(zSql);
-    if( rc ) return rc;
     rc = sqlite3_open_v2(pSrc->zFile, &pSrc->db, openFlags, 0);
   }
   if( rc!=SQLITE_OK ){
@@ -572,6 +686,7 @@ static int unionOpenDatabase(UnionTab *pTab, int iSrc, char **pzErr){
     }else{
       sqlite3_close(pSrc->db);
       pSrc->db = 0;
+      unionInvokeOpenClose(pTab, pSrc, 1, 0);
     }
   }
 
@@ -627,6 +742,132 @@ static int unionFinalizeCsrStmt(UnionCsr *pCsr){
   return rc;
 }
 
+/* 
+** Return true if the argument is a space, tab, CR or LF character.
+*/
+static int union_isspace(char c){
+  return (c==' ' || c=='\n' || c=='\r' || c=='\t');
+}
+
+/* 
+** Return true if the argument is an alphanumeric character in the 
+** ASCII range.
+*/
+static int union_isidchar(char c){
+  return ((c>='a' && c<='z') || (c>='A' && c<'Z') || (c>='0' && c<='9'));
+}
+
+/*
+** This function is called to handle all arguments following the first 
+** (the SQL statement) passed to a swarmvtab (not unionvtab) CREATE 
+** VIRTUAL TABLE statement. It may bind parameters to the SQL statement 
+** or configure members of the UnionTab object passed as the second
+** argument.
+**
+** Refer to header comments at the top of this file for a description
+** of the arguments parsed.
+**
+** This function is a no-op if *pRc is other than SQLITE_OK when it is
+** called. Otherwise, if an error occurs, *pRc is set to an SQLite error
+** code. In this case *pzErr may be set to point to a buffer containing
+** an English language error message. It is the responsibility of the 
+** caller to eventually free the buffer using sqlite3_free().
+*/
+static void unionConfigureVtab(
+  int *pRc,                       /* IN/OUT: Error code */
+  UnionTab *pTab,                 /* Table to configure */
+  sqlite3_stmt *pStmt,            /* SQL statement to find sources */
+  int nArg,                       /* Number of entries in azArg[] array */
+  const char * const *azArg,      /* Array of arguments to consider */
+  char **pzErr                    /* OUT: Error message */
+){
+  int rc = *pRc;
+  int i;
+  if( rc==SQLITE_OK ){
+    pTab->bHasContext = (sqlite3_column_count(pStmt)>4);
+  }
+  for(i=0; rc==SQLITE_OK && i<nArg; i++){
+    char *zArg = unionStrdup(&rc, azArg[i]);
+    if( zArg ){
+      int nOpt = 0;               /* Size of option name in bytes */
+      char *zOpt;                 /* Pointer to option name */
+      char *zVal;                 /* Pointer to value */
+
+      unionDequote(zArg);
+      zOpt = zArg;
+      while( union_isspace(*zOpt) ) zOpt++;
+      zVal = zOpt;
+      if( *zVal==':' ) zVal++;
+      while( union_isidchar(*zVal) ) zVal++;
+      nOpt = zVal-zOpt;
+
+      while( union_isspace(*zVal) ) zVal++;
+      if( *zVal=='=' ){
+        zOpt[nOpt] = '\0';
+        zVal++;
+        while( union_isspace(*zVal) ) zVal++;
+        zVal = unionStrdup(&rc, zVal);
+        if( zVal ){
+          unionDequote(zVal);
+          if( zOpt[0]==':' ){
+            /* A value to bind to the SQL statement */
+            int iParam = sqlite3_bind_parameter_index(pStmt, zOpt);
+            if( iParam==0 ){
+              *pzErr = sqlite3_mprintf(
+                  "swarmvtab: no such SQL parameter: %s", zOpt
+              );
+              rc = SQLITE_ERROR;
+            }else{
+              rc = sqlite3_bind_text(pStmt, iParam, zVal, -1, SQLITE_TRANSIENT);
+            }
+          }else if( nOpt==7 && 0==sqlite3_strnicmp(zOpt, "maxopen", 7) ){
+            pTab->nMaxOpen = atoi(zVal);
+            if( pTab->nMaxOpen<=0 ){
+              *pzErr = sqlite3_mprintf("swarmvtab: illegal maxopen value");
+              rc = SQLITE_ERROR;
+            }
+          }else if( nOpt==7 && 0==sqlite3_strnicmp(zOpt, "missing", 7) ){
+            if( pTab->pNotFound ){
+              *pzErr = sqlite3_mprintf(
+                  "swarmvtab: duplicate \"missing\" option");
+              rc = SQLITE_ERROR;
+            }else{
+              pTab->pNotFound = unionPreparePrintf(&rc, pzErr, pTab->db,
+                  "SELECT \"%w\"(?%s)", zVal, pTab->bHasContext ? ",?" : ""
+              );
+            }
+          }else if( nOpt==9 && 0==sqlite3_strnicmp(zOpt, "openclose", 9) ){
+            if( pTab->pOpenClose ){
+              *pzErr = sqlite3_mprintf(
+                  "swarmvtab: duplicate \"openclose\" option");
+              rc = SQLITE_ERROR;
+            }else{
+              pTab->pOpenClose = unionPreparePrintf(&rc, pzErr, pTab->db,
+                  "SELECT \"%w\"(?,?%s)", zVal, pTab->bHasContext ? ",?" : ""
+              );
+            }
+          }else{
+            *pzErr = sqlite3_mprintf("swarmvtab: unrecognized option: %s",zOpt);
+            rc = SQLITE_ERROR;
+          }
+          sqlite3_free(zVal);
+        }
+      }else{
+        if( i==0 && nArg==1 ){
+          pTab->pNotFound = unionPreparePrintf(&rc, pzErr, pTab->db,
+              "SELECT \"%w\"(?)", zArg
+          );
+        }else{
+          *pzErr = sqlite3_mprintf( "swarmvtab: parse error: %s", azArg[i]);
+          rc = SQLITE_ERROR;
+        }
+      }
+      sqlite3_free(zArg);
+    }
+  }
+  *pRc = rc;
+}
+
 /* 
 ** xConnect/xCreate method.
 **
@@ -654,7 +895,7 @@ static int unionConnect(
     /* unionvtab tables may only be created in the temp schema */
     *pzErr = sqlite3_mprintf("%s tables must be created in TEMP schema", zVtab);
     rc = SQLITE_ERROR;
-  }else if( argc!=4 && argc!=5 ){
+  }else if( argc<4 || (argc>4 && bSwarm==0) ){
     *pzErr = sqlite3_mprintf("wrong number of arguments for %s", zVtab);
     rc = SQLITE_ERROR;
   }else{
@@ -673,6 +914,17 @@ static int unionConnect(
 
     /* Allocate the UnionTab structure */
     pTab = unionMalloc(&rc, sizeof(UnionTab));
+    if( pTab ){
+      assert( rc==SQLITE_OK );
+      pTab->db = db;
+      pTab->bSwarm = bSwarm;
+      pTab->nMaxOpen = SWARMVTAB_MAX_OPEN;
+    }
+
+    /* Parse other CVT arguments, if any */
+    if( bSwarm ){
+      unionConfigureVtab(&rc, pTab, pStmt, argc-4, &argv[4], pzErr);
+    }
 
     /* Iterate through the rows returned by the SQL statement specified
     ** as an argument to the CREATE VIRTUAL TABLE statement. */
@@ -715,17 +967,15 @@ static int unionConnect(
         }else{
           pSrc->zDb = unionStrdup(&rc, zDb);
         }
+        if( pTab->bHasContext ){
+          const char *zContext = (const char*)sqlite3_column_text(pStmt, 4);
+          pSrc->zContext = unionStrdup(&rc, zContext);
+        }
       }
     }
     unionFinalize(&rc, pStmt, pzErr);
     pStmt = 0;
 
-    /* Capture the not-found callback UDF name */
-    if( rc==SQLITE_OK && argc>=5 ){
-      pTab->zNotFoundCallback = unionStrdup(&rc, argv[4]);
-      unionDequote(pTab->zNotFoundCallback);
-    }
-
     /* It is an error if the SELECT statement returned zero rows. If only
     ** because there is no way to determine the schema of the virtual 
     ** table in this case.  */
@@ -738,9 +988,6 @@ static int unionConnect(
     ** compatible schemas. For swarmvtab, attach the first database and
     ** check that the first table is a rowid table only.  */
     if( rc==SQLITE_OK ){
-      pTab->db = db;
-      pTab->bSwarm = bSwarm;
-      pTab->nMaxOpen = SWARMVTAB_MAX_OPEN;
       if( bSwarm ){
         rc = unionOpenDatabase(pTab, 0, pzErr);
       }else{
index 40cc2db6527d20b09d9eb9557cf0f2b81f3fa678..657245305ab92d07bf07cd264379e927eb3e2e54 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C In\sthe\sLEMON\sparser\sgenerator,\sprovide\sreduce\sactions\swith\saccess\sto\sthe\nlookahead\stoken.
-D 2017-12-15T12:22:21.137
+C Enhance\sthe\s"swarmvtab"\sextension.\sSee\sheader\scomments\sin\sext/misc/unionvtab.c\nfor\sdetails.
+D 2017-12-15T20:21:17.831
 F Makefile.in 6a879cbf01e37f9eac131414955f71774b566502d9a57ded1b8585b507503cb8
 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
 F Makefile.msc a2492b29176edc3c754aa7a2f7daa20cd3fa20a56e3ee64e376092836177c42a
@@ -287,7 +287,7 @@ F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
 F ext/misc/spellfix.c 41cf26c6b89fcaa8798ae10ae64d39c1f1d9d6995152e545bd491c13058b8fac
 F ext/misc/stmt.c 6f16443abb3551e3f5813bb13ba19a30e7032830015b0f92fe0c0453045c0a11
 F ext/misc/totype.c 4a167594e791abeed95e0a8db028822b5e8fe512
-F ext/misc/unionvtab.c 1e0ebc5078e1a916db191bcd88f87e94ea7ba4aa563ee30ff706261cb4b39461
+F ext/misc/unionvtab.c de36c2c45583d68f99e45b392311967066b02e2651d05697da783698b245b387
 F ext/misc/vfslog.c fe40fab5c077a40477f7e5eba994309ecac6cc95
 F ext/misc/vfsstat.c bf10ef0bc51e1ad6756629e1edb142f7a8db1178
 F ext/misc/vtablog.c 31d0d8f4406795679dcd3a67917c213d3a2a5fb3ea5de35f6e773491ed7e13c9
@@ -1260,8 +1260,9 @@ F test/subselect.test 0966aa8e720224dbd6a5e769a3ec2a723e332303
 F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a
 F test/subtype1.test 7fe09496352f97053af1437150751be2d0a0cae8
 F test/superlock.test ec94f0556b6488d97f71c79f9061ae08d9ab8f12
-F test/swarmvtab.test c2279311b44de032f86a8295a9b06818d864856f9428b4c99eee91a0d419cf25
-F test/swarmvtab2.test 9a3a68a1e58d00f4ed6c68d12d52f2df971b9e22a80a41f6f8c1409abba8e5b4
+F test/swarmvtab.test 9a3fd5ab3e9b3c976ad1b3d7646aab725114f2ac26b59395d0778b33bab6cdaf
+F test/swarmvtab2.test c948cb2fdfc5b01d85e8f6d6504854202dc1a0782ab2a0ed61538f27cbd0aa5c
+F test/swarmvtab3.test c4c8d09e56ae99b90187ac225458f13f373873ea296fc442c7ad7511f25e7314
 F test/swarmvtabfault.test 00aec54665909490f5c383f3cae3b5d18bd97c12490b429ff8752a3027acfa42
 F test/symlink.test c9ebe7330d228249e447038276bfc8a7b22f4849
 F test/sync.test 2f84bdbc2b2df1fcb0220575b4b9f8cea94b7529
@@ -1680,7 +1681,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 3765aaf712998af5ffb6bc680a0c1419f2b5deb47ecbc1835ba5879127c4dbe3
-R 545f6d4adf455d1fa5987c428909cf33
-U drh
-Z 7981b7f16ad8b88f8d8fe8aa12f607a9
+P 42af190f4f86ad60de02800054010fafd484ac86ca41e2a13799b2e583eea98c
+R 23f432208fb9ea9e9693297333855e09
+U dan
+Z ee61a0a249e7c85137482dc433509d6a
index 999df6a24e9b5c2b0291156497588f9c18da42a5..72e6a84022283f5f1d9c398459df676ac648816c 100644 (file)
@@ -1 +1 @@
-42af190f4f86ad60de02800054010fafd484ac86ca41e2a13799b2e583eea98c
\ No newline at end of file
+01c173651ab22b7b0c139eded6f2ad8504efd09088df8ae6a3471230ebf2306f
\ No newline at end of file
index 4cdcf29ca8b247883c28784059b98d637f9b40d3..9d2919bee1f2d22afc700e7a9a6151ca0f9e8d13 100644 (file)
@@ -213,7 +213,7 @@ do_catchsql_test 3.1 {
         ("test.db2", "t1", 11, 20)
     ', 'fetch_db_no_such_function'
   );
-} {1 {no such function: fetch_db_no_such_function}}
+} {1 {sql error: no such function: fetch_db_no_such_function}}
 
 do_catchsql_test 3.2 {
   CREATE VIRTUAL TABLE temp.xyz USING swarmvtab(
index cf1bdd0af9a08dbcba84ddc45402623d1db27a8c..1cc7fbb378445e0573aaf4b5892f9425c02644af 100644 (file)
@@ -14,7 +14,7 @@
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
-set testprefix swarmvtab
+set testprefix swarmvtab2
 do_not_use_codec
 
 ifcapable !vtab {
diff --git a/test/swarmvtab3.test b/test/swarmvtab3.test
new file mode 100644 (file)
index 0000000..70d6c7d
--- /dev/null
@@ -0,0 +1,233 @@
+# 2017-07-15
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this file is the "swarmvtab" extension
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix swarmvtab3
+do_not_use_codec
+
+ifcapable !vtab {
+  finish_test
+  return
+}
+
+load_static_extension db unionvtab
+
+set nFile $sqlite_open_file_count
+
+do_execsql_test 1.0 {
+  CREATE TEMP TABLE swarm(id, tbl, minval, maxval);
+}
+
+# Set up 100 databases with filenames "remote_test.dbN", where N is between
+# 0 and 99.
+do_test 1.1 {
+  for {set i 0} {$i < 100} {incr i} {
+    set file remote_test.db$i
+    forcedelete $file
+    forcedelete test.db$i
+    sqlite3 rrr $file
+    rrr eval {
+      CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+      INSERT INTO t1 VALUES($i, $i);
+    }
+    rrr close
+    db eval {
+      INSERT INTO swarm VALUES($i, 't1', $i, $i);
+    }
+    set ::dbcache(test.db$i) 0
+  }
+} {}
+
+proc missing_db {filename} {
+  set remote "remote_$filename"
+  forcedelete $filename
+  file copy $remote $filename
+}
+db func missing_db missing_db
+
+proc openclose_db {filename bClose} {
+  if {$bClose} {
+    incr ::dbcache($filename) -1
+  } else {
+    incr ::dbcache($filename) 1
+  }
+  if {$::dbcache($filename)==0} {
+    forcedelete $filename
+  }
+}
+db func openclose_db openclose_db
+
+proc check_dbcache {} {
+  set n 0
+  for {set i 0} {$i<100} {incr i} {
+    set exists [file exists test.db$i]
+    if {$exists!=($::dbcache(test.db$i)!=0)} {
+      error "inconsistent ::dbcache and disk"
+    }
+    incr n $exists
+  }
+  return $n
+}
+
+foreach {tn nMaxOpen cvt} {
+  1 5 {
+    CREATE VIRTUAL TABLE temp.s USING swarmvtab(
+        'SELECT :prefix || id, tbl, minval, minval FROM swarm',
+        :prefix='test.db',
+        missing=missing_db,
+        openclose=openclose_db,
+        maxopen=5
+    )
+  }
+
+  2 3 {
+    CREATE VIRTUAL TABLE temp.s USING swarmvtab(
+        'SELECT :prefix || id, tbl, minval, minval FROM swarm',
+        :prefix='test.db',
+        missing =       'missing_db',
+        openclose=[openclose_db],
+        maxopen = 3
+    )
+  }
+
+  3 1 {
+    CREATE VIRTUAL TABLE temp.s USING swarmvtab(
+        'SELECT :prefix||''.''||:suffix||id, tbl, minval, minval FROM swarm',
+        :prefix=test, :suffix=db,
+        missing =       'missing_db',
+        openclose=[openclose_db],
+        maxopen = 1
+    )
+  }
+
+} {
+  execsql { DROP TABLE IF EXISTS s }
+  
+  do_execsql_test 1.$tn.1 $cvt
+
+  do_execsql_test 1.$tn.2 {
+    SELECT b FROM s WHERE a<10;
+  } {0 1 2 3 4 5 6 7 8 9}
+
+  do_test 1.$tn.3 { check_dbcache } $nMaxOpen
+
+  do_execsql_test 1.$tn.4 {
+    SELECT b FROM s WHERE (b%10)=0;
+  } {0 10 20 30 40 50 60 70 80 90}
+
+  do_test 1.$tn.5 { check_dbcache } $nMaxOpen
+}
+
+execsql { DROP TABLE IF EXISTS s }
+for {set i 0} {$i < 100} {incr i} {
+  forcedelete remote_test.db$i
+}
+
+#----------------------------------------------------------------------------
+#
+do_execsql_test 2.0 {
+  DROP TABLE IF EXISTS swarm;
+  CREATE TEMP TABLE swarm(file, tbl, minval, maxval, ctx);
+}
+
+catch { array unset ::dbcache }
+
+# Set up 100 databases with filenames "remote_test.dbN", where N is a
+# random integer between 0 and 1,000,000
+# 0 and 99.
+do_test 2.1 {
+  for {set i 0} {$i < 100} {incr i} {
+    while 1 {
+      set ctx [expr abs(int(rand() *1000000))]
+      if {[info exists ::dbcache($ctx)]==0} break
+    }
+
+    set file test_remote.db$ctx
+    forcedelete $file
+    forcedelete test.db$i
+    sqlite3 rrr $file
+    rrr eval {
+      CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+      INSERT INTO t1 VALUES($i, $i);
+    }
+    rrr close
+    db eval {
+      INSERT INTO swarm VALUES('test.db' || $i, 't1', $i, $i, $file)
+    }
+    set ::dbcache(test.db$i) 0
+  }
+} {}
+
+proc missing_db {filename ctx} {
+  file copy $ctx $filename
+}
+db func missing_db missing_db
+
+proc openclose_db {filename ctx bClose} {
+  if {$bClose} {
+    incr ::dbcache($filename) -1
+  } else {
+    incr ::dbcache($filename) 1
+  }
+  if {$::dbcache($filename)==0} {
+    forcedelete $filename
+  }
+}
+db func openclose_db openclose_db
+
+proc check_dbcache {} {
+  set n 0
+  foreach k [array names ::dbcache] {
+    set exists [file exists $k]
+    if {$exists!=($::dbcache($k)!=0)} {
+      error "inconsistent ::dbcache and disk ($k)"
+    }
+    incr n $exists
+  }
+  return $n
+}
+
+foreach {tn nMaxOpen cvt} {
+  2 5 {
+    CREATE VIRTUAL TABLE temp.s USING swarmvtab(
+        'SELECT file, tbl, minval, minval, ctx FROM swarm',
+        missing=missing_db,
+        openclose=openclose_db,
+        maxopen=5
+    )
+  }
+} {
+  execsql { DROP TABLE IF EXISTS s }
+  
+  do_execsql_test 1.$tn.1 $cvt
+
+  do_execsql_test 1.$tn.2 {
+    SELECT b FROM s WHERE a<10;
+  } {0 1 2 3 4 5 6 7 8 9}
+
+  do_test 1.$tn.3 { check_dbcache } $nMaxOpen
+
+  do_execsql_test 1.$tn.4 {
+    SELECT b FROM s WHERE (b%10)=0;
+  } {0 10 20 30 40 50 60 70 80 90}
+
+  do_test 1.$tn.5 { check_dbcache } $nMaxOpen
+}
+
+db close
+forcedelete {*}[glob test_remote.db*]
+
+finish_test
+