--- /dev/null
+/*
+** 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.
+**
+*************************************************************************
+**
+*/
+#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_UNIONVTAB)
+
+#include "sqlite3ext.h"
+SQLITE_EXTENSION_INIT1
+#include <assert.h>
+#include <string.h>
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+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 */
+};
+
+/*
+** 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.
+*/
+struct UnionTab {
+ sqlite3_vtab base; /* Base class - must be first */
+ sqlite3 *db; /* Database handle */
+ int nSrc; /* Number of elements in the aSrc[] array */
+ UnionSrc *aSrc; /* Array of source tables, sorted by rowid */
+};
+
+/*
+** 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, int nByte){
+ void *pRet;
+ assert( nByte>0 );
+ if( *pRc==SQLITE_OK ){
+ pRet = sqlite3_malloc(nByte);
+ if( pRet ){
+ memset(pRet, 0, 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 ){
+ int nByte = strlen(zIn) + 1;
+ zRet = unionMalloc(pRc, nByte);
+ if( zRet ){
+ memcpy(zRet, zIn, 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){
+ 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( 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';
+ }
+}
+
+static sqlite3_stmt *unionPrepare(
+ int *pRc,
+ sqlite3 *db,
+ const char *zSql,
+ char **pzErr
+){
+ sqlite3_stmt *pRet = 0;
+ 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 void unionReset(int *pRc, sqlite3_stmt *pStmt, char **pzErr){
+ int rc = sqlite3_reset(pStmt);
+ if( *pRc==SQLITE_OK ){
+ *pRc = rc;
+ if( rc && pzErr ){
+ *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(sqlite3_db_handle(pStmt)));
+ }
+ }
+}
+
+static void unionFinalize(int *pRc, sqlite3_stmt *pStmt){
+ int rc = sqlite3_finalize(pStmt);
+ if( *pRc==SQLITE_OK ) *pRc = rc;
+}
+
+/*
+** xDisconnect method.
+*/
+static int unionDisconnect(sqlite3_vtab *pVtab){
+ if( pVtab ){
+ UnionTab *pTab = (UnionTab*)pVtab;
+ int i;
+ for(i=0; i<pTab->nSrc; i++){
+ sqlite3_free(pTab->aSrc[i].zDb);
+ sqlite3_free(pTab->aSrc[i].zTab);
+ }
+ sqlite3_free(pTab->aSrc);
+ sqlite3_free(pTab);
+ }
+ return SQLITE_OK;
+}
+
+static char *unionSourceToStr(
+ int *pRc,
+ UnionSrc *pSrc,
+ sqlite3_stmt *pStmt,
+ char **pzErr
+){
+ 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",
+ (pSrc->zDb ? pSrc->zDb : ""),
+ (pSrc->zDb ? "." : ""),
+ pSrc->zTab
+ );
+ }
+ }
+ return zRet;
+}
+
+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;
+
+ 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->aSrc[0], pStmt, pzErr);
+ }
+ for(i=1; i<pTab->nSrc; i++){
+ char *z = unionSourceToStr(&rc, &pTab->aSrc[i], pStmt, pzErr);
+ if( rc==SQLITE_OK && sqlite3_stricmp(z, z0) ){
+ *pzErr = sqlite3_mprintf("source table schema mismatch");
+ rc = SQLITE_ERROR;
+ }
+ sqlite3_free(z);
+ }
+
+ unionFinalize(&rc, pStmt);
+ sqlite3_free(z0);
+ }
+ return rc;
+}
+
+/*
+** xConnect/xCreate method.
+**
+** The argv[] array contains the following:
+**
+** argv[0] -> module name ("unionvtab")
+** argv[1] -> database name
+** argv[2] -> table name
+** argv[3] -> SQL statement
+*/
+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;
+
+ 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");
+ rc = SQLITE_ERROR;
+ }else if( argc!=4 ){
+ *pzErr = sqlite3_mprintf("wrong number of arguments for unionvtab");
+ 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;
+ }
+ if( zSql2==0 ){
+ rc = SQLITE_NOMEM;
+ }
+ pTab = unionMalloc(&rc, sizeof(UnionTab));
+ pStmt = unionPrepare(&rc, db, zSql2, 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_realloc(
+ 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;
+ }
+
+ pSrc = &pTab->aSrc[pTab->nSrc++];
+ pSrc->zDb = unionStrdup(&rc, zDb);
+ pSrc->zTab = unionStrdup(&rc, zTab);
+ pSrc->iMin = iMin;
+ pSrc->iMax = iMax;
+ }
+ unionFinalize(&rc, pStmt);
+ pStmt = 0;
+ sqlite3_free(zSql1);
+ sqlite3_free(zSql2);
+ zSql1 = 0;
+ zSql2 = 0;
+
+ /* Verify that all source tables exist and have compatible schemas. */
+ if( rc==SQLITE_OK ){
+ pTab->db = db;
+ rc = unionSourceCheck(pTab, pzErr);
+ }
+
+ /* Compose a CREATE TABLE statement and pass it to declare_vtab() */
+ if( rc==SQLITE_OK ){
+ zSql1 = 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;
+ }
+ pStmt = unionPrepare(&rc, db, zSql1, pzErr);
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_text(pStmt, 1, pTab->aSrc[0].zDb, -1, SQLITE_STATIC);
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){
+ const char *zDecl = (const char*)sqlite3_column_text(pStmt, 0);
+ rc = sqlite3_declare_vtab(db, zDecl);
+ }
+ }
+
+ unionFinalize(&rc, pStmt);
+ sqlite3_free(zSql1);
+ }
+
+ 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;
+ pCsr = (UnionCsr*)unionMalloc(&rc, sizeof(UnionCsr));
+ *ppCursor = &pCsr->base;
+ return rc;
+}
+
+/*
+** xClose
+*/
+static int unionClose(sqlite3_vtab_cursor *cur){
+ UnionCsr *pCsr = (UnionCsr*)cur;
+ sqlite3_finalize(pCsr->pStmt);
+ sqlite3_free(pCsr);
+ return SQLITE_OK;
+}
+
+
+/*
+** xNext
+*/
+static int unionNext(sqlite3_vtab_cursor *cur){
+ UnionCsr *pCsr = (UnionCsr*)cur;
+ int rc;
+ assert( pCsr->pStmt );
+ if( sqlite3_step(pCsr->pStmt)!=SQLITE_ROW ){
+ rc = sqlite3_finalize(pCsr->pStmt);
+ pCsr->pStmt = 0;
+ }else{
+ rc = SQLITE_OK;
+ }
+ 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 bMinValid = 0;
+ int bMaxValid = 0;
+ sqlite3_int64 iMin;
+ sqlite3_int64 iMax;
+
+ 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 ){
+ assert( argc>=1 );
+ iMax = sqlite3_value_int64(argv[0]);
+ bMaxValid = 1;
+ }
+ if( idxNum & SQLITE_INDEX_CONSTRAINT_GE ){
+ assert( argc>=1 );
+ iMin = sqlite3_value_int64(argv[argc-1]);
+ bMinValid = 1;
+ }
+ }
+
+
+ sqlite3_finalize(pCsr->pStmt);
+ pCsr->pStmt = 0;
+
+ for(i=0; i<pTab->nSrc; i++){
+ UnionSrc *pSrc = &pTab->aSrc[i];
+ if( (bMinValid && iMin>pSrc->iMax) || (bMaxValid && 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( zSql ){
+ if( bMinValid && bMaxValid && iMin==iMax ){
+ zSql = sqlite3_mprintf("%z WHERE rowid=%lld", zSql, iMin);
+ }else{
+ const char *zWhere = "WHERE";
+ if( bMinValid && iMin>pSrc->iMin ){
+ zSql = sqlite3_mprintf("%z WHERE rowid>=%lld", zSql, iMin);
+ zWhere = "AND";
+ }
+ if( bMaxValid && iMax<pSrc->iMax ){
+ zSql = sqlite3_mprintf("%z %s rowid<=%lld", zSql, zWhere, iMax);
+ }
+ }
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ pCsr->pStmt = unionPrepare(&rc, pTab->db, zSql, &pTab->base.zErrMsg);
+ }
+ sqlite3_free(zSql);
+
+ if( rc!=SQLITE_OK ) return rc;
+ return unionNext(pVtabCursor);
+}
+
+/*
+** xBestIndex.
+*/
+static int unionBestIndex(
+ sqlite3_vtab *tab,
+ sqlite3_index_info *pIdxInfo
+){
+ 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 ){
+ switch( p->op ){
+ case SQLITE_INDEX_CONSTRAINT_EQ:
+ if( iEq<0 ) iEq = i;
+ break;
+ case SQLITE_INDEX_CONSTRAINT_LE:
+ case SQLITE_INDEX_CONSTRAINT_LT:
+ if( iLt<0 ) iLt = i;
+ break;
+ case SQLITE_INDEX_CONSTRAINT_GE:
+ case SQLITE_INDEX_CONSTRAINT_GT:
+ if( iGt<0 ) 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;
+ }else{
+ int iCons = 1;
+ int idxNum = 0;
+ sqlite3_int64 nRow = 1000000;
+ if( iLt>=0 ){
+ nRow = nRow / 2;
+ pIdxInfo->aConstraintUsage[iLt].argvIndex = iCons++;
+ idxNum |= SQLITE_INDEX_CONSTRAINT_LE;
+ }
+ if( iGt>=0 ){
+ nRow = nRow / 2;
+ pIdxInfo->aConstraintUsage[iGt].argvIndex = iCons++;
+ idxNum |= SQLITE_INDEX_CONSTRAINT_GE;
+ }
+ 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, /* 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 */
+ };
+
+ return sqlite3_create_module(db, "unionvtab", &unionModule, 0);
+}
+
+#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);
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ rc = createUnionVtab(db);
+#endif
+ return rc;
+}
+
+#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_UNIONVTAB) */
-C Fix\sa\sregister\sallocation\sproblem\sin\sPRAGMA\sintegrity_check\sthat\scaused\nthe\ssame\sregister\sto\sbe\sused\sfor\stwo\sdifferent\spurposes\son\sthe\sfirst\nATTACHed\sdatabase\sif\sthe\sschema\sfor\sthe\sATTACHed\sdatabase\swas\snoticable\nmore\scomplex\sthan\sthe\sschema\sfor\sthe\sfirst\sdatabase.\nFix\sfor\sticket\s[a4e06e75a9ab61a1].
-D 2017-07-15T20:33:19.899
-F Makefile.in eda8bedf08c4c93e2137ef1218b3d3302488c68c2774918de0335a1133aab157
+C Add\sthe\s"unionvtab"\svirtual\stable\sextension\sin\sext/misc/unionvtab.c.
+D 2017-07-15T20:48:30.280
+F Makefile.in d9873c9925917cca9990ee24be17eb9613a668012c85a343aef7e5536ae266e8
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
F Makefile.msc 20850e3e8d4d4791e0531955852d768eb06f24138214870d543abb1a47346fba
F README.md f5c87359573c4d255425e588a56554b50fdcc2afba4e017a2e02a43701456afd
F ext/misc/spellfix.c a4723b6aff748a417b5091b68a46443265c40f0d
F ext/misc/stmt.c 6f16443abb3551e3f5813bb13ba19a30e7032830015b0f92fe0c0453045c0a11
F ext/misc/totype.c 4a167594e791abeed95e0a8db028822b5e8fe512
+F ext/misc/unionvtab.c 594b8cebd11297c45d87fed5985548fa141d917f08e672aadea06c14f18bcf42
F ext/misc/vfslog.c fe40fab5c077a40477f7e5eba994309ecac6cc95
F ext/misc/vfsstat.c bf10ef0bc51e1ad6756629e1edb142f7a8db1178
F ext/misc/vtshim.c 1976e6dd68dd0d64508c91a6dfab8e75f8aaf6cd
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
-F main.mk 0288de0e6b44c2fa709b354d4fab5bce423259cc1653c35305c6e50d23ec561a
+F main.mk 5b7d72ab03dd70aa1401f934d31e85aefd6fc542eb58094d7a95d6921390b2d0
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
F src/pcache.c 62835bed959e2914edd26afadfecce29ece0e870
F src/pcache.h 521bb9610d38ef17a3cc9b5ddafd4546c2ea67fa3d0e464823d73c2a28d50e11
F src/pcache1.c 1195a21fe28e223e024f900b2011e80df53793f0356a24caace4188b098540dc
-F src/pragma.c 2ae4088e9c3ca0e63ffc3ada7f2d2d66e91f0b3db50c7f7ddb2f56e9e37fd638
+F src/pragma.c d42b6b5af353bcd763f1901accb63e07855bb8d74c81d246be12488e424e6804
F src/pragma.h bb83728944b42f6d409c77f5838a8edbdb0fe83046c5496ffc9602b40340a324
F src/prepare.c dd250f904739b1dc449c131ac527c35e3424d94082dd111321bd83f80c6bb0fe
F src/printf.c 8757834f1b54dae512fb25eb1acc8e94a0d15dd2290b58f2563f65973265adb2
F src/status.c a9e66593dfb28a9e746cba7153f84d49c1ddc4b1
F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34
F src/tclsqlite.c 2c29b0b76e91edfd1b43bf135c32c8674710089197327682b6b7e6af88062c3d
-F src/test1.c 1c0726cdf7389ed053a9b9aa0dc3c63f3b9bbc607a25decae6549682008510b3
+F src/test1.c cfb78b728b37ae3a2b14fe1b3a6c766e0da41370eda112594e698c94011b622e
F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5
F src/test3.c b8434949dfb8aff8dfa082c8b592109e77844c2135ed3c492113839b6956255b
F src/test4.c 18ec393bb4d0ad1de729f0b94da7267270f3d8e6
F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff
F test/types2.test 1aeb81976841a91eef292723649b5c4fe3bc3cac
F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a
+F test/unionvtab.test c206279d350f473da61daeeaa58637d31a691ad7e7ba1d8826d5f5ea7545676d
F test/unique.test 93f8b2ef5ea51b9495f8d6493429b1fd0f465264
F test/unique2.test 3674e9f2a3f1fbbfd4772ac74b7a97090d0f77d2
F test/unixexcl.test d936ba2b06794018e136418addd59a2354eeae97
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 687bd478710eb827e041533eea67115464f5a0de767bb6cfdbe36a0d3c597fa1
-R 522613d16f7d47bea7a7f05e6d39255f
-U drh
-Z 4ddd385f7f3a9276d818cfc9da4dbe0b
+P 253945d480b052bfe311888022b5eb0be91c8c80cda05036e58207d57520262c
+R ee0a9dc5b1464f766485894fa2069cae
+U dan
+Z d195e18cbc4e4f34f8cf4b9b0b0c8aaa
--- /dev/null
+# 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 percentile.c extension
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix unionvtab
+
+load_static_extension db unionvtab
+
+#-------------------------------------------------------------------------
+# Warm body tests.
+#
+forcedelete test.db2
+do_execsql_test 1.0 {
+ ATTACH 'test.db2' AS aux;
+
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
+ CREATE TABLE t2(a INTEGER PRIMARY KEY, b TEXT);
+ CREATE TABLE aux.t3(a INTEGER PRIMARY KEY, b TEXT);
+
+
+ INSERT INTO t1 VALUES(1, 'one'), (2, 'two'), (3, 'three');
+ INSERT INTO t2 VALUES(10, 'ten'), (11, 'eleven'), (12, 'twelve');
+ INSERT INTO t3 VALUES(20, 'twenty'), (21, 'twenty-one'), (22, 'twenty-two');
+}
+
+do_execsql_test 1.1 {
+ CREATE VIRTUAL TABLE temp.uuu USING unionvtab(
+ "VALUES(NULL, 't1', 1, 9), ('main', 't2', 10, 19), ('aux', 't3', 20, 29)"
+ );
+ SELECT * FROM uuu;
+} {
+ 1 one 2 two 3 three
+ 10 ten 11 eleven 12 twelve
+ 20 twenty 21 twenty-one 22 twenty-two
+}
+
+do_execsql_test 1.2 {
+ PRAGMA table_info(uuu);
+} {
+ 0 a INTEGER 0 {} 0
+ 1 b TEXT 0 {} 0
+}
+
+do_execsql_test 1.3 {
+ SELECT * FROM uuu WHERE rowid = 3;
+ SELECT * FROM uuu WHERE rowid = 11;
+} {3 three 11 eleven}
+
+do_execsql_test 1.4 {
+ SELECT * FROM uuu WHERE rowid IN (12, 10, 2);
+} {2 two 10 ten 12 twelve}
+
+do_execsql_test 1.5 {
+ SELECT * FROM uuu WHERE rowid BETWEEN 3 AND 11;
+} {3 three 10 ten 11 eleven}
+
+do_execsql_test 1.6 {
+ SELECT * FROM uuu WHERE rowid BETWEEN 11 AND 15;
+} {11 eleven 12 twelve}
+
+do_execsql_test 1.7 {
+ SELECT * FROM uuu WHERE rowid BETWEEN -46 AND 1500;
+} {
+ 1 one 2 two 3 three
+ 10 ten 11 eleven 12 twelve
+ 20 twenty 21 twenty-one 22 twenty-two
+}
+
+#-------------------------------------------------------------------------
+# Error conditions.
+#
+# 2.1.*: Attempt to create a unionvtab table outside of the TEMP schema.
+# 2.2.*: Tables that do not exist.
+# 2.3.*: Non WITHOUT ROWID tables.
+# 2.4.*: Tables with mismatched schemas.
+#
+do_catchsql_test 2.1.1 {
+ CREATE VIRTUAL TABLE u1 USING unionvtab("VALUES(NULL, 't1', 1, 100)");
+} {1 {unionvtab tables must be created in TEMP schema}}
+do_catchsql_test 2.1.2 {
+ CREATE VIRTUAL TABLE main.u1 USING unionvtab("VALUES('', 't1', 1, 100)");
+} {1 {unionvtab tables must be created in TEMP schema}}
+do_catchsql_test 2.1.3 {
+ CREATE VIRTUAL TABLE aux.u1 USING unionvtab("VALUES('', 't1', 1, 100)");
+} {1 {unionvtab tables must be created in TEMP schema}}
+
+do_catchsql_test 2.2.1 {
+ CREATE VIRTUAL TABLE temp.u1 USING unionvtab("VALUES(NULL, 't555', 1, 100)");
+} {1 {no such table: t555}}
+do_catchsql_test 2.2.2 {
+ CREATE VIRTUAL TABLE temp.u1 USING unionvtab("VALUES('aux', 't555', 1, 100)");
+} {1 {no such table: aux.t555}}
+do_catchsql_test 2.2.3 {
+ CREATE VIRTUAL TABLE temp.u1 USING unionvtab("VALUES('xua', 't555', 1, 100)");
+} {1 {unknown database 'xua'}}
+
+do_execsql_test 2.4.0 {
+ CREATE TABLE x1(a BLOB, b);
+ CREATE TABLE x2(a BLOB, b);
+ CREATE TEMP TABLE x3(a BLOB, b);
+
+ CREATE TABLE aux.y1(one, two, three INTEGER PRIMARY KEY);
+ CREATE TEMP TABLE y2(one, two, three INTEGER PRIMARY KEY);
+ CREATE TABLE y3(one, two, three INTEGER PRIMARY KEY);
+}
+
+foreach {tn dbs res} {
+ 1 {x1 x2 x3} {0 {}}
+ 2 {y1 y2 y3} {0 {}}
+ 3 {x1 y2 y3} {1 {source table schema mismatch}}
+ 4 {x1 y2 x3} {1 {source table schema mismatch}}
+ 5 {x1 x2 y3} {1 {source table schema mismatch}}
+} {
+ set L [list]
+ set iMin 0
+ foreach e $dbs {
+ set E [split $e .]
+ if {[llength $E]>1} {
+ lappend L "('[lindex $E 0]', '[lindex $E 1]', $iMin, $iMin)"
+ } else {
+ lappend L "(NULL, '$e', $iMin, $iMin)"
+ }
+ incr iMin
+ }
+
+ set sql "CREATE VIRTUAL TABLE temp.a1 USING unionvtab(\"VALUES [join $L ,]\")"
+ do_catchsql_test 2.4.$tn "
+ DROP TABLE IF EXISTS temp.a1;
+ CREATE VIRTUAL TABLE temp.a1 USING unionvtab(\"VALUES [join $L ,]\");
+ " $res
+}
+
+
+finish_test
+