| /* |
| ** 2017 July 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 contains the implementation of the "unionvtab" and "swarmvtab" |
| ** virtual tables. These modules provide read-only access to multiple tables, |
| ** possibly in multiple database files, via a single database object. |
| ** The source tables must have the following characteristics: |
| ** |
| ** * They must all be rowid tables (not VIRTUAL or WITHOUT ROWID |
| ** tables or views). |
| ** |
| ** * Each table must have the same set of columns, declared in |
| ** the same order and with the same declared types. |
| ** |
| ** * The tables must not feature a user-defined column named "_rowid_". |
| ** |
| ** * Each table must contain a distinct range of rowid values. |
| ** |
| ** The difference between the two virtual table modules is that for |
| ** "unionvtab", all source tables must be located in the main database or |
| ** in databases ATTACHed to the main database by the user. For "swarmvtab", |
| ** the tables may be located in any database file on disk. The "swarmvtab" |
| ** implementation takes care of opening and closing database files |
| ** automatically. |
| ** |
| ** UNIONVTAB |
| ** |
| ** A "unionvtab" virtual table is created as follows: |
| ** |
| ** CREATE VIRTUAL TABLE <name> USING unionvtab(<sql-statement>); |
| ** |
| ** The implementation evalutes <sql statement> 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: |
| ** |
| ** 1. The name of the database containing the table ("main" or "temp" or |
| ** the name of an attached database). Or NULL to indicate that all |
| ** databases should be searched for the table in the usual fashion. |
| ** |
| ** 2. The name of the database table. |
| ** |
| ** 3. The smallest rowid in the range of rowids that may be stored in the |
| ** database table (an integer). |
| ** |
| ** 4. The largest rowid in the range of rowids that may be stored in the |
| ** database table (an integer). |
| ** |
| ** SWARMVTAB |
| ** |
| ** LEGACY SYNTAX: |
| ** |
| ** A "swarmvtab" virtual table is created similarly to a unionvtab table: |
| ** |
| ** CREATE VIRTUAL TABLE <name> |
| ** USING swarmvtab(<sql-statement>, <callback>); |
| ** |
| ** The difference is that for a swarmvtab table, the first column returned |
| ** by the <sql statement> must return a path or URI that can be used to open |
| ** 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 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 |
| |
| /* |
| ** Largest and smallest possible 64-bit signed integers. These macros |
| ** copied from sqliteInt.h. |
| */ |
| #ifndef LARGEST_INT64 |
| # define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32)) |
| #endif |
| #ifndef SMALLEST_INT64 |
| # define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64) |
| #endif |
| |
| /* |
| ** The following is also copied from sqliteInt.h. To facilitate coverage |
| ** testing. |
| */ |
| #ifndef ALWAYS |
| # if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) |
| # define ALWAYS(X) (1) |
| # define NEVER(X) (0) |
| # elif !defined(NDEBUG) |
| # define ALWAYS(X) ((X)?1:(assert(0),0)) |
| # define NEVER(X) ((X)?(assert(0),1):0) |
| # else |
| # define ALWAYS(X) (X) |
| # define NEVER(X) (X) |
| # endif |
| #endif |
| |
| /* |
| ** The swarmvtab module attempts to keep the number of open database files |
| ** at or below this limit. This may not be possible if there are too many |
| ** simultaneous queries. |
| */ |
| #define SWARMVTAB_MAX_OPEN 9 |
| |
| typedef struct UnionCsr UnionCsr; |
| typedef struct UnionTab UnionTab; |
| typedef struct UnionSrc UnionSrc; |
| |
| /* |
| ** Each source table (row returned by the initialization query) is |
| ** represented by an instance of the following structure stored in the |
| ** UnionTab.aSrc[] array. |
| */ |
| struct UnionSrc { |
| char *zDb; /* Database containing source table */ |
| char *zTab; /* Source table name */ |
| sqlite3_int64 iMin; /* Minimum rowid */ |
| sqlite3_int64 iMax; /* Maximum rowid */ |
| |
| /* 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 */ |
| }; |
| |
| /* |
| ** Virtual table type for union vtab. |
| */ |
| 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 */ |
| UnionSrc *aSrc; /* Array of source tables, sorted by rowid */ |
| |
| /* Used by swarmvtab only */ |
| int bHasContext; /* Has context strings */ |
| char *zSourceStr; /* Expected unionSourceToStr() value */ |
| 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 */ |
| }; |
| |
| /* |
| ** Virtual table cursor type for union vtab. |
| */ |
| 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 */ |
| }; |
| |
| /* |
| ** Given UnionTab table pTab and UnionSrc object pSrc, return the database |
| ** handle that should be used to access the table identified by pSrc. This |
| ** is the main db handle for "unionvtab" tables, or the source-specific |
| ** handle for "swarmvtab". |
| */ |
| #define unionGetDb(pTab, pSrc) ((pTab)->bSwarm ? (pSrc)->db : (pTab)->db) |
| |
| /* |
| ** If *pRc is other than SQLITE_OK when this function is called, it |
| ** always returns NULL. Otherwise, it attempts to allocate and return |
| ** a pointer to nByte bytes of zeroed memory. If the memory allocation |
| ** is attempted but fails, NULL is returned and *pRc is set to |
| ** SQLITE_NOMEM. |
| */ |
| static void *unionMalloc(int *pRc, sqlite3_int64 nByte){ |
| void *pRet; |
| assert( nByte>0 ); |
| if( *pRc==SQLITE_OK ){ |
| pRet = sqlite3_malloc64(nByte); |
| if( pRet ){ |
| memset(pRet, 0, (size_t)nByte); |
| }else{ |
| *pRc = SQLITE_NOMEM; |
| } |
| }else{ |
| pRet = 0; |
| } |
| return pRet; |
| } |
| |
| /* |
| ** If *pRc is other than SQLITE_OK when this function is called, it |
| ** always returns NULL. Otherwise, it attempts to allocate and return |
| ** a copy of the nul-terminated string passed as the second argument. |
| ** If the allocation is attempted but fails, NULL is returned and *pRc is |
| ** set to SQLITE_NOMEM. |
| */ |
| static char *unionStrdup(int *pRc, const char *zIn){ |
| char *zRet = 0; |
| if( zIn ){ |
| sqlite3_int64 nByte = strlen(zIn) + 1; |
| zRet = unionMalloc(pRc, nByte); |
| if( zRet ){ |
| memcpy(zRet, zIn, (size_t)nByte); |
| } |
| } |
| return zRet; |
| } |
| |
| /* |
| ** If the first character of the string passed as the only argument to this |
| ** function is one of the 4 that may be used as an open quote character |
| ** in SQL, this function assumes that the input is a well-formed quoted SQL |
| ** string. In this case the string is dequoted in place. |
| ** |
| ** If the first character of the input is not an open quote, then this |
| ** function is a no-op. |
| */ |
| static void unionDequote(char *z){ |
| if( z ){ |
| char q = z[0]; |
| |
| /* Set stack variable q to the close-quote character */ |
| if( q=='[' || q=='\'' || q=='"' || q=='`' ){ |
| int iIn = 1; |
| int iOut = 0; |
| if( q=='[' ) q = ']'; |
| while( ALWAYS(z[iIn]) ){ |
| if( z[iIn]==q ){ |
| if( z[iIn+1]!=q ){ |
| /* Character iIn was the close quote. */ |
| iIn++; |
| break; |
| }else{ |
| /* Character iIn and iIn+1 form an escaped quote character. Skip |
| ** the input cursor past both and copy a single quote character |
| ** to the output buffer. */ |
| iIn += 2; |
| z[iOut++] = q; |
| } |
| }else{ |
| z[iOut++] = z[iIn++]; |
| } |
| } |
| z[iOut] = '\0'; |
| } |
| } |
| } |
| |
| /* |
| ** This function is a no-op if *pRc is set to other than SQLITE_OK when it |
| ** is called. NULL is returned in this case. |
| ** |
| ** Otherwise, the SQL statement passed as the third argument is prepared |
| ** against the database handle passed as the second. If the statement is |
| ** successfully prepared, a pointer to the new statement handle is |
| ** returned. It is the responsibility of the caller to eventually free the |
| ** statement by calling sqlite3_finalize(). Alternatively, if statement |
| ** compilation fails, NULL is returned, *pRc is set to an SQLite error |
| ** code and *pzErr may be set to an error message buffer allocated by |
| ** sqlite3_malloc(). |
| */ |
| static sqlite3_stmt *unionPrepare( |
| int *pRc, /* IN/OUT: Error code */ |
| sqlite3 *db, /* Database handle */ |
| const char *zSql, /* SQL statement to prepare */ |
| char **pzErr /* OUT: Error message */ |
| ){ |
| sqlite3_stmt *pRet = 0; |
| assert( pzErr ); |
| if( *pRc==SQLITE_OK ){ |
| int rc = sqlite3_prepare_v2(db, zSql, -1, &pRet, 0); |
| if( rc!=SQLITE_OK ){ |
| *pzErr = sqlite3_mprintf("sql error: %s", sqlite3_errmsg(db)); |
| *pRc = rc; |
| } |
| } |
| return pRet; |
| } |
| |
| /* |
| ** Like unionPrepare(), except prepare the results of vprintf(zFmt, ...) |
| ** instead of a constant SQL string. |
| */ |
| static sqlite3_stmt *unionPreparePrintf( |
| int *pRc, /* IN/OUT: Error code */ |
| char **pzErr, /* OUT: Error message */ |
| sqlite3 *db, /* Database handle */ |
| const char *zFmt, /* printf() format string */ |
| ... /* Trailing printf args */ |
| ){ |
| sqlite3_stmt *pRet = 0; |
| char *zSql; |
| va_list ap; |
| va_start(ap, zFmt); |
| |
| zSql = sqlite3_vmprintf(zFmt, ap); |
| if( *pRc==SQLITE_OK ){ |
| if( zSql==0 ){ |
| *pRc = SQLITE_NOMEM; |
| }else{ |
| pRet = unionPrepare(pRc, db, zSql, pzErr); |
| } |
| } |
| sqlite3_free(zSql); |
| |
| va_end(ap); |
| return pRet; |
| } |
| |
| |
| /* |
| ** Call sqlite3_reset() on SQL statement pStmt. If *pRc is set to |
| ** SQLITE_OK when this function is called, then it is set to the |
| ** value returned by sqlite3_reset() before this function exits. |
| ** In this case, *pzErr may be set to point to an error message |
| ** buffer allocated by sqlite3_malloc(). |
| */ |
| #if 0 |
| static void unionReset(int *pRc, sqlite3_stmt *pStmt, char **pzErr){ |
| int rc = sqlite3_reset(pStmt); |
| if( *pRc==SQLITE_OK ){ |
| *pRc = rc; |
| if( rc ){ |
| *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(sqlite3_db_handle(pStmt))); |
| } |
| } |
| } |
| #endif |
| |
| /* |
| ** Call sqlite3_finalize() on SQL statement pStmt. If *pRc is set to |
| ** SQLITE_OK when this function is called, then it is set to the |
| ** value returned by sqlite3_finalize() before this function exits. |
| */ |
| static void unionFinalize(int *pRc, sqlite3_stmt *pStmt, char **pzErr){ |
| sqlite3 *db = sqlite3_db_handle(pStmt); |
| int rc = sqlite3_finalize(pStmt); |
| if( *pRc==SQLITE_OK ){ |
| *pRc = rc; |
| if( rc ){ |
| *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db)); |
| } |
| } |
| } |
| |
| /* |
| ** 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 |
| ** code is returned if an error occurs, or SQLITE_OK otherwise. |
| */ |
| 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); |
| p = *pp; |
| assert( p->db ); |
| sqlite3_close(p->db); |
| p->db = 0; |
| *pp = 0; |
| pTab->nOpen--; |
| unionInvokeOpenClose(pTab, p, 1, 0); |
| } |
| } |
| |
| /* |
| ** xDisconnect method. |
| */ |
| static int unionDisconnect(sqlite3_vtab *pVtab){ |
| if( pVtab ){ |
| UnionTab *pTab = (UnionTab*)pVtab; |
| int i; |
| for(i=0; i<pTab->nSrc; i++){ |
| UnionSrc *pSrc = &pTab->aSrc[i]; |
| int bHaveSrcDb = (pSrc->db!=0); |
| sqlite3_close(pSrc->db); |
| if( bHaveSrcDb ){ |
| unionInvokeOpenClose(pTab, pSrc, 1, 0); |
| } |
| sqlite3_free(pSrc->zDb); |
| sqlite3_free(pSrc->zTab); |
| sqlite3_free(pSrc->zFile); |
| sqlite3_free(pSrc->zContext); |
| } |
| sqlite3_finalize(pTab->pNotFound); |
| sqlite3_finalize(pTab->pOpenClose); |
| 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. |
| ** |
| ** Otherwise, this function checks that the source table passed as the |
| ** second argument (a) exists, (b) is not a view and (c) has a column |
| ** named "_rowid_" of type "integer" that is the primary key. |
| ** If this is not the case, *pRc is set to SQLITE_ERROR and NULL is |
| ** returned. |
| ** |
| ** Finally, if the source table passes the checks above, a nul-terminated |
| ** string describing the column names and types belonging to the source |
| ** table is returned. Tables with the same set of column names and types |
| ** cause this function to return identical strings. Is is the responsibility |
| ** of the caller to free the returned string using sqlite3_free() when |
| ** it is no longer required. |
| */ |
| static char *unionSourceToStr( |
| int *pRc, /* IN/OUT: Error code */ |
| UnionTab *pTab, /* Virtual table object */ |
| UnionSrc *pSrc, /* Source table to test */ |
| char **pzErr /* OUT: Error message */ |
| ){ |
| char *zRet = 0; |
| if( *pRc==SQLITE_OK ){ |
| sqlite3 *db = unionGetDb(pTab, pSrc); |
| int rc = unionIsIntkeyTable(db, pSrc, pzErr); |
| sqlite3_stmt *pStmt = unionPrepare(&rc, 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) ){ |
| const char *z = (const char*)sqlite3_column_text(pStmt, 0); |
| zRet = unionStrdup(&rc, z); |
| } |
| unionFinalize(&rc, pStmt, pzErr); |
| } |
| *pRc = rc; |
| } |
| |
| return zRet; |
| } |
| |
| /* |
| ** Check that all configured source tables exist and have the same column |
| ** names and datatypes. If this is not the case, or if some other error |
| ** occurs, return an SQLite error code. In this case *pzErr may be set |
| ** to point to an error message buffer allocated by sqlite3_mprintf(). |
| ** Or, if no problems regarding the source tables are detected and no |
| ** other error occurs, SQLITE_OK is returned. |
| */ |
| static int unionSourceCheck(UnionTab *pTab, char **pzErr){ |
| int rc = SQLITE_OK; |
| char *z0 = 0; |
| int i; |
| |
| assert( *pzErr==0 ); |
| z0 = unionSourceToStr(&rc, pTab, &pTab->aSrc[0], pzErr); |
| for(i=1; i<pTab->nSrc; i++){ |
| 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; |
| } |
| sqlite3_free(z); |
| } |
| 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){ |
| 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->pNotFound ){ |
| sqlite3_close(pSrc->db); |
| pSrc->db = 0; |
| 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_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 |
| ** is open. If the database is opened successfully and the schema is as |
| ** expected, or if it is already open when this function is called, SQLITE_OK |
| ** is returned. |
| ** |
| ** Alternatively If an error occurs while opening the databases, or if the |
| ** database schema is unsuitable, an SQLite error code is returned and (*pzErr) |
| ** may be set to point to an English language error message. In this case it is |
| ** the responsibility of the caller to eventually free the error message buffer |
| ** using sqlite3_free(). |
| */ |
| static int unionOpenDatabase(UnionTab *pTab, int iSrc, char **pzErr){ |
| int rc = SQLITE_OK; |
| UnionSrc *pSrc = &pTab->aSrc[iSrc]; |
| |
| assert( pTab->bSwarm && iSrc<pTab->nSrc ); |
| if( pSrc->db==0 ){ |
| unionCloseSources(pTab, pTab->nMaxOpen-1); |
| 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{ |
| if( sqlite3_stricmp(z, pTab->zSourceStr) ){ |
| *pzErr = sqlite3_mprintf("source table schema mismatch"); |
| rc = SQLITE_ERROR; |
| } |
| sqlite3_free(z); |
| } |
| } |
| } |
| |
| if( rc==SQLITE_OK ){ |
| pSrc->pNextClosable = pTab->pClosable; |
| pTab->pClosable = pSrc; |
| pTab->nOpen++; |
| }else{ |
| sqlite3_close(pSrc->db); |
| pSrc->db = 0; |
| unionInvokeOpenClose(pTab, pSrc, 1, 0); |
| } |
| } |
| |
| return rc; |
| } |
| |
| |
| /* |
| ** This function is a no-op for unionvtab tables. For swarmvtab, increment |
| ** the reference count for source table iTab. If the reference count was |
| ** zero before it was incremented, also remove the source from the closable |
| ** list. |
| */ |
| static void unionIncrRefcount(UnionTab *pTab, int iTab){ |
| if( pTab->bSwarm ){ |
| UnionSrc *pSrc = &pTab->aSrc[iTab]; |
| assert( pSrc->nUser>=0 && pSrc->db ); |
| if( pSrc->nUser==0 ){ |
| UnionSrc **pp; |
| for(pp=&pTab->pClosable; *pp!=pSrc; pp=&(*pp)->pNextClosable); |
| *pp = pSrc->pNextClosable; |
| pSrc->pNextClosable = 0; |
| } |
| pSrc->nUser++; |
| } |
| } |
| |
| /* |
| ** Finalize the SQL statement pCsr->pStmt and return the result. |
| ** |
| ** If this is a swarmvtab table (not unionvtab) and pCsr->pStmt was not |
| ** NULL when this function was called, also decrement the reference |
| ** count on the associated source table. If this means the source tables |
| ** refcount is now zero, add it to the closable list. |
| */ |
| static int unionFinalizeCsrStmt(UnionCsr *pCsr){ |
| int rc = SQLITE_OK; |
| if( pCsr->pStmt ){ |
| UnionTab *pTab = (UnionTab*)pCsr->base.pVtab; |
| UnionSrc *pSrc = &pTab->aSrc[pCsr->iTab]; |
| rc = sqlite3_finalize(pCsr->pStmt); |
| pCsr->pStmt = 0; |
| if( pTab->bSwarm ){ |
| pSrc->nUser--; |
| assert( pSrc->nUser>=0 ); |
| if( pSrc->nUser==0 ){ |
| pSrc->pNextClosable = pTab->pClosable; |
| pTab->pClosable = pSrc; |
| } |
| unionCloseSources(pTab, pTab->nMaxOpen); |
| } |
| } |
| 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 = (int)(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. |
| ** |
| ** The argv[] array contains the following: |
| ** |
| ** 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, |
| sqlite3_vtab **ppVtab, |
| char **pzErr |
| ){ |
| UnionTab *pTab = 0; |
| int rc = SQLITE_OK; |
| int bSwarm = (pAux==0 ? 0 : 1); |
| const char *zVtab = (bSwarm ? "swarmvtab" : "unionvtab"); |
| |
| 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 || (argc>4 && bSwarm==0) ){ |
| *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 */ |
| char *zArg = unionStrdup(&rc, argv[3]); /* Copy of argument to CVT */ |
| |
| /* Prepare the SQL statement. Instead of executing it directly, sort |
| ** the results by the "minimum rowid" field. This makes it easier to |
| ** check that there are no rowid range overlaps between source tables |
| ** and that the UnionTab.aSrc[] array is always sorted by rowid. */ |
| unionDequote(zArg); |
| pStmt = unionPreparePrintf(&rc, pzErr, db, |
| "SELECT * FROM (%z) ORDER BY 3", zArg |
| ); |
| |
| /* 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. */ |
| while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ |
| const char *zDb = (const char*)sqlite3_column_text(pStmt, 0); |
| const char *zTab = (const char*)sqlite3_column_text(pStmt, 1); |
| sqlite3_int64 iMin = sqlite3_column_int64(pStmt, 2); |
| sqlite3_int64 iMax = sqlite3_column_int64(pStmt, 3); |
| UnionSrc *pSrc; |
| |
| /* Grow the pTab->aSrc[] array if required. */ |
| if( nAlloc<=pTab->nSrc ){ |
| int nNew = nAlloc ? nAlloc*2 : 8; |
| UnionSrc *aNew = (UnionSrc*)sqlite3_realloc64( |
| pTab->aSrc, nNew*sizeof(UnionSrc) |
| ); |
| if( aNew==0 ){ |
| rc = SQLITE_NOMEM; |
| break; |
| }else{ |
| memset(&aNew[pTab->nSrc], 0, (nNew-pTab->nSrc)*sizeof(UnionSrc)); |
| pTab->aSrc = aNew; |
| nAlloc = nNew; |
| } |
| } |
| |
| /* Check for problems with the specified range of rowids */ |
| if( iMax<iMin || (pTab->nSrc>0 && iMin<=pTab->aSrc[pTab->nSrc-1].iMax) ){ |
| *pzErr = sqlite3_mprintf("rowid range mismatch error"); |
| rc = SQLITE_ERROR; |
| } |
| |
| 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); |
| }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; |
| |
| /* 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 ){ |
| if( bSwarm ){ |
| rc = unionOpenDatabase(pTab, 0, pzErr); |
| }else{ |
| rc = unionSourceCheck(pTab, pzErr); |
| } |
| } |
| |
| /* Compose a CREATE TABLE statement and pass it to declare_vtab() */ |
| if( rc==SQLITE_OK ){ |
| UnionSrc *pSrc = &pTab->aSrc[0]; |
| sqlite3 *tdb = unionGetDb(pTab, pSrc); |
| pStmt = unionPreparePrintf(&rc, pzErr, tdb, "SELECT " |
| "'CREATE TABLE xyz('" |
| " || group_concat(quote(name) || ' ' || type, ', ')" |
| " || ')'," |
| "max((cid+1) * (type='INTEGER' COLLATE nocase AND pk=1))-1 " |
| "FROM pragma_table_info(%Q, ?)", |
| pSrc->zTab, pSrc->zDb |
| ); |
| } |
| if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ |
| const char *zDecl = (const char*)sqlite3_column_text(pStmt, 0); |
| rc = sqlite3_declare_vtab(db, zDecl); |
| pTab->iPK = sqlite3_column_int(pStmt, 1); |
| } |
| |
| unionFinalize(&rc, pStmt, pzErr); |
| } |
| |
| if( rc!=SQLITE_OK ){ |
| unionDisconnect((sqlite3_vtab*)pTab); |
| pTab = 0; |
| } |
| |
| *ppVtab = (sqlite3_vtab*)pTab; |
| return rc; |
| } |
| |
| /* |
| ** xOpen |
| */ |
| static int unionOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ |
| UnionCsr *pCsr; |
| int rc = SQLITE_OK; |
| (void)p; /* Suppress harmless warning */ |
| pCsr = (UnionCsr*)unionMalloc(&rc, sizeof(UnionCsr)); |
| *ppCursor = &pCsr->base; |
| return rc; |
| } |
| |
| /* |
| ** xClose |
| */ |
| static int unionClose(sqlite3_vtab_cursor *cur){ |
| UnionCsr *pCsr = (UnionCsr*)cur; |
| unionFinalizeCsrStmt(pCsr); |
| sqlite3_free(pCsr); |
| return SQLITE_OK; |
| } |
| |
| /* |
| ** This function does the work of the xNext() method. Except that, if it |
| ** returns SQLITE_ROW, it should be called again within the same xNext() |
| ** method call. See unionNext() for details. |
| */ |
| 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 = unionFinalizeCsrStmt(pCsr); |
| 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 = unionOpenDatabase(pTab, pCsr->iTab, &pTab->base.zErrMsg); |
| pCsr->pStmt = unionPreparePrintf(&rc, &pTab->base.zErrMsg, pSrc->db, |
| "SELECT rowid, * FROM %Q %s %lld", |
| pSrc->zTab, |
| (pSrc->iMax>pCsr->iMaxRowid ? "WHERE _rowid_ <=" : "-- "), |
| pCsr->iMaxRowid |
| ); |
| if( rc==SQLITE_OK ){ |
| assert( pCsr->pStmt ); |
| unionIncrRefcount(pTab, pCsr->iTab); |
| rc = SQLITE_ROW; |
| } |
| } |
| } |
| } |
| } |
| |
| return rc; |
| } |
| |
| /* |
| ** xNext |
| */ |
| static int unionNext(sqlite3_vtab_cursor *cur){ |
| int rc; |
| do { |
| rc = doUnionNext((UnionCsr*)cur); |
| }while( rc==SQLITE_ROW ); |
| return rc; |
| } |
| |
| /* |
| ** xColumn |
| */ |
| static int unionColumn( |
| sqlite3_vtab_cursor *cur, |
| sqlite3_context *ctx, |
| int i |
| ){ |
| UnionCsr *pCsr = (UnionCsr*)cur; |
| sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pStmt, i+1)); |
| return SQLITE_OK; |
| } |
| |
| /* |
| ** xRowid |
| */ |
| static int unionRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ |
| UnionCsr *pCsr = (UnionCsr*)cur; |
| *pRowid = sqlite3_column_int64(pCsr->pStmt, 0); |
| return SQLITE_OK; |
| } |
| |
| /* |
| ** xEof |
| */ |
| static int unionEof(sqlite3_vtab_cursor *cur){ |
| UnionCsr *pCsr = (UnionCsr*)cur; |
| return pCsr->pStmt==0; |
| } |
| |
| /* |
| ** xFilter |
| */ |
| static int unionFilter( |
| sqlite3_vtab_cursor *pVtabCursor, |
| int idxNum, const char *idxStr, |
| int argc, sqlite3_value **argv |
| ){ |
| UnionTab *pTab = (UnionTab*)(pVtabCursor->pVtab); |
| UnionCsr *pCsr = (UnionCsr*)pVtabCursor; |
| int rc = SQLITE_OK; |
| int i; |
| char *zSql = 0; |
| int bZero = 0; |
| |
| sqlite3_int64 iMin = SMALLEST_INT64; |
| sqlite3_int64 iMax = LARGEST_INT64; |
| |
| assert( idxNum==0 |
| || idxNum==SQLITE_INDEX_CONSTRAINT_EQ |
| || idxNum==SQLITE_INDEX_CONSTRAINT_LE |
| || idxNum==SQLITE_INDEX_CONSTRAINT_GE |
| || idxNum==SQLITE_INDEX_CONSTRAINT_LT |
| || idxNum==SQLITE_INDEX_CONSTRAINT_GT |
| || idxNum==(SQLITE_INDEX_CONSTRAINT_GE|SQLITE_INDEX_CONSTRAINT_LE) |
| ); |
| |
| (void)idxStr; /* Suppress harmless warning */ |
| |
| if( idxNum==SQLITE_INDEX_CONSTRAINT_EQ ){ |
| assert( argc==1 ); |
| iMin = iMax = sqlite3_value_int64(argv[0]); |
| }else{ |
| |
| if( idxNum & (SQLITE_INDEX_CONSTRAINT_LE|SQLITE_INDEX_CONSTRAINT_LT) ){ |
| assert( argc>=1 ); |
| iMax = sqlite3_value_int64(argv[0]); |
| if( idxNum & SQLITE_INDEX_CONSTRAINT_LT ){ |
| if( iMax==SMALLEST_INT64 ){ |
| bZero = 1; |
| }else{ |
| iMax--; |
| } |
| } |
| } |
| |
| if( idxNum & (SQLITE_INDEX_CONSTRAINT_GE|SQLITE_INDEX_CONSTRAINT_GT) ){ |
| assert( argc>=1 ); |
| iMin = sqlite3_value_int64(argv[argc-1]); |
| if( idxNum & SQLITE_INDEX_CONSTRAINT_GT ){ |
| if( iMin==LARGEST_INT64 ){ |
| bZero = 1; |
| }else{ |
| iMin++; |
| } |
| } |
| } |
| } |
| |
| unionFinalizeCsrStmt(pCsr); |
| if( bZero ){ |
| return SQLITE_OK; |
| } |
| |
| for(i=0; i<pTab->nSrc; i++){ |
| UnionSrc *pSrc = &pTab->aSrc[i]; |
| if( iMin>pSrc->iMax || iMax<pSrc->iMin ){ |
| continue; |
| } |
| |
| zSql = sqlite3_mprintf("%z%sSELECT rowid, * FROM %s%q%s%Q" |
| , zSql |
| , (zSql ? " UNION ALL " : "") |
| , (pSrc->zDb ? "'" : "") |
| , (pSrc->zDb ? pSrc->zDb : "") |
| , (pSrc->zDb ? "'." : "") |
| , pSrc->zTab |
| ); |
| if( zSql==0 ){ |
| rc = SQLITE_NOMEM; |
| break; |
| } |
| |
| if( iMin==iMax ){ |
| zSql = sqlite3_mprintf("%z WHERE rowid=%lld", zSql, iMin); |
| }else{ |
| const char *zWhere = "WHERE"; |
| if( iMin!=SMALLEST_INT64 && iMin>pSrc->iMin ){ |
| zSql = sqlite3_mprintf("%z WHERE rowid>=%lld", zSql, iMin); |
| zWhere = "AND"; |
| } |
| if( iMax!=LARGEST_INT64 && iMax<pSrc->iMax ){ |
| zSql = sqlite3_mprintf("%z %s rowid<=%lld", zSql, zWhere, iMax); |
| } |
| } |
| |
| if( pTab->bSwarm ){ |
| pCsr->iTab = i; |
| pCsr->iMaxRowid = iMax; |
| rc = unionOpenDatabase(pTab, i, &pTab->base.zErrMsg); |
| break; |
| } |
| } |
| |
| if( zSql==0 ){ |
| return rc; |
| }else{ |
| sqlite3 *db = unionGetDb(pTab, &pTab->aSrc[pCsr->iTab]); |
| pCsr->pStmt = unionPrepare(&rc, db, zSql, &pTab->base.zErrMsg); |
| if( pCsr->pStmt ){ |
| unionIncrRefcount(pTab, pCsr->iTab); |
| } |
| sqlite3_free(zSql); |
| } |
| if( rc!=SQLITE_OK ) return rc; |
| return unionNext(pVtabCursor); |
| } |
| |
| /* |
| ** xBestIndex. |
| ** |
| ** This implementation searches for constraints on the rowid field. EQ, |
| ** LE, LT, GE and GT are handled. |
| ** |
| ** If there is an EQ comparison, then idxNum is set to INDEX_CONSTRAINT_EQ. |
| ** In this case the only argument passed to xFilter is the rhs of the == |
| ** operator. |
| ** |
| ** Otherwise, if an LE or LT constraint is found, then the INDEX_CONSTRAINT_LE |
| ** or INDEX_CONSTRAINT_LT (but not both) bit is set in idxNum. The first |
| ** argument to xFilter is the rhs of the <= or < operator. Similarly, if |
| ** an GE or GT constraint is found, then the INDEX_CONSTRAINT_GE or |
| ** INDEX_CONSTRAINT_GT bit is set in idxNum. The rhs of the >= or > operator |
| ** is passed as either the first or second argument to xFilter, depending |
| ** on whether or not there is also a LT|LE constraint. |
| */ |
| static int unionBestIndex( |
| sqlite3_vtab *tab, |
| sqlite3_index_info *pIdxInfo |
| ){ |
| UnionTab *pTab = (UnionTab*)tab; |
| int iEq = -1; |
| int iLt = -1; |
| int iGt = -1; |
| int i; |
| |
| for(i=0; i<pIdxInfo->nConstraint; i++){ |
| struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i]; |
| if( p->usable && (p->iColumn<0 || p->iColumn==pTab->iPK) ){ |
| switch( p->op ){ |
| case SQLITE_INDEX_CONSTRAINT_EQ: |
| iEq = i; |
| break; |
| case SQLITE_INDEX_CONSTRAINT_LE: |
| case SQLITE_INDEX_CONSTRAINT_LT: |
| iLt = i; |
| break; |
| case SQLITE_INDEX_CONSTRAINT_GE: |
| case SQLITE_INDEX_CONSTRAINT_GT: |
| iGt = i; |
| break; |
| } |
| } |
| } |
| |
| if( iEq>=0 ){ |
| pIdxInfo->estimatedRows = 1; |
| pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_UNIQUE; |
| pIdxInfo->estimatedCost = 3.0; |
| pIdxInfo->idxNum = SQLITE_INDEX_CONSTRAINT_EQ; |
| pIdxInfo->aConstraintUsage[iEq].argvIndex = 1; |
| pIdxInfo->aConstraintUsage[iEq].omit = 1; |
| }else{ |
| int iCons = 1; |
| int idxNum = 0; |
| sqlite3_int64 nRow = 1000000; |
| if( iLt>=0 ){ |
| nRow = nRow / 2; |
| pIdxInfo->aConstraintUsage[iLt].argvIndex = iCons++; |
| pIdxInfo->aConstraintUsage[iLt].omit = 1; |
| idxNum |= pIdxInfo->aConstraint[iLt].op; |
| } |
| if( iGt>=0 ){ |
| nRow = nRow / 2; |
| pIdxInfo->aConstraintUsage[iGt].argvIndex = iCons++; |
| pIdxInfo->aConstraintUsage[iGt].omit = 1; |
| idxNum |= pIdxInfo->aConstraint[iGt].op; |
| } |
| pIdxInfo->estimatedRows = nRow; |
| pIdxInfo->estimatedCost = 3.0 * (double)nRow; |
| pIdxInfo->idxNum = idxNum; |
| } |
| |
| return SQLITE_OK; |
| } |
| |
| /* |
| ** Register the unionvtab virtual table module with database handle db. |
| */ |
| static int createUnionVtab(sqlite3 *db){ |
| static sqlite3_module unionModule = { |
| 0, /* iVersion */ |
| unionConnect, |
| unionConnect, |
| unionBestIndex, /* xBestIndex - query planner */ |
| unionDisconnect, |
| unionDisconnect, |
| unionOpen, /* xOpen - open a cursor */ |
| unionClose, /* xClose - close a cursor */ |
| unionFilter, /* xFilter - configure scan constraints */ |
| unionNext, /* xNext - advance a cursor */ |
| unionEof, /* xEof - check for end of scan */ |
| unionColumn, /* xColumn - read data */ |
| unionRowid, /* xRowid - read data */ |
| 0, /* xUpdate */ |
| 0, /* xBegin */ |
| 0, /* xSync */ |
| 0, /* xCommit */ |
| 0, /* xRollback */ |
| 0, /* xFindMethod */ |
| 0, /* xRename */ |
| 0, /* xSavepoint */ |
| 0, /* xRelease */ |
| 0, /* xRollbackTo */ |
| 0 /* xShadowName */ |
| }; |
| int rc; |
| |
| 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 */ |
| |
| #ifdef _WIN32 |
| __declspec(dllexport) |
| #endif |
| int sqlite3_unionvtab_init( |
| sqlite3 *db, |
| char **pzErrMsg, |
| const sqlite3_api_routines *pApi |
| ){ |
| int rc = SQLITE_OK; |
| SQLITE_EXTENSION_INIT2(pApi); |
| (void)pzErrMsg; /* Suppress harmless warning */ |
| #ifndef SQLITE_OMIT_VIRTUALTABLE |
| rc = createUnionVtab(db); |
| #endif |
| return rc; |
| } |