/ Artifact Content
Login

Artifact 3cff393ee86d15fbfbe31f30cd752b46d7779b52:


/*
** 2016 February 10
**
** 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.
**
*************************************************************************
*/

typedef sqlite3_int64 i64;

typedef struct IdxConstraint IdxConstraint;
typedef struct IdxContext IdxContext;
typedef struct IdxScan IdxScan;
typedef struct IdxWhere IdxWhere;

/*
** A single constraint. Equivalent to either "col = ?" or "col < ?".
**
** pLink:
**   ... todo ...
*/
struct IdxConstraint {
  char *zColl;                    /* Collation sequence */
  int bRange;                     /* True for range, false for eq */
  int iCol;                       /* Constrained table column */
  i64 depmask;                    /* Dependency mask */
  IdxConstraint *pNext;           /* Next constraint in pEq or pRange list */
  IdxConstraint *pLink;           /* See above */
};

/*
** A WHERE clause. Made up of IdxConstraint objects.
**
**   a=? AND b=? AND (c=? OR d=?) AND (e=? OR f=?)
**
*/
struct IdxWhere {
  IdxConstraint *pEq;             /* List of == constraints */
  IdxConstraint *pRange;          /* List of < constraints */
  IdxWhere **apOr;                /* Array of OR branches (joined by pNextOr) */
  IdxWhere *pNextOr;              /* Next in OR'd terms */
  IdxWhere *pParent;              /* Parent object (or NULL) */
};

/*
** A single scan of a single table.
*/
struct IdxScan {
  char *zTable;                   /* Name of table to scan */
  int iDb;                        /* Database containing table zTable */
  i64 covering;                   /* Mask of columns required for cov. index */
  IdxConstraint *pOrder;          /* ORDER BY columns */
  IdxWhere where;                 /* WHERE Constraints */
  IdxScan *pNextScan;             /* Next IdxScan object for same query */
};

/*
** Context object passed to idxWhereInfo()
*/
struct IdxContext {
  IdxWhere *pCurrent;             /* Current where clause */
  IdxScan *pScan;                 /* List of scan objects */
  sqlite3 *dbm;                   /* In-memory db for this analysis */
  int rc;                         /* Error code (if error has occurred) */
};

typedef struct PragmaTable PragmaTable;
typedef struct PragmaCursor PragmaCursor;

struct PragmaTable {
  sqlite3_vtab base;
  sqlite3 *db;
};

struct PragmaCursor {
  sqlite3_vtab_cursor base;
  sqlite3_stmt *pStmt;
  i64 iRowid;
};

/*
** Connect to or create a pragma virtual table.
*/
static int pragmaConnect(
  sqlite3 *db,
  void *pAux,
  int argc, const char *const*argv,
  sqlite3_vtab **ppVtab,
  char **pzErr
){
  const char *zSchema = 
    "CREATE TABLE a(tbl HIDDEN, cid, name, type, notnull, dflt_value, pk)";
  PragmaTable *pTab = 0;
  int rc = SQLITE_OK;

  rc = sqlite3_declare_vtab(db, zSchema);
  if( rc==SQLITE_OK ){
    pTab = (PragmaTable *)sqlite3_malloc64(sizeof(PragmaTable));
    if( pTab==0 ) rc = SQLITE_NOMEM;
  }

  assert( rc==SQLITE_OK || pTab==0 );
  if( rc==SQLITE_OK ){
    memset(pTab, 0, sizeof(PragmaTable));
    pTab->db = db;
  }

  *ppVtab = (sqlite3_vtab*)pTab;
  return rc;
}

/*
** Disconnect from or destroy a pragma virtual table.
*/
static int pragmaDisconnect(sqlite3_vtab *pVtab){
  sqlite3_free(pVtab);
  return SQLITE_OK;
}

