#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#ifndef SQLITE_OMIT_VIRTUALTABLE
#ifndef LARGEST_INT64
# define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32))
#endif
#ifndef SMALLEST_INT64
# define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64)
#endif
#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
#define SWARMVTAB_MAX_OPEN 9
typedef struct UnionCsr UnionCsr;
typedef struct UnionTab UnionTab;
typedef struct UnionSrc UnionSrc;
struct UnionSrc {
char *zDb;
char *zTab;
sqlite3_int64 iMin;
sqlite3_int64 iMax;
char *zFile;
char *zContext;
int nUser;
sqlite3 *db;
UnionSrc *pNextClosable;
};
struct UnionTab {
sqlite3_vtab base;
sqlite3 *db;
int bSwarm;
int iPK;
int nSrc;
UnionSrc *aSrc;
int bHasContext;
char *zSourceStr;
sqlite3_stmt *pNotFound;
sqlite3_stmt *pOpenClose;
UnionSrc *pClosable;
int nOpen;
int nMaxOpen;
};
struct UnionCsr {
sqlite3_vtab_cursor base;
sqlite3_stmt *pStmt;
sqlite3_int64 iMaxRowid;
int iTab;
};
#define unionGetDb(pTab, pSrc) ((pTab)->bSwarm ? (pSrc)->db : (pTab)->db)
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;
}
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;
}
static void unionDequote(char *z){
if( z ){
char q = z[0];
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 ){
iIn++;
break;
}else{
iIn += 2;
z[iOut++] = q;
}
}else{
z[iOut++] = z[iIn++];
}
}
z[iOut] = '\0';
}
}
}
static sqlite3_stmt *unionPrepare(
int *pRc,
sqlite3 *db,
const char *zSql,
char **pzErr
){
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;
}
static sqlite3_stmt *unionPreparePrintf(
int *pRc,
char **pzErr,
sqlite3 *db,
const char *zFmt,
...
){
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;
}
#if 0#endif
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));
}
}
}
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;
}
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);
}
}
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;
}
static int unionIsIntkeyTable(
sqlite3 *db,
UnionSrc *pSrc,
char **pzErr
){
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;
}
static char *unionSourceToStr(
int *pRc,
UnionTab *pTab,
UnionSrc *pSrc,
char **pzErr
){
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;
}
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;
}
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;
}
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;
}
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++;
}
}
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;
}
static int union_isspace(char c){
return (c==' ' || c=='\n' || c=='\r' || c=='\t');
}
static int union_isidchar(char c){
return ((c>='a' && c<='z') || (c>='A' && c<'Z') || (c>='0' && c<='9'));
}
static void unionConfigureVtab(
int *pRc,
UnionTab *pTab,
sqlite3_stmt *pStmt,
int nArg,
const char * const *azArg,
char **pzErr
){
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;
char *zOpt;
char *zVal;
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]==':' ){
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;
}
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]) ){
*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;
sqlite3_stmt *pStmt = 0;
char *zArg = unionStrdup(&rc, argv[3]);
unionDequote(zArg);
pStmt = unionPreparePrintf(&rc, pzErr, db,
"SELECT * FROM (%z) ORDER BY 3", zArg
);
pTab = unionMalloc(&rc, sizeof(UnionTab));
if( pTab ){
assert( rc==SQLITE_OK );
pTab->db = db;
pTab->bSwarm = bSwarm;
pTab->nMaxOpen = SWARMVTAB_MAX_OPEN;
}
if( bSwarm ){
unionConfigureVtab(&rc, pTab, pStmt, argc-4, &argv[4], pzErr);
}
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;
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;
}
}
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;
if( rc==SQLITE_OK && pTab->nSrc==0 ){
*pzErr = sqlite3_mprintf("no source tables configured");
rc = SQLITE_ERROR;
}
if( rc==SQLITE_OK ){
if( bSwarm ){
rc = unionOpenDatabase(pTab, 0, pzErr);
}else{
rc = unionSourceCheck(pTab, pzErr);
}
}
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;
}
static int unionOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
UnionCsr *pCsr;
int rc = SQLITE_OK;
(void)p;
pCsr = (UnionCsr*)unionMalloc(&rc, sizeof(UnionCsr));
*ppCursor = &pCsr->base;
return rc;
}
static int unionClose(sqlite3_vtab_cursor *cur){
UnionCsr *pCsr = (UnionCsr*)cur;
unionFinalizeCsrStmt(pCsr);
sqlite3_free(pCsr);
return SQLITE_OK;
}
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 ){
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;
}
static int unionNext(sqlite3_vtab_cursor *cur){
int rc;
do {
rc = doUnionNext((UnionCsr*)cur);
}while( rc==SQLITE_ROW );
return rc;
}
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;
}
static int unionRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
UnionCsr *pCsr = (UnionCsr*)cur;
*pRowid = sqlite3_column_int64(pCsr->pStmt, 0);
return SQLITE_OK;
}
static int unionEof(sqlite3_vtab_cursor *cur){
UnionCsr *pCsr = (UnionCsr*)cur;
return pCsr->pStmt==0;
}
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;
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);
}
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;
}
static int createUnionVtab(sqlite3 *db){
static sqlite3_module unionModule = {
0,
unionConnect,
unionConnect,
unionBestIndex,
unionDisconnect,
unionDisconnect,
unionOpen,
unionClose,
unionFilter,
unionNext,
unionEof,
unionColumn,
unionRowid,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
};
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
#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;
#ifndef SQLITE_OMIT_VIRTUALTABLE
rc = createUnionVtab(db);
#endif
return rc;
}