]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the "unionvtab" virtual table extension in ext/misc/unionvtab.c.
authordan <dan@noemail.net>
Sat, 15 Jul 2017 20:48:30 +0000 (20:48 +0000)
committerdan <dan@noemail.net>
Sat, 15 Jul 2017 20:48:30 +0000 (20:48 +0000)
FossilOrigin-Name: 62a86aa6c0519cf1fa232169122d3d6ae8d2f66b20530fb934a82a15712bd2f0

Makefile.in
ext/misc/unionvtab.c [new file with mode: 0644]
main.mk
manifest
manifest.uuid
src/pragma.c
src/test1.c
test/unionvtab.test [new file with mode: 0644]

index e71cbd80913521183af3968be7e2352f0168eae0..bce57b717bba8534d5a5e142d4ba654ec10c503e 100644 (file)
@@ -435,6 +435,7 @@ TESTSRC += \
   $(TOP)/ext/misc/series.c \
   $(TOP)/ext/misc/spellfix.c \
   $(TOP)/ext/misc/totype.c \
+  $(TOP)/ext/misc/unionvtab.c \
   $(TOP)/ext/misc/wholenumber.c
 
 # Source code to the library files needed by the test fixture
diff --git a/ext/misc/unionvtab.c b/ext/misc/unionvtab.c
new file mode 100644 (file)
index 0000000..c60f148
--- /dev/null
@@ -0,0 +1,626 @@
+/*
+** 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) */
diff --git a/main.mk b/main.mk
index 4bd2f54ce4538adafc02eec1e456a8cbeee4b19b..d3450a63cbf9ae02e95f104bf9222d4724376d55 100644 (file)
--- a/main.mk
+++ b/main.mk
@@ -341,6 +341,7 @@ TESTSRC += \
   $(TOP)/ext/misc/series.c \
   $(TOP)/ext/misc/spellfix.c \
   $(TOP)/ext/misc/totype.c \
+  $(TOP)/ext/misc/unionvtab.c \
   $(TOP)/ext/misc/wholenumber.c \
   $(TOP)/ext/misc/vfslog.c \
   $(TOP)/ext/fts5/fts5_tcl.c \
index a0044545dc485e1df0053ef5109c774837fabba6..51a952a0f863a17f48cb4087cdead7c974858b64 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,6 +1,6 @@
-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
@@ -281,6 +281,7 @@ F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
 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
@@ -375,7 +376,7 @@ F ext/userauth/userauth.c 3410be31283abba70255d71fd24734e017a4497f
 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
@@ -444,7 +445,7 @@ F src/parse.y e384cb73f99e1b074085c974b37f4d830e885359e4b60837e30f7d67c16ba65b
 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
@@ -462,7 +463,7 @@ F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6
 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
@@ -1431,6 +1432,7 @@ F test/tt3_vacuum.c 1753f45917699c9c1f66b64c717a717c9379f776
 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
@@ -1633,7 +1635,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 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
index cc1f259bccae48f912f1d6ab17ce886c4b4ff5d6..58f352fc6eeb3a8b7aae9797d9f22a6f8eb8fa33 100644 (file)
@@ -1 +1 @@
-253945d480b052bfe311888022b5eb0be91c8c80cda05036e58207d57520262c
\ No newline at end of file
+62a86aa6c0519cf1fa232169122d3d6ae8d2f66b20530fb934a82a15712bd2f0
\ No newline at end of file
index 45809bcb14b336075366ee90a0dc6e25c7650fdc..0e13efa7d20ac4a013284d4fc8fdf601a065a129 100644 (file)
@@ -2371,10 +2371,14 @@ static int pragmaVtabFilter(
   pragmaVtabCursorClear(pCsr);
   j = (pTab->pName->mPragFlg & PragFlg_Result1)!=0 ? 0 : 1;
   for(i=0; i<argc; i++, j++){
+    const char *zText = (const char*)sqlite3_value_text(argv[i]);
     assert( j<ArraySize(pCsr->azArg) );
-    pCsr->azArg[j] = sqlite3_mprintf("%s", sqlite3_value_text(argv[i]));
-    if( pCsr->azArg[j]==0 ){
-      return SQLITE_NOMEM;
+    assert( pCsr->azArg[j]==0 );
+    if( zText ){
+      pCsr->azArg[j] = sqlite3_mprintf("%s", zText);
+      if( pCsr->azArg[j]==0 ){
+        return SQLITE_NOMEM;
+      }
     }
   }
   sqlite3StrAccumInit(&acc, 0, 0, 0, pTab->db->aLimit[SQLITE_LIMIT_SQL_LENGTH]);
index 4b97f2c223299722010036ed5d8c00646e21f41b..df7685f244d698a788aec0c3aff26abf5892afc4 100644 (file)
@@ -6920,6 +6920,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd(
   extern int sqlite3_spellfix_init(sqlite3*,char**,const sqlite3_api_routines*);
   extern int sqlite3_totype_init(sqlite3*,char**,const sqlite3_api_routines*);
   extern int sqlite3_wholenumber_init(sqlite3*,char**,const sqlite3_api_routines*);
+  extern int sqlite3_unionvtab_init(sqlite3*,char**,const sqlite3_api_routines*);
   static const struct {
     const char *zExtName;
     int (*pInit)(sqlite3*,char**,const sqlite3_api_routines*);
@@ -6939,6 +6940,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd(
     { "series",                sqlite3_series_init               },
     { "spellfix",              sqlite3_spellfix_init             },
     { "totype",                sqlite3_totype_init               },
+    { "unionvtab",             sqlite3_unionvtab_init            },
     { "wholenumber",           sqlite3_wholenumber_init          },
   };
   sqlite3 *db;
diff --git a/test/unionvtab.test b/test/unionvtab.test
new file mode 100644 (file)
index 0000000..29403af
--- /dev/null
@@ -0,0 +1,147 @@
+# 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
+