]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Enhance the code in unionvtab.c to also provide the "swarmvtab" virtual table
authordan <dan@noemail.net>
Wed, 2 Aug 2017 19:59:56 +0000 (19:59 +0000)
committerdan <dan@noemail.net>
Wed, 2 Aug 2017 19:59:56 +0000 (19:59 +0000)
module. There are still several problems on this branch.

FossilOrigin-Name: 03d94388d62fd0f1fae377d273bbd5561208adc34bd97f7ce27783b30a369fd7

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

index f75fc69f0beffb13f880e4fac0914b2124e4c250..208c576eabfde44c1b26e81ceef4f2027d54eb50 100644 (file)
@@ -65,6 +65,8 @@ SQLITE_EXTENSION_INIT1
 # define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64)
 #endif
 
+#define SWARMVTAB_MAX_ATTACHED 9
+
 typedef struct UnionCsr UnionCsr;
 typedef struct UnionTab UnionTab;
 typedef struct UnionSrc UnionSrc;
@@ -79,6 +81,11 @@ struct UnionSrc {
   char *zTab;                     /* Source table name */
   sqlite3_int64 iMin;             /* Minimum rowid */
   sqlite3_int64 iMax;             /* Maximum rowid */
+
+  /* Fields used by swarmvtab only */
+  char *zFile;                    /* File to ATTACH */
+  int bAttached;                  /* True if currently attached */
+  UnionSrc *pNextAttached;        /* Next in list of all attached sources */
 };
 
 /*
@@ -87,9 +94,17 @@ struct UnionSrc {
 struct UnionTab {
   sqlite3_vtab base;              /* Base class - must be first */
   sqlite3 *db;                    /* Database handle */
+  int bSwarm;                     /* 1 for "swarmvtab", 0 for "unionvtab" */
   int iPK;                        /* INTEGER PRIMARY KEY column, or -1 */
   int nSrc;                       /* Number of elements in the aSrc[] array */
+  sqlite3_stmt *pSourceStr;       /* Used by unionSourceToStr() */
   UnionSrc *aSrc;                 /* Array of source tables, sorted by rowid */
+
+  /* Used by swarmvtab only */
+  char *zSourceStr;               /* Expected unionSourceToStr() value */
+  UnionSrc *pAttached;            /* First in list of attached sources */
+  int nAttach;                    /* Current number of attached sources */
+  int nMaxAttach;                 /* Maximum number of attached sources */
 };
 
 /*
@@ -98,6 +113,10 @@ struct UnionTab {
 struct UnionCsr {
   sqlite3_vtab_cursor base;       /* Base class - must be first */
   sqlite3_stmt *pStmt;            /* SQL statement to run */
+
+  /* Used by swarmvtab only */
+  sqlite3_int64 iMaxRowid;        /* Last rowid to visit */
+  int iTab;                       /* Index of table read by pStmt */
 };
 
 /*
@@ -204,7 +223,7 @@ static sqlite3_stmt *unionPrepare(
   sqlite3_stmt *pRet = 0;
   if( *pRc==SQLITE_OK ){
     int rc = sqlite3_prepare_v2(db, zSql, -1, &pRet, 0);
-    if( rc!=SQLITE_OK ){
+    if( rc!=SQLITE_OK && pzErr ){
       *pzErr = sqlite3_mprintf("sql error: %s", sqlite3_errmsg(db));
       *pRc = rc;
     }
@@ -270,6 +289,31 @@ static void unionFinalize(int *pRc, sqlite3_stmt *pStmt){
   if( *pRc==SQLITE_OK ) *pRc = rc;
 }
 
+static int unionDetachDatabase(UnionTab *pTab, char **pzErr){
+  sqlite3_stmt *pStmt = 0;
+  int rc = SQLITE_OK;
+  UnionSrc **pp;
+  assert( pTab->pAttached );
+
+  pp = &pTab->pAttached;
+  while( (*pp)->pNextAttached ){
+    pp = &(*pp)->pNextAttached;
+  }
+
+  pStmt = unionPreparePrintf(&rc, pzErr, pTab->db, "DETACH %s", (*pp)->zDb);
+  if( rc==SQLITE_OK ) sqlite3_step(pStmt);
+  unionFinalize(&rc, pStmt);
+  assert( rc==SQLITE_OK );
+  if( rc==SQLITE_OK ){
+    assert( (*pp)->bAttached && (*pp)->pNextAttached==0 );
+    (*pp)->bAttached = 0;
+    *pp = 0;
+    pTab->nAttach--;
+  }
+
+  return rc;
+}
+
 /*
 ** xDisconnect method.
 */