/*
** xBestIndex method for pragma virtual tables.
*/
static int pragmaBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
  int i;

  pIdxInfo->estimatedCost = 1.0e6;  /* Initial cost estimate */

  /* Look for a valid tbl=? constraint. */
  for(i=0; i<pIdxInfo->nConstraint; i++){
    if( pIdxInfo->aConstraint[i].usable==0 ) continue;
    if( pIdxInfo->aConstraint[i].op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
    if( pIdxInfo->aConstraint[i].iColumn!=0 ) continue;
    pIdxInfo->idxNum = 1;
    pIdxInfo->estimatedCost = 1.0;
    pIdxInfo->aConstraintUsage[i].argvIndex = 1;
    pIdxInfo->aConstraintUsage[i].omit = 1;
    break;
  }
  if( i==pIdxInfo->nConstraint ){
    tab->zErrMsg = sqlite3_mprintf("missing required tbl=? constraint");
    return SQLITE_ERROR;
  }
  return SQLITE_OK;
}

/*
** Open a new pragma cursor.
*/
static int pragmaOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
  PragmaTable *pTab = (PragmaTable *)pVTab;
  PragmaCursor *pCsr;

  pCsr = (PragmaCursor*)sqlite3_malloc64(sizeof(PragmaCursor));
  if( pCsr==0 ){
    return SQLITE_NOMEM;
  }else{
    memset(pCsr, 0, sizeof(PragmaCursor));
    pCsr->base.pVtab = pVTab;
  }

  *ppCursor = (sqlite3_vtab_cursor*)pCsr;
  return SQLITE_OK;
}

/*
** Move a statvfs cursor to the next entry in the file.
*/
static int pragmaNext(sqlite3_vtab_cursor *pCursor){
  PragmaCursor *pCsr = (PragmaCursor*)pCursor;
  int rc = SQLITE_OK;

  if( sqlite3_step(pCsr->pStmt)!=SQLITE_ROW ){
    rc = sqlite3_finalize(pCsr->pStmt);
    pCsr->pStmt = 0;
  }
  pCsr->iRowid++;
  return rc;
}

static int pragmaEof(sqlite3_vtab_cursor *pCursor){
  PragmaCursor *pCsr = (PragmaCursor*)pCursor;
  return pCsr->pStmt==0;
}

static int pragmaFilter(
  sqlite3_vtab_cursor *pCursor, 
  int idxNum, const char *idxStr,
  int argc, sqlite3_value **argv
){
  PragmaCursor *pCsr = (PragmaCursor*)pCursor;
  PragmaTable *pTab = (PragmaTable*)(pCursor->pVtab);
  char *zSql;
  const char *zTbl;
  int rc = SQLITE_OK;

  if( pCsr->pStmt ){
    sqlite3_finalize(pCsr->pStmt);
    pCsr->pStmt = 0;
  }
  pCsr->iRowid = 0;

  assert( argc==1 );
  zTbl = (const char*)sqlite3_value_text(argv[0]);
  zSql = sqlite3_mprintf("PRAGMA table_info(%Q)", zTbl);
  if( zSql==0 ){
    rc = SQLITE_NOMEM;
  }else{
    rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0);
  }
  return pragmaNext(pCursor);;
}

/*
** xColumn method.
*/
static int pragmaColumn(
  sqlite3_vtab_cursor *pCursor, 
  sqlite3_context *ctx, 
  int iCol
){
  PragmaCursor *pCsr = (PragmaCursor *)pCursor;
  if( iCol>0 ){
    sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pStmt, iCol-1));
  }
  return SQLITE_OK;
}

static int pragmaRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
  PragmaCursor *pCsr = (PragmaCursor *)pCursor;
  *pRowid = pCsr->iRowid;
  return SQLITE_OK;
}

