From 3e6ac1643c3beebc9d2c23f819e25a229a0e8a1d Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 11 Feb 2016 21:01:16 +0000 Subject: [PATCH] Experimental integration of schemalint functionality with the shell tool. Does not work yet. FossilOrigin-Name: ed49f297bcee86674ed673e195610b8cc1d35647 --- main.mk | 2 +- manifest | 31 +-- manifest.uuid | 2 +- src/main.c | 7 + src/pragma.c | 1 + src/shell.c | 36 +++- src/shell_indexes.c | 468 ++++++++++++++++++++++++++++++++++++++++++++ src/sqlite.h.in | 9 + src/sqliteInt.h | 4 + src/where.c | 191 ++++++------------ 10 files changed, 601 insertions(+), 150 deletions(-) create mode 100644 src/shell_indexes.c diff --git a/main.mk b/main.mk index 22d40fa258..a10f043695 100644 --- a/main.mk +++ b/main.mk @@ -472,7 +472,7 @@ 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) diff --git a/manifest b/manifest index 78c07a5223..7da4cdeb1d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\slatest\strunk\schanges\swith\sthis\sbranch. -D 2016-02-09T15:10:56.225 +C Experimental\sintegration\sof\sschemalint\sfunctionality\swith\sthe\sshell\stool.\sDoes\snot\swork\syet. +D 2016-02-11T21:01:16.253 F Makefile.in dac2776c84e0d533b158a9af6e57e05c4a6b19f3 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc b0493f10caddb8adf992a4e6f1943141fc7c6816 @@ -13,7 +13,7 @@ F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903 F autoconf/Makefile.am 1c1657650775960804945dc392e14d9e43c5ed84 F autoconf/Makefile.msc a35b2aab24d1603f3f0ae65cf01686c2578d319c F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7 -F autoconf/README.txt e9757a381e5ce2553dbaa6247bb8ad00eb8d87aa w autoconf/README +F autoconf/README.txt e9757a381e5ce2553dbaa6247bb8ad00eb8d87aa F autoconf/configure.ac 72a5e42beb090b32bca580285dc0ab3c4670adb8 F autoconf/tea/Makefile.in b438a7020446c8a8156e8d97c8914a04833da6fd F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873 @@ -272,7 +272,7 @@ F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 -F main.mk cd48a5d8a6dc59229f4f3fe40771104f2918f789 +F main.mk eeff3d12ebe5945dac88147c641407c22a731131 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 @@ -313,7 +313,7 @@ F src/insert.c 046199e085e69e05af7bef197d53c5b4b402b6fa F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d F src/legacy.c b1b0880fc474abfab89e737b0ecfde0bd7a60902 F src/loadext.c 84996d7d70a605597d79c1f1d7b2012a5fd34f2b -F src/main.c b67a45397b93b7ba8fbd6bfcb03423d245baed05 +F src/main.c 816b9a98a6aca0fd643e77f3610d6a4a1a4c7e24 F src/malloc.c 337e9808b5231855fe28857950f4f60ae42c417f F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 6919bcf12f221868ea066eec27e579fed95ce98b @@ -341,7 +341,7 @@ F src/parse.y d7bff41d460f2df96fb890f36700e85cb0fc5634 F src/pcache.c 73895411fa6b7bd6f0091212feabbe833b358d23 F src/pcache.h 4d0ccaad264d360981ec5e6a2b596d6e85242545 F src/pcache1.c 72f644dc9e1468c72922eff5904048427b817051 -F src/pragma.c 80ee77226d0008d9188356a6cbbe6010866e1bee +F src/pragma.c cfd521558fccd3864ec664af09a061e9e692583f F src/pragma.h 64c78a648751b9f4f297276c4eb7507b14b4628c F src/prepare.c c12b786713df3e8270c0f85f988c5359d8b4d87c F src/printf.c 63e6fb12bbe702dd664dc3703776c090383a5a26 @@ -349,11 +349,12 @@ F src/random.c ba2679f80ec82c4190062d756f22d0c358180696 F src/resolve.c 9f7ce3a3c087afb7597b7c916c99126ff3f12f0c F src/rowset.c 9fe4b3ad7cc00944386bb600233d8f523de07a6e F src/select.c ff80004a9a6ece891a8d9327a88e7b6e2588ee6d -F src/shell.c dcd7a83645ef2a58ee9c6d0ea4714d877d7835c4 -F src/sqlite.h.in cf22ad1d52dca2c9862d63833e581028119aab7e +F src/shell.c 2cde87e03712204231167c4a6c61b0eb5129e105 +F src/shell_indexes.c 3cff393ee86d15fbfbe31f30cd752b46d7779b52 +F src/sqlite.h.in c7db059d3b810b70b83d9ed1436fa813eba22462 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h dfbe62ffd95b99afe2140d8c35b180d11924072d -F src/sqliteInt.h 3aeaff9611acd790c8e76719b33db09ab885d537 +F src/sqliteInt.h a1d0d9613ed7da3657396795e44991fef188c8ee F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46 F src/status.c 70912d7be68e9e2dbc4010c93d344af61d4c59ba F src/table.c 51b46b2a62d1b3a959633d593b89bab5e2c9155e @@ -427,7 +428,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c d21b99fd1458159d0b1ecdccc8ee6ada4fdc4c54 F src/wal.h 2f7c831cf3b071fa548bf2d5cac640846a7ff19c F src/walker.c 0f142b5bd3ed2041fc52d773880748b212e63354 -F src/where.c 438b89caa0cbe17cd32703a8f93baca9789d0474 +F src/where.c 89d5845353fe6d2e77bce52a2c8bea0781c69dad F src/whereInt.h 78b6b4de94db84aecbdc07fe3e38f648eb391e9a F src/wherecode.c 791a784bbf8749d560fdb0b990b607bc4f44a38d F src/whereexpr.c de117970b29471177a6901d60ad83a194671dc03 @@ -980,7 +981,7 @@ F test/savepoint4.test c8f8159ade6d2acd9128be61e1230f1c1edc6cc0 F test/savepoint5.test 0735db177e0ebbaedc39812c8d065075d563c4fd F test/savepoint6.test f41279c5e137139fa5c21485773332c7adb98cd7 F test/savepoint7.test db3db281486c925095f305aad09fe806e5188ff3 -F test/savepointfault.test f044eac64b59f09746c7020ee261734de82bf9b2 w test/savepoint3.test +F test/savepointfault.test f044eac64b59f09746c7020ee261734de82bf9b2 F test/scanstatus.test 5253c219e331318a437f436268e0e82345700285 F test/schema.test 8f7999be894260f151adf15c2c7540f1c6d6a481 F test/schema2.test 906408621ea881fdb496d878b1822572a34e32c5 @@ -1346,7 +1347,7 @@ F test/whereH.test e4b07f7a3c2f5d31195cd33710054c78667573b2 F test/whereI.test eab5b226bbc344ac70d7dc09b963a064860ae6d7 F test/whereJ.test 55a3221706a7ab706293f17cc8f96da563bf0767 F test/whereK.test f8e3cf26a8513ecc7f514f54df9f0572c046c42b -F test/wherefault.test 1374c3aa198388925246475f84ad4cd5f9528864 w test/where8m.test +F test/wherefault.test 1374c3aa198388925246475f84ad4cd5f9528864 F test/wherelimit.test 5e9fd41e79bb2b2d588ed999d641d9c965619b31 F test/wild001.test bca33f499866f04c24510d74baf1e578d4e44b1c F test/win32heap.test ea19770974795cff26e11575e12d422dbd16893c @@ -1429,7 +1430,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 9341491c3a11d5a66e4f88d2af9b0d3799b4f27a ca72be8618e5d466d6f35819ca8bbd2b84269959 -R c3affb84a750d99cc1d1198e66d07e76 +P 1a4182eedd0143c3f71b3d97f1d1bb25adeba617 +R 6253a80a5a7097c3b24b24ce68900613 U dan -Z dbcdec085ed65802fe686b0a9369fd51 +Z bddf3d3a7cd183a6a2362ed54e10358b diff --git a/manifest.uuid b/manifest.uuid index b2c40d21e2..38f9cc7372 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -1a4182eedd0143c3f71b3d97f1d1bb25adeba617 \ No newline at end of file +ed49f297bcee86674ed673e195610b8cc1d35647 \ No newline at end of file diff --git a/src/main.c b/src/main.c index 922af1315a..72f12d2ac2 100644 --- a/src/main.c +++ b/src/main.c @@ -792,6 +792,13 @@ int sqlite3_db_config(sqlite3 *db, int op, ...){ 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 */ diff --git a/src/pragma.c b/src/pragma.c index c34d5421c2..2b4058ca16 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1048,6 +1048,7 @@ void sqlite3Pragma( ** 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; diff --git a/src/shell.c b/src/shell.c index 3f8b22f4fa..e2fe41291d 100644 --- a/src/shell.c +++ b/src/shell.c @@ -156,6 +156,7 @@ static void setTextMode(FILE *out){ # define setTextMode(X) #endif +#include "shell_indexes.c" /* True if the timer is enabled */ static int enableTimer = 0; @@ -592,7 +593,8 @@ 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 */ @@ -1544,6 +1546,19 @@ static void explain_data_delete(ShellState *p){ 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 @@ -1570,6 +1585,13 @@ static int shell_exec( *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 ){ @@ -3609,6 +3631,15 @@ static int do_meta_command(char *zLine, ShellState *p){ 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 ){ @@ -4903,6 +4934,9 @@ int SQLITE_CDECL main(int argc, char **argv){ 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"); diff --git a/src/shell_indexes.c b/src/shell_indexes.c new file mode 100644 index 0000000000..8e105fec1b --- /dev/null +++ b/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; +} + + diff --git a/src/sqlite.h.in b/src/sqlite.h.in index fce396c0f6..03d3d72f6f 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -1909,6 +1909,15 @@ struct sqlite3_mem_methods { #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 /* diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 760c1f4d21..57c58195e0 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1274,6 +1274,10 @@ struct sqlite3 { #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 }; /* diff --git a/src/where.c b/src/where.c index af6bb20384..3724fa33e0 100644 --- a/src/where.c +++ b/src/where.c @@ -3906,176 +3906,103 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ } #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( +static void whereTraceWC( Parse *pParse, - int bInitialSpace, struct SrcList_item *pItem, - char *zIn, - WhereClause *pWC + WhereClause *pWC, + int bOr ){ sqlite3 *db = pParse->db; Table *pTab = pItem->pTab; - char *zBuf = zIn; - int iCol; + 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; - int bFirst = !bInitialSpace; - int bOr = (pWC->op==TK_OR); - /* List of WO_SINGLE constraints */ - for(iCol=0; iColnCol; iCol++){ + /* 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, iCol, opMask, 0); + for(pTerm=whereScanInit(&scan, pWC, pItem->iCursor, ii, 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); + 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; } } - /* Add composite - (WO_OR|WO_AND) - constraints */ + /* Callbacks for 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); + 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; } } - - return zBuf; } + 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; + 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 xTrace() callback once 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); - struct SrcList_item *pItem = &pWInfo->pTabList->a[ii]; - if( pItem->pSelect ) continue; - pTab = pItem->pTab; - nCol = pTab->nCol; + /* Table name callback */ + x(pCtx, SQLITE_WHEREINFO_TABLE, pTab->zName, iDb, pItem->colUsed); - /* 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; + /* 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); + } } } } - 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); + /* WHERE callbacks */ + whereTraceWC(pParse, pItem, p->pWC, 0); + } } } }