Index: ext/misc/unionvtab.c ================================================================== --- ext/misc/unionvtab.c +++ ext/misc/unionvtab.c @@ -34,11 +34,11 @@ ** ** UNIONVTAB ** ** A "unionvtab" virtual table is created as follows: ** -** CREATE VIRTUAL TABLE USING unionvtab(); +** CREATE VIRTUAL TABLE USING unionvtab(); ** ** The implementation evalutes whenever a unionvtab virtual ** table is created or opened. It should return one row for each source ** database table. The four columns required of each row are: ** @@ -56,16 +56,19 @@ ** ** SWARMVTAB ** ** A "swarmvtab" virtual table is created similarly to a unionvtab table: ** -** CREATE VIRTUAL TABLE USING swarmvtab(); +** CREATE VIRTUAL TABLE +** USING swarmvtab(, ); ** ** The difference is that for a swarmvtab table, the first column returned ** by the must return a path or URI that can be used to open -** the database file containing the source table. -** +** the database file containing the source table. The 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. */ #include "sqlite3ext.h" SQLITE_EXTENSION_INIT1 #include @@ -141,10 +144,11 @@ int nSrc; /* Number of elements in the aSrc[] array */ UnionSrc *aSrc; /* Array of source tables, sorted by rowid */ /* Used by swarmvtab only */ char *zSourceStr; /* Expected unionSourceToStr() value */ + char *zNotFoundCallback; /* UDF to invoke if file not found on open */ UnionSrc *pClosable; /* First in list of closable sources */ int nOpen; /* Current number of open sources */ int nMaxOpen; /* Maximum number of open sources */ }; @@ -377,10 +381,11 @@ sqlite3_free(pSrc->zTab); sqlite3_free(pSrc->zFile); sqlite3_close(pSrc->db); } sqlite3_free(pTab->zSourceStr); + sqlite3_free(pTab->zNotFoundCallback); sqlite3_free(pTab->aSrc); sqlite3_free(pTab); } return SQLITE_OK; } @@ -489,10 +494,39 @@ sqlite3_free(z0); 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; + 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( zSql==0 ){ + *pzErr = sqlite3_mprintf("out of memory"); + return SQLITE_NOMEM; + } + 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 ){ + *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(pSrc->db)); + } + return rc; +} + /* ** This function may only be called for swarmvtab tables. The results of ** calling it on a unionvtab table are undefined. ** ** For a swarmvtab table, this function ensures that source database iSrc @@ -511,14 +545,12 @@ UnionSrc *pSrc = &pTab->aSrc[iSrc]; assert( pTab->bSwarm && iSrcnSrc ); if( pSrc->db==0 ){ unionCloseSources(pTab, pTab->nMaxOpen-1); - rc = sqlite3_open_v2(pSrc->zFile, &pSrc->db, SQLITE_OPEN_READONLY, 0); - if( rc!=SQLITE_OK ){ - *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(pSrc->db)); - }else{ + rc = unionOpenDatabaseInner(pTab, pSrc, pzErr); + if( rc==SQLITE_OK ){ char *z = unionSourceToStr(&rc, pTab, pSrc, pzErr); if( rc==SQLITE_OK ){ if( pTab->zSourceStr==0 ){ pTab->zSourceStr = z; }else{ @@ -596,14 +628,15 @@ /* ** xConnect/xCreate method. ** ** The argv[] array contains the following: ** -** argv[0] -> module name ("unionvtab") +** argv[0] -> module name ("unionvtab" or "swarmvtab") ** argv[1] -> database name ** argv[2] -> table name ** argv[3] -> SQL statement +** argv[4] -> not-found callback UDF name */ static int unionConnect( sqlite3 *db, void *pAux, int argc, const char *const*argv, @@ -617,11 +650,11 @@ if( sqlite3_stricmp("temp", argv[1]) ){ /* 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 ){ + }else if( argc!=4 && argc!=5 ){ *pzErr = sqlite3_mprintf("wrong number of arguments for %s", zVtab); rc = SQLITE_ERROR; }else{ int nAlloc = 0; /* Allocated size of pTab->aSrc[] */ sqlite3_stmt *pStmt = 0; /* Argument statement */ @@ -682,10 +715,16 @@ } } } unionFinalize(&rc, pStmt, pzErr); pStmt = 0; + + /* Capture the not-found callback UDF name */ + if( 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. */ if( rc==SQLITE_OK && pTab->nSrc==0 ){ ADDED test/swarmvtab2.test Index: test/swarmvtab2.test ================================================================== --- /dev/null +++ test/swarmvtab2.test @@ -0,0 +1,74 @@ +# 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 +} + + +db close +foreach name [glob -nocomplain test*.db] { + forcedelete $name +} +sqlite3 db test.db +load_static_extension db unionvtab +proc create_database {filename} { + sqlite3 dbx $filename + set num [regsub -all {[^0-9]+} $filename {}] + set num [string trimleft $num 0] + set start [expr {$num*1000}] + set end [expr {$start+999}] + dbx eval { + CREATE TABLE t2(a INTEGER PRIMARY KEY,b); + WITH RECURSIVE c(x) AS ( + VALUES($start) UNION ALL SELECT x+1 FROM c WHERE x<$end + ) + INSERT INTO t2(a,b) SELECT x, printf('**%05d**',x) FROM c; + } + dbx close +} +db func create_database create_database +do_execsql_test 100 { + CREATE TABLE t1(filename, tablename, istart, iend); + WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<99) + INSERT INTO t1 SELECT printf('test%03d.db',x),'t2',x*1000,x*1000+999 FROM c; + CREATE VIRTUAL TABLE temp.v1 USING swarmvtab( + 'SELECT * FROM t1', 'create_database' + ); +} {} +do_execsql_test 110 { + SELECT b FROM v1 WHERE a=3875; +} {**03875**} +do_test 120 { + lsort [glob -nocomplain test?*.db] +} {test001.db test003.db} +do_execsql_test 130 { + SELECT b FROM v1 WHERE a BETWEEN 3999 AND 4000 ORDER BY a; +} {**03999** **04000**} +do_test 140 { + lsort [glob -nocomplain test?*.db] +} {test001.db test003.db test004.db} +do_execsql_test 150 { + SELECT b FROM v1 WHERE a>=99998; +} {**99998** **99999**} +do_test 160 { + lsort -dictionary [glob -nocomplain test?*.db] +} {test001.db test003.db test004.db test099.db} + +finish_test