static int registerPragmaVtabs(sqlite3 *db){
  static sqlite3_module pragma_module = {
    0,                            /* iVersion */
    pragmaConnect,                /* xCreate */
    pragmaConnect,                /* xConnect */
    pragmaBestIndex,              /* xBestIndex */
    pragmaDisconnect,             /* xDisconnect */
    pragmaDisconnect,             /* xDestroy */
    pragmaOpen,                   /* xOpen - open a cursor */
    pragmaClose,                  /* xClose - close a cursor */
    pragmaFilter,                 /* xFilter - configure scan constraints */
    pragmaNext,                   /* xNext - advance a cursor */
    pragmaEof,                    /* xEof - check for end of scan */
    pragmaColumn,                 /* xColumn - read data */
    pragmaRowid,                  /* xRowid - read data */
    0,                            /* xUpdate */
    0,                            /* xBegin */
    0,                            /* xSync */
    0,                            /* xCommit */
    0,                            /* xRollback */
    0,                            /* xFindMethod */
    0,                            /* xRename */
  };
  return sqlite3_create_module(db, "pragma_table_info", &pragma_module, 0);
}

/*
** Allocate and return nByte bytes of zeroed memory using sqlite3_malloc(). 
** If the allocation fails, set *pRc to SQLITE_NOMEM and return NULL.
*/
static void *idxMalloc(int *pRc, int nByte){
  void *pRet;
  assert( *pRc==SQLITE_OK );
  assert( nByte>0 );
  pRet = sqlite3_malloc(nByte);
  if( pRet ){
    memset(pRet, 0, nByte);
  }else{
    *pRc = SQLITE_NOMEM;
  }
  return pRet;
}

/*
** Allocate and return a new IdxConstraint object. Set the IdxConstraint.zColl
** variable to point to a copy of nul-terminated string zColl.
*/
static IdxConstraint *idxNewConstraint(int *pRc, const char *zColl){
  IdxConstraint *pNew;
  int nColl = strlen(zColl);

  assert( *pRc==SQLITE_OK );
  pNew = (IdxConstraint*)idxMalloc(pRc, sizeof(IdxConstraint) * nColl + 1);
  if( pNew ){
    pNew->zColl = (char*)&pNew[1];
    memcpy(pNew->zColl, zColl, nColl+1);
  }
  return pNew;
}

/*
** SQLITE_DBCONFIG_WHEREINFO callback.
*/
static void idxWhereInfo(
  void *pCtx,                     /* Pointer to IdxContext structure */
  int eOp, 
  const char *zVal, 
  int iVal, 
  i64 mask
){
  IdxContext *p = (IdxContext*)pCtx;

#if 1
  const char *zOp = 
    eOp==SQLITE_WHEREINFO_TABLE ? "TABLE" :
    eOp==SQLITE_WHEREINFO_EQUALS ? "EQUALS" :
    eOp==SQLITE_WHEREINFO_RANGE ? "RANGE" :
    eOp==SQLITE_WHEREINFO_ORDERBY ? "ORDERBY" :
    eOp==SQLITE_WHEREINFO_NEXTOR ? "NEXTOR" :
    eOp==SQLITE_WHEREINFO_ENDOR ? "ENDOR" :
    eOp==SQLITE_WHEREINFO_BEGINOR ? "BEGINOR" :
    "!error!";
  printf("op=%s zVal=%s iVal=%d mask=%llx\n", zOp, zVal, iVal, mask);
#endif

  if( p->rc==SQLITE_OK ){
    assert( eOp==SQLITE_WHEREINFO_TABLE || p->pScan!=0 );
    switch( eOp ){
      case SQLITE_WHEREINFO_TABLE: {
        int nVal = strlen(zVal);
        IdxScan *pNew = (IdxScan*)idxMalloc(&p->rc, sizeof(IdxScan) + nVal + 1);
        if( !pNew ) return;
        pNew->zTable = (char*)&pNew[1];
        memcpy(pNew->zTable, zVal, nVal+1);
        pNew->pNextScan = p->pScan;
        pNew->covering = mask;
        p->pScan = pNew;
        p->pCurrent = &pNew->where;
        break;
      }

      case SQLITE_WHEREINFO_ORDERBY: {
        IdxConstraint *pNew = idxNewConstraint(&p->rc, zVal);
        IdxConstraint **pp;
        if( pNew==0 ) return;
        pNew->iCol = iVal;
        for(pp=&p->pScan->pOrder; *pp; pp=&(*pp)->pNext);
        *pp = pNew;
        break;
      }

      case SQLITE_WHEREINFO_EQUALS:
      case SQLITE_WHEREINFO_RANGE: {
        IdxConstraint *pNew = idxNewConstraint(&p->rc, zVal);
        if( pNew==0 ) return;
        pNew->iCol = iVal;
        pNew->depmask = mask;

        if( eOp==SQLITE_WHEREINFO_RANGE ){
          pNew->pNext = p->pCurrent->pRange;
          p->pCurrent->pRange = pNew;
        }else{
          pNew->pNext = p->pCurrent->pEq;
          p->pCurrent->pEq = pNew;
        }
        break;
      }

      case SQLITE_WHEREINFO_BEGINOR: {
        assert( 0 );
        break;
      }
      case SQLITE_WHEREINFO_ENDOR: {
        assert( 0 );
        break;
      }
      case SQLITE_WHEREINFO_NEXTOR: {
        assert( 0 );
        break;
      }
    }
  }
}

