From 0fe7e7d9242dfccb1240c613a2e4608c0023259c Mon Sep 17 00:00:00 2001 From: drh <> Date: Tue, 1 Feb 2022 14:58:29 +0000 Subject: [PATCH 1/9] Add new interfaces to enable virtual table to process IN operator constraints all at once, rather than one element at a time. FossilOrigin-Name: eb84b80e1f6d8c32bf0c9e1731f0233de0160a13f714f766779ae01fdf504e7b --- manifest | 33 ++++++++++--------- manifest.uuid | 2 +- src/loadext.c | 3 ++ src/sqlite.h.in | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ src/sqlite3ext.h | 6 ++++ src/sqliteInt.h | 9 +++--- src/vdbe.c | 21 ++++++++++++ src/vdbeInt.h | 7 ++++ src/vdbeapi.c | 77 ++++++++++++++++++++++++++++++++++++++++++++ src/where.c | 37 +++++++++++++++++++-- src/whereInt.h | 1 + src/wherecode.c | 25 ++++++++++---- 12 files changed, 277 insertions(+), 28 deletions(-) diff --git a/manifest b/manifest index 1b9116fe1a..6161852d73 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C CLI:\sTake\sextra\scare\sto\snot\ssplit\sa\smulti-byte\sunicode\scharacter\swhen\sdoing\nwordwrap. -D 2022-02-01T13:17:11.049 +C Add\snew\sinterfaces\sto\senable\svirtual\stable\sto\sprocess\sIN\soperator\sconstraints\nall\sat\sonce,\srather\sthan\sone\selement\sat\sa\stime. +D 2022-02-01T14:58:29.660 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -515,7 +515,7 @@ F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 1eea44389de3768ac98588c1410171cd53e7c6ad1af74049983dcbac82093de0 F src/json.c 78fdec9af3a8bfb5ae685707b2701276fec1942b8f5f26689b2701debe32bcd2 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa -F src/loadext.c 657534339585ac234839e5187aa51d8802f292e0771c4f874b3af1f1223f81e2 +F src/loadext.c aa919a6a7884f8b34d7b791841b24d14b1b0ab43f45b3940f4851043b2855c0c F src/main.c 2b6b0dbfeb14d4bb57e368604b0736b2aa42b51b00339d399b01d6b1fc9b4960 F src/malloc.c fec841aa0a0400a6f7d20706178a5d8e8219a6bf562b6fe712c17f6c26813266 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 @@ -554,10 +554,10 @@ F src/resolve.c 24032ae57aec10df2f3fa2e20be0aae7d256bc704124b76c52d763440c7c0fe9 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c a6d2d4bed279d7fe4fcedaf297eaf6441e8e17c6e3947a32d24d23be52ac02f2 F src/shell.c.in 2f58e6aa6b3d2012db32f1c5fa4591e9d12fd582904632b4fc8f57a382b98fd3 -F src/sqlite.h.in eaade58049152dac850d57415bcced885ca27ae9582f8aea2cfb7f1db78a521b +F src/sqlite.h.in 0aed2b88e91d03314121cd1e546441e37513929793c3cf7584b5b7ce445a9128 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 -F src/sqlite3ext.h 5d54cf13d3406d8eb65d921a0d3c349de6126b732e695e79ecd4830ce86b4f8a -F src/sqliteInt.h 8ef2996e02476f73e41ba977f819bda0cc68b7ce238cf404b9b8930df57bc1d0 +F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6 +F src/sqliteInt.h 838df3e9ba9390058076d2f50c7efde9e0e8747303e788cf5bbe05402ab10924 F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -624,10 +624,10 @@ F src/upsert.c 8789047a8f0a601ea42fa0256d1ba3190c13746b6ba940fe2d25643a7e991937 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 F src/util.c 602fe229f32a96ceccae4f40824129669582096f7c355f53dbac156c9fecef23 F src/vacuum.c 6c38ddc52f0619865c91dae9c441d4d48bf3040d7dc1bc5b22da1e45547ed0b3 -F src/vdbe.c cfe1980fbeb87eb35297b4a41808034761f26277cf45c9cf3e4eac20edcba1d5 +F src/vdbe.c d6694187a2819df7c2df3bd568fd059617c3edef4aa87e28a8121b02818f4ebf F src/vdbe.h 25dabb25c7e157b84e59260cfb5b466c3ac103ede9f36f4db371332c47601abe -F src/vdbeInt.h d89d5d2150500cfb08615329fd20aea9d746bba5f2c3ecb8a17e2d2d9be029e5 -F src/vdbeapi.c 22c79072ae7d8a01e9bcae8ba16e918d60d202eaa9553b5fda38f99f7464d99a +F src/vdbeInt.h 24d58f12f642dcac102fa75d08e99ad06b6cbc66bf4948bb61e2e223ef9518b6 +F src/vdbeapi.c 4d26cc9eb1a0f937e8d360578dc56d00145bac08afd503ba6ac843007f7d8c1f F src/vdbeaux.c e761b8011baec7a4773f0a7594783f2cd71f699ab187c4aad917529ab8acd3fe F src/vdbeblob.c 5e61ce31aca17db8fb60395407457a8c1c7fb471dde405e0cd675974611dcfcd F src/vdbemem.c eb6042667c02c3ef1f968235b4a170e31b23a4b6a57f65a8454eab4d36f14b7f @@ -639,9 +639,9 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c b9df133a705093da8977da5eb202eaadb844839f1c7297c08d33471f5491843d F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b -F src/where.c c4a80044708b1000a2b875a6623acfb636acd1c740063b60ea50e359d305829e -F src/whereInt.h 0748a6fce98b41862445906922a809146ff7ef4de16ed9022b0bc4e5c43aa60a -F src/wherecode.c c313ccf5ed13dc7e88c64f93733f414dee369a212508a866878696d83c64fc36 +F src/where.c 45173f696570d3bc5a939c80c147afe7b79763c6402d3ba7f0dc5477153afa59 +F src/whereInt.h 1d821657238a0bd12b3c8f2926c7f8f9294bc5efe20af53c7c50d53a0a026cb9 +F src/wherecode.c 5879604677f0bdfb8d95ff616d834daecc12256346b7d9ad96a7e84a1cb08fdc F src/whereexpr.c ddb6ab49f745154c37dbdb291433c933e00175929647290a11f487af701d0392 F src/window.c dfaec4abc6012cbc18e4a202ca3a5d5a0efcc4011d86a06d882ddaab8aedee4d F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 @@ -1942,8 +1942,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 1b528e31f8c62797e0814568b520c0680ff23a2ee877ca6aa70a167d40ebdf80 -R fe91994efd36a88115500529351b7e2e +P 00b1b7020a564976da3237532434e47ccf17eb5d620e6ac45f3e70b5d5739200 +R c34f556d2df8080b897efb591747c003 +T *branch * batch-in-operator +T *sym-batch-in-operator * +T -sym-trunk * U drh -Z 4d3a3178c78a3baab7f87b0b2d56c876 +Z 972db892fdcb2bcf2d566ac41a6d13d0 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 0347e193b4..5d98a43c51 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -00b1b7020a564976da3237532434e47ccf17eb5d620e6ac45f3e70b5d5739200 \ No newline at end of file +eb84b80e1f6d8c32bf0c9e1731f0233de0160a13f714f766779ae01fdf504e7b \ No newline at end of file diff --git a/src/loadext.c b/src/loadext.c index a4aad7e748..603516e18a 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -489,6 +489,9 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_error_offset, sqlite3_vtab_rhs_value, sqlite3_vtab_distinct, + sqlite3_vtab_in, + sqlite3_vtab_in_first, + sqlite3_vtab_in_next }; /* True if x is the directory separator character diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 82f844d6a7..40aa917757 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -9604,6 +9604,90 @@ SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_info*,int); */ int sqlite3_vtab_distinct(sqlite3_index_info*); +/* +** CAPI3REF: Identify and handle IN(...) constraints in xBestIndex +** +** This API may only be used from within an xBestIndex() callback. The +** results of calling it from outside of an xBestIndex() callback are +** undefined. +** +** When a column of a virtual table is subject to a "col IN (...)" +** constraint, this is passed through to the xBestIndex method of the +** virtual table as an SQLITE_INDEX_CONSTRAINT_EQ constraint. Then, if +** the virtual table indicates that it can handle the constraint, SQLite +** uses the xFilter and xNext methods of the virtual table to query +** separately for each distinct element in RHS of the IN(...) expression. +** This API allows a virtual table implementation to determine which +** SQLITE_INDEX_CONSTRAINT_EQ constraints are actually IN(...) constraints, +** and to handle them using a single xFilter scan. +** +** If the second argument passed to this function is not the index of an +** SQLITE_INDEX_CONSTRAINT_EQ constraint in the aConstraint[] array of the +** sqlite3_index_info object, or if the SQLITE_INDEX_CONSTRAINT_EQ is not +** really an IN(...) expression, then this function is a no-op. Zero is +** returned in this case. +** +** Otherwise, if parameter iCons is the index of an SQLITE_INDEX_CONSTRAINT_EQ +** constraint that is really an IN(...) expression, then this function +** returns non-zero. In this case, the call also specifies whether SQLite +** should invoke xFilter() once for each element on the RHS of the IN(...) +** expression (the default, if bHandle is zero), or just once for the entire +** list (if bHandle is non-zero), should the associated +** aConstraintUsage[].argvIndex variable be set by xBestIndex. +** +** In cases where the list on the RHS of an IN(...) constraint is passed to a +** single xFilter() call, the (sqlite3_value*) passed appears in most +** respects to be a NULL value. Except, it may be used with the +** sqlite3_vtab_in_first() and sqlite3_vtab_in_next() APIs to interate through +** the list of values. +*/ +int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle); + +/* +** CAPI3REF: Visit the first element of an IN(...) list passed to xFilter +** +** This API may only be used from within an xFilter() callback. The +** results of calling it from outside of an xFilter() callback are +** undefined. +** +** If the (sqlite3_value*) passed as the first argument to this function +** is not a value representing the RHS of an IN(...) operator (see +** API function sqlite3_vtab_in()), of if the RHS of the IN(...) operator +** is an empty set, this function sets (*ppOut) to NULL and returns +** SQLITE_OK. +** +** Otherwise, if no error occurs, it sets (*ppOut) to point to an object +** containing the first value in the list and returns SQLITE_OK. The +** pointer is valid until either the xFilter() call returns or until the +** next call to sqlite3_vtab_in_first() or sqlite3_vtab_in_next() with +** the same first argument. +** +** If an error occurs, (*ppOut) is set to NULL and an SQLite error code +** returned. +*/ +int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut); + +/* +** CAPI3REF: Visit the next element of an IN(...) list passed to xFilter +** +** This function is called after a successful call to sqlite3_vtab_in_first() +** to visit the next and subsequent elements of an IN(...) list passed +** to an xFilter callback. Example usage: +** +**
+**   for(rc=sqlite3_vtab_in_first(pList, &pVal);
+**       rc==SQLITE_OK && pVal
+**       rc=sqlite3_vtab_in_next(pList, &pVal)
+**   ){
+**     // do something with pVal
+**   }
+**   if( rc!=SQLITE_OK ){
+**     // an error has occurred
+**   }
+** 
+*/ +int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut); + /* ** CAPI3REF: Constraint values in xBestIndex() ** METHOD: sqlite3_index_info diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 88010b9d8d..2eac4f3f05 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -348,6 +348,9 @@ struct sqlite3_api_routines { int (*error_offset)(sqlite3*); int (*vtab_rhs_value)(sqlite3_index_info*,int,sqlite3_value**); int (*vtab_distinct)(sqlite3_index_info*); + int (*vtab_in)(sqlite3_index_info*,int,int); + int (*vtab_in_first)(sqlite3_value*,sqlite3_value**); + int (*vtab_in_next)(sqlite3_value*,sqlite3_value**); }; /* @@ -663,6 +666,9 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_error_offset sqlite3_api->error_offset #define sqlite3_vtab_rhs_value sqlite3_api->vtab_rhs_value #define sqlite3_vtab_distinct sqlite3_api->vtab_distinct +#define sqlite3_vtab_in sqlite3_api->vtab_in +#define sqlite3_vtab_in_first sqlite3_api->vtab_in_first +#define sqlite3_vtab_in_next sqlite3_api->vtab_in_next #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 6d93650713..374fc95541 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1231,10 +1231,11 @@ typedef struct With With; /* ** A bit in a Bitmask */ -#define MASKBIT(n) (((Bitmask)1)<<(n)) -#define MASKBIT64(n) (((u64)1)<<(n)) -#define MASKBIT32(n) (((unsigned int)1)<<(n)) -#define ALLBITS ((Bitmask)-1) +#define MASKBIT(n) (((Bitmask)1)<<(n)) +#define MASKBIT64(n) (((u64)1)<<(n)) +#define MASKBIT32(n) (((unsigned int)1)<<(n)) +#define SMASKBIT32(n) ((n)<=31?((unsigned int)1)<<(n):0) +#define ALLBITS ((Bitmask)-1) /* A VList object records a mapping between parameters/variables/wildcards ** in the SQL statement (such as $abc, @pqr, or :xyz) and the integer diff --git a/src/vdbe.c b/src/vdbe.c index d4ff33141b..5643d43ce9 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -7734,6 +7734,27 @@ case OP_VOpen: { } #endif /* SQLITE_OMIT_VIRTUALTABLE */ +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VInitIn P1 P2 P3 * * +** Synopsis: r[P2]=cursor over eph table P1. +** +** Initialize register P2 as a value that can be used as an iterator over +** the contents of ephemeral table P1 by an xFilter callback implementation. +** Register P3 is used as a cache by the iterator. +*/ +case OP_VInitIn: { /* out2 */ + VdbeCursor *pC; + pC = p->apCsr[pOp->p1]; + pOut = out2Prerelease(p, pOp); + pOut->z = (char*)(pC->uc.pCursor); + pOut->u.pVal = &aMem[pOp->p3]; + pOut->uTemp = SQLITE_VTAB_IN_MAGIC; + pOut->flags = MEM_Null; + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + + #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VFilter P1 P2 P3 P4 * ** Synopsis: iplan=r[P3] zplan='P4' diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 376c9edac9..9ddb742a49 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -195,6 +195,12 @@ struct VdbeFrame { */ #define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))]) +/* Magic number for Mem.uTemp when it is acting as as the cache for the +** IN(...) iterator for sqlite3_vtab_in_next() +*/ +#define SQLITE_VTAB_IN_MAGIC 0xd3ab12ec + + /* ** Internally, the vdbe manipulates nearly all SQL values as Mem ** structures. Each Mem struct may cache multiple representations (string, @@ -207,6 +213,7 @@ struct sqlite3_value { int nZero; /* Extra zero bytes when MEM_Zero and MEM_Blob set */ const char *zPType; /* Pointer type when MEM_Term|MEM_Subtype|MEM_Null */ FuncDef *pDef; /* Used only when flags==MEM_Agg */ + sqlite3_value *pVal;/* Current value for xFilter IN(...) iterator */ } u; u16 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */ u8 enc; /* SQLITE_UTF8, SQLITE_UTF16BE, SQLITE_UTF16LE */ diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 17df807de4..1521eee79b 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -846,6 +846,83 @@ int sqlite3_vtab_nochange(sqlite3_context *p){ return sqlite3_value_nochange(p->pOut); } +/* +** The first argument is an iterator value created by VDBE instruction +** OP_VInitIn. The iterator is guaranteed to point to a valid entry. This +** function attempts to load the current value from the iterator into +** object pVal->u.pVal. If successful, (*ppOut) is set to point to +** pVal->u.pVal and SQLITE_OK is returned. Otherwise, if an error +** occurs, an SQLite error code is returned and (*ppOut) is left unchanged. +*/ +static int vtabInLoadValue(sqlite3_value *pVal, sqlite3_value **ppOut){ + BtCursor *pCsr = (BtCursor*)pVal->z; + sqlite3_value *pOut = pVal->u.pVal; + int sz; + int rc; + + sz = (int)sqlite3BtreePayloadSize(pCsr); + if( sz>pVal->szMalloc ){ + if( pVal->szMalloc==0 ) pVal->zMalloc = 0; + pVal->zMalloc = sqlite3DbReallocOrFree(pVal->db, pVal->zMalloc, sz*2); + if( pVal->zMalloc ){ + pVal->szMalloc = sqlite3DbMallocSize(pVal->db, pVal->zMalloc); + }else{ + pVal->szMalloc = 0; + return SQLITE_NOMEM_BKPT; + } + } + + rc = sqlite3BtreePayload(pCsr, 0, sz, pVal->zMalloc); + if( rc==SQLITE_OK ){ + u32 iSerial; + int iOff = 1 + getVarint32((const u8*)&pVal->zMalloc[1], iSerial); + sqlite3VdbeSerialGet((const u8*)&pVal->zMalloc[iOff], iSerial, pOut); + pOut->enc = ENC(pVal->db); + *ppOut = pOut; + } + return rc; +} + +/* +** Implementation of sqlite3_vtab_in_first() (if bNext==0) and +** sqlite3_vtab_in_next() (if bNext!=0). +*/ +static int vtabInOp(sqlite3_value *pVal, sqlite3_value **ppOut, int bNext){ + int rc = SQLITE_OK; + *ppOut = 0; + if( pVal && pVal->uTemp==SQLITE_VTAB_IN_MAGIC ){ + BtCursor *pCsr = (BtCursor*)pVal->z; + + if( bNext ){ + rc = sqlite3BtreeNext(pCsr, 0); + }else{ + int dummy = 0; + rc = sqlite3BtreeFirst(pCsr, &dummy); + } + + if( rc==SQLITE_OK && sqlite3BtreeEof(pCsr)==0 ){ + rc = vtabInLoadValue(pVal, ppOut); + } + } + return rc; +} + +/* +** Set the iterator value pVal to point to the first value in the set. +** Set (*ppOut) to point to this value before returning. +*/ +int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut){ + return vtabInOp(pVal, ppOut, 0); +} + +/* +** Set the iterator value pVal to point to the next value in the set. +** Set (*ppOut) to point to this value before returning. +*/ +int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut){ + return vtabInOp(pVal, ppOut, 1); +} + /* ** Return the current time for a statement. If the current time ** is requested more than once within the same run of a single prepared diff --git a/src/where.c b/src/where.c index 4efb7084da..0db38ed7cb 100644 --- a/src/where.c +++ b/src/where.c @@ -33,6 +33,8 @@ struct HiddenIndexInfo { WhereClause *pWC; /* The Where clause being analyzed */ Parse *pParse; /* The parsing context */ int eDistinct; /* Value to return from sqlite3_vtab_distinct() */ + u32 mIn; /* Mask of terms that are IN (...) */ + u32 mHandleIn; /* Terms that vtab will handle as IN (...) */ sqlite3_value *aRhs[1]; /* RHS values for constraints. MUST BE LAST ** because extra space is allocated to hold up ** to nTerm such values */ @@ -1119,6 +1121,7 @@ static sqlite3_index_info *allocateIndexInfo( int nOrderBy; sqlite3_index_info *pIdxInfo; u16 mNoOmit = 0; + u32 mIn = 0; const Table *pTab; int eDistinct = 0; ExprList *pOrderBy = pWInfo->pOrderBy; @@ -1143,6 +1146,12 @@ static sqlite3_index_info *allocateIndexInfo( testcase( pTerm->eOperator & WO_ALL ); if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue; if( pTerm->wtFlags & TERM_VNULL ) continue; + if( (pTerm->eOperator & WO_IN)!=0 + && ExprHasProperty(pTerm->pExpr, EP_xIsSelect)==0 + ){ + mIn |= SMASKBIT32(i); + } + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); assert( pTerm->u.x.leftColumn>=XN_ROWID ); assert( pTerm->u.x.leftColumnnCol ); @@ -1204,7 +1213,7 @@ static sqlite3_index_info *allocateIndexInfo( /* No matches cause a break out of the loop */ break; } - if( i==n){ + if( i==n ){ nOrderBy = n; if( (pWInfo->wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY)) ){ eDistinct = 1 + ((pWInfo->wctrlFlags & WHERE_DISTINCTBY)!=0); @@ -1232,6 +1241,7 @@ static sqlite3_index_info *allocateIndexInfo( pHidden->pWC = pWC; pHidden->pParse = pParse; pHidden->eDistinct = eDistinct; + pHidden->mIn = mIn; for(i=j=0, pTerm=pWC->a; inTerm; i++, pTerm++){ u16 op; if( (pTerm->wtFlags & TERM_OK)==0 ) continue; @@ -3499,6 +3509,7 @@ static int whereLoopAddVirtualOne( int *pbRetryLimit /* OUT: Retry without LIMIT/OFFSET */ ){ WhereClause *pWC = pBuilder->pWC; + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; struct sqlite3_index_constraint *pIdxCons; struct sqlite3_index_constraint_usage *pUsage = pIdxInfo->aConstraintUsage; int i; @@ -3537,6 +3548,7 @@ static int whereLoopAddVirtualOne( pIdxInfo->estimatedRows = 25; pIdxInfo->idxFlags = 0; pIdxInfo->colUsed = (sqlite3_int64)pSrc->colUsed; + pHidden->mHandleIn = 0; /* Invoke the virtual table xBestIndex() method */ rc = vtabBestIndex(pParse, pSrc->pTab, pIdxInfo); @@ -3593,7 +3605,9 @@ static int whereLoopAddVirtualOne( pNew->u.vtab.bOmitOffset = 1; } } - if( (pTerm->eOperator & WO_IN)!=0 ){ + if( SMASKBIT32(i) & pHidden->mHandleIn ){ + pNew->u.vtab.mHandleIn |= SMASKBIT32(iTerm); + }else if( (pTerm->eOperator & WO_IN)!=0 ){ /* A virtual table that is constrained by an IN clause may not ** consume the ORDER BY clause because (1) the order of IN terms ** is not necessarily related to the order of output terms and @@ -3691,6 +3705,25 @@ const char *sqlite3_vtab_collation(sqlite3_index_info *pIdxInfo, int iCons){ return zRet; } +/* +** Return true if constraint iCons is really an IN(...) constraint, or +** false otherwise. If iCons is an IN(...) constraint, set (if bHandle!=0) +** or clear (if bHandle==0) the flag to handle it using an iterator. +*/ +int sqlite3_vtab_in(sqlite3_index_info *pIdxInfo, int iCons, int bHandle){ + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + u32 m = SMASKBIT32(iCons); + if( m & pHidden->mIn ){ + if( bHandle==0 ){ + pHidden->mHandleIn &= ~m; + }else{ + pHidden->mHandleIn |= m; + } + return 1; + } + return 0; +} + /* ** This interface is callable from within the xBestIndex callback only. ** diff --git a/src/whereInt.h b/src/whereInt.h index 14c40fcb43..fc3740f519 100644 --- a/src/whereInt.h +++ b/src/whereInt.h @@ -128,6 +128,7 @@ struct WhereLoop { i8 isOrdered; /* True if satisfies ORDER BY */ u16 omitMask; /* Terms that may be omitted */ char *idxStr; /* Index identifier string */ + u32 mHandleIn; /* Terms to handle as IN(...) instead of == */ } vtab; } u; u32 wsFlags; /* WHERE_* flags describing the plan */ diff --git a/src/wherecode.c b/src/wherecode.c index 2beb596e69..8d7163ce08 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -1532,8 +1532,15 @@ Bitmask sqlite3WhereCodeOneLoopStart( pTerm = pLoop->aLTerm[j]; if( NEVER(pTerm==0) ) continue; if( pTerm->eOperator & WO_IN ){ - codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); - addrNotFound = pLevel->addrNxt; + if( SMASKBIT32(j) & pLoop->u.vtab.mHandleIn ){ + int iTab = pParse->nTab++; + int iCache = ++pParse->nMem; + sqlite3CodeRhsOfIN(pParse, pTerm->pExpr, iTab); + sqlite3VdbeAddOp3(v, OP_VInitIn, iTab, iTarget, iCache); + }else{ + codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); + addrNotFound = pLevel->addrNxt; + } }else{ Expr *pRight = pTerm->pExpr->pRight; codeExprOrVector(pParse, pRight, iTarget, 1); @@ -1568,13 +1575,19 @@ Bitmask sqlite3WhereCodeOneLoopStart( iIn = 0; } for(j=nConstraint-1; j>=0; j--){ + int bIn; /* True to generate byte code to loop over RHS IN values */ pTerm = pLoop->aLTerm[j]; - if( (pTerm->eOperator & WO_IN)!=0 ) iIn--; + if( (pTerm->eOperator & WO_IN)!=0 + && (SMASKBIT32(j) & pLoop->u.vtab.mHandleIn)==0 + ){ + bIn = 1; + }else{ + bIn = 0; + } + if( bIn ) iIn--; if( j<16 && (pLoop->u.vtab.omitMask>>j)&1 ){ disableTerm(pLevel, pTerm); - }else if( (pTerm->eOperator & WO_IN)!=0 - && sqlite3ExprVectorSize(pTerm->pExpr->pLeft)==1 - ){ + }else if( bIn && sqlite3ExprVectorSize(pTerm->pExpr->pLeft)==1 ){ Expr *pCompare; /* The comparison operator */ Expr *pRight; /* RHS of the comparison */ VdbeOp *pOp; /* Opcode to access the value of the IN constraint */ From a9f18f0172d01cf1da8e962dcd33010fac28ef56 Mon Sep 17 00:00:00 2001 From: drh <> Date: Tue, 1 Feb 2022 16:30:57 +0000 Subject: [PATCH 2/9] Index in 2nd argument to sqlite3_vtab_in() should be on the aConstraint[] array, not the internal array of all constraints. FossilOrigin-Name: 5acf90a931b27b7d627c0a8fee68170430e09b028d6643b959b0ec14fd59f7ac --- manifest | 15 ++++++--------- manifest.uuid | 2 +- src/where.c | 15 +++++++-------- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/manifest b/manifest index 6161852d73..7b186939fd 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\snew\sinterfaces\sto\senable\svirtual\stable\sto\sprocess\sIN\soperator\sconstraints\nall\sat\sonce,\srather\sthan\sone\selement\sat\sa\stime. -D 2022-02-01T14:58:29.660 +C Index\sin\s2nd\sargument\sto\ssqlite3_vtab_in()\sshould\sbe\son\sthe\saConstraint[]\narray,\snot\sthe\sinternal\sarray\sof\sall\sconstraints. +D 2022-02-01T16:30:57.444 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -639,7 +639,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c b9df133a705093da8977da5eb202eaadb844839f1c7297c08d33471f5491843d F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b -F src/where.c 45173f696570d3bc5a939c80c147afe7b79763c6402d3ba7f0dc5477153afa59 +F src/where.c cbf02091ed71784f7972ca39aaecb22822781f3b96905ece1a30e80161629769 F src/whereInt.h 1d821657238a0bd12b3c8f2926c7f8f9294bc5efe20af53c7c50d53a0a026cb9 F src/wherecode.c 5879604677f0bdfb8d95ff616d834daecc12256346b7d9ad96a7e84a1cb08fdc F src/whereexpr.c ddb6ab49f745154c37dbdb291433c933e00175929647290a11f487af701d0392 @@ -1942,11 +1942,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 00b1b7020a564976da3237532434e47ccf17eb5d620e6ac45f3e70b5d5739200 -R c34f556d2df8080b897efb591747c003 -T *branch * batch-in-operator -T *sym-batch-in-operator * -T -sym-trunk * +P eb84b80e1f6d8c32bf0c9e1731f0233de0160a13f714f766779ae01fdf504e7b +R 320e3be3dedfcb44854348cea4a95b96 U drh -Z 972db892fdcb2bcf2d566ac41a6d13d0 +Z 4a8e49183eb36a5777c7e6fa2fd8d0f2 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 5d98a43c51..93bc0e7791 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -eb84b80e1f6d8c32bf0c9e1731f0233de0160a13f714f766779ae01fdf504e7b \ No newline at end of file +5acf90a931b27b7d627c0a8fee68170430e09b028d6643b959b0ec14fd59f7ac \ No newline at end of file diff --git a/src/where.c b/src/where.c index 0db38ed7cb..4f65e934fc 100644 --- a/src/where.c +++ b/src/where.c @@ -1121,7 +1121,6 @@ static sqlite3_index_info *allocateIndexInfo( int nOrderBy; sqlite3_index_info *pIdxInfo; u16 mNoOmit = 0; - u32 mIn = 0; const Table *pTab; int eDistinct = 0; ExprList *pOrderBy = pWInfo->pOrderBy; @@ -1146,11 +1145,6 @@ static sqlite3_index_info *allocateIndexInfo( testcase( pTerm->eOperator & WO_ALL ); if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue; if( pTerm->wtFlags & TERM_VNULL ) continue; - if( (pTerm->eOperator & WO_IN)!=0 - && ExprHasProperty(pTerm->pExpr, EP_xIsSelect)==0 - ){ - mIn |= SMASKBIT32(i); - } assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); assert( pTerm->u.x.leftColumn>=XN_ROWID ); @@ -1241,14 +1235,19 @@ static sqlite3_index_info *allocateIndexInfo( pHidden->pWC = pWC; pHidden->pParse = pParse; pHidden->eDistinct = eDistinct; - pHidden->mIn = mIn; + pHidden->mIn = 0; for(i=j=0, pTerm=pWC->a; inTerm; i++, pTerm++){ u16 op; if( (pTerm->wtFlags & TERM_OK)==0 ) continue; pIdxCons[j].iColumn = pTerm->u.x.leftColumn; pIdxCons[j].iTermOffset = i; op = pTerm->eOperator & WO_ALL; - if( op==WO_IN ) op = WO_EQ; + if( op==WO_IN ){ + if( ExprHasProperty(pTerm->pExpr, EP_xIsSelect)==0 ){ + pHidden->mIn |= SMASKBIT32(j); + } + op = WO_EQ; + } if( op==WO_AUX ){ pIdxCons[j].op = pTerm->eMatchOp; }else if( op & (WO_ISNULL|WO_IS) ){ From b30298d3ea05b73fb9c7d1556f6b2b054dd33d7a Mon Sep 17 00:00:00 2001 From: drh <> Date: Tue, 1 Feb 2022 21:59:43 +0000 Subject: [PATCH 3/9] Tweaks to the sqlite3_vtab_in() interface. FossilOrigin-Name: 75040183b8e14f20bfedfdcc1a9fb968f2f0193bc698605d1b4791a3699b93d9 --- manifest | 16 +++--- manifest.uuid | 2 +- src/sqlite.h.in | 144 +++++++++++++++++++++++++++++------------------- src/vdbeapi.c | 28 +++++----- src/where.c | 4 +- 5 files changed, 112 insertions(+), 82 deletions(-) diff --git a/manifest b/manifest index 7b186939fd..6f0bf53a93 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Index\sin\s2nd\sargument\sto\ssqlite3_vtab_in()\sshould\sbe\son\sthe\saConstraint[]\narray,\snot\sthe\sinternal\sarray\sof\sall\sconstraints. -D 2022-02-01T16:30:57.444 +C Tweaks\sto\sthe\ssqlite3_vtab_in()\sinterface. +D 2022-02-01T21:59:43.937 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -554,7 +554,7 @@ F src/resolve.c 24032ae57aec10df2f3fa2e20be0aae7d256bc704124b76c52d763440c7c0fe9 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c a6d2d4bed279d7fe4fcedaf297eaf6441e8e17c6e3947a32d24d23be52ac02f2 F src/shell.c.in 2f58e6aa6b3d2012db32f1c5fa4591e9d12fd582904632b4fc8f57a382b98fd3 -F src/sqlite.h.in 0aed2b88e91d03314121cd1e546441e37513929793c3cf7584b5b7ce445a9128 +F src/sqlite.h.in 72f3e57c4c0b4284ab9238312f7fd797967cc43f44558a80469a3d9b86a7be2b F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6 F src/sqliteInt.h 838df3e9ba9390058076d2f50c7efde9e0e8747303e788cf5bbe05402ab10924 @@ -627,7 +627,7 @@ F src/vacuum.c 6c38ddc52f0619865c91dae9c441d4d48bf3040d7dc1bc5b22da1e45547ed0b3 F src/vdbe.c d6694187a2819df7c2df3bd568fd059617c3edef4aa87e28a8121b02818f4ebf F src/vdbe.h 25dabb25c7e157b84e59260cfb5b466c3ac103ede9f36f4db371332c47601abe F src/vdbeInt.h 24d58f12f642dcac102fa75d08e99ad06b6cbc66bf4948bb61e2e223ef9518b6 -F src/vdbeapi.c 4d26cc9eb1a0f937e8d360578dc56d00145bac08afd503ba6ac843007f7d8c1f +F src/vdbeapi.c 84e7e8d161c8fb7259eaa5fe7234f2334ef9fb013674ce34705b56166052b5fa F src/vdbeaux.c e761b8011baec7a4773f0a7594783f2cd71f699ab187c4aad917529ab8acd3fe F src/vdbeblob.c 5e61ce31aca17db8fb60395407457a8c1c7fb471dde405e0cd675974611dcfcd F src/vdbemem.c eb6042667c02c3ef1f968235b4a170e31b23a4b6a57f65a8454eab4d36f14b7f @@ -639,7 +639,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c b9df133a705093da8977da5eb202eaadb844839f1c7297c08d33471f5491843d F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b -F src/where.c cbf02091ed71784f7972ca39aaecb22822781f3b96905ece1a30e80161629769 +F src/where.c 392d552fa6636e94d242954247e277eb9b0d9c45446afb0c0a57f8c6fcb3f792 F src/whereInt.h 1d821657238a0bd12b3c8f2926c7f8f9294bc5efe20af53c7c50d53a0a026cb9 F src/wherecode.c 5879604677f0bdfb8d95ff616d834daecc12256346b7d9ad96a7e84a1cb08fdc F src/whereexpr.c ddb6ab49f745154c37dbdb291433c933e00175929647290a11f487af701d0392 @@ -1942,8 +1942,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P eb84b80e1f6d8c32bf0c9e1731f0233de0160a13f714f766779ae01fdf504e7b -R 320e3be3dedfcb44854348cea4a95b96 +P 5acf90a931b27b7d627c0a8fee68170430e09b028d6643b959b0ec14fd59f7ac +R e3dc8c48dc1f78fd77820f7d72429762 U drh -Z 4a8e49183eb36a5777c7e6fa2fd8d0f2 +Z 89712fde261ff7ce2e71753d08fff668 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 93bc0e7791..bfff22a8a9 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5acf90a931b27b7d627c0a8fee68170430e09b028d6643b959b0ec14fd59f7ac \ No newline at end of file +75040183b8e14f20bfedfdcc1a9fb968f2f0193bc698605d1b4791a3699b93d9 \ No newline at end of file diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 40aa917757..e8122912ee 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -9607,72 +9607,94 @@ int sqlite3_vtab_distinct(sqlite3_index_info*); /* ** CAPI3REF: Identify and handle IN(...) constraints in xBestIndex ** -** This API may only be used from within an xBestIndex() callback. The -** results of calling it from outside of an xBestIndex() callback are -** undefined. +** This interface may only be used from within an +** [xBestIndex|xBestIndex() method of a [virtual table] implementation. +** The result of invoking this interface from any other context is +** undefined and probably harmful. ** -** When a column of a virtual table is subject to a "col IN (...)" -** constraint, this is passed through to the xBestIndex method of the -** virtual table as an SQLITE_INDEX_CONSTRAINT_EQ constraint. Then, if -** the virtual table indicates that it can handle the constraint, SQLite -** uses the xFilter and xNext methods of the virtual table to query -** separately for each distinct element in RHS of the IN(...) expression. -** This API allows a virtual table implementation to determine which -** SQLITE_INDEX_CONSTRAINT_EQ constraints are actually IN(...) constraints, -** and to handle them using a single xFilter scan. +** A constraint on a virtual table of the form "column IN (...)" is +** communicated to the xBestIndex method as a +** [SQLITE_INDEX_CONSTRAINT_EQ] constraint. If xBestIndex wants to use +** this constraint, it must set the corresponding +** aConstraintUsage[].argvIndex to a postive integer. Then, under +** the usual mode of handling IN operators, SQLite generate bytecode +** that invokes the [xFilter|xFilter() method] once for each value +** on the right-hand side of the IN operator. Thus the virtual table +** only sees a single value from the right-hand side of the IN operator +** at a time. +** +** In some cases, however, it would be advantageous for the virtual +** table to see all values on the right-hand of the IN operator all at +** once. The sqlite3_vtab_in() interfaces facilitates this in two ways: +** +**
    +**
  1. +** A call to sqlite3_vtab_in(P,I,-1) will return true (non-zero) +** if and only if the I-th constraint in P->aConstraint[] is +** an IN operator that can be processed all at once. In other words, +** sqlite3_vtab_in() with -1 in the third argument is a mechanism +** by which the virtual table can ask SQLite if all-at-once processing +** of the IN operator is even possible. +** +**

  2. +** A call to sqlite3_vtab_in(P,I,F) with F set to 1 or 0 indicates +** to SQLite that the virtual table does or does not want to process +** the IN operator all-at-once. Thus when the third parameter (F) is +** non-negative, this interface is the mechanism by which the virtual +** table tells SQLite how it wants to process in IN operator. +**