@@ -280,13 +324,51 @@ static int unionDisconnect(sqlite3_vtab *pVtab){
     for(i=0; i<pTab->nSrc; i++){
       sqlite3_free(pTab->aSrc[i].zDb);
       sqlite3_free(pTab->aSrc[i].zTab);
+      sqlite3_free(pTab->aSrc[i].zFile);
     }
+    while( pTab->pAttached ){
+      if( unionDetachDatabase(pTab, 0)!=SQLITE_OK ) break;
+    }
+    sqlite3_finalize(pTab->pSourceStr);
+    sqlite3_free(pTab->zSourceStr);
     sqlite3_free(pTab->aSrc);
     sqlite3_free(pTab);
   }
   return SQLITE_OK;
 }
 
+/*
+** Check that the table identified by pSrc is a rowid table. If not,
+** return SQLITE_ERROR and set (*pzErr) to point to an English language
+** error message. If the table is a rowid table and no error occurs,
+** return SQLITE_OK and leave (*pzErr) unmodified.
+*/
+static int unionIsIntkeyTable(
+  sqlite3 *db,                    /* Database handle */
+  UnionSrc *pSrc,                 /* Source table to test */
+  char **pzErr                    /* OUT: Error message */
+){
+  int bPk = 0;
+  const char *zType = 0;
+  int rc;
+
+  sqlite3_table_column_metadata(
+      db, pSrc->zDb, pSrc->zTab, "_rowid_", &zType, 0, 0, &bPk, 0
+  );
+  rc = sqlite3_errcode(db);
+  if( rc==SQLITE_ERROR 
+   || (rc==SQLITE_OK && (!bPk || sqlite3_stricmp("integer", zType)))
+  ){
+    rc = SQLITE_ERROR;
+    *pzErr = sqlite3_mprintf("no such rowid table: %s%s%s",
+        (pSrc->zDb ? pSrc->zDb : ""),
+        (pSrc->zDb ? "." : ""),
+        pSrc->zTab
+    );
+  }
+  return rc;
+}
+
 /*
 ** This function is a no-op if *pRc is other than SQLITE_OK when it is
 ** called. In this case it returns NULL.
@@ -306,41 +388,28 @@ static int unionDisconnect(sqlite3_vtab *pVtab){
 */
 static char *unionSourceToStr(
   int *pRc,                       /* IN/OUT: Error code */
-  sqlite3 *db,                    /* Database handle */
+  UnionTab *pTab,                 /* Virtual table object */
   UnionSrc *pSrc,                 /* Source table to test */
-  sqlite3_stmt *pStmt,
   char **pzErr                    /* OUT: Error message */
 ){
   char *zRet = 0;
   if( *pRc==SQLITE_OK ){
-    int bPk = 0;
-    const char *zType = 0;
-    int rc;
-
-    sqlite3_table_column_metadata(
-        db, pSrc->zDb, pSrc->zTab, "_rowid_", &zType, 0, 0, &bPk, 0
-    );
-    rc = sqlite3_errcode(db);
-    if( rc==SQLITE_ERROR 
-     || (rc==SQLITE_OK && (!bPk || sqlite3_stricmp("integer", zType)))
-    ){
-      rc = SQLITE_ERROR;
-      *pzErr = sqlite3_mprintf("no such rowid table: %s%s%s",
-          (pSrc->zDb ? pSrc->zDb : ""),
-          (pSrc->zDb ? "." : ""),
-          pSrc->zTab
+    int rc = unionIsIntkeyTable(pTab->db, pSrc, pzErr);
+    if( rc==SQLITE_OK && pTab->pSourceStr==0 ){
+      pTab->pSourceStr = unionPrepare(&rc, pTab->db, 
+        "SELECT group_concat(quote(name) || '.' || quote(type)) "
+        "FROM pragma_table_info(?, ?)", pzErr
       );
     }
-
     if( rc==SQLITE_OK ){
-      sqlite3_bind_text(pStmt, 1, pSrc->zTab, -1, SQLITE_STATIC);
-      sqlite3_bind_text(pStmt, 2, pSrc->zDb, -1, SQLITE_STATIC);
-      if( SQLITE_ROW==sqlite3_step(pStmt) ){
-        zRet = unionStrdup(&rc, (const char*)sqlite3_column_text(pStmt, 0));
+      sqlite3_bind_text(pTab->pSourceStr, 1, pSrc->zTab, -1, SQLITE_STATIC);
+      sqlite3_bind_text(pTab->pSourceStr, 2, pSrc->zDb, -1, SQLITE_STATIC);
+      if( SQLITE_ROW==sqlite3_step(pTab->pSourceStr) ){
+        const char *z = (const char*)sqlite3_column_text(pTab->pSourceStr, 0);
+        zRet = unionStrdup(&rc, z);
       }
-      unionReset(&rc, pStmt, pzErr);
+      unionReset(&rc, pTab->pSourceStr, pzErr);
     }
-
     *pRc = rc;
   }
 
@@ -356,25 +425,19 @@ static char *unionSourceToStr(
 ** other error occurs, SQLITE_OK is returned.
 */
 static int unionSourceCheck(UnionTab *pTab, char **pzErr){
-  const char *zSql = 
-      "SELECT group_concat(quote(name) || '.' || quote(type)) "
-      "FROM pragma_table_info(?, ?)";
   int rc = SQLITE_OK;
 
+  assert( *pzErr==0 );
   if( pTab->nSrc==0 ){
     *pzErr = sqlite3_mprintf("no source tables configured");
     rc = SQLITE_ERROR;
   }else{
-    sqlite3_stmt *pStmt = 0;
     char *z0 = 0;
     int i;
 
-    pStmt = unionPrepare(&rc, pTab->db, zSql, pzErr);
-    if( rc==SQLITE_OK ){
-      z0 = unionSourceToStr(&rc, pTab->db, &pTab->aSrc[0], pStmt, pzErr);
-    }
+    z0 = unionSourceToStr(&rc, pTab, &pTab->aSrc[0], pzErr);
     for(i=1; i<pTab->nSrc; i++){
-      char *z = unionSourceToStr(&rc, pTab->db, &pTab->aSrc[i], pStmt, pzErr);
+      char *z = unionSourceToStr(&rc, pTab, &pTab->aSrc[i], pzErr);
       if( rc==SQLITE_OK && sqlite3_stricmp(z, z0) ){
         *pzErr = sqlite3_mprintf("source table schema mismatch");
         rc = SQLITE_ERROR;
@@ -382,12 +445,58 @@ static int unionSourceCheck(UnionTab *pTab, char **pzErr){
       sqlite3_free(z);
     }
 
-    unionFinalize(&rc, pStmt);
+    unionFinalize(&rc, pTab->pSourceStr);
+    pTab->pSourceStr = 0;
     sqlite3_free(z0);
   }
   return rc;
 }
 
+static int unionAttachDatabase(UnionTab *pTab, int iSrc, char **pzErr){
+  int rc = SQLITE_OK;
+  UnionSrc *pSrc = &pTab->aSrc[iSrc];
+
+  assert( pTab->bSwarm && iSrc<pTab->nSrc );
+  if( pSrc->bAttached==0 ){
+    sqlite3_stmt *pStmt;
+
+    if( pTab->nAttach>=pTab->nMaxAttach ){
+      rc = unionDetachDatabase(pTab, pzErr);
+    }
+
+    pStmt = unionPreparePrintf(
+        &rc, pzErr, pTab->db, "ATTACH %Q AS %s", pSrc->zFile, pSrc->zDb
+    );
+    if( rc==SQLITE_OK ){
+      sqlite3_step(pStmt);
+      rc = sqlite3_finalize(pStmt);
+    }
+    if( rc!=SQLITE_OK ){
+      *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db));
+    }else{
+      char *z = unionSourceToStr(&rc, pTab, pSrc, pzErr);
+      if( rc==SQLITE_OK ){
+        if( pTab->zSourceStr==0 ){
+          pTab->zSourceStr = z;
+        }else{
+          if( sqlite3_stricmp(z, pTab->zSourceStr) ){
+            *pzErr = sqlite3_mprintf("source table schema mismatch");
+            rc = SQLITE_ERROR;
+          }
+          sqlite3_free(z);
+        }
+      }
+    }
+
+    pSrc->pNextAttached = pTab->pAttached;
+    pSrc->bAttached = 1;
+    pTab->pAttached = pSrc;
+    pTab->nAttach++;
+  }
+
+  return rc;
+}
+
 /* 
 ** xConnect/xCreate method.
 **
@@ -407,14 +516,16 @@ static int unionConnect(
 ){
   UnionTab *pTab = 0;
   int rc = SQLITE_OK;
+  int bSwarm = (pAux==0 ? 0 : 1);
+  const char *zVtab = (bSwarm ? "swarmvtab" : "unionvtab");
+  const char *zName = argv[2];
 
-  (void)pAux;   /* Suppress harmless 'unused parameter' warning */
   if( sqlite3_stricmp("temp", argv[1]) ){
     /* unionvtab tables may only be created in the temp schema */
-    *pzErr = sqlite3_mprintf("unionvtab tables must be created in TEMP schema");
+    *pzErr = sqlite3_mprintf("%s tables must be created in TEMP schema", zVtab);
     rc = SQLITE_ERROR;
   }else if( argc!=4 ){
-    *pzErr = sqlite3_mprintf("wrong number of arguments for unionvtab");
+    *pzErr = sqlite3_mprintf("wrong number of arguments for %s", zVtab);
     rc = SQLITE_ERROR;
   }else{
     int nAlloc = 0;               /* Allocated size of pTab->aSrc[] */
@@ -464,19 +575,44 @@ static int unionConnect(
         rc = SQLITE_ERROR;
       }
 
-      pSrc = &pTab->aSrc[pTab->nSrc++];
-      pSrc->zDb = unionStrdup(&rc, zDb);
-      pSrc->zTab = unionStrdup(&rc, zTab);
-      pSrc->iMin = iMin;
-      pSrc->iMax = iMax;
+      if( rc==SQLITE_OK ){
+        pSrc = &pTab->aSrc[pTab->nSrc++];
+        pSrc->zTab = unionStrdup(&rc, zTab);
+        pSrc->iMin = iMin;
+        pSrc->iMax = iMax;
+        if( bSwarm ){
+          pSrc->zFile = unionStrdup(&rc, zDb);
+          pSrc->zDb = sqlite3_mprintf("swm_%s_%d", zName, pTab->nSrc);
+          if( pSrc->zDb==0 ) rc = SQLITE_NOMEM;
+        }else{
+          pSrc->zDb = unionStrdup(&rc, zDb);
+          pSrc->bAttached = 1;
+        }
+      }
     }
     unionFinalize(&rc, pStmt);
     pStmt = 0;
 
-    /* Verify that all source tables exist and have compatible schemas. */
+    /* 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.  */
+    if( rc==SQLITE_OK && pTab->nSrc==0 ){
+      *pzErr = sqlite3_mprintf("no source tables configured");
+      rc = SQLITE_ERROR;
+    }
+
+    /* For unionvtab, verify that all source tables exist and have 
+    ** 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;
-      rc = unionSourceCheck(pTab, pzErr);
+      pTab->bSwarm = bSwarm;
+      pTab->nMaxAttach = SWARMVTAB_MAX_ATTACHED;
+      if( bSwarm ){
+        rc = unionAttachDatabase(pTab, 0, pzErr);
+      }else{
+        rc = unionSourceCheck(pTab, pzErr);
+      }
     }
 
     /* Compose a CREATE TABLE statement and pass it to declare_vtab() */
@@ -535,16 +671,40 @@ static int unionClose(sqlite3_vtab_cursor *cur){
 /*
 ** xNext
 */
-static int unionNext(sqlite3_vtab_cursor *cur){
-  UnionCsr *pCsr = (UnionCsr*)cur;
-  int rc;
+static int doUnionNext(UnionCsr *pCsr){
+  int rc = SQLITE_OK;
   assert( pCsr->pStmt );
   if( sqlite3_step(pCsr->pStmt)!=SQLITE_ROW ){
+    UnionTab *pTab = (UnionTab*)pCsr->base.pVtab;
     rc = sqlite3_finalize(pCsr->pStmt);
     pCsr->pStmt = 0;
-  }else{
-    rc = SQLITE_OK;
+    if( rc==SQLITE_OK && pTab->bSwarm ){
+      pCsr->iTab++;
+      if( pCsr->iTab<pTab->nSrc ){
+        UnionSrc *pSrc = &pTab->aSrc[pCsr->iTab];
+        if( pCsr->iMaxRowid>=pSrc->iMin ){
+          /* It is necessary to scan the next table. */
+          rc = unionAttachDatabase(pTab, pCsr->iTab, &pTab->base.zErrMsg);
+          pCsr->pStmt = unionPreparePrintf(&rc, &pTab->base.zErrMsg, pTab->db,
+              "SELECT rowid, * FROM %Q.%Q %s %lld",
+              pSrc->zDb, pSrc->zTab,
+              (pSrc->iMax>pCsr->iMaxRowid ? "WHERE _rowid_ <=" : "-- "),
+              pCsr->iMaxRowid
+          );
+          if( rc==SQLITE_OK ) rc = SQLITE_ROW;
+        }
+      }
+    }
   }
+
+  return rc;
+}
+
+static int unionNext(sqlite3_vtab_cursor *cur){
+  int rc;
+  do {
+    rc = doUnionNext((UnionCsr*)cur);
+  }while( rc==SQLITE_ROW );
   return rc;
 }
 
@@ -674,6 +834,13 @@ static int unionFilter(
         zSql = sqlite3_mprintf("%z %s rowid<=%lld", zSql, zWhere, iMax);
       }
     }
+
+    if( pTab->bSwarm ){
+      pCsr->iTab = i;
+      pCsr->iMaxRowid = iMax;
+      rc = unionAttachDatabase(pTab, i, &pTab->base.zErrMsg);
+      break;
+    }
   }
 
 
@@ -791,8 +958,13 @@ static int createUnionVtab(sqlite3 *db){
     0,                            /* xRelease */
     0                             /* xRollbackTo */
   };
+  int rc;
 
-  return sqlite3_create_module(db, "unionvtab", &unionModule, 0);
+  rc = sqlite3_create_module(db, "unionvtab", &unionModule, 0);
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_create_module(db, "swarmvtab", &unionModule, (void*)db);
+  }
+  return rc;
 }
 
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
index f5cc3788d3bcd7eb7e647e2a7068b47d88545d58..e4cae44b96238a712abf26ca4cbb90b9866aa366 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Avoid\sredundant\scalls\sto\ssqlite3ApiExit()\sin\ssqlite3_step().
-D 2017-08-02T19:04:37.188
+C Enhance\sthe\scode\sin\sunionvtab.c\sto\salso\sprovide\sthe\s"swarmvtab"\svirtual\stable\nmodule.\sThere\sare\sstill\sseveral\sproblems\son\sthis\sbranch.
+D 2017-08-02T19:59:56.307
 F Makefile.in d9873c9925917cca9990ee24be17eb9613a668012c85a343aef7e5536ae266e8
 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
 F Makefile.msc 02b469e9dcd5b7ee63fc1fb05babc174260ee4cfa4e0ef2e48c3c6801567a016
@@ -281,7 +281,7 @@ F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
 F ext/misc/spellfix.c a4723b6aff748a417b5091b68a46443265c40f0d
 F ext/misc/stmt.c 6f16443abb3551e3f5813bb13ba19a30e7032830015b0f92fe0c0453045c0a11
 F ext/misc/totype.c 4a167594e791abeed95e0a8db028822b5e8fe512
-F ext/misc/unionvtab.c 56fd163d2b6d2f4df0078be482fc9a874658ce51cce33f180c08834193449c78
+F ext/misc/unionvtab.c f9acc9c3e78e48171bd72735f08948585104b811546cee02d012cbd481432665
 F ext/misc/vfslog.c fe40fab5c077a40477f7e5eba994309ecac6cc95
 F ext/misc/vfsstat.c bf10ef0bc51e1ad6756629e1edb142f7a8db1178
 F ext/misc/vtshim.c 1976e6dd68dd0d64508c91a6dfab8e75f8aaf6cd
@@ -1231,6 +1231,7 @@ F test/subselect.test 0966aa8e720224dbd6a5e769a3ec2a723e332303
 F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a
 F test/subtype1.test 7fe09496352f97053af1437150751be2d0a0cae8
 F test/superlock.test ec94f0556b6488d97f71c79f9061ae08d9ab8f12
+F test/swarmvtab.test 49dc94deb7363a375a980e9d52815533138e3046a4a21d5eb1c312552d408033
 F test/symlink.test c9ebe7330d228249e447038276bfc8a7b22f4849
 F test/sync.test 2f84bdbc2b2df1fcb0220575b4b9f8cea94b7529
 F test/sync2.test 6be8ed007fa063b147773c1982b5bdba97a32badc536bdc6077eff5cf8710ece
@@ -1640,7 +1641,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P bcc6dacb9114df709ef1bde24264c2193d9e39fc7fab024d5ebfc6056033274c
-R c2ce69750ff40b8566324e65e78319fb
-U drh
-Z 4c16d972871bc4f6fb8b1229cdad47da
+P 527974d4caba8bce7c89a28ea04a573b14c558657c14d9ad3c64bf1e0884caf8
+R 31849be59b061a67f41a14392566e88f
+T *branch * union-vtab
+T *sym-union-vtab *
+T -sym-trunk *
+U dan
+Z 1bdb6d432069947ff558a9e41aa801b1
index 83e2d03bc1cef734be44df7c33e3e4018498d349..f82310988f08cb9b6757f97a44cf5cdda92e681f 100644 (file)
@@ -1 +1 @@
-527974d4caba8bce7c89a28ea04a573b14c558657c14d9ad3c64bf1e0884caf8
\ No newline at end of file
+03d94388d62fd0f1fae377d273bbd5561208adc34bd97f7ce27783b30a369fd7
\ No newline at end of file
diff --git a/test/swarmvtab.test b/test/swarmvtab.test
new file mode 100644 (file)
index 0000000..c9b89a1
--- /dev/null
@@ -0,0 +1,91 @@
+# 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 swarmvtab
+
+ifcapable !vtab {
+  finish_test
+  return
+}
+
+load_static_extension db unionvtab
+
+do_execsql_test 1.0 {
+  CREATE TABLE t0(a INTEGER PRIMARY KEY, b TEXT);
+  WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<400) 
+  INSERT INTO t0 SELECT i, hex(randomblob(50)) FROM s;
+
+  CREATE TABLE dir(f, t, imin, imax);
+}
+
+do_test 1.1 {
+  for {set i 0} {$i < 40} {incr i} {
+    set iMin [expr $i*10 + 1]
+    set iMax [expr $iMin+9]
+
+    forcedelete "test.db$i"
+    execsql [subst {
+      ATTACH 'test.db$i' AS aux;
+      CREATE TABLE aux.t$i (a INTEGER PRIMARY KEY, b TEXT);
+      INSERT INTO aux.t$i SELECT * FROM t0 WHERE a BETWEEN $iMin AND $iMax;
+      DETACH aux;
+      INSERT INTO dir VALUES('test.db$i', 't$i', $iMin, $iMax);
+    }]
+  }
+
+  execsql {
+    CREATE VIRTUAL TABLE temp.s1 USING swarmvtab('SELECT * FROM dir');
+    SELECT count(*) FROM pragma_database_list;
+  }
+} {3}
+
+do_execsql_test 1.2 { 
+  DROP TABLE s1; 
+  SELECT name FROM pragma_database_list;
+} {main temp}
+
+do_execsql_test 1.3 {
+  CREATE VIRTUAL TABLE temp.s1 USING swarmvtab('SELECT * FROM dir');
+  SELECT count(*) FROM s1 WHERE rowid<50;
+} {49}
+
+proc do_compare_test {tn where} {
+  set sql [subst {
+    SELECT (
+        SELECT group_concat(a || ',' || b, ',') FROM t0 WHERE $where
+    )==(
+        SELECT group_concat(a || ',' || b, ',') FROM s1 WHERE $where
+    )
+  }]
+
+  uplevel [list do_execsql_test $tn $sql 1]
+}
+
+do_compare_test 1.4 "rowid = 55"
+do_compare_test 1.5 "rowid BETWEEN 20 AND 100"
+do_compare_test 1.6 "rowid > 350"
+do_compare_test 1.7 "rowid >= 350"
+
+# The following both each an assert() in SQLite:
+#
+#do_compare_test 1.8 "rowid >= 200"
+#do_test 1.x { db close } {}
+#
+do_execsql_test 1.x { DROP TABLE s1 }
+
+
+finish_test
+