/*
** An error associated with database handle db has just occurred. Pass
** the error message to callback function xOut.
*/
static void idxDatabaseError(
  sqlite3 *db,                    /* Database handle */
  char **pzErrmsg                 /* Write error here */
){
  *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db));
}

static int idxCreateTables(sqlite3 *db, sqlite3 *dbm, IdxScan *pScan){
  int rc = SQLITE_OK;
  IdxScan *pIter;
  for(pIter=pScan; pIter; pIter=pIter->pNextScan){
  }
}

static void idxScanFree(IdxScan *pScan){
}

/*
** The xOut callback is invoked to return command output to the user. The
** second argument is always a nul-terminated string. The first argument is
** passed zero if the string contains normal output or non-zero if it is an
** error message.
*/
int shellIndexesCommand(
  sqlite3 *db,                         /* Database handle */
  const char *zSql,                    /* SQL to find indexes for */
  void (*xOut)(void*, const char*),    /* Output callback */
  void *pOutCtx,                       /* Context for xOut() */
  char **pzErrmsg                      /* OUT: Error message (sqlite3_malloc) */
){
  int rc = SQLITE_OK;
  sqlite3 *dbm = 0;
  IdxContext ctx;
  sqlite3_stmt *pStmt = 0;        /* Statement compiled from zSql */

  memset(&ctx, 0, sizeof(IdxContext));

  /* Open an in-memory database to work with. The main in-memory 
  ** database schema contains tables similar to those in the users 
  ** database (handle db). The attached in-memory db (aux) contains
  ** application tables used by the code in this file.  */
  rc = sqlite3_open(":memory:", &dbm);
  if( rc==SQLITE_OK ){
    rc = sqlite3_exec(dbm, 
        "ATTACH ':memory:' AS aux;"
        "CREATE TABLE aux.depmask(mask PRIMARY KEY) WITHOUT ROWID;"
        , 0, 0, 0
    );
  }
  if( rc!=SQLITE_OK ){
    idxDatabaseError(dbm, pzErrmsg);
    goto indexes_out;
  }

  /* Analyze the SELECT statement in zSql. */
  ctx.dbm = dbm;
  sqlite3_db_config(db, SQLITE_DBCONFIG_WHEREINFO, idxWhereInfo, (void*)&ctx);
  rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
  sqlite3_db_config(db, SQLITE_DBCONFIG_WHEREINFO, (void*)0, (void*)0);
  if( rc!=SQLITE_OK ){
    idxDatabaseError(db, pzErrmsg);
    goto indexes_out;
  }

  /* Create tables within the main in-memory database. These tables
  ** have the same names, columns and declared types as the tables in
  ** the user database. All constraints except for PRIMARY KEY are
  ** removed. */
  rc = idxCreateTables(db, dbm, ctx.pScan);
  if( rc!=SQLITE_OK ){
    goto indexes_out;
  }

  /* Create candidate indexes within the in-memory database file */

 indexes_out:
  idxScanFree(ctx.pScan);
  sqlite3_close(dbm);
  return rc;
}