+** +** The sqlite3_vtab_in(P,I,F) interface can be invoked multiple times +** within the same xBestIndex method call. For any given P and I parameters, +** the return value from sqlite3_vtab_in(P,I,F) will always be the same +** for every invocation within the same xBestIndex call. If the interface +** returns true (non-zero), that means that the constraint is an IN operator +** that can be processed all-at-once. If the constraint is not an IN +** operator or cannot be processed all-at-once, then the interface returns +** false. +** +** All-at-once processing of the IN operator is selected if both of the +** following conditions are met: +** +**
    +**
  1. The P->aConstraintUsage[I].argvIndex value is set to a positive +** integer. This is how the virtual table tells SQLite that it wants to +** use the I-th constraint. +** +**

  2. The last call to sqlite3_vtab_in(P,I,F) for which F was +** non-negative had F>=1. +**

** -** If the second argument passed to this function is not the index of an -** SQLITE_INDEX_CONSTRAINT_EQ constraint in the aConstraint[] array of the -** sqlite3_index_info object, or if the SQLITE_INDEX_CONSTRAINT_EQ is not -** really an IN(...) expression, then this function is a no-op. Zero is -** returned in this case. -** -** Otherwise, if parameter iCons is the index of an SQLITE_INDEX_CONSTRAINT_EQ -** constraint that is really an IN(...) expression, then this function -** returns non-zero. In this case, the call also specifies whether SQLite -** should invoke xFilter() once for each element on the RHS of the IN(...) -** expression (the default, if bHandle is zero), or just once for the entire -** list (if bHandle is non-zero), should the associated -** aConstraintUsage[].argvIndex variable be set by xBestIndex. -** -** In cases where the list on the RHS of an IN(...) constraint is passed to a -** single xFilter() call, the (sqlite3_value*) passed appears in most -** respects to be a NULL value. Except, it may be used with the -** sqlite3_vtab_in_first() and sqlite3_vtab_in_next() APIs to interate through -** the list of values. +** If either or both of the conditions above are false, then uses the +** traditional one-at-a-time processing strategy for IN constraint. +** If both conditions are true, then the argvIndex-th parameter to the +** xFilter method will be an [sqlite3_value] that appears to be NULL, +** but which can be passed to [sqlite3_vtab_in_first()] and +** [sqlite3_vtab_in_next()] to find all values on the right-hand side +** of the IN constraint. */ int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle); /* -** CAPI3REF: Visit the first element of an IN(...) list passed to xFilter +** CAPI3REF: Find all elements on the right-hand side of an IN constraint. ** -** This API may only be used from within an xFilter() callback. The -** results of calling it from outside of an xFilter() callback are -** undefined. +** These interfaces are only useful from within the +** [xFilter|xFilter() method] of a virtual table implementation. +** The result of invoking these interfaces from any other context +** is undefined and probably harmful. ** -** If the (sqlite3_value*) passed as the first argument to this function -** is not a value representing the RHS of an IN(...) operator (see -** API function sqlite3_vtab_in()), of if the RHS of the IN(...) operator -** is an empty set, this function sets (*ppOut) to NULL and returns -** SQLITE_OK. +** The X parameter in a call to sqlite3_vtab_in_first(X,P) or +** sqlite3_vtab_in_next(X,P) must be one of the parameters to the +** xFilter method which invokes those routines, and specifically +** a parameter that was previously selected for all-at-once IN constraint +** processing use the [sqlite3_vtab_in()] interface in the +** [xBestIndex|xBestIndex method]. If the X parameter is not +** an xFilter argument that was selected for all-at-once IN constraint +** processing, then these routines return SQLITE_MISUSE or perhaps +** exhibit some other undefined or harmful behavior. ** -** Otherwise, if no error occurs, it sets (*ppOut) to point to an object -** containing the first value in the list and returns SQLITE_OK. The -** pointer is valid until either the xFilter() call returns or until the -** next call to sqlite3_vtab_in_first() or sqlite3_vtab_in_next() with -** the same first argument. -** -** If an error occurs, (*ppOut) is set to NULL and an SQLite error code -** returned. -*/ -int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut); - -/* -** CAPI3REF: Visit the next element of an IN(...) list passed to xFilter -** -** This function is called after a successful call to sqlite3_vtab_in_first() -** to visit the next and subsequent elements of an IN(...) list passed -** to an xFilter callback. Example usage: +** Use these routines to access all values on the right-hand side +** of the IN constraint using code like the following: ** **
 **   for(rc=sqlite3_vtab_in_first(pList, &pVal);
@@ -9685,7 +9707,15 @@ int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut);
 **     // an error has occurred
 **   }
 ** 
+** +** On success, the sqlite3_vtab_in_first(X,P) and sqlite3_vtab_in_next(X,P) +** routines return SQLITE_OK and set *P to point to the first or next value +** on the RHS of the IN constraint. If there are no more values on the +** right hand side of the IN constraint, then *P is set to NULL and these +** routines return [SQLITE_DONE]. The return value might be +** some other value, such as SQLITE_NOMEM, in the event of a malfunction. */ +int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut); int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut); /* diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 1521eee79b..46b466b6bb 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -888,21 +888,21 @@ static int vtabInLoadValue(sqlite3_value *pVal, sqlite3_value **ppOut){ ** sqlite3_vtab_in_next() (if bNext!=0). */ static int vtabInOp(sqlite3_value *pVal, sqlite3_value **ppOut, int bNext){ - int rc = SQLITE_OK; + int rc; + BtCursor *pCsr; *ppOut = 0; - if( pVal && pVal->uTemp==SQLITE_VTAB_IN_MAGIC ){ - BtCursor *pCsr = (BtCursor*)pVal->z; - - if( bNext ){ - rc = sqlite3BtreeNext(pCsr, 0); - }else{ - int dummy = 0; - rc = sqlite3BtreeFirst(pCsr, &dummy); - } - - if( rc==SQLITE_OK && sqlite3BtreeEof(pCsr)==0 ){ - rc = vtabInLoadValue(pVal, ppOut); - } + if( pVal==0 ) return SQLITE_MISUSE; + if( pVal->uTemp!=SQLITE_VTAB_IN_MAGIC ) return SQLITE_MISUSE; + pCsr = (BtCursor*)pVal->z; + if( bNext ){ + rc = sqlite3BtreeNext(pCsr, 0); + }else{ + int dummy = 0; + rc = sqlite3BtreeFirst(pCsr, &dummy); + if( rc==SQLITE_OK && sqlite3BtreeEof(pCsr) ) rc = SQLITE_DONE; + } + if( rc==SQLITE_OK ){ + rc = vtabInLoadValue(pVal, ppOut); } return rc; } diff --git a/src/where.c b/src/where.c index 4f65e934fc..169c4ccf14 100644 --- a/src/where.c +++ b/src/where.c @@ -3605,7 +3605,7 @@ static int whereLoopAddVirtualOne( } } if( SMASKBIT32(i) & pHidden->mHandleIn ){ - pNew->u.vtab.mHandleIn |= SMASKBIT32(iTerm); + pNew->u.vtab.mHandleIn |= MASKBIT32(iTerm); }else if( (pTerm->eOperator & WO_IN)!=0 ){ /* A virtual table that is constrained by an IN clause may not ** consume the ORDER BY clause because (1) the order of IN terms @@ -3715,7 +3715,7 @@ int sqlite3_vtab_in(sqlite3_index_info *pIdxInfo, int iCons, int bHandle){ if( m & pHidden->mIn ){ if( bHandle==0 ){ pHidden->mHandleIn &= ~m; - }else{ + }else if( bHandle>0 ){ pHidden->mHandleIn |= m; } return 1; From 30e314e4cbc7614d322b13703295af961c5e38c6 Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 2 Feb 2022 14:36:58 +0000 Subject: [PATCH 4/9] Refactor sqlite3_vtab_in() to make use of the existing sqlite3_value_pointer() mechanism for passing the list of IN operator RHS values into xFilter, for improved memory safety. FossilOrigin-Name: 8965929be236fe1a6994f31b94c1b7590c7c1e809470c542a76f3e0e275d032f --- manifest | 16 +++++------ manifest.uuid | 2 +- src/vdbe.c | 23 +++++++++------ src/vdbeInt.h | 25 +++++++++++----- src/vdbeapi.c | 79 +++++++++++++++++++++------------------------------ 5 files changed, 74 insertions(+), 71 deletions(-) diff --git a/manifest b/manifest index 6f0bf53a93..225a935344 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Tweaks\sto\sthe\ssqlite3_vtab_in()\sinterface. -D 2022-02-01T21:59:43.937 +C Refactor\ssqlite3_vtab_in()\sto\smake\suse\sof\sthe\sexisting\nsqlite3_value_pointer()\smechanism\sfor\spassing\sthe\slist\sof\sIN\soperator\nRHS\svalues\sinto\sxFilter,\sfor\simproved\smemory\ssafety. +D 2022-02-02T14:36:58.288 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -624,10 +624,10 @@ F src/upsert.c 8789047a8f0a601ea42fa0256d1ba3190c13746b6ba940fe2d25643a7e991937 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 F src/util.c 602fe229f32a96ceccae4f40824129669582096f7c355f53dbac156c9fecef23 F src/vacuum.c 6c38ddc52f0619865c91dae9c441d4d48bf3040d7dc1bc5b22da1e45547ed0b3 -F src/vdbe.c d6694187a2819df7c2df3bd568fd059617c3edef4aa87e28a8121b02818f4ebf +F src/vdbe.c 13a4de20ee07bdfb3dc74ab49b7912208e309caf762a8d1678fb111e2223af35 F src/vdbe.h 25dabb25c7e157b84e59260cfb5b466c3ac103ede9f36f4db371332c47601abe -F src/vdbeInt.h 24d58f12f642dcac102fa75d08e99ad06b6cbc66bf4948bb61e2e223ef9518b6 -F src/vdbeapi.c 84e7e8d161c8fb7259eaa5fe7234f2334ef9fb013674ce34705b56166052b5fa +F src/vdbeInt.h b45599a2b59f1ce042512ab6786b0b82a8cf3002f6b0fa60b4834e2cd3ac61d8 +F src/vdbeapi.c a6ae9ef8180b2c51555cc96b8c5b928d45738a90f667a28a1959bcd09646643d F src/vdbeaux.c e761b8011baec7a4773f0a7594783f2cd71f699ab187c4aad917529ab8acd3fe F src/vdbeblob.c 5e61ce31aca17db8fb60395407457a8c1c7fb471dde405e0cd675974611dcfcd F src/vdbemem.c eb6042667c02c3ef1f968235b4a170e31b23a4b6a57f65a8454eab4d36f14b7f @@ -1942,8 +1942,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 5acf90a931b27b7d627c0a8fee68170430e09b028d6643b959b0ec14fd59f7ac -R e3dc8c48dc1f78fd77820f7d72429762 +P 75040183b8e14f20bfedfdcc1a9fb968f2f0193bc698605d1b4791a3699b93d9 +R 685a004fac313bda3614fce6fec3f1d8 U drh -Z 89712fde261ff7ce2e71753d08fff668 +Z f2a69210d29b19f1b295e7a7f5450a22 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index bfff22a8a9..55846307b1 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -75040183b8e14f20bfedfdcc1a9fb968f2f0193bc698605d1b4791a3699b93d9 \ No newline at end of file +8965929be236fe1a6994f31b94c1b7590c7c1e809470c542a76f3e0e275d032f \ No newline at end of file diff --git a/src/vdbe.c b/src/vdbe.c index 5643d43ce9..5410a79127 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -7736,20 +7736,27 @@ case OP_VOpen: { #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VInitIn P1 P2 P3 * * -** Synopsis: r[P2]=cursor over eph table P1. +** Synopsis: r[P2]=ValueList(P1,P3) ** -** Initialize register P2 as a value that can be used as an iterator over -** the contents of ephemeral table P1 by an xFilter callback implementation. -** Register P3 is used as a cache by the iterator. +** Set register P2 to be a pointer to a ValueList object for cursor P1 +** with cache register P3 and output register P3+1. This ValueList object +** can be used as the first argument to sqlite3_vtab_in_first() and +** sqlite3_vtab_in_next() to extract all of the values stored in the P1 +** cursor. Register P3 is used to hold the values returned by +** sqlite3_vtab_in_first() and sqlite3_vtab_in_next(). */ case OP_VInitIn: { /* out2 */ - VdbeCursor *pC; + VdbeCursor *pC; /* The cursor containing the RHS values */ + ValueList *pRhs; /* New ValueList object to put in reg[P2] */ + pC = p->apCsr[pOp->p1]; + pRhs = sqlite3_malloc64( sizeof(*pRhs) ); + if( pRhs==0 ) goto no_mem; + pRhs->pCsr = pC->uc.pCursor; + pRhs->pOut = &aMem[pOp->p3]; pOut = out2Prerelease(p, pOp); - pOut->z = (char*)(pC->uc.pCursor); - pOut->u.pVal = &aMem[pOp->p3]; - pOut->uTemp = SQLITE_VTAB_IN_MAGIC; pOut->flags = MEM_Null; + sqlite3VdbeMemSetPointer(pOut, pRhs, "ValueList", sqlite3_free); break; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 9ddb742a49..f02b37c6a2 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -195,12 +195,6 @@ struct VdbeFrame { */ #define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))]) -/* Magic number for Mem.uTemp when it is acting as as the cache for the -** IN(...) iterator for sqlite3_vtab_in_next() -*/ -#define SQLITE_VTAB_IN_MAGIC 0xd3ab12ec - - /* ** Internally, the vdbe manipulates nearly all SQL values as Mem ** structures. Each Mem struct may cache multiple representations (string, @@ -213,7 +207,6 @@ struct sqlite3_value { int nZero; /* Extra zero bytes when MEM_Zero and MEM_Blob set */ const char *zPType; /* Pointer type when MEM_Term|MEM_Subtype|MEM_Null */ FuncDef *pDef; /* Used only when flags==MEM_Agg */ - sqlite3_value *pVal;/* Current value for xFilter IN(...) iterator */ } u; u16 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */ u8 enc; /* SQLITE_UTF8, SQLITE_UTF16BE, SQLITE_UTF16LE */ @@ -489,6 +482,24 @@ struct PreUpdate { Index *pPk; /* PK index if pTab is WITHOUT ROWID */ }; +/* +** An instance of this object is used to pass an vector of values into +** OP_VFilter, the xFilter method of a virtual table. The vector is the +** set of values on the right-hand side of an IN constraint. +** +** The value as passed into xFilter is an sqlite3_value with a "pointer" +** type, such as is generated by sqlite3_result_pointer() and read by +** sqlite3_value_pointer. Such values have MEM_Term|MEM_Subtype|MEM_Null +** and a subtype of 'p'. The sqlite3_vtab_in_first() and _next() interfaces +** know how to use this object to step through all the values in the +** right operand of the IN constraint. +*/ +typedef struct ValueList ValueList; +struct ValueList { + BtCursor *pCsr; /* An ephemeral table holding all values */ + sqlite3_value *pOut; /* Register to hold each decoded output value */ +}; + /* ** Function prototypes */ diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 46b466b6bb..75d7bf15ce 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -846,63 +846,48 @@ int sqlite3_vtab_nochange(sqlite3_context *p){ return sqlite3_value_nochange(p->pOut); } -/* -** The first argument is an iterator value created by VDBE instruction -** OP_VInitIn. The iterator is guaranteed to point to a valid entry. This -** function attempts to load the current value from the iterator into -** object pVal->u.pVal. If successful, (*ppOut) is set to point to -** pVal->u.pVal and SQLITE_OK is returned. Otherwise, if an error -** occurs, an SQLite error code is returned and (*ppOut) is left unchanged. -*/ -static int vtabInLoadValue(sqlite3_value *pVal, sqlite3_value **ppOut){ - BtCursor *pCsr = (BtCursor*)pVal->z; - sqlite3_value *pOut = pVal->u.pVal; - int sz; - int rc; - - sz = (int)sqlite3BtreePayloadSize(pCsr); - if( sz>pVal->szMalloc ){ - if( pVal->szMalloc==0 ) pVal->zMalloc = 0; - pVal->zMalloc = sqlite3DbReallocOrFree(pVal->db, pVal->zMalloc, sz*2); - if( pVal->zMalloc ){ - pVal->szMalloc = sqlite3DbMallocSize(pVal->db, pVal->zMalloc); - }else{ - pVal->szMalloc = 0; - return SQLITE_NOMEM_BKPT; - } - } - - rc = sqlite3BtreePayload(pCsr, 0, sz, pVal->zMalloc); - if( rc==SQLITE_OK ){ - u32 iSerial; - int iOff = 1 + getVarint32((const u8*)&pVal->zMalloc[1], iSerial); - sqlite3VdbeSerialGet((const u8*)&pVal->zMalloc[iOff], iSerial, pOut); - pOut->enc = ENC(pVal->db); - *ppOut = pOut; - } - return rc; -} - /* ** Implementation of sqlite3_vtab_in_first() (if bNext==0) and ** sqlite3_vtab_in_next() (if bNext!=0). */ -static int vtabInOp(sqlite3_value *pVal, sqlite3_value **ppOut, int bNext){ +static int valueFromValueList( + sqlite3_value *pVal, /* Pointer to the ValueList object */ + sqlite3_value **ppOut, /* Store the next value from the list here */ + int bNext /* 1 for _next(). 0 for _first() */ +){ int rc; - BtCursor *pCsr; + ValueList *pRhs; + *ppOut = 0; if( pVal==0 ) return SQLITE_MISUSE; - if( pVal->uTemp!=SQLITE_VTAB_IN_MAGIC ) return SQLITE_MISUSE; - pCsr = (BtCursor*)pVal->z; + pRhs = (ValueList*)sqlite3_value_pointer(pVal, "ValueList"); + if( pRhs==0 ) return SQLITE_MISUSE; if( bNext ){ - rc = sqlite3BtreeNext(pCsr, 0); + rc = sqlite3BtreeNext(pRhs->pCsr, 0); }else{ int dummy = 0; - rc = sqlite3BtreeFirst(pCsr, &dummy); - if( rc==SQLITE_OK && sqlite3BtreeEof(pCsr) ) rc = SQLITE_DONE; + rc = sqlite3BtreeFirst(pRhs->pCsr, &dummy); + if( rc==SQLITE_OK && sqlite3BtreeEof(pRhs->pCsr) ) rc = SQLITE_DONE; } if( rc==SQLITE_OK ){ - rc = vtabInLoadValue(pVal, ppOut); + u32 sz; /* Size of current row in bytes */ + Mem sMem; /* Raw content of current row */ + memset(&sMem, 0, sizeof(sMem)); + sz = sqlite3BtreePayloadSize(pRhs->pCsr); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pRhs->pCsr,(int)sz,&sMem); + if( rc==SQLITE_OK ){ + u8 *zBuf = (u8*)sMem.z; + u32 iSerial; + sqlite3_value *pOut = pRhs->pOut; + int iOff = 1 + getVarint32(&zBuf[1], iSerial); + sqlite3VdbeSerialGet(&zBuf[iOff], iSerial, pOut); + if( (pOut->flags & MEM_Ephem)!=0 && sqlite3VdbeMemMakeWriteable(pOut) ){ + rc = SQLITE_NOMEM; + }else{ + *ppOut = pOut; + } + } + sqlite3VdbeMemRelease(&sMem); } return rc; } @@ -912,7 +897,7 @@ static int vtabInOp(sqlite3_value *pVal, sqlite3_value **ppOut, int bNext){ ** Set (*ppOut) to point to this value before returning. */ int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut){ - return vtabInOp(pVal, ppOut, 0); + return valueFromValueList(pVal, ppOut, 0); } /* @@ -920,7 +905,7 @@ int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut){ ** Set (*ppOut) to point to this value before returning. */ int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut){ - return vtabInOp(pVal, ppOut, 1); + return valueFromValueList(pVal, ppOut, 1); } /* From 38d1e443501f41f895c34a38dfa247d2ec6d4359 Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 2 Feb 2022 15:10:45 +0000 Subject: [PATCH 5/9] Be sure that sqlite3_vtab_in_first() and _next() set the correct encoding. FossilOrigin-Name: 04edf36ee8e043c83235a5169a7ced23f211edd2f7ef3290d96413d5fd229ad7 --- manifest | 12 ++++++------ manifest.uuid | 2 +- src/vdbeapi.c | 1 + 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/manifest b/manifest index 225a935344..1ec679a8f9 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Refactor\ssqlite3_vtab_in()\sto\smake\suse\sof\sthe\sexisting\nsqlite3_value_pointer()\smechanism\sfor\spassing\sthe\slist\sof\sIN\soperator\nRHS\svalues\sinto\sxFilter,\sfor\simproved\smemory\ssafety. -D 2022-02-02T14:36:58.288 +C Be\ssure\sthat\ssqlite3_vtab_in_first()\sand\s_next()\sset\sthe\scorrect\sencoding. +D 2022-02-02T15:10:45.512 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -627,7 +627,7 @@ F src/vacuum.c 6c38ddc52f0619865c91dae9c441d4d48bf3040d7dc1bc5b22da1e45547ed0b3 F src/vdbe.c 13a4de20ee07bdfb3dc74ab49b7912208e309caf762a8d1678fb111e2223af35 F src/vdbe.h 25dabb25c7e157b84e59260cfb5b466c3ac103ede9f36f4db371332c47601abe F src/vdbeInt.h b45599a2b59f1ce042512ab6786b0b82a8cf3002f6b0fa60b4834e2cd3ac61d8 -F src/vdbeapi.c a6ae9ef8180b2c51555cc96b8c5b928d45738a90f667a28a1959bcd09646643d +F src/vdbeapi.c 32d353cec191cd3e3d3c3457dd219ce10f43d430d9ccb9649abca81f5f622031 F src/vdbeaux.c e761b8011baec7a4773f0a7594783f2cd71f699ab187c4aad917529ab8acd3fe F src/vdbeblob.c 5e61ce31aca17db8fb60395407457a8c1c7fb471dde405e0cd675974611dcfcd F src/vdbemem.c eb6042667c02c3ef1f968235b4a170e31b23a4b6a57f65a8454eab4d36f14b7f @@ -1942,8 +1942,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 75040183b8e14f20bfedfdcc1a9fb968f2f0193bc698605d1b4791a3699b93d9 -R 685a004fac313bda3614fce6fec3f1d8 +P 8965929be236fe1a6994f31b94c1b7590c7c1e809470c542a76f3e0e275d032f +R 577d7ced4e44dba6062d7cb550bb1c26 U drh -Z f2a69210d29b19f1b295e7a7f5450a22 +Z d0000616f45072bd895fd0e1857966da # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 55846307b1..c4199e1bff 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8965929be236fe1a6994f31b94c1b7590c7c1e809470c542a76f3e0e275d032f \ No newline at end of file +04edf36ee8e043c83235a5169a7ced23f211edd2f7ef3290d96413d5fd229ad7 \ No newline at end of file diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 75d7bf15ce..95fb72c3c9 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -881,6 +881,7 @@ static int valueFromValueList( sqlite3_value *pOut = pRhs->pOut; int iOff = 1 + getVarint32(&zBuf[1], iSerial); sqlite3VdbeSerialGet(&zBuf[iOff], iSerial, pOut); + pOut->enc = ENC(pOut->db); if( (pOut->flags & MEM_Ephem)!=0 && sqlite3VdbeMemMakeWriteable(pOut) ){ rc = SQLITE_NOMEM; }else{ From 3d7a69e5ef1c94a74ef01de2a817e7544abcf2a8 Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 2 Feb 2022 16:24:01 +0000 Subject: [PATCH 6/9] Relax the restriction that the RHS of the IN operator must be a list in order for sqlite3_vtab_in() to work. Change an unreachable branch into an assert(). FossilOrigin-Name: 3bf2153440dce0e8c0572c4fd39e6b9f34ead75ccab2cea80a646d4ff9d19146 --- manifest | 14 +++++++------- manifest.uuid | 2 +- src/vdbeapi.c | 3 ++- src/where.c | 4 +--- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/manifest b/manifest index 1ec679a8f9..7221a4a0c8 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Be\ssure\sthat\ssqlite3_vtab_in_first()\sand\s_next()\sset\sthe\scorrect\sencoding. -D 2022-02-02T15:10:45.512 +C Relax\sthe\srestriction\sthat\sthe\sRHS\sof\sthe\sIN\soperator\smust\sbe\sa\slist\sin\sorder\nfor\ssqlite3_vtab_in()\sto\swork.\s\sChange\san\sunreachable\sbranch\sinto\san\sassert(). +D 2022-02-02T16:24:01.752 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -627,7 +627,7 @@ F src/vacuum.c 6c38ddc52f0619865c91dae9c441d4d48bf3040d7dc1bc5b22da1e45547ed0b3 F src/vdbe.c 13a4de20ee07bdfb3dc74ab49b7912208e309caf762a8d1678fb111e2223af35 F src/vdbe.h 25dabb25c7e157b84e59260cfb5b466c3ac103ede9f36f4db371332c47601abe F src/vdbeInt.h b45599a2b59f1ce042512ab6786b0b82a8cf3002f6b0fa60b4834e2cd3ac61d8 -F src/vdbeapi.c 32d353cec191cd3e3d3c3457dd219ce10f43d430d9ccb9649abca81f5f622031 +F src/vdbeapi.c 06bff35393ca5daa3e02e38fb516df320bd52720a2781eb70c2db23ea1c746dd F src/vdbeaux.c e761b8011baec7a4773f0a7594783f2cd71f699ab187c4aad917529ab8acd3fe F src/vdbeblob.c 5e61ce31aca17db8fb60395407457a8c1c7fb471dde405e0cd675974611dcfcd F src/vdbemem.c eb6042667c02c3ef1f968235b4a170e31b23a4b6a57f65a8454eab4d36f14b7f @@ -639,7 +639,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c b9df133a705093da8977da5eb202eaadb844839f1c7297c08d33471f5491843d F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b -F src/where.c 392d552fa6636e94d242954247e277eb9b0d9c45446afb0c0a57f8c6fcb3f792 +F src/where.c d7d996a5d4501fbf14997e965d7f641fe7b2a0dd89b2296e5310b506da3fb822 F src/whereInt.h 1d821657238a0bd12b3c8f2926c7f8f9294bc5efe20af53c7c50d53a0a026cb9 F src/wherecode.c 5879604677f0bdfb8d95ff616d834daecc12256346b7d9ad96a7e84a1cb08fdc F src/whereexpr.c ddb6ab49f745154c37dbdb291433c933e00175929647290a11f487af701d0392 @@ -1942,8 +1942,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 8965929be236fe1a6994f31b94c1b7590c7c1e809470c542a76f3e0e275d032f -R 577d7ced4e44dba6062d7cb550bb1c26 +P 04edf36ee8e043c83235a5169a7ced23f211edd2f7ef3290d96413d5fd229ad7 +R 2c3c1902f73f372b9534bd85d910dc24 U drh -Z d0000616f45072bd895fd0e1857966da +Z bd0932878b05cf48df93c8b67c69411f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index c4199e1bff..3180a6bf73 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -04edf36ee8e043c83235a5169a7ced23f211edd2f7ef3290d96413d5fd229ad7 \ No newline at end of file +3bf2153440dce0e8c0572c4fd39e6b9f34ead75ccab2cea80a646d4ff9d19146 \ No newline at end of file diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 95fb72c3c9..9cc200298e 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -867,7 +867,8 @@ static int valueFromValueList( }else{ int dummy = 0; rc = sqlite3BtreeFirst(pRhs->pCsr, &dummy); - if( rc==SQLITE_OK && sqlite3BtreeEof(pRhs->pCsr) ) rc = SQLITE_DONE; + assert( rc==SQLITE_OK || sqlite3BtreeEof(pRhs->pCsr) ); + if( sqlite3BtreeEof(pRhs->pCsr) ) rc = SQLITE_DONE; } if( rc==SQLITE_OK ){ u32 sz; /* Size of current row in bytes */ diff --git a/src/where.c b/src/where.c index 169c4ccf14..11eae60e6a 100644 --- a/src/where.c +++ b/src/where.c @@ -1243,9 +1243,7 @@ static sqlite3_index_info *allocateIndexInfo( pIdxCons[j].iTermOffset = i; op = pTerm->eOperator & WO_ALL; if( op==WO_IN ){ - if( ExprHasProperty(pTerm->pExpr, EP_xIsSelect)==0 ){ - pHidden->mIn |= SMASKBIT32(j); - } + pHidden->mIn |= SMASKBIT32(j); op = WO_EQ; } if( op==WO_AUX ){ From 2725db836b4544f0748b2665d2632d4550c442ff Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 2 Feb 2022 18:47:56 +0000 Subject: [PATCH 7/9] Improved documentation for sqlite3_vtab_in(). No code changes. FossilOrigin-Name: c99df4ab5db2c32b044366c5b0ac70fd8887d1456d53323e75fede23cc61c236 --- manifest | 12 +++--- manifest.uuid | 2 +- src/sqlite.h.in | 102 ++++++++++++++++++++++++++---------------------- 3 files changed, 62 insertions(+), 54 deletions(-) diff --git a/manifest b/manifest index 7221a4a0c8..1e9ca0f15e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Relax\sthe\srestriction\sthat\sthe\sRHS\sof\sthe\sIN\soperator\smust\sbe\sa\slist\sin\sorder\nfor\ssqlite3_vtab_in()\sto\swork.\s\sChange\san\sunreachable\sbranch\sinto\san\sassert(). -D 2022-02-02T16:24:01.752 +C Improved\sdocumentation\sfor\ssqlite3_vtab_in().\s\sNo\scode\schanges. +D 2022-02-02T18:47:56.749 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -554,7 +554,7 @@ F src/resolve.c 24032ae57aec10df2f3fa2e20be0aae7d256bc704124b76c52d763440c7c0fe9 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c a6d2d4bed279d7fe4fcedaf297eaf6441e8e17c6e3947a32d24d23be52ac02f2 F src/shell.c.in 2f58e6aa6b3d2012db32f1c5fa4591e9d12fd582904632b4fc8f57a382b98fd3 -F src/sqlite.h.in 72f3e57c4c0b4284ab9238312f7fd797967cc43f44558a80469a3d9b86a7be2b +F src/sqlite.h.in a6e6fd9defb576af6444a85f446aaa738dea3384c48db4fe9007fb8ff954b7c5 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6 F src/sqliteInt.h 838df3e9ba9390058076d2f50c7efde9e0e8747303e788cf5bbe05402ab10924 @@ -1942,8 +1942,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 04edf36ee8e043c83235a5169a7ced23f211edd2f7ef3290d96413d5fd229ad7 -R 2c3c1902f73f372b9534bd85d910dc24 +P 3bf2153440dce0e8c0572c4fd39e6b9f34ead75ccab2cea80a646d4ff9d19146 +R f229e1cc04b78384226f7f6c44d75044 U drh -Z bd0932878b05cf48df93c8b67c69411f +Z 5557e45c47098a153d05816f88cff23e # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 3180a6bf73..472918b743 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3bf2153440dce0e8c0572c4fd39e6b9f34ead75ccab2cea80a646d4ff9d19146 \ No newline at end of file +c99df4ab5db2c32b044366c5b0ac70fd8887d1456d53323e75fede23cc61c236 \ No newline at end of file diff --git a/src/sqlite.h.in b/src/sqlite.h.in index e8122912ee..ead938a7dd 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -9605,21 +9605,22 @@ SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_info*,int); int sqlite3_vtab_distinct(sqlite3_index_info*); /* -** CAPI3REF: Identify and handle IN(...) constraints in xBestIndex +** CAPI3REF: Identify and handle IN constraints in xBestIndex ** ** This interface may only be used from within an -** [xBestIndex|xBestIndex() method of a [virtual table] implementation. +** [xBestIndex|xBestIndex() method] of a [virtual table] implementation. ** The result of invoking this interface from any other context is ** undefined and probably harmful. ** -** A constraint on a virtual table of the form "column IN (...)" is +** ^(A constraint on a virtual table of the form +** "[IN operator|column IN (...)]" is ** communicated to the xBestIndex method as a -** [SQLITE_INDEX_CONSTRAINT_EQ] constraint. If xBestIndex wants to use +** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use ** this constraint, it must set the corresponding -** aConstraintUsage[].argvIndex to a postive integer. Then, under -** the usual mode of handling IN operators, SQLite generate bytecode +** aConstraintUsage[].argvIndex to a postive integer. ^(Then, under +** the usual mode of handling IN operators, SQLite generates [bytecode] ** that invokes the [xFilter|xFilter() method] once for each value -** on the right-hand side of the IN operator. Thus the virtual table +** on the right-hand side of the IN operator.)^ Thus the virtual table ** only sees a single value from the right-hand side of the IN operator ** at a time. ** @@ -9629,45 +9630,46 @@ int sqlite3_vtab_distinct(sqlite3_index_info*); ** **
    **
  1. -** A call to sqlite3_vtab_in(P,I,-1) will return true (non-zero) -** if and only if the I-th constraint in P->aConstraint[] is -** an IN operator that can be processed all at once. In other words, +** ^A call to sqlite3_vtab_in(P,N,-1) will return true (non-zero) +** if and only if the [sqlite3_index_info|P->aConstraint][N] constraint +** is an [IN operator] that can be processed all at once. ^In other words, ** sqlite3_vtab_in() with -1 in the third argument is a mechanism ** by which the virtual table can ask SQLite if all-at-once processing ** of the IN operator is even possible. ** **

  2. -** A call to sqlite3_vtab_in(P,I,F) with F set to 1 or 0 indicates +** ^A call to sqlite3_vtab_in(P,N,F) with F==1 or F==0 indicates ** to SQLite that the virtual table does or does not want to process -** the IN operator all-at-once. Thus when the third parameter (F) is -** non-negative, this interface is the mechanism by which the virtual -** table tells SQLite how it wants to process in IN operator. +** the IN operator all-at-once, respectively. ^Thus when the third +** parameter (F) is non-negative, this interface is the mechanism by +** which the virtual table tells SQLite how it wants to process in +** IN operator. **

** -** The sqlite3_vtab_in(P,I,F) interface can be invoked multiple times -** within the same xBestIndex method call. For any given P and I parameters, -** the return value from sqlite3_vtab_in(P,I,F) will always be the same -** for every invocation within the same xBestIndex call. If the interface -** returns true (non-zero), that means that the constraint is an IN operator -** that can be processed all-at-once. If the constraint is not an IN +** ^The sqlite3_vtab_in(P,N,F) interface can be invoked multiple times +** within the same xBestIndex method call. ^For any given P,N pair, +** the return value from sqlite3_vtab_in(P,N,F) will always be the same +** within the same xBestIndex call. ^If the interface returns true +** (non-zero), that means that the constraint is an IN operator +** that can be processed all-at-once. ^If the constraint is not an IN ** operator or cannot be processed all-at-once, then the interface returns ** false. ** -** All-at-once processing of the IN operator is selected if both of the +** ^(All-at-once processing of the IN operator is selected if both of the ** following conditions are met: ** **
    -**
  1. The P->aConstraintUsage[I].argvIndex value is set to a positive +**

  2. The P->aConstraintUsage[N].argvIndex value is set to a positive ** integer. This is how the virtual table tells SQLite that it wants to -** use the I-th constraint. +** use the N-th constraint. ** -**

  3. The last call to sqlite3_vtab_in(P,I,F) for which F was +**

  4. The last call to sqlite3_vtab_in(P,N,F) for which F was ** non-negative had F>=1. -**

+** )^ ** -** If either or both of the conditions above are false, then uses the -** traditional one-at-a-time processing strategy for IN constraint. -** If both conditions are true, then the argvIndex-th parameter to the +** ^If either or both of the conditions above are false, then SQLite uses +** the traditional one-at-a-time processing strategy for IN constraint. +** ^If both conditions are true, then the argvIndex-th parameter to the ** xFilter method will be an [sqlite3_value] that appears to be NULL, ** but which can be passed to [sqlite3_vtab_in_first()] and ** [sqlite3_vtab_in_next()] to find all values on the right-hand side @@ -9679,41 +9681,47 @@ int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle); ** CAPI3REF: Find all elements on the right-hand side of an IN constraint. ** ** These interfaces are only useful from within the -** [xFilter|xFilter() method] of a virtual table implementation. +** [xFilter|xFilter() method] of a [virtual table] implementation. ** The result of invoking these interfaces from any other context ** is undefined and probably harmful. ** ** The X parameter in a call to sqlite3_vtab_in_first(X,P) or ** sqlite3_vtab_in_next(X,P) must be one of the parameters to the -** xFilter method which invokes those routines, and specifically +** xFilter method which invokes these routines, and specifically ** a parameter that was previously selected for all-at-once IN constraint ** processing use the [sqlite3_vtab_in()] interface in the -** [xBestIndex|xBestIndex method]. If the X parameter is not +** [xBestIndex|xBestIndex method]. ^(If the X parameter is not ** an xFilter argument that was selected for all-at-once IN constraint -** processing, then these routines return SQLITE_MISUSE or perhaps +** processing, then these routines return [SQLITE_MISUSE])^ or perhaps ** exhibit some other undefined or harmful behavior. ** -** Use these routines to access all values on the right-hand side +** ^(Use these routines to access all values on the right-hand side ** of the IN constraint using code like the following: ** -**
-**   for(rc=sqlite3_vtab_in_first(pList, &pVal);
-**       rc==SQLITE_OK && pVal
-**       rc=sqlite3_vtab_in_next(pList, &pVal)
-**   ){
-**     // do something with pVal
-**   }
-**   if( rc!=SQLITE_OK ){
-**     // an error has occurred
-**   }
-** 
+**
+**    for(rc=sqlite3_vtab_in_first(pList, &pVal);
+**        rc==SQLITE_OK && pVal
+**        rc=sqlite3_vtab_in_next(pList, &pVal)
+**    ){
+**      // do something with pVal
+**    }
+**    if( rc!=SQLITE_OK ){
+**      // an error has occurred
+**    }
+** 
)^ ** -** On success, the sqlite3_vtab_in_first(X,P) and sqlite3_vtab_in_next(X,P) +** ^On success, the sqlite3_vtab_in_first(X,P) and sqlite3_vtab_in_next(X,P) ** routines return SQLITE_OK and set *P to point to the first or next value -** on the RHS of the IN constraint. If there are no more values on the +** on the RHS of the IN constraint. ^If there are no more values on the ** right hand side of the IN constraint, then *P is set to NULL and these -** routines return [SQLITE_DONE]. The return value might be +** routines return [SQLITE_DONE]. ^The return value might be ** some other value, such as SQLITE_NOMEM, in the event of a malfunction. +** +** The *ppOut values returned by these routines are only valid until the +** next call to either of these routines or until the end of the xFilter +** method from which these routines were called. If the virtual table +** implementation needs to retain the *ppOut values for longer, it must make +** copies. */ int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut); int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut); From d96ab99588bb30e2d0e42e7302c0490011d315e5 Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 2 Feb 2022 19:15:53 +0000 Subject: [PATCH 8/9] Test cases for sqlite3_vtab_in() and sqlite3_vtab_distinct(). FossilOrigin-Name: 21afb81d0a73af39aacd9329b1441faa2b535a52a52036daec89fd303a8b344f --- manifest | 29 +-- manifest.uuid | 2 +- src/test_bestindex.c | 191 +++++++++++++++--- test/bestindex1.test | 24 ++- test/bestindex2.test | 5 +- test/bestindex3.test | 5 +- test/bestindex4.test | 11 +- test/bestindex5.test | 7 +- test/bestindex6.test | 6 +- test/bestindex7.test | 6 +- test/bestindex8.test | 460 +++++++++++++++++++++++++++++++++++++++++++ test/rowvalue5.test | 4 +- 12 files changed, 699 insertions(+), 51 deletions(-) create mode 100644 test/bestindex8.test diff --git a/manifest b/manifest index 1e9ca0f15e..95ed26d241 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Improved\sdocumentation\sfor\ssqlite3_vtab_in().\s\sNo\scode\schanges. -D 2022-02-02T18:47:56.749 +C Test\scases\sfor\ssqlite3_vtab_in()\sand\ssqlite3_vtab_distinct(). +D 2022-02-02T19:15:53.331 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -574,7 +574,7 @@ F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5 F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871 F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0 -F src/test_bestindex.c ba0314d3bca8b00fa0daaf113a314986f73ea8ad85a7562f983638d09a29045c +F src/test_bestindex.c 8294d8223b7f18a3ddb7f9a0e30815dcca4e61681f78b538c870f7d934f88b81 F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 F src/test_config.c 284c29912736f68b0a583a920bf63fd8f9125dffb8a75cb0676e58502b2f7908 @@ -724,13 +724,14 @@ F test/backup_malloc.test 0c9abdf74c51e7bedb66d504cd684f28d4bd4027 F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f F test/badutf2.test f310fd3b24a491b6b77bccdf14923b85d6ebcce751068c180d93a6b8ff854399 F test/bc_common.tcl b5e42d80305be95697e6370e015af571e5333a1c -F test/bestindex1.test 7cc626f1f4a7483bb6b38487d467db4477083be5cd93958aeda5d5127640dc81 -F test/bestindex2.test 60266e2854055788459cbfd86cef575601eabe74a2c61faba72601739fea4398 -F test/bestindex3.test e061a6ed0f519beee037ba7e7a4c37f80c8a7e4a303e2559ed1f760e4b0235eb -F test/bestindex4.test 82250e7dcc6d5b15244edc9d6554b1760583af1b8548c2a255a1c4f28e744c0e -F test/bestindex5.test 67c1166131bb59f9e47c00118f7d432ca5491e6cae6ca3f87ca9db20103a78f9 -F test/bestindex6.test d856a9bb63d927493575823eed44053bc36251e241aa364e54d0f2a2d302e1d4 -F test/bestindex7.test a11348824aed0de2bb9030f092636929000cd72882bdf919adacc3792f67ccbd +F test/bestindex1.test 856a453dff8c68b4568601eed5a8b5e20b4763af9229f3947c215729ed878db0 +F test/bestindex2.test 394ff8fbf34703391247116d6a44e1c50ee7282236ee77909044573cefc37bc0 +F test/bestindex3.test 34bea272b0e0f835651b16a3931dbe7ac927039be6b2e1cb617bbe1d584b492b +F test/bestindex4.test 3039894f2dad50f3a68443dffad1b44c9b067ac03870102df1ce3d9a46ea602e +F test/bestindex5.test a0c90b2dad7836e80a01379e200e5f8ec9476d49b349af02c0dbff2fb75dc98d +F test/bestindex6.test 16942535b551273f3ad9df8d7cc4b7f22b1fcd8882714358859eb049a6f99dd4 +F test/bestindex7.test f094c669a6400777f4d2ddc3ed28e39169f1adb5be3d59b55f22ccf8c414b71e +F test/bestindex8.test 025477dd9bdb462f4faef3dd8838306f4e230620677778f4fe27e6736789e1b8 F test/between.test b9a65fb065391980119e8a781a7409d3fcf059d89968279c750e190a9a1d5263 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc @@ -1327,7 +1328,7 @@ F test/rowvalue.test 02214016f747854ef636e64ff204778649937aa801ca78e2495a960f8e0 F test/rowvalue2.test 060d238b7e5639a7c5630cb5e63e311b44efef2b F test/rowvalue3.test 3068f508753af69884b12125995f023da0dbb256 F test/rowvalue4.test 441e7e366ac6d939a3a95a574031c56ec2a854077a91d66eee5ff1d86cb5be58 -F test/rowvalue5.test c81c7d8cf36711ab37675ad7376084ae2a359cb6 +F test/rowvalue5.test 00740304ea6a53a8704640c7405690f0045d5d2a6b4b04dde7bccc14c3068ea7 F test/rowvalue6.test d19b54feb604d5601f8614b15e214e0774c01087 F test/rowvalue7.test c1cbdbf407029db01f87764097c6ac02a1c5a37efd2776eff32a9cdfdf6f2dba F test/rowvalue8.test 5900eddad9e2c3c2e26f1a95f74aafc1232ee5e0 @@ -1942,8 +1943,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 3bf2153440dce0e8c0572c4fd39e6b9f34ead75ccab2cea80a646d4ff9d19146 -R f229e1cc04b78384226f7f6c44d75044 +P c99df4ab5db2c32b044366c5b0ac70fd8887d1456d53323e75fede23cc61c236 +R 73bbb4581fa3d24d1ee5b28ca166750f U drh -Z 5557e45c47098a153d05816f88cff23e +Z 0537364a2100dee8e5b21a6d334bfbef # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 472918b743..ef06714857 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c99df4ab5db2c32b044366c5b0ac70fd8887d1456d53323e75fede23cc61c236 \ No newline at end of file +21afb81d0a73af39aacd9329b1441faa2b535a52a52036daec89fd303a8b344f \ No newline at end of file diff --git a/src/test_bestindex.c b/src/test_bestindex.c index 6cda889e5e..67a0c8258d 100644 --- a/src/test_bestindex.c +++ b/src/test_bestindex.c @@ -299,7 +299,21 @@ static int tclFilter( const char *zVal = (const char*)sqlite3_value_text(argv[ii]); Tcl_Obj *pVal; if( zVal==0 ){ + sqlite3_value *pMem; pVal = Tcl_NewObj(); + for(rc=sqlite3_vtab_in_first(argv[ii], &pMem); + rc==SQLITE_OK && pMem; + rc=sqlite3_vtab_in_next(argv[ii], &pMem) + ){ + Tcl_Obj *pVal2 = 0; + zVal = (const char*)sqlite3_value_text(pMem); + if( zVal ){ + pVal2 = Tcl_NewStringObj(zVal, -1); + }else{ + pVal2 = Tcl_NewObj(); + } + Tcl_ListObjAppendElement(interp, pVal, pVal2); + } }else{ pVal = Tcl_NewStringObj(zVal, -1); } @@ -374,20 +388,13 @@ static int tclEof(sqlite3_vtab_cursor *pVtabCursor){ return (pCsr->pStmt==0); } -static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ - tcl_vtab *pTab = (tcl_vtab*)tab; - Tcl_Interp *interp = pTab->interp; - Tcl_Obj *pArg; - Tcl_Obj *pScript; +static void testBestIndexObjConstraints( + Tcl_Interp *interp, + sqlite3_index_info *pIdxInfo +){ int ii; - int rc = SQLITE_OK; - - pScript = Tcl_DuplicateObj(pTab->pCmd); - Tcl_IncrRefCount(pScript); - Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("xBestIndex", -1)); - - pArg = Tcl_NewObj(); - Tcl_IncrRefCount(pArg); + Tcl_Obj *pRes = Tcl_NewObj(); + Tcl_IncrRefCount(pRes); for(ii=0; iinConstraint; ii++){ struct sqlite3_index_constraint const *pCons = &pIdxInfo->aConstraint[ii]; Tcl_Obj *pElem = Tcl_NewObj(); @@ -437,15 +444,21 @@ static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("usable", -1)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewIntObj(pCons->usable)); - Tcl_ListObjAppendElement(0, pArg, pElem); + Tcl_ListObjAppendElement(0, pRes, pElem); Tcl_DecrRefCount(pElem); } - Tcl_ListObjAppendElement(0, pScript, pArg); - Tcl_DecrRefCount(pArg); + Tcl_SetObjResult(interp, pRes); + Tcl_DecrRefCount(pRes); +} - pArg = Tcl_NewObj(); - Tcl_IncrRefCount(pArg); +static void testBestIndexObjOrderby( + Tcl_Interp *interp, + sqlite3_index_info *pIdxInfo +){ + int ii; + Tcl_Obj *pRes = Tcl_NewObj(); + Tcl_IncrRefCount(pRes); for(ii=0; iinOrderBy; ii++){ struct sqlite3_index_orderby const *pOrder = &pIdxInfo->aOrderBy[ii]; Tcl_Obj *pElem = Tcl_NewObj(); @@ -456,17 +469,150 @@ static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("desc", -1)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewIntObj(pOrder->desc)); - Tcl_ListObjAppendElement(0, pArg, pElem); + Tcl_ListObjAppendElement(0, pRes, pElem); Tcl_DecrRefCount(pElem); } - Tcl_ListObjAppendElement(0, pScript, pArg); - Tcl_DecrRefCount(pArg); + Tcl_SetObjResult(interp, pRes); + Tcl_DecrRefCount(pRes); +} - Tcl_ListObjAppendElement(0, pScript, Tcl_NewWideIntObj(pIdxInfo->colUsed)); +/* +** Implementation of the handle passed to each xBestIndex callback. This +** object features the following sub-commands: +** +** $hdl constraints +** $hdl orderby +** $hdl mask +** +** $hdl distinct +** Return the result (an integer) of calling sqlite3_vtab_distinct() +** on the index-info structure. +** +** $hdl in IDX BOOLEAN +** Wrapper around sqlite3_vtab_in(). Returns an integer. +** +** $hdl rhs_value IDX ?DEFAULT? +** Wrapper around sqlite3_vtab_rhs_value(). +*/ +static int SQLITE_TCLAPI testBestIndexObj( + ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + const char *azSub[] = { + "constraints", /* 0 */ + "orderby", /* 1 */ + "mask", /* 2 */ + "distinct", /* 3 */ + "in", /* 4 */ + "rhs_value", /* 5 */ + 0 + }; + int ii; + sqlite3_index_info *pIdxInfo = (sqlite3_index_info*)clientData; + if( objc<2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND"); + return TCL_ERROR; + } + if( Tcl_GetIndexFromObj(interp, objv[1], azSub, "sub-command", 0, &ii) ){ + return TCL_ERROR; + } + + if( ii<4 && objc!=2 ){ + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + if( ii==4 && objc!=4 ){ + Tcl_WrongNumArgs(interp, 2, objv, "INDEX BOOLEAN"); + return TCL_ERROR; + } + if( ii==5 && objc!=3 && objc!=4 ){ + Tcl_WrongNumArgs(interp, 2, objv, "INDEX ?DEFAULT?"); + return TCL_ERROR; + } + + switch( ii ){ + case 0: assert( sqlite3_stricmp(azSub[ii], "constraints")==0 ); + testBestIndexObjConstraints(interp, pIdxInfo); + break; + + case 1: assert( sqlite3_stricmp(azSub[ii], "orderby")==0 ); + testBestIndexObjOrderby(interp, pIdxInfo); + break; + + case 2: assert( sqlite3_stricmp(azSub[ii], "mask")==0 ); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj(pIdxInfo->colUsed)); + break; + + case 3: assert( sqlite3_stricmp(azSub[ii], "distinct")==0 ); { + int bDistinct = sqlite3_vtab_distinct(pIdxInfo); + Tcl_SetObjResult(interp, Tcl_NewIntObj(bDistinct)); + break; + } + + case 4: assert( sqlite3_stricmp(azSub[ii], "in")==0 ); { + int iCons; + int bHandle; + if( Tcl_GetIntFromObj(interp, objv[2], &iCons) + || Tcl_GetBooleanFromObj(interp, objv[3], &bHandle) + ){ + return TCL_ERROR; + } + Tcl_SetObjResult(interp, + Tcl_NewIntObj(sqlite3_vtab_in(pIdxInfo, iCons, bHandle)) + ); + break; + } + + case 5: assert( sqlite3_stricmp(azSub[ii], "rhs_value")==0 ); { + int iCons = 0; + int rc; + sqlite3_value *pVal = 0; + const char *zVal = ""; + if( Tcl_GetIntFromObj(interp, objv[2], &iCons) ){ + return TCL_ERROR; + } + rc = sqlite3_vtab_rhs_value(pIdxInfo, iCons, &pVal); + if( rc!=SQLITE_OK && rc!=SQLITE_NOTFOUND ){ + Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE); + return TCL_ERROR; + } + if( pVal ){ + zVal = (const char*)sqlite3_value_text(pVal); + }else if( objc==4 ){ + zVal = Tcl_GetString(objv[3]); + } + Tcl_SetObjResult(interp, Tcl_NewStringObj(zVal, -1)); + break; + } + } + + return TCL_OK; +} + +static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + tcl_vtab *pTab = (tcl_vtab*)tab; + Tcl_Interp *interp = pTab->interp; + int rc = SQLITE_OK; + + static int iNext = 43; + char zHdl[24]; + Tcl_Obj *pScript; + + pScript = Tcl_DuplicateObj(pTab->pCmd); + Tcl_IncrRefCount(pScript); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("xBestIndex", -1)); + + sqlite3_snprintf(sizeof(zHdl), zHdl, "bestindex%d", iNext++); + Tcl_CreateObjCommand(interp, zHdl, testBestIndexObj, pIdxInfo, 0); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(zHdl, -1)); rc = Tcl_EvalObjEx(interp, pScript, TCL_EVAL_GLOBAL); + Tcl_DeleteCommand(interp, zHdl); Tcl_DecrRefCount(pScript); + if( rc!=TCL_OK ){ const char *zErr = Tcl_GetStringResult(interp); rc = SQLITE_ERROR; @@ -493,6 +639,7 @@ static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ rc = SQLITE_ERROR; pTab->base.zErrMsg = sqlite3_mprintf("%s", zErr); }else{ + int ii; int iArgv = 1; for(ii=0; rc==SQLITE_OK && ii=0} + } $idxinsert +} + +#------------------------------------------------------------------------- +reset_db +register_tcl_module db + +proc vtab_command {src method args} { + switch -- $method { + xConnect { + return "CREATE TABLE xxx(a, b)" + } + + xBestIndex { + set hdl [lindex $args 0] + set ret [list] + + set iCons 0 + foreach cons [$hdl constraints] { + array set C $cons + if {($C(op)=="limit" || $C(op)=="offset") && $C(usable)} { + lappend ret use $iCons + } + incr iCons + } + + return $ret + } + + xFilter { + lappend ::lFilterArgs [lindex $args 2] + return [list sql "SELECT rowid, a, b FROM $src"] + } + + } + + return {} +} + +do_execsql_test 2.0 { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); + CREATE VIRTUAL TABLE vt1 USING tcl(vtab_command t1); +} + +do_test 2.1 { + set ::lFilterArgs [list] + execsql { SELECT * FROM vt1 LIMIT 10 } + set ::lFilterArgs +} {10} + +do_test 2.2 { + set ::lFilterArgs [list] + execsql { SELECT * FROM vt1 LIMIT 5 OFFSET 50 } + set ::lFilterArgs +} {{5 50}} + +do_test 2.3 { + set ::lFilterArgs [list] + execsql { SELECT * FROM vt1 ORDER BY a, b LIMIT 1 OFFSET 1 } + set ::lFilterArgs +} {{1 1}} + +do_test 2.4 { + set ::lFilterArgs [list] + execsql { SELECT * FROM vt1 ORDER BY a, +b LIMIT 1 OFFSET 1 } + set ::lFilterArgs +} {{}} + +#------------------------------------------------------------------------- +reset_db +register_tcl_module db + +proc vtab_command {src method args} { + switch -- $method { + xConnect { + return "CREATE TABLE xxx(a, b)" + } + + xBestIndex { + set hdl [lindex $args 0] + set lCons [$hdl constraints] + + set ret [list] + for {set i 0} {$i < [llength $lCons]} {incr i} { + array set C [lindex $lCons $i] + if {$C(usable)} { + lappend ret use $i + $hdl in $i 1 + } + } + return $ret + } + + xFilter { + set lArg [lindex $args 2] + lappend ::lFilterArg {*}$lArg + return [list sql "SELECT rowid, a, b FROM $src"] + } + + } + + return {} +} + +do_execsql_test 3.0 { + CREATE TABLE t1(a, b); + CREATE INDEX i1 ON t1(a, b); + CREATE VIRTUAL TABLE vt1 USING tcl(vtab_command t1); +} + +foreach {tn sql lfa} { + 1 "SELECT * FROM vt1 WHERE b IN (10, 20, 30)" {{10 20 30}} + 2 "SELECT * FROM vt1 WHERE b IN ('abc', 'def')" {{abc def}} + 3 "SELECT * FROM vt1 WHERE a IS NULL AND b IN ('abc', 'def')" {{} {abc def}} + 4 "SELECT * FROM vt1 WHERE a IN (1,2,3) AND b IN ('abc', 'def')" + {{1 2 3} {abc def}} + + 5 "SELECT * FROM vt1 + WHERE a IN (SELECT 1 UNION SELECT 2) AND b IN ('abc', 'def')" + {{1 2} {abc def}} + + 6 "SELECT * FROM vt1 + WHERE b IN ('abc', 'def') AND a IN (SELECT 1 UNION SELECT 2)" + {{abc def} {1 2}} +} { + do_test 3.$tn { + set ::lFilterArg [list] + execsql $sql + set ::lFilterArg + } $lfa +} + +#explain_i { SELECT * FROM vt1 WHERE b IN (10, 20, 30) } + +#------------------------------------------------------------------------- +reset_db +register_tcl_module db + +proc vtab_command {src method args} { + switch -- $method { + xConnect { + return "CREATE TABLE xxx(a, b, c)" + } + + xBestIndex { + set hdl [lindex $args 0] + set lCons [$hdl constraints] + + set ret [list] + for {set i 0} {$i < [llength $lCons]} {incr i} { + lappend ::lBestIndexRhs [$hdl rhs_value $i -] + } + return $ret + } + + xFilter { + return [list sql "SELECT rowid, a, b, c FROM $src"] + } + + } + + return {} +} + +do_execsql_test 4.0 { + CREATE TABLE t1(a, b, c); + CREATE VIRTUAL TABLE vt1 USING tcl(vtab_command t1); +} + +foreach {tn sql lbir} { + 1 "SELECT * FROM vt1 WHERE b = 10" {10} + 2 "SELECT * FROM vt1 WHERE a = 'abc' AND b < 30" {abc 30} + 3 "SELECT * FROM vt1 WHERE a = 'abc' AND b < 30+2" {abc -} + 4 "SELECT * FROM vt1 WHERE a IN (1,2,3) AND b < 30+2" {- -} + 5 "SELECT * FROM vt1 WHERE a IS 111 AND b < 30+2" {111 -} +} { + do_test 4.$tn { + set ::lBestIndexRhs [list] + execsql $sql + set ::lBestIndexRhs + } $lbir +} + +#------------------------------------------------------------------------- +reset_db +db cache size 0 +register_tcl_module db + +set ::vtab_handle_in 1 +proc vtab_command {src method args} { + switch -- $method { + xConnect { + return "CREATE TABLE xxx(a, b, c)" + } + + xBestIndex { + set lCols [list a b c] + + set hdl [lindex $args 0] + set lCons [$hdl constraints] + set lOrder [$hdl order] + + set L "" + set O "" + set W [list] + set a 0 + for {set i 0} {$i < [llength $lCons]} {incr i} { + array set C [lindex $lCons $i] + if {$C(usable)} { + if { $C(op)=="eq" } { + set bIn 0 + if {$::vtab_handle_in} { set bIn [$hdl in $i 1] } + if {$bIn} { + lappend W "[lindex $lCols $C(column)] IN (%I$a%)" + } else { + lappend W "[lindex $lCols $C(column)] = %$a%" + } + lappend ret omit $i + } + if { $C(op)=="limit" } { set L " LIMIT %$a%" ; lappend ret use $i } + if { $C(op)=="offset" } { set O " OFFSET %$a%" ; lappend ret use $i } + incr a + } + } + + set order "" + set selectlist "rowid, a, b, c" + if {[llength $lOrder]} { + array set sl [list] + set lO [list] + foreach s $lOrder { + array set C $s + set ad "" + if {$C(desc)} { set ad " DESC" } + lappend lO "[lindex $lCols $C(column)]$ad" + set sl($C(column)) 1 + } + if {[$hdl distinct]==2} { + set selectlist "DISTINCT 0" + foreach i {0 1 2} { + if {[info exists sl($i)]} { + append selectlist ", [lindex $lCols $i]" + } else { + append selectlist ", 0" + } + } + } else { + set order " ORDER BY [join $lO ,]" + } + } + + set where "" + if {[llength $W]} { set where " WHERE [join $W { AND }]" } + set sql "SELECT $selectlist FROM $src$where$order$L$O" + + lappend ret idxStr $sql + return $ret + } + + xFilter { + foreach {idxnum idxstr lArg} $args {} + set ii 0 + set sql $idxstr + foreach a $lArg { + set sql [string map [list %$ii% $a] $sql] + set sql [string map [list %I$ii% [join $a ,]] $sql] + incr ii + } + lappend ::lFilterSql $sql + + if {[regexp {OFFSET (.*)$} $sql -> off]} { + set real_sql " + WITH c(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM c WHERE i<$off ) + SELECT 0,0,0,0 FROM c + UNION ALL SELECT * FROM ( + $sql + ) + " + } else { + set real_sql $sql + } + + return [list sql $real_sql] + } + + } + + return {} +} + +do_execsql_test 5.0 { + CREATE TABLE t1(a, b, c); + CREATE VIRTUAL TABLE vt1 USING tcl(vtab_command t1); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(2, 3, 4); + INSERT INTO t1 VALUES(3, 4, 5); + INSERT INTO t1 VALUES(1, 5, 6); + INSERT INTO t1 VALUES(2, 6, 7); + INSERT INTO t1 VALUES(3, 7, 8); + INSERT INTO t1 VALUES(1, 8, 9); + INSERT INTO t1 VALUES(2, 9, 0); +} + +proc do_vtab_test {tn sql vtsql {res {}}} { + set ::lFilterSql [list] + uplevel [list do_execsql_test $tn.1 $sql $res] + uplevel [list do_test $tn.2 {set ::lFilterSql} [list {*}$vtsql]] +} + +do_vtab_test 5.1.1 { + SELECT DISTINCT a FROM vt1 +} { + {SELECT DISTINCT 0, a, 0, 0 FROM t1} +} {1 2 3} + +do_vtab_test 5.1.2 { + SELECT DISTINCT a FROM vt1 ORDER BY a +} { + {SELECT rowid, a, b, c FROM t1 ORDER BY a} +} {1 2 3} + +do_vtab_test 5.1.3 { + SELECT DISTINCT a FROM vt1 WHERE c IN (4,5,6,7,8) +} { + {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c IN (4,5,6,7,8)} +} {2 3 1} + +set ::vtab_handle_in 0 +do_vtab_test 5.1.4 { + SELECT DISTINCT a FROM vt1 WHERE c IN (4,5,6,7,8) +} { + {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 4} + {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 5} + {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 6} + {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 7} + {SELECT DISTINCT 0, a, 0, 0 FROM t1 WHERE c = 8} +} {2 3 1} + +set ::vtab_handle_in 1 +do_vtab_test 5.1.5a { + SELECT a, b, c FROM vt1 WHERE c IN (4,5,6,7,8) LIMIT 2 OFFSET 2 +} { + {SELECT rowid, a, b, c FROM t1 WHERE c IN (4,5,6,7,8) LIMIT 2 OFFSET 2} +} {1 5 6 2 6 7} + +set ::vtab_handle_in 0 +do_vtab_test 5.1.5b { + SELECT a, b, c FROM vt1 WHERE c IN (4,5,6,7,8) LIMIT 2 OFFSET 2 +} { + {SELECT rowid, a, b, c FROM t1 WHERE c = 4} + {SELECT rowid, a, b, c FROM t1 WHERE c = 5} + {SELECT rowid, a, b, c FROM t1 WHERE c = 6} + {SELECT rowid, a, b, c FROM t1 WHERE c = 7} +} {1 5 6 2 6 7} +set ::vtab_handle_in 1 + +finish_test diff --git a/test/rowvalue5.test b/test/rowvalue5.test index 58b39f2793..b91dfa49ac 100644 --- a/test/rowvalue5.test +++ b/test/rowvalue5.test @@ -46,7 +46,9 @@ proc vtab_command {method args} { set OP(glob) GLOB set OP(regexp) REGEXP - set clist [lindex $args 0] + set hdl [lindex $args 0] + set clist [$hdl constraints] + set ret [list] set elist [list] set i 0 From cc0db61364b9eec3d91c069e280a258a555aa65d Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 2 Feb 2022 19:30:24 +0000 Subject: [PATCH 9/9] Additional test cases. FossilOrigin-Name: 733d81c3a6a513b0b893a7d14894f36aebbbca9da375c326db8a72df4f0c6238 --- manifest | 12 ++++++------ manifest.uuid | 2 +- test/bestindex8.test | 31 +++++++++++++++++-------------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/manifest b/manifest index 95ed26d241..89b1c257a5 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Test\scases\sfor\ssqlite3_vtab_in()\sand\ssqlite3_vtab_distinct(). -D 2022-02-02T19:15:53.331 +C Additional\stest\scases. +D 2022-02-02T19:30:24.554 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -731,7 +731,7 @@ F test/bestindex4.test 3039894f2dad50f3a68443dffad1b44c9b067ac03870102df1ce3d9a4 F test/bestindex5.test a0c90b2dad7836e80a01379e200e5f8ec9476d49b349af02c0dbff2fb75dc98d F test/bestindex6.test 16942535b551273f3ad9df8d7cc4b7f22b1fcd8882714358859eb049a6f99dd4 F test/bestindex7.test f094c669a6400777f4d2ddc3ed28e39169f1adb5be3d59b55f22ccf8c414b71e -F test/bestindex8.test 025477dd9bdb462f4faef3dd8838306f4e230620677778f4fe27e6736789e1b8 +F test/bestindex8.test abd0016fc04f19dc382976750b06df5463d2757e11e78a8ba7d7dc50671f3337 F test/between.test b9a65fb065391980119e8a781a7409d3fcf059d89968279c750e190a9a1d5263 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc @@ -1943,8 +1943,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P c99df4ab5db2c32b044366c5b0ac70fd8887d1456d53323e75fede23cc61c236 -R 73bbb4581fa3d24d1ee5b28ca166750f +P 21afb81d0a73af39aacd9329b1441faa2b535a52a52036daec89fd303a8b344f +R bd0b74bd91f16e8219ac183cf658cda6 U drh -Z 0537364a2100dee8e5b21a6d334bfbef +Z 4de6a8e85d90e316804149623317b18b # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ef06714857..e0d2343378 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -21afb81d0a73af39aacd9329b1441faa2b535a52a52036daec89fd303a8b344f \ No newline at end of file +733d81c3a6a513b0b893a7d14894f36aebbbca9da375c326db8a72df4f0c6238 \ No newline at end of file diff --git a/test/bestindex8.test b/test/bestindex8.test index a9cc56a806..6816400771 100644 --- a/test/bestindex8.test +++ b/test/bestindex8.test @@ -49,7 +49,7 @@ proc vtab_command {src method args} { } { lappend ret orderby 1 lappend ret idxnum 1 - #puts "ORDER-BY-CONSUMED" + set ::lOrderByConsumed 1 } return $ret } @@ -78,22 +78,22 @@ do_execsql_test 1.0 { INSERT INTO t0(c0) VALUES (1), (0); } -foreach {tn sql bDistinct idxinsert res} { - 1 "SELECT a, b FROM vt1" 0 0 {a b c d a b c d} - 2 "SELECT DISTINCT a, b FROM vt1" 2 1 {a b c d} - 3 "SELECT DISTINCT a FROM vt1" 2 1 {a c} - 4 "SELECT DISTINCT b FROM vt1" 2 1 {b d} - 5 "SELECT DISTINCT b FROM vt1 ORDER BY a" 0 1 {b d} - 6 "SELECT DISTINCT t0.c0 FROM vt1, t0 ORDER BY vt1.a" 0 1 {1 0} - 7 "SELECT DISTINCT a, b FROM vt1 ORDER BY a, b" 1 0 {a b c d} - 8 "SELECT DISTINCT a, b FROM vt1 ORDER BY a" 0 1 {a b c d} - 9 "SELECT DISTINCT a FROM vt1 ORDER BY a, b" 0 1 {a c} +foreach {tn sql bDistinct idxinsert bConsumed res} { + 1 "SELECT a, b FROM vt1" 0 0 0 {a b c d a b c d} + 2 "SELECT DISTINCT a, b FROM vt1" 2 1 1 {a b c d} + 3 "SELECT DISTINCT a FROM vt1" 2 1 1 {a c} + 4 "SELECT DISTINCT b FROM vt1" 2 1 0 {b d} + 5 "SELECT DISTINCT b FROM vt1 ORDER BY a" 0 1 1 {b d} + 6 "SELECT DISTINCT t0.c0 FROM vt1, t0 ORDER BY vt1.a" 0 1 1 {1 0} + 7 "SELECT DISTINCT a, b FROM vt1 ORDER BY a, b" 1 0 1 {a b c d} + 8 "SELECT DISTINCT a, b FROM vt1 ORDER BY a" 0 1 1 {a b c d} + 9 "SELECT DISTINCT a FROM vt1 ORDER BY a, b" 0 1 1 {a c} - 10 "SELECT DISTINCT a, b FROM vt1 WHERE b='b'" 2 1 {a b} - 11 "SELECT DISTINCT a, b FROM vt1 WHERE +b='b'" 2 1 {a b} + 10 "SELECT DISTINCT a, b FROM vt1 WHERE b='b'" 2 1 1 {a b} + 11 "SELECT DISTINCT a, b FROM vt1 WHERE +b='b'" 2 1 1 {a b} } { set ::lBestIndexDistinct "" -if {$tn==10} breakpoint + set ::lOrderByConsumed 0 do_execsql_test 1.$tn.1 $sql $res do_test 1.$tn.2 { set ::lBestIndexDistinct @@ -101,6 +101,9 @@ if {$tn==10} breakpoint do_test 1.$tn.3 { expr {[lsearch [execsql "explain $sql"] IdxInsert]>=0} } $idxinsert + do_test 1.$tn.4 { + set ::lOrderByConsumed + } $bConsumed } #-------------------------------------------------------------------------