Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Change the way TK_SELECT_COLUMN is handled so that the subquery is only generated once even if part of the vector comparison is used for indexing and the other part is now. This change also is a pathway to vector assignment in UPDATE statements. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | rowvalue |
Files: | files | file ages | folders |
SHA1: |
d8feea7dcde83179bff303072426561c |
User & Date: | drh 2016-08-20 00:07:01.602 |
Context
2016-08-20
| ||
00:51 | Add support for vector assignments in the SET clause of an UPDATE statement. (check-in: f320d47d6b user: drh tags: rowvalue) | |
00:07 | Change the way TK_SELECT_COLUMN is handled so that the subquery is only generated once even if part of the vector comparison is used for indexing and the other part is now. This change also is a pathway to vector assignment in UPDATE statements. (check-in: d8feea7dcd user: drh tags: rowvalue) | |
2016-08-19
| ||
19:58 | Replace the magic number (-2) with its symbol XN_EXPR in the exprMightBeIndexed() routine. No logic changes. (check-in: d4a5af69cc user: drh tags: rowvalue) | |
Changes
Changes to src/expr.c.
︙ | ︙ | |||
330 331 332 333 334 335 336 | return pExpr->x.pSelect->pEList->nExpr; } return pExpr->x.pList->nExpr; } #ifndef SQLITE_OMIT_SUBQUERY /* | > | > | < | > | > > > > > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 | return pExpr->x.pSelect->pEList->nExpr; } return pExpr->x.pList->nExpr; } #ifndef SQLITE_OMIT_SUBQUERY /* ** Interpret the pVector input as a vector expression. If pVector is ** an ordinary scalar expression, treat it as a vector of size 1. ** ** Return a pointer to a subexpression of pVector that is the i-th ** column of the vector (numbered starting with 0). The caller must ** ensure that i is within range. ** ** pVector retains ownership of the returned subexpression. ** ** If the vector is a (SELECT ...) then the expression returned is ** just the expression for the i-th term of the result set, and is ** necessarily ready to be evaluated because the table cursor might ** not have been positioned yet. */ Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){ assert( i<sqlite3ExprVectorSize(pVector) ); if( sqlite3ExprIsVector(pVector) ){ if( pVector->op==TK_SELECT ){ return pVector->x.pSelect->pEList->a[i].pExpr; }else{ return pVector->x.pList->a[i].pExpr; } } return pVector; } #endif /* !defined(SQLITE_OMIT_SUBQUERY) */ #ifndef SQLITE_OMIT_SUBQUERY /* ** Compute and return a new Expr object which when passed to ** sqlite3ExprCode() will generate all necessary code to compute ** the iField-th column of the vector expression pVector. ** ** The caller owns the returned Expr object and is responsible for ** ensuring that the returned value eventually gets freed. ** ** Ownership of pVector is controlled by the takeOwnership parameter. If ** takeOwnership is true, this routine takes responsibility for freeing ** pVector, and may do so before returning, hence the caller must not reference ** pVector again. If takeOwnership is false, then the caller takes ** responsibility for freeing pVector and must ensure the pVector remains ** valid as long as the returned value remains in use. */ Expr *sqlite3ExprForVectorField( Parse *pParse, /* Parsing context */ Expr *pVector, /* The vector. List of expressions or a sub-SELECT */ int iField, /* Which column of the vector to return */ int takeOwnership /* True to take ownership of pVector before returning */ ){ Expr *pRet; assert( sqlite3ExprIsVector(pVector) ); /* FIXME: Add support for takeOwnership!=0 */ assert( takeOwnership==0 ); if( pVector->flags & EP_xIsSelect ){ /* The TK_SELECT_COLUMN Expr node: ** ** pLeft: pVector containing TK_SELECT ** pRight: pVector if ownership taken ** iColumn: Index of a column in pVector ** pLeft->iTable: First in an array of register holding result, or 0 ** if the result is not yet computed. ** ** sqlite3ExprDelete() specifically skips the recursive delete of ** pLeft on TK_SELECT_COLUMN nodes. But pRight is followed, so pVector ** is included on pRight if ownership is taken. Typically there will ** be multiple TK_SELECT_COLUMN nodes with the same pLeft pointer to ** the pVector, but only one of them will own the pVector. */ pRet = sqlite3PExpr(pParse, TK_SELECT_COLUMN, pVector, 0, 0); if( pRet ) pRet->iColumn = iField; assert( pRet==0 || pRet->iTable==0 ); }else{ pRet = sqlite3ExprDup(pParse->db, pVector->x.pList->a[iField].pExpr, 0); } return pRet; } #endif /* !define(SQLITE_OMIT_SUBQUERY) */ /* ** If expression pExpr is of type TK_SELECT, generate code to evaluate ** it. Return the register in which the result is stored (or, if the ** sub-select returns more than one column, the first in an array ** of registers in which the result is stored). ** |
︙ | ︙ | |||
2021 2022 2023 2024 2025 2026 2027 | int affinity_ok = 1; int i; /* Check that the affinity that will be used to perform each ** comparison is the same as the affinity of each column. If ** it not, it is not possible to use any index. */ for(i=0; i<nExpr && affinity_ok; i++){ | | | 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 | int affinity_ok = 1; int i; /* Check that the affinity that will be used to perform each ** comparison is the same as the affinity of each column. If ** it not, it is not possible to use any index. */ for(i=0; i<nExpr && affinity_ok; i++){ Expr *pLhs = sqlite3VectorFieldSubexpr(pX->pLeft, i); int iCol = pEList->a[i].pExpr->iColumn; char idxaff = pTab->aCol[iCol].affinity; char cmpaff = sqlite3CompareAffinity(pLhs, idxaff); switch( cmpaff ){ case SQLITE_AFF_BLOB: break; case SQLITE_AFF_TEXT: |
︙ | ︙ | |||
2047 2048 2049 2050 2051 2052 2053 | for(pIdx=pTab->pIndex; pIdx && eType==0 && affinity_ok; pIdx=pIdx->pNext){ if( pIdx->nKeyCol<nExpr ) continue; if( mustBeUnique && (pIdx->nKeyCol!=nExpr || !IsUniqueIndex(pIdx)) ){ continue; } for(i=0; i<nExpr; i++){ | | | 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 | for(pIdx=pTab->pIndex; pIdx && eType==0 && affinity_ok; pIdx=pIdx->pNext){ if( pIdx->nKeyCol<nExpr ) continue; if( mustBeUnique && (pIdx->nKeyCol!=nExpr || !IsUniqueIndex(pIdx)) ){ continue; } for(i=0; i<nExpr; i++){ Expr *pLhs = sqlite3VectorFieldSubexpr(pX->pLeft, i); Expr *pRhs = pEList->a[i].pExpr; CollSeq *pReq = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); int j; assert( pReq || pParse->nErr ); if( pReq==0 ) break; |
︙ | ︙ | |||
2155 2156 2157 2158 2159 2160 2161 | char *zRet; assert( pExpr->op==TK_IN ); zRet = sqlite3DbMallocZero(pParse->db, nVal+1); if( zRet ){ int i; for(i=0; i<nVal; i++){ | | | 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 | char *zRet; assert( pExpr->op==TK_IN ); zRet = sqlite3DbMallocZero(pParse->db, nVal+1); if( zRet ){ int i; for(i=0; i<nVal; i++){ Expr *pA = sqlite3VectorFieldSubexpr(pLeft, i); char a = sqlite3ExprAffinity(pA); if( pSelect ){ zRet[i] = sqlite3CompareAffinity(pSelect->pEList->a[i].pExpr, a); }else{ zRet[i] = a; } } |
︙ | ︙ | |||
2309 2310 2311 2312 2313 2314 2315 | } sqlite3DbFree(pParse->db, dest.zAffSdst); assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ assert( pEList!=0 ); assert( pEList->nExpr>0 ); assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); for(i=0; i<nVal; i++){ | | | 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 | } sqlite3DbFree(pParse->db, dest.zAffSdst); assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ assert( pEList!=0 ); assert( pEList->nExpr>0 ); assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); for(i=0; i<nVal; i++){ Expr *p = (nVal>1) ? sqlite3VectorFieldSubexpr(pLeft, i) : pLeft; pKeyInfo->aColl[i] = sqlite3BinaryCompareCollSeq( pParse, p, pEList->a[i].pExpr ); } } }else if( ALWAYS(pExpr->x.pList!=0) ){ /* Case 2: expr IN (exprlist) |
︙ | ︙ | |||
2549 2550 2551 2552 2553 2554 2555 | if( nVector>1 && (pLeft->flags & EP_xIsSelect) ){ int regSelect = sqlite3CodeSubselect(pParse, pLeft, 0, 0); for(i=0; i<nVector; i++){ sqlite3VdbeAddOp3(v, OP_Copy, regSelect+i, r1+aiMap[i], 0); } }else{ for(i=0; i<nVector; i++){ | | | 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 | if( nVector>1 && (pLeft->flags & EP_xIsSelect) ){ int regSelect = sqlite3CodeSubselect(pParse, pLeft, 0, 0); for(i=0; i<nVector; i++){ sqlite3VdbeAddOp3(v, OP_Copy, regSelect+i, r1+aiMap[i], 0); } }else{ for(i=0; i<nVector; i++){ Expr *pLhs = sqlite3VectorFieldSubexpr(pLeft, i); sqlite3ExprCode(pParse, pLhs, r1+aiMap[i]); } } /* If sqlite3FindInIndex() did not find or create an index that is ** suitable for evaluating the IN operator, then evaluate using a ** sequence of comparisons. |
︙ | ︙ | |||
2608 2609 2610 2611 2612 2613 2614 | ** ** Otherwise, if NULL and false are handled differently, and the ** IN(...) operation is not a vector operation, and the LHS of the ** operator is NULL, then the result is false if the index is ** completely empty, or NULL otherwise. */ if( destIfNull==destIfFalse ){ for(i=0; i<nVector; i++){ | | | 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 | ** ** Otherwise, if NULL and false are handled differently, and the ** IN(...) operation is not a vector operation, and the LHS of the ** operator is NULL, then the result is false if the index is ** completely empty, or NULL otherwise. */ if( destIfNull==destIfFalse ){ for(i=0; i<nVector; i++){ Expr *p = sqlite3VectorFieldSubexpr(pExpr->pLeft, i); if( sqlite3ExprCanBeNull(p) ){ sqlite3VdbeAddOp2(v, OP_IsNull, r1+aiMap[i], destIfNull); VdbeCoverage(v); } } }else if( nVector==1 && sqlite3ExprCanBeNull(pExpr->pLeft) ){ int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, r1); VdbeCoverage(v); |
︙ | ︙ | |||
2650 2651 2652 2653 2654 2655 2656 | addrNext = 1+sqlite3VdbeAddOp2(v, OP_Rewind, iIdx, destIfFalse); VdbeCoverage(v); for(i=0; i<nVector; i++){ Expr *p; CollSeq *pColl; int r2 = sqlite3GetTempReg(pParse); | | | | 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 | addrNext = 1+sqlite3VdbeAddOp2(v, OP_Rewind, iIdx, destIfFalse); VdbeCoverage(v); for(i=0; i<nVector; i++){ Expr *p; CollSeq *pColl; int r2 = sqlite3GetTempReg(pParse); p = sqlite3VectorFieldSubexpr(pLeft, i); pColl = sqlite3ExprCollSeq(pParse, p); sqlite3VdbeAddOp3(v, OP_Column, iIdx, i, r2); sqlite3VdbeAddOp4(v, OP_Eq, r1+i, 0, r2, (void*)pColl,P4_COLLSEQ); sqlite3VdbeChangeP5(v, SQLITE_JUMPIFNULL); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Next, iIdx, addrNext); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfFalse); sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-3); sqlite3ReleaseTempReg(pParse, r2); } sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfNull); /* The key was found in the index. If it contains any NULL values, ** then the result of the IN(...) operator is NULL. Otherwise, the ** result is 1. */ sqlite3VdbeJumpHere(v, addr); for(i=0; i<nVector; i++){ Expr *p = sqlite3VectorFieldSubexpr(pExpr->pLeft, i); if( sqlite3ExprCanBeNull(p) ){ sqlite3VdbeAddOp2(v, OP_IsNull, r1+aiMap[i], destIfNull); VdbeCoverage(v); } } }else if( rRhsHasNull==0 ){ |
︙ | ︙ | |||
3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 | testcase( op==TK_SELECT ); if( op==TK_SELECT && (nCol = pExpr->x.pSelect->pEList->nExpr)!=1 ){ sqlite3SubselectError(pParse, nCol, 1); }else{ inReg = sqlite3CodeSubselect(pParse, pExpr, 0, 0); } break; } case TK_IN: { int destIfFalse = sqlite3VdbeMakeLabel(v); int destIfNull = sqlite3VdbeMakeLabel(v); sqlite3VdbeAddOp2(v, OP_Null, 0, target); sqlite3ExprCodeIN(pParse, pExpr, destIfFalse, destIfNull); sqlite3VdbeAddOp2(v, OP_Integer, 1, target); | > > > > > > > | 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 | testcase( op==TK_SELECT ); if( op==TK_SELECT && (nCol = pExpr->x.pSelect->pEList->nExpr)!=1 ){ sqlite3SubselectError(pParse, nCol, 1); }else{ inReg = sqlite3CodeSubselect(pParse, pExpr, 0, 0); } break; } case TK_SELECT_COLUMN: { if( pExpr->pLeft->iTable==0 ){ pExpr->pLeft->iTable = sqlite3CodeSubselect(pParse, pExpr->pLeft, 0, 0); } inReg = pExpr->pLeft->iTable + pExpr->iColumn; break; } case TK_IN: { int destIfFalse = sqlite3VdbeMakeLabel(v); int destIfNull = sqlite3VdbeMakeLabel(v); sqlite3VdbeAddOp2(v, OP_Null, 0, target); sqlite3ExprCodeIN(pParse, pExpr, destIfFalse, destIfNull); sqlite3VdbeAddOp2(v, OP_Integer, 1, target); |
︙ | ︙ |
Changes to src/sqliteInt.h.
︙ | ︙ | |||
2294 2295 2296 2297 2298 2299 2300 | #if SQLITE_MAX_EXPR_DEPTH>0 int nHeight; /* Height of the tree headed by this node */ #endif int iTable; /* TK_COLUMN: cursor number of table holding column ** TK_REGISTER: register number ** TK_TRIGGER: 1 -> new, 0 -> old | | > | > | 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 | #if SQLITE_MAX_EXPR_DEPTH>0 int nHeight; /* Height of the tree headed by this node */ #endif int iTable; /* TK_COLUMN: cursor number of table holding column ** TK_REGISTER: register number ** TK_TRIGGER: 1 -> new, 0 -> old ** EP_Unlikely: 134217728 times likelihood ** TK_SELECT: 1st register of result vector */ ynVar iColumn; /* TK_COLUMN: column index. -1 for rowid. ** TK_VARIABLE: variable number (always >= 1). ** TK_SELECT_COLUMN: column of the result vector */ i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */ i16 iRightJoinTable; /* If EP_FromJoin, the right table of the join */ u8 op2; /* TK_REGISTER: original value of Expr.op ** TK_COLUMN: the value of p5 for OP_Column ** TK_AGG_FUNCTION: nesting depth */ AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */ Table *pTab; /* Table for TK_COLUMN expressions. */ |
︙ | ︙ | |||
4269 4270 4271 4272 4273 4274 4275 | #if defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST) int sqlite3DbstatRegister(sqlite3*); #endif int sqlite3ExprVectorSize(Expr *pExpr); int sqlite3ExprIsVector(Expr *pExpr); | > | | 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 | #if defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST) int sqlite3DbstatRegister(sqlite3*); #endif int sqlite3ExprVectorSize(Expr *pExpr); int sqlite3ExprIsVector(Expr *pExpr); Expr *sqlite3VectorFieldSubexpr(Expr*, int); Expr *sqlite3ExprForVectorField(Parse*,Expr*,int,int); #endif /* SQLITEINT_H */ |
Changes to src/vdbemem.c.
︙ | ︙ | |||
1567 1568 1569 1570 1571 1572 1573 | alloc.pParse = pParse; alloc.pIdx = pIdx; alloc.ppRec = ppRec; for(i=0; i<nElem; i++){ sqlite3_value *pVal = 0; | | | 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 | alloc.pParse = pParse; alloc.pIdx = pIdx; alloc.ppRec = ppRec; for(i=0; i<nElem; i++){ sqlite3_value *pVal = 0; Expr *pElem = (pExpr ? sqlite3VectorFieldSubexpr(pExpr, i) : 0); u8 aff = sqlite3IndexColumnAffinity(pParse->db, pIdx, iVal+i); alloc.iVal = iVal+i; rc = stat4ValueFromExpr(pParse, pElem, aff, &alloc, &pVal); if( !pVal ) break; nExtract++; } } |
︙ | ︙ |
Changes to src/wherecode.c.
︙ | ︙ | |||
373 374 375 376 377 378 379 | Expr *pX = pTerm->pExpr; Vdbe *v = pParse->pVdbe; int iReg; /* Register holding results */ assert( pLevel->pWLoop->aLTerm[iEq]==pTerm ); assert( iTarget>0 ); if( pX->op==TK_EQ || pX->op==TK_IS ){ | < < < < < < < < < < < < < < < < < < < < < < < < < < | < | 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 | Expr *pX = pTerm->pExpr; Vdbe *v = pParse->pVdbe; int iReg; /* Register holding results */ assert( pLevel->pWLoop->aLTerm[iEq]==pTerm ); assert( iTarget>0 ); if( pX->op==TK_EQ || pX->op==TK_IS ){ iReg = sqlite3ExprCodeTarget(pParse, pX->pRight, iTarget); }else if( pX->op==TK_ISNULL ){ iReg = iTarget; sqlite3VdbeAddOp2(v, OP_Null, 0, iReg); #ifndef SQLITE_OMIT_SUBQUERY }else{ int eType; int iTab; |
︙ | ︙ | |||
1097 1098 1099 1100 1101 1102 1103 | pTerm = pLoop->aLTerm[j]; if( NEVER(pTerm==0) ) continue; if( pTerm->eOperator & WO_IN ){ codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); addrNotFound = pLevel->addrNxt; }else{ Expr *pRight = pTerm->pExpr->pRight; | < < < | < | 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 | pTerm = pLoop->aLTerm[j]; if( NEVER(pTerm==0) ) continue; if( pTerm->eOperator & WO_IN ){ codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); addrNotFound = pLevel->addrNxt; }else{ Expr *pRight = pTerm->pExpr->pRight; codeExprOrVector(pParse, pRight, iTarget, 1); } } sqlite3VdbeAddOp2(v, OP_Integer, pLoop->u.vtab.idxNum, iReg); sqlite3VdbeAddOp2(v, OP_Integer, nConstraint, iReg+1); sqlite3VdbeAddOp4(v, OP_VFilter, iCur, addrNotFound, iReg, pLoop->u.vtab.idxStr, pLoop->u.vtab.needFree ? P4_MPRINTF : P4_STATIC); |
︙ | ︙ |
Changes to src/whereexpr.c.
︙ | ︙ | |||
869 870 871 872 873 874 875 | return 1; } } } return 0; } | < < < < < < < < < < < < < < < < < < < < < < < | 869 870 871 872 873 874 875 876 877 878 879 880 881 882 | return 1; } } } return 0; } /* ** The input to this routine is an WhereTerm structure with only the ** "pExpr" field filled in. The job of this routine is to analyze the ** subexpression and populate all the other fields of the WhereTerm ** structure. ** ** If the expression is of the form "<expr> <op> X" it gets commuted |
︙ | ︙ | |||
1207 1208 1209 1210 1211 1212 1213 | )){ int nLeft = sqlite3ExprVectorSize(pExpr->pLeft); if( nLeft==sqlite3ExprVectorSize(pExpr->pRight) ){ int i; for(i=0; i<sqlite3ExprVectorSize(pExpr->pLeft); i++){ int idxNew; Expr *pNew; | | | | > > > | 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 | )){ int nLeft = sqlite3ExprVectorSize(pExpr->pLeft); if( nLeft==sqlite3ExprVectorSize(pExpr->pRight) ){ int i; for(i=0; i<sqlite3ExprVectorSize(pExpr->pLeft); i++){ int idxNew; Expr *pNew; Expr *pLeft = sqlite3ExprForVectorField(pParse, pExpr->pLeft, i, 0); Expr *pRight = sqlite3ExprForVectorField(pParse, pExpr->pRight, i, 0); pNew = sqlite3PExpr(pParse, pExpr->op, pLeft, pRight, 0); idxNew = whereClauseInsert(pWC, pNew, TERM_DYNAMIC); exprAnalyze(pSrc, pWC, idxNew); markTermAsChild(pWC, idxNew, idxTerm); } pTerm = &pWC->a[idxTerm]; pTerm->wtFlags = TERM_CODED; pTerm->eOperator = 0; } } /* If there is a vector IN term - e.g. "(a, b) IN (SELECT ...)" - create ** a virtual term for each vector component. The expression object ** used by each such virtual term is pExpr (the full vector IN(...) ** expression). The WhereTerm.iField variable identifies the index within |
︙ | ︙ |