**
*************************************************************************
**
+** This file contains the implementation of the "unionvtab" virtual
+** table. This module provides 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.
+**
+** 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).
+**
*/
-#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_UNIONVTAB)
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#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
+
typedef struct UnionCsr UnionCsr;
typedef struct UnionTab UnionTab;
typedef struct UnionSrc UnionSrc;
sqlite3_int64 iMax; /* Maximum rowid */
};
-/*
-** 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 */
-};
-
/*
** Virtual table type for union vtab.
*/
UnionSrc *aSrc; /* Array of source tables, sorted by rowid */
};
+/*
+** 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 */
+};
+
/*
** If *pRc is other than SQLITE_OK when this function is called, it
** always returns NULL. Otherwise, it attempts to allocate and return
}
}
+/*
+** 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,
- sqlite3 *db,
- const char *zSql,
- char **pzErr
+ 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;
if( *pRc==SQLITE_OK ){
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().
+*/
static void unionReset(int *pRc, sqlite3_stmt *pStmt, char **pzErr){
int rc = sqlite3_reset(pStmt);
if( *pRc==SQLITE_OK ){
}
}
+/*
+** 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){
int rc = sqlite3_finalize(pStmt);
if( *pRc==SQLITE_OK ) *pRc = rc;
return SQLITE_OK;
}
+/*
+** 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,
- UnionSrc *pSrc,
+ int *pRc, /* IN/OUT: Error code */
+ sqlite3 *db, /* Database handle */
+ UnionSrc *pSrc, /* Source table to test */
sqlite3_stmt *pStmt,
- char **pzErr
+ char **pzErr /* OUT: Error message */
){
char *zRet = 0;
if( *pRc==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(pRc, (const char*)sqlite3_column_text(pStmt, 0));
- }
- unionReset(pRc, pStmt, pzErr);
- if( *pRc==SQLITE_OK && zRet==0 ){
- *pRc = SQLITE_ERROR;
- *pzErr = sqlite3_mprintf("no such table: %s%s%s",
+ int bPk = 0;
+ const char *zType = 0;
+
+ int rc = sqlite3_table_column_metadata(
+ db, pSrc->zDb, pSrc->zTab, "_rowid_", &zType, 0, 0, &bPk, 0
+ );
+ 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
);
}
+
+ 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));
+ }
+ unionReset(&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){
const char *zSql =
"SELECT group_concat(quote(name) || '.' || quote(type)) "
pStmt = unionPrepare(&rc, pTab->db, zSql, pzErr);
if( rc==SQLITE_OK ){
- z0 = unionSourceToStr(&rc, &pTab->aSrc[0], pStmt, pzErr);
+ z0 = unionSourceToStr(&rc, pTab->db, &pTab->aSrc[0], pStmt, pzErr);
}
for(i=1; i<pTab->nSrc; i++){
- char *z = unionSourceToStr(&rc, &pTab->aSrc[i], pStmt, pzErr);
+ char *z = unionSourceToStr(&rc, pTab->db, &pTab->aSrc[i], pStmt, pzErr);
if( rc==SQLITE_OK && sqlite3_stricmp(z, z0) ){
*pzErr = sqlite3_mprintf("source table schema mismatch");
rc = SQLITE_ERROR;
}else{
int nAlloc = 0; /* Allocated size of pTab->aSrc[] */
sqlite3_stmt *pStmt = 0; /* Argument statement */
- char *zSql1 = unionStrdup(&rc, argv[3]);
- char *zSql2 = 0;
-
- if( zSql1 ){
- unionDequote(zSql1);
- zSql2 = sqlite3_mprintf("SELECT * FROM (%s) ORDER BY 3", zSql1);
- sqlite3_free(zSql1);
- zSql1 = 0;
+ char *zSql = 0; /* SQL 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. */
+ if( zArg ){
+ unionDequote(zArg);
+ zSql = sqlite3_mprintf("SELECT * FROM (%s) ORDER BY 3", zArg);
+ sqlite3_free(zArg);
+ zArg = 0;
}
- if( zSql2==0 ){
+ if( zSql==0 ){
rc = SQLITE_NOMEM;
}
+ pStmt = unionPrepare(&rc, db, zSql, pzErr);
+
+ /* Allocate the UnionTab structure */
pTab = unionMalloc(&rc, sizeof(UnionTab));
- pStmt = unionPrepare(&rc, db, zSql2, 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 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_realloc(
}
}
+ /* 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;
}
unionFinalize(&rc, pStmt);
pStmt = 0;
- sqlite3_free(zSql1);
- sqlite3_free(zSql2);
- zSql1 = 0;
- zSql2 = 0;
+ sqlite3_free(zArg);
+ sqlite3_free(zSql);
+ zArg = 0;
+ zSql = 0;
/* Verify that all source tables exist and have compatible schemas. */
if( rc==SQLITE_OK ){
/* Compose a CREATE TABLE statement and pass it to declare_vtab() */
if( rc==SQLITE_OK ){
- zSql1 = sqlite3_mprintf("SELECT "
+ zSql = sqlite3_mprintf("SELECT "
"'CREATE TABLE xyz('"
" || group_concat(quote(name) || ' ' || type, ', ')"
" || ')'"
"FROM pragma_table_info(%Q, ?)",
pTab->aSrc[0].zTab
);
- if( zSql1==0 ) rc = SQLITE_NOMEM;
+ if( zSql==0 ) rc = SQLITE_NOMEM;
}
- pStmt = unionPrepare(&rc, db, zSql1, pzErr);
+ pStmt = unionPrepare(&rc, db, zSql, pzErr);
if( rc==SQLITE_OK ){
sqlite3_bind_text(pStmt, 1, pTab->aSrc[0].zDb, -1, SQLITE_STATIC);
if( SQLITE_ROW==sqlite3_step(pStmt) ){
}
unionFinalize(&rc, pStmt);
- sqlite3_free(zSql1);
+ sqlite3_free(zSql);
}
if( rc!=SQLITE_OK ){
int rc = SQLITE_OK;
int i;
char *zSql = 0;
+ int bZero = 0;
- int bMinValid = 0;
- int bMaxValid = 0;
- sqlite3_int64 iMin;
- sqlite3_int64 iMax;
+ 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)
+ );
if( idxNum==SQLITE_INDEX_CONSTRAINT_EQ ){
assert( argc==1 );
iMin = iMax = sqlite3_value_int64(argv[0]);
- bMinValid = bMaxValid = 1;
}else{
- if( idxNum & SQLITE_INDEX_CONSTRAINT_LE ){
+
+ if( idxNum & (SQLITE_INDEX_CONSTRAINT_LE|SQLITE_INDEX_CONSTRAINT_LT) ){
assert( argc>=1 );
iMax = sqlite3_value_int64(argv[0]);
- bMaxValid = 1;
+ if( idxNum & SQLITE_INDEX_CONSTRAINT_LT ){
+ if( iMax==SMALLEST_INT64 ){
+ bZero = 1;
+ }else{
+ iMax--;
+ }
+ }
}
- if( idxNum & SQLITE_INDEX_CONSTRAINT_GE ){
+
+ if( idxNum & (SQLITE_INDEX_CONSTRAINT_GE|SQLITE_INDEX_CONSTRAINT_GT) ){
assert( argc>=1 );
iMin = sqlite3_value_int64(argv[argc-1]);
- bMinValid = 1;
+ if( idxNum & SQLITE_INDEX_CONSTRAINT_GT ){
+ if( iMin==LARGEST_INT64 ){
+ bZero = 1;
+ }else{
+ iMin++;
+ }
+ }
}
}
-
sqlite3_finalize(pCsr->pStmt);
pCsr->pStmt = 0;
+ if( bZero ){
+ return SQLITE_OK;
+ }
for(i=0; i<pTab->nSrc; i++){
UnionSrc *pSrc = &pTab->aSrc[i];
- if( (bMinValid && iMin>pSrc->iMax) || (bMaxValid && iMax<pSrc->iMin) ){
+ if( iMin>pSrc->iMax || iMax<pSrc->iMin ){
continue;
}
}
if( zSql ){
- if( bMinValid && bMaxValid && iMin==iMax ){
+ if( iMin==iMax ){
zSql = sqlite3_mprintf("%z WHERE rowid=%lld", zSql, iMin);
}else{
const char *zWhere = "WHERE";
- if( bMinValid && iMin>pSrc->iMin ){
+ if( iMin!=SMALLEST_INT64 && iMin>pSrc->iMin ){
zSql = sqlite3_mprintf("%z WHERE rowid>=%lld", zSql, iMin);
zWhere = "AND";
}
- if( bMaxValid && iMax<pSrc->iMax ){
+ if( iMax!=LARGEST_INT64 && iMax<pSrc->iMax ){
zSql = sqlite3_mprintf("%z %s rowid<=%lld", zSql, zWhere, iMax);
}
}
/*
** 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,
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;
if( iLt>=0 ){
nRow = nRow / 2;
pIdxInfo->aConstraintUsage[iLt].argvIndex = iCons++;
- idxNum |= SQLITE_INDEX_CONSTRAINT_LE;
+ pIdxInfo->aConstraintUsage[iLt].omit = 1;
+ idxNum |= pIdxInfo->aConstraint[iLt].op;
}
if( iGt>=0 ){
nRow = nRow / 2;
pIdxInfo->aConstraintUsage[iGt].argvIndex = iCons++;
- idxNum |= SQLITE_INDEX_CONSTRAINT_GE;
+ pIdxInfo->aConstraintUsage[iGt].omit = 1;
+ idxNum |= pIdxInfo->aConstraint[iGt].op;
}
pIdxInfo->estimatedRows = nRow;
pIdxInfo->estimatedCost = 3.0 * (double)nRow;
return SQLITE_OK;
}
+/*
+** Register the unionvtab virtual table module with database handle db.
+*/
static int createUnionVtab(sqlite3 *db){
static sqlite3_module unionModule = {
0, /* iVersion */
return rc;
}
-#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_UNIONVTAB) */