Index: main.mk ================================================================== --- main.mk +++ main.mk @@ -470,11 +470,11 @@ libsqlite3.a: $(LIBOBJ) $(AR) libsqlite3.a $(LIBOBJ) $(RANLIB) libsqlite3.a -sqlite3$(EXE): $(TOP)/src/shell.c libsqlite3.a sqlite3.h +sqlite3$(EXE): $(TOP)/src/shell.c libsqlite3.a sqlite3.h $(TOP)/src/shell_indexes.c $(TCCX) $(READLINE_FLAGS) -o sqlite3$(EXE) $(SHELL_OPT) \ $(TOP)/src/shell.c libsqlite3.a $(LIBREADLINE) $(TLIBS) $(THREADLIB) sqldiff$(EXE): $(TOP)/tool/sqldiff.c sqlite3.c sqlite3.h $(TCCX) -o sqldiff$(EXE) -DSQLITE_THREADSAFE=0 \ Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -790,10 +790,17 @@ int sz = va_arg(ap, int); /* IMP: R-47871-25994 */ int cnt = va_arg(ap, int); /* IMP: R-04460-53386 */ rc = setupLookaside(db, pBuf, sz, cnt); break; } +#ifdef SQLITE_SCHEMA_LINT + case SQLITE_DBCONFIG_WHEREINFO: { + db->xWhereInfo = va_arg(ap, void(*)(void*, int, const char*, int, i64)); + db->pWhereInfoCtx = va_arg(ap, void*); + break; + } +#endif default: { static const struct { int op; /* The opcode */ u32 mask; /* Mask of the bit in sqlite3.flags to set/clear */ } aFlagOp[] = { Index: src/pragma.c ================================================================== --- src/pragma.c +++ src/pragma.c @@ -1046,10 +1046,11 @@ ** cid: Column id (numbered from left to right, starting at 0) ** name: Column name ** type: Column declaration type. ** notnull: True if 'NOT NULL' is part of column declaration ** dflt_value: The default value for the column, if any. + ** pk: Non-zero for PK fields. */ case PragTyp_TABLE_INFO: if( zRight ){ Table *pTab; pTab = sqlite3FindTable(db, zRight, zDb); if( pTab ){ Index: src/shell.c ================================================================== --- src/shell.c +++ src/shell.c @@ -154,10 +154,11 @@ #else # define setBinaryMode(X) # define setTextMode(X) #endif +#include "shell_indexes.c" /* True if the timer is enabled */ static int enableTimer = 0; /* Return the current wall-clock time */ @@ -590,11 +591,12 @@ */ typedef struct ShellState ShellState; struct ShellState { sqlite3 *db; /* The database */ int echoOn; /* True to echo input commands */ - int autoEQP; /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */ + int autoEQP; /* Run EXPLAIN QUERY PLAN prior to each SQL stmt */ + int bRecommend; /* Instead of sqlite3_exec(), recommend indexes */ int statsOn; /* True to display memory stats before each finalize */ int scanstatsOn; /* True to display scan stats before each finalize */ int countChanges; /* True to display change counts */ int backslashOn; /* Resolve C-style \x escapes in SQL input text */ int outCount; /* Revert to stdout when reaching zero */ @@ -1541,10 +1543,23 @@ sqlite3_free(p->aiIndent); p->aiIndent = 0; p->nIndent = 0; p->iIndent = 0; } + +typedef struct RecCommandCtx RecCommandCtx; +struct RecCommandCtx { + int (*xCallback)(void*,int,char**,char**,int*); + ShellState *pArg; +}; + +static void recCommandOut(void *pCtx, const char *zLine){ + const char *zCol = "output"; + RecCommandCtx *p = (RecCommandCtx*)pCtx; + int t = SQLITE_TEXT; + p->xCallback(p->pArg, 1, (char**)&zLine, (char**)&zCol, &t); +} /* ** Execute a statement or set of statements. Print ** any result rows/columns depending on the current mode ** set via the supplied callback. @@ -1567,10 +1582,17 @@ const char *zLeftover; /* Tail of unprocessed SQL */ if( pzErrMsg ){ *pzErrMsg = NULL; } + + if( pArg->bRecommend ){ + RecCommandCtx ctx; + ctx.xCallback = xCallback; + ctx.pArg = pArg; + rc = shellIndexesCommand(db, zSql, recCommandOut, &ctx, pzErrMsg); + }else while( zSql[0] && (SQLITE_OK == rc) ){ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover); if( SQLITE_OK != rc ){ if( pzErrMsg ){ @@ -3607,10 +3629,19 @@ rc = 1; } sqlite3_close(pSrc); }else + if( c=='r' && n>=2 && strncmp(azArg[0], "recommend", n)==0 ){ + if( nArg==2 ){ + p->bRecommend = booleanValue(azArg[1]); + }else{ + raw_printf(stderr, "Usage: .recommend on|off\n"); + rc = 1; + } + }else + if( c=='s' && strncmp(azArg[0], "scanstats", n)==0 ){ if( nArg==2 ){ p->scanstatsOn = booleanValue(azArg[1]); #ifndef SQLITE_ENABLE_STMT_SCANSTATUS @@ -4901,10 +4932,13 @@ }else if( rc!=0 ){ utf8_printf(stderr,"Error: unable to process SQL \"%s\"\n", z); if( bail_on_error ) return rc; } } + + }else if( strcmp(z, "-recommend") ){ + data.bRecommend = 1; }else{ utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z); raw_printf(stderr,"Use -help for a list of options.\n"); return 1; } ADDED src/shell_indexes.c Index: src/shell_indexes.c ================================================================== --- /dev/null +++ src/shell_indexes.c @@ -0,0 +1,468 @@ +/* +** 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; inConstraint; 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; +} + + Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -1907,10 +1907,19 @@ ** */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ #define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */ +#define SQLITE_DBCONFIG_WHEREINFO 1004 /* xWhereInfo void* */ + +#define SQLITE_WHEREINFO_TABLE 1 +#define SQLITE_WHEREINFO_EQUALS 2 +#define SQLITE_WHEREINFO_RANGE 3 +#define SQLITE_WHEREINFO_ORDERBY 4 +#define SQLITE_WHEREINFO_BEGINOR 5 +#define SQLITE_WHEREINFO_ENDOR 6 +#define SQLITE_WHEREINFO_NEXTOR 7 /* ** CAPI3REF: Enable Or Disable Extended Result Codes ** METHOD: sqlite3 Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -1271,10 +1271,14 @@ void (*xUnlockNotify)(void **, int); /* Unlock notify callback */ sqlite3 *pNextBlocked; /* Next in list of all blocked connections */ #endif #ifdef SQLITE_USER_AUTHENTICATION sqlite3_userauth auth; /* User authentication information */ +#endif +#ifdef SQLITE_SCHEMA_LINT + void (*xWhereInfo)(void*, int, const char*, int, i64); + void *pWhereInfoCtx; #endif }; /* ** A macro to discover the encoding of a database. Index: src/where.c ================================================================== --- src/where.c +++ src/where.c @@ -3904,180 +3904,107 @@ } return 0; } #ifdef SQLITE_SCHEMA_LINT -static char *whereAppendPrintf(sqlite3 *db, const char *zFmt, ...){ - va_list ap; - char *zRes = 0; - va_start(ap, zFmt); - zRes = sqlite3_vmprintf(zFmt, ap); - if( zRes==0 ){ - db->mallocFailed = 1; - }else if( db->mallocFailed ){ - sqlite3_free(zRes); - zRes = 0; - } - va_end(ap); - return zRes; -} - -/* -** Append a representation of term pTerm to the string in zIn and return -** the result. Or, if an OOM occurs, free zIn and return a NULL pointer. -*/ -static char *whereAppendSingleTerm( - Parse *pParse, - Table *pTab, - int iCol, - int bOr, - char *zIn, - WhereTerm *pTerm -){ - char *zBuf; - sqlite3 *db = pParse->db; - Expr *pX = pTerm->pExpr; - CollSeq *pColl; - const char *zOp = 0; - - if( pTerm->eOperator & (WO_IS|WO_EQ|WO_IN) ){ - zOp = "eq"; - }else if( pTerm->eOperator & (WO_LT|WO_LE|WO_GE|WO_GT) ){ - zOp = "range"; - } - pColl = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pX->pRight); - - if( zOp ){ - const char *zFmt = bOr ? "%z{{%s \"%w\" \"%w\" %lld}}" : - "%z{%s \"%w\" \"%w\" %lld}"; - zBuf = whereAppendPrintf(db, zFmt, zIn, - zOp, pTab->aCol[iCol].zName, - (pColl ? pColl->zName : "BINARY"), - pTerm->prereqRight - ); - }else{ - zBuf = zIn; - } - - return zBuf; -} - -static char *whereTraceWC( - Parse *pParse, - int bInitialSpace, - struct SrcList_item *pItem, - char *zIn, - WhereClause *pWC -){ - sqlite3 *db = pParse->db; - Table *pTab = pItem->pTab; - char *zBuf = zIn; - int iCol; - int ii; - int bFirst = !bInitialSpace; - int bOr = (pWC->op==TK_OR); - - /* List of WO_SINGLE constraints */ - for(iCol=0; iColnCol; iCol++){ - int opMask = WO_SINGLE; - WhereScan scan; - WhereTerm *pTerm; - for(pTerm=whereScanInit(&scan, pWC, pItem->iCursor, iCol, opMask, 0); - pTerm; - pTerm=whereScanNext(&scan) - ){ - /* assert( iCol==pTerm->u.leftColumn ); */ - if( bFirst==0 ) zBuf = whereAppendPrintf(db, "%z ", zBuf); - zBuf = whereAppendSingleTerm(pParse, pTab, iCol, bOr, zBuf, pTerm); - bFirst = 0; - } - } - - /* Add composite - (WO_OR|WO_AND) - constraints */ - for(ii=0; iinTerm; ii++){ - WhereTerm *pTerm = &pWC->a[ii]; - if( pTerm->eOperator & (WO_OR|WO_AND) ){ - const char *zFmt = ((pTerm->eOperator&WO_OR) ? "%z%s{or " : "%z%s{"); - zBuf = whereAppendPrintf(db, zFmt, zBuf, bFirst ? "" : " "); - zBuf = whereTraceWC(pParse, 0, pItem, zBuf, &pTerm->u.pOrInfo->wc); - zBuf = whereAppendPrintf(db, "%z}", zBuf); - bFirst = 0; - } - } - - return zBuf; -} +static void whereTraceWC( + Parse *pParse, + struct SrcList_item *pItem, + WhereClause *pWC, + int bOr +){ + sqlite3 *db = pParse->db; + Table *pTab = pItem->pTab; + void (*x)(void*, int, const char*, int, i64) = db->xWhereInfo; + void *pCtx = db->pWhereInfoCtx; + int bFirst = 1; /* True until first callback is made */ + int ii; + + /* Issue callbacks for WO_SINGLE constraints */ + for(ii=0; iinCol; ii++){ + int opMask = WO_SINGLE; + WhereScan scan; + WhereTerm *pTerm; + for(pTerm=whereScanInit(&scan, pWC, pItem->iCursor, ii, opMask, 0); + pTerm; + pTerm=whereScanNext(&scan) + ){ + int eOp; + Expr *pX = pTerm->pExpr; + CollSeq *pC = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pX->pRight); + if( pTerm->eOperator & (WO_IS|WO_EQ|WO_IN) ){ + eOp = SQLITE_WHEREINFO_EQUALS; + }else{ + eOp = SQLITE_WHEREINFO_RANGE; + } + if( bOr && !bFirst ) x(pCtx, SQLITE_WHEREINFO_NEXTOR, 0, 0, 0); + x(pCtx, eOp, (pC ? pC->zName : "BINARY"), ii, pTerm->prereqRight); + bFirst = 0; + } + } + + /* Callbacks for composite - (WO_OR|WO_AND) - constraints */ + for(ii=0; iinTerm; ii++){ + WhereTerm *pTerm = &pWC->a[ii]; + if( pTerm->eOperator & WO_OR ){ + assert( bOr==0 ); + x(pCtx, SQLITE_WHEREINFO_BEGINOR, 0, 0, 0); + whereTraceWC(pParse, pItem, &pTerm->u.pOrInfo->wc, 1); + x(pCtx, SQLITE_WHEREINFO_ENDOR, 0, 0, 0); + } + if( pTerm->eOperator & WO_AND ){ + if( bOr && !bFirst ) x(pCtx, SQLITE_WHEREINFO_NEXTOR, 0, 0, 0); + whereTraceWC(pParse, pItem, &pTerm->u.pAndInfo->wc, 0); + bFirst = 0; + } + } +} + static void whereTraceBuilder( Parse *pParse, WhereLoopBuilder *p ){ sqlite3 *db = pParse->db; - if( db->xTrace ){ - ExprList *pOrderBy = p->pOrderBy; - WhereInfo *pWInfo = p->pWInfo; - int nTablist = pWInfo->pTabList->nSrc; - int ii; - - /* Loop through each element of the FROM clause. Ignore any sub-selects - ** or views. Invoke the xTrace() callback once for each real table. */ - for(ii=0; iipTabList->a[ii]; - if( pItem->pSelect ) continue; - pTab = pItem->pTab; - nCol = pTab->nCol; - - /* Append the table name to the buffer. */ - zBuf = whereAppendPrintf(db, "\"%w\"", pTab->zName); - - /* Append the list of columns required to create a covering index */ - zBuf = whereAppendPrintf(db, "%z {cols", zBuf); - if( 0==(pItem->colUsed & ((u64)1 << (sizeof(Bitmask)*8-1))) ){ - for(iCol=0; iColcolUsed & ((u64)1 << iCol) ){ - const char *zName = pTab->aCol[iCol].zName; - zBuf = whereAppendPrintf(db, "%z \"%w\"", zBuf, zName); - } - } - } - zBuf = whereAppendPrintf(db, "%z}",zBuf); - - /* Append the contents of WHERE clause */ - zBuf = whereTraceWC(pParse, 1, pItem, zBuf, p->pWC); - - /* Append the ORDER BY clause, if any */ - if( pOrderBy ){ - int i; - int bFirst = 1; - for(i=0; inExpr; i++){ - Expr *pExpr = pOrderBy->a[i].pExpr; - CollSeq *pColl = sqlite3ExprCollSeq(pParse, pExpr); - - pExpr = sqlite3ExprSkipCollate(pExpr); - if( pExpr->op==TK_COLUMN && pExpr->iTable==pItem->iCursor ){ - if( pExpr->iColumn>=0 ){ - const char *zName = pTab->aCol[pExpr->iColumn].zName; - zBuf = whereAppendPrintf(db, "%z%s\"%w\" \"%w\" %s", zBuf, - bFirst ? " {orderby " : " ", zName, pColl->zName, - (pOrderBy->a[i].sortOrder ? "DESC" : "ASC") - ); - bFirst = 0; - } - } - } - if( bFirst==0 ) zBuf = whereAppendPrintf(db, "%z}", zBuf); - } - - /* Pass the buffer to the xTrace() callback, then free it */ - db->xTrace(db->pTraceArg, zBuf); - sqlite3DbFree(db, zBuf); + if( db->xWhereInfo && db->init.busy==0 ){ + void (*x)(void*, int, const char*, int, i64) = db->xWhereInfo; + void *pCtx = db->pWhereInfoCtx; + int ii; + int nTab = p->pWInfo->pTabList->nSrc; + + /* Loop through each element of the FROM clause. Ignore any sub-selects + ** or views. Invoke the xWhereInfo() callback multiple times for each + ** real table. */ + for(ii=0; iipWInfo->pTabList->nSrc; ii++){ + struct SrcList_item *pItem = &p->pWInfo->pTabList->a[ii]; + if( pItem->pSelect==0 ){ + Table *pTab = pItem->pTab; + int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + + /* Table name callback */ + x(pCtx, SQLITE_WHEREINFO_TABLE, pTab->zName, iDb, pItem->colUsed); + + /* ORDER BY callbacks */ + if( p->pOrderBy ){ + int i; + int bFirst = 1; + for(i=0; ipOrderBy->nExpr; i++){ + Expr *pExpr = p->pOrderBy->a[i].pExpr; + CollSeq *pColl = sqlite3ExprCollSeq(pParse, pExpr); + pExpr = sqlite3ExprSkipCollate(pExpr); + if( pExpr->op==TK_COLUMN && pExpr->iTable==pItem->iCursor ){ + int iCol = pExpr->iColumn; + if( iCol>=0 ){ + x(pCtx, SQLITE_WHEREINFO_ORDERBY, pColl->zName, iCol, 0); + } + } + } + } + + /* WHERE callbacks */ + whereTraceWC(pParse, pItem, p->pWC, 0); + } } } } #else # define whereTraceBuilder(x,y)