> On 21 Mar 2018, at 16:14, Kirill Yukhin wrote: > > Hello, > My comments inlined. > > On 21 мар 02:48, Nikita Pettik wrote: >> Originally in SQLite, to open table (i.e. btree) it was required to pass >> number of page root to OP_OpenRead or OP_OpenWrite opcodes as an >> argument. However, now there are only Tarantool spaces and nothing >> prevents from operating directly on pointers to them. On the other hand, >> pointers are able to expire after schema changes (i.e. after DML >> routine). For this reason, schema version is saved to VDBE at compile >> time and checked each time during cursor opening. >> >> Part of #3252 >> --- >> src/box/sql/analyze.c | 17 +++++++++++++++-- >> src/box/sql/build.c | 7 ++++++- >> src/box/sql/expr.c | 14 ++++++++++++-- >> src/box/sql/fkey.c | 11 ++++++++++- >> src/box/sql/insert.c | 37 ++++++++++++++++++++++++++++++++----- >> src/box/sql/select.c | 11 ++++++++++- >> src/box/sql/vdbe.c | 12 ++++++++++-- >> src/box/sql/vdbe.h | 1 + >> src/box/sql/vdbeInt.h | 1 + >> src/box/sql/vdbeaux.c | 13 +++++++++++++ >> src/box/sql/where.c | 12 +++++++++++- >> 11 files changed, 121 insertions(+), 15 deletions(-) >> >> diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c >> index db06d0182..57fc33c70 100644 >> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c >> index 9929dfb96..5d1227afa 100644 >> --- a/src/box/sql/vdbe.c >> +++ b/src/box/sql/vdbe.c >> @@ -3217,9 +3217,17 @@ case OP_OpenWrite: >> >> assert(p2 >= 1); >> pBtCur = pCur->uc.pCursor; >> - pBtCur->space = space_by_id(SQLITE_PAGENO_TO_SPACEID(p2)); >> + if (box_schema_version() == p->schema_ver) { >> + pIn3 = &aMem[pOp->p3]; >> + /* Make sure that 64-bit pointer can fit into int64_t. */ >> + assert(sizeof(pBtCur->space) >= sizeof(pIn3->u.i)); > I don't like this. If we're going to extensively use pointers space/index then > let's extend Memory struct adding dedicated types to the union. > Note, that new opcode (say, LoadPtr) will be needed. Done. > >> + pBtCur->space = ((struct space *) pIn3->u.i); >> + } else { >> + pBtCur->space = space_by_id(SQLITE_PAGENO_TO_SPACEID(p2)); >> + } > Don't surround single stmt withcurly braces pls. This is only only advise (https://www.kernel.org/doc/html/v4.10/process/coding-style.html#placing-braces-and-spaces ): "Do not unnecessarily use braces where a single statement will do." So, I guess, both variants are allowed. > Also, if schema was changed then error should be returned (stmt expired or > smth). I have dedicated separate patch (the last one) for this issue. It can’t be done right here since new DDL processing is also introduced in next patch. In order to make no confusions, I will remove this diff and place it to the last patch. >> diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h >> index 7241963e4..a1ecf729d 100644 >> --- a/src/box/sql/vdbe.h >> +++ b/src/box/sql/vdbe.h >> diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h >> index 8b622de5b..99262ab7b 100644 >> --- a/src/box/sql/vdbeInt.h >> +++ b/src/box/sql/vdbeInt.h >> @@ -378,6 +378,7 @@ struct Vdbe { >> i64 nFkConstraint; /* Number of imm. FK constraints this VM */ >> i64 nStmtDefCons; /* Number of def. constraints when stmt started */ >> i64 nStmtDefImmCons; /* Number of def. imm constraints when stmt started */ >> + uint32_t schema_ver; /* Schema version at the moment of VDBE creation. */ >> >> /* >> * In recursive triggers we can execute INSERT/UPDATE OR IGNORE >> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c >> index 92bf9943b..b35d0712e 100644 >> --- a/src/box/sql/vdbeaux.c >> +++ b/src/box/sql/vdbeaux.c >> @@ -66,6 +66,7 @@ sqlite3VdbeCreate(Parse * pParse) >> p->magic = VDBE_MAGIC_INIT; >> p->pParse = pParse; >> p->autoCommit = (char)box_txn() == 0 ? 1 : 0; >> + p->schema_ver = box_schema_version(); >> if (!p->autoCommit) { >> p->psql_txn = in_txn()->psql_txn; >> p->nDeferredCons = p->psql_txn->nDeferredConsSave; >> @@ -413,6 +414,18 @@ sqlite3VdbeAddOp4Int(Vdbe * p, /* Add the opcode to this VM */ >> return addr; >> } >> >> +int >> +sqlite3VdbeAddOp4Int64(Vdbe *p, int op, int p1, int p2, int p3, int64_t p4) >> +{ >> + int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3); >> + VdbeOp *pOp = &p->aOp[addr]; >> + pOp->p4type = P4_INT64; >> + pOp->p4.pI64 = sqlite3DbMallocRawNN(sqlite3VdbeDb(p), sizeof(int64_t)); >> + if (p->db->mallocFailed == 0) >> + *pOp->p4.pI64 = p4; >> + return addr; >> +} > This is useless if LoadPTR will be introduced. Done. The whole patch is below: ======================================================================= Originally in SQLite, to open table (i.e. btree) it was required to pass number of page root to OP_OpenRead or OP_OpenWrite opcodes as an argument. However, now there are only Tarantool spaces and nothing prevents from operating directly on pointers to them. Thus, to pass pointers from compile time to runtime, opcode OP_LoadPtr has been introduced. It fetches pointer from P4 and stores to the register specified by P2. It is worth mentioning that, pointers are able to expire after schema changes (i.e. after DML routine). For this reason, schema version is saved to VDBE at compile time and checked each time during cursor opening. Part of #3252 --- src/box/sql/analyze.c | 17 +++++- src/box/sql/build.c | 7 ++- src/box/sql/expr.c | 14 ++++- src/box/sql/fkey.c | 11 +++- src/box/sql/insert.c | 36 ++++++++++-- src/box/sql/opcodes.c | 137 +++++++++++++++++++++---------------------- src/box/sql/opcodes.h | 157 +++++++++++++++++++++++++------------------------- src/box/sql/select.c | 11 +++- src/box/sql/vdbe.c | 13 +++++ src/box/sql/vdbe.h | 2 + src/box/sql/vdbeInt.h | 3 + src/box/sql/vdbeaux.c | 11 ++++ src/box/sql/where.c | 12 +++- 13 files changed, 272 insertions(+), 159 deletions(-) diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c index db06d0182..d121dd2b9 100644 --- a/src/box/sql/analyze.c +++ b/src/box/sql/analyze.c @@ -174,10 +174,16 @@ openStatTable(Parse * pParse, /* Parsing context */ /* Open the sql_stat[134] tables for writing. */ for (i = 0; aTable[i]; i++) { + struct space *space = + space_by_id(SQLITE_PAGENO_TO_SPACEID(aRoot[i])); + assert(space != NULL); + int space_ptr_reg = ++pParse->nMem; + sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0, + (void *) space); int addr; addr = sqlite3VdbeAddOp3(v, OP_OpenWrite, iStatCur + i, aRoot[i], - 0); + space_ptr_reg); v->aOp[addr].p4.pKeyInfo = 0; v->aOp[addr].p4type = P4_KEYINFO; sqlite3VdbeChangeP5(v, aCreateTbl[i]); @@ -814,6 +820,7 @@ analyzeOneTable(Parse * pParse, /* Parser context */ int iTabCur; /* Table cursor */ Vdbe *v; /* The virtual machine being built up */ int i; /* Loop counter */ + int space_ptr_reg = iMem++; int regStat4 = iMem++; /* Register to hold Stat4Accum object */ int regChng = iMem++; /* Index of changed index field */ int regKey = iMem++; /* Key argument passed to stat_push() */ @@ -910,7 +917,13 @@ analyzeOneTable(Parse * pParse, /* Parser context */ /* Open a read-only cursor on the index being analyzed. */ assert(sqlite3SchemaToIndex(db, pIdx->pSchema) == 0); - sqlite3VdbeAddOp2(v, OP_OpenRead, iIdxCur, pIdx->tnum); + struct space *space = + space_by_id(SQLITE_PAGENO_TO_SPACEID(pIdx->tnum)); + assert(space != NULL); + sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0, + (void *) space); + sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum, + space_ptr_reg); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "%s", pIdx->zName)); diff --git a/src/box/sql/build.c b/src/box/sql/build.c index 9ad0c0605..9cdfd0b7a 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -2603,7 +2603,12 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage) sqlite3VdbeJumpHere(v, addr1); if (memRootPage < 0) sqlite3VdbeAddOp2(v, OP_Clear, tnum, 0); - sqlite3VdbeAddOp4(v, OP_OpenWrite, iIdx, tnum, 0, + struct space *space = space_by_id(SQLITE_PAGENO_TO_SPACEID(tnum)); + assert(space != NULL); + int space_ptr_reg = ++pParse->nMem; + sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0, + (void *) space); + sqlite3VdbeAddOp4(v, OP_OpenWrite, iIdx, tnum, space_ptr_reg, (char *)pKey, P4_KEYINFO); sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR | ((memRootPage >= 0) ? diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c index b69a176cb..009538095 100644 --- a/src/box/sql/expr.c +++ b/src/box/sql/expr.c @@ -35,7 +35,9 @@ */ #include #include "sqliteInt.h" +#include "tarantoolInt.h" #include "box/session.h" +#include "box/schema.h" /* Forward declarations */ static void exprCodeBetween(Parse *, Expr *, int, @@ -2586,8 +2588,16 @@ sqlite3FindInIndex(Parse * pParse, /* Parsing context */ pIdx->zName), P4_DYNAMIC); #endif - sqlite3VdbeAddOp2(v, OP_OpenRead, iTab, - pIdx->tnum); + struct space *space = + space_by_id(SQLITE_PAGENO_TO_SPACEID(pIdx->tnum)); + assert(space != NULL); + int space_ptr_reg = ++pParse->nMem; + sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, + space_ptr_reg, 0, + (void *) space); + sqlite3VdbeAddOp3(v, OP_OpenRead, iTab, + pIdx->tnum, + space_ptr_reg); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "%s", pIdx->zName)); assert(IN_INDEX_INDEX_DESC == diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c index 439f38352..77565cb50 100644 --- a/src/box/sql/fkey.c +++ b/src/box/sql/fkey.c @@ -35,7 +35,9 @@ */ #include #include "sqliteInt.h" +#include "tarantoolInt.h" #include "box/session.h" +#include "box/schema.h" #ifndef SQLITE_OMIT_FOREIGN_KEY #ifndef SQLITE_OMIT_TRIGGER @@ -434,7 +436,14 @@ fkLookupParent(Parse * pParse, /* Parse context */ int regTemp = sqlite3GetTempRange(pParse, nCol); int regRec = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp2(v, OP_OpenRead, iCur, pIdx->tnum); + struct space *space = + space_by_id(SQLITE_PAGENO_TO_SPACEID(pIdx->tnum)); + assert(space != NULL); + int space_ptr_reg = ++pParse->nMem; + sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0, + (void *) space); + sqlite3VdbeAddOp3(v, OP_OpenRead, iCur, pIdx->tnum, + space_ptr_reg); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); for (i = 0; i < nCol; i++) { sqlite3VdbeAddOp2(v, OP_Copy, diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c index 54fcca5c9..4f3e2f316 100644 --- a/src/box/sql/insert.c +++ b/src/box/sql/insert.c @@ -34,7 +34,9 @@ * to handle INSERT statements in SQLite. */ #include "sqliteInt.h" +#include "tarantoolInt.h" #include "box/session.h" +#include "box/schema.h" /* * Generate code that will open pTab as cursor iCur. @@ -51,7 +53,12 @@ sqlite3OpenTable(Parse * pParse, /* Generate code into this VDBE */ Index *pPk = sqlite3PrimaryKeyIndex(pTab); assert(pPk != 0); assert(pPk->tnum == pTab->tnum); - sqlite3VdbeAddOp3(v, opcode, iCur, pPk->tnum, 0); + struct space *space = space_by_id(SQLITE_PAGENO_TO_SPACEID(pPk->tnum)); + assert(space != NULL); + int space_ptr_reg = ++pParse->nMem; + sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0, + (void *) space); + sqlite3VdbeAddOp3(v, opcode, iCur, pPk->tnum, space_ptr_reg); sqlite3VdbeSetP4KeyInfo(pParse, pPk); VdbeComment((v, "%s", pTab->zName)); } @@ -183,7 +190,7 @@ readsTable(Parse * p, Table * pTab) for (i = 1; i < iEnd; i++) { VdbeOp *pOp = sqlite3VdbeGetOp(v, i); assert(pOp != 0); - if (pOp->opcode == OP_OpenRead && pOp->p3 == 0) { + if (pOp->opcode == OP_OpenRead) { Index *pIndex; int tnum = pOp->p2; if (tnum == pTab->tnum) { @@ -1560,6 +1567,10 @@ sqlite3OpenTableAndIndices(Parse * pParse, /* Parsing context */ *piDataCur = iDataCur; if (piIdxCur) *piIdxCur = iBase; + struct space *space = space_by_id(SQLITE_PAGENO_TO_SPACEID(pTab->tnum)); + assert(space != NULL); + int space_ptr_reg = ++pParse->nMem; + sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0, (void *) space); /* One iteration of this cycle adds OpenRead/OpenWrite which * opens cursor for current index. @@ -1607,7 +1618,8 @@ sqlite3OpenTableAndIndices(Parse * pParse, /* Parsing context */ p5 = 0; } if (aToOpen == 0 || aToOpen[i + 1]) { - sqlite3VdbeAddOp2(v, op, iIdxCur, pIdx->tnum); + sqlite3VdbeAddOp3(v, op, iIdxCur, pIdx->tnum, + space_ptr_reg); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); sqlite3VdbeChangeP5(v, p5); VdbeComment((v, "%s", pIdx->zName)); @@ -1911,10 +1923,24 @@ xferOptimization(Parse * pParse, /* Parser context */ break; } assert(pSrcIdx); - sqlite3VdbeAddOp2(v, OP_OpenRead, iSrc, pSrcIdx->tnum); + struct space *space_src = + space_by_id(SQLITE_PAGENO_TO_SPACEID(pSrcIdx->tnum)); + assert(space_src != NULL); + int space_src_ptr_reg = ++pParse->nMem; + sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_src_ptr_reg, 0, + (void *) space_src); + sqlite3VdbeAddOp3(v, OP_OpenRead, iSrc, pSrcIdx->tnum, + space_src_ptr_reg); sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx); VdbeComment((v, "%s", pSrcIdx->zName)); - sqlite3VdbeAddOp2(v, OP_OpenWrite, iDest, pDestIdx->tnum); + struct space *space_dest = + space_by_id(SQLITE_PAGENO_TO_SPACEID(pDestIdx->tnum)); + assert(space_dest != NULL); + int space_dest_ptr_reg = ++pParse->nMem; + sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_dest_ptr_reg, 0, + (void *) space_dest); + sqlite3VdbeAddOp3(v, OP_OpenWrite, iDest, pDestIdx->tnum, + space_dest_ptr_reg); sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx); sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR); VdbeComment((v, "%s", pDestIdx->zName)); diff --git a/src/box/sql/opcodes.c b/src/box/sql/opcodes.c index 7a40b28a8..b108d5f9e 100644 --- a/src/box/sql/opcodes.c +++ b/src/box/sql/opcodes.c @@ -78,76 +78,77 @@ const char *sqlite3OpcodeName(int i){ /* 64 */ "Integer" OpHelp("r[P2]=P1"), /* 65 */ "Bool" OpHelp("r[P2]=P1"), /* 66 */ "Int64" OpHelp("r[P2]=P4"), - /* 67 */ "String" OpHelp("r[P2]='P4' (len=P1)"), - /* 68 */ "Null" OpHelp("r[P2..P3]=NULL"), - /* 69 */ "SoftNull" OpHelp("r[P1]=NULL"), - /* 70 */ "Blob" OpHelp("r[P2]=P4 (len=P1, subtype=P3)"), - /* 71 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"), - /* 72 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), - /* 73 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), - /* 74 */ "SCopy" OpHelp("r[P2]=r[P1]"), + /* 67 */ "LoadPtr" OpHelp("r[P2] = P4"), + /* 68 */ "String" OpHelp("r[P2]='P4' (len=P1)"), + /* 69 */ "Null" OpHelp("r[P2..P3]=NULL"), + /* 70 */ "SoftNull" OpHelp("r[P1]=NULL"), + /* 71 */ "Blob" OpHelp("r[P2]=P4 (len=P1, subtype=P3)"), + /* 72 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"), + /* 73 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), + /* 74 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), /* 75 */ "String8" OpHelp("r[P2]='P4'"), - /* 76 */ "IntCopy" OpHelp("r[P2]=r[P1]"), - /* 77 */ "ResultRow" OpHelp("output=r[P1@P2]"), - /* 78 */ "CollSeq" OpHelp(""), - /* 79 */ "Function0" OpHelp("r[P3]=func(r[P2@P5])"), - /* 80 */ "Function" OpHelp("r[P3]=func(r[P2@P5])"), - /* 81 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), - /* 82 */ "RealAffinity" OpHelp(""), - /* 83 */ "Cast" OpHelp("affinity(r[P1])"), - /* 84 */ "Permutation" OpHelp(""), - /* 85 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), - /* 86 */ "Column" OpHelp("r[P3]=PX"), - /* 87 */ "Affinity" OpHelp("affinity(r[P1@P2])"), - /* 88 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), - /* 89 */ "Count" OpHelp("r[P2]=count()"), - /* 90 */ "FkCheckCommit" OpHelp(""), - /* 91 */ "TTransaction" OpHelp(""), - /* 92 */ "ReadCookie" OpHelp(""), - /* 93 */ "SetCookie" OpHelp(""), - /* 94 */ "ReopenIdx" OpHelp("root=P2"), - /* 95 */ "OpenRead" OpHelp("root=P2"), - /* 96 */ "OpenWrite" OpHelp("root=P2"), - /* 97 */ "OpenTEphemeral" OpHelp("nColumn = P2"), - /* 98 */ "SorterOpen" OpHelp(""), - /* 99 */ "SequenceTest" OpHelp("if (cursor[P1].ctr++) pc = P2"), - /* 100 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), - /* 101 */ "Close" OpHelp(""), - /* 102 */ "ColumnsUsed" OpHelp(""), - /* 103 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), - /* 104 */ "NextId" OpHelp("r[P3]=get_max(space_index[P1]{Column[P2]})"), - /* 105 */ "NextIdEphemeral" OpHelp("r[P3]=get_max(space_index[P1]{Column[P2]})"), - /* 106 */ "FCopy" OpHelp("reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)]"), - /* 107 */ "Delete" OpHelp(""), - /* 108 */ "ResetCount" OpHelp(""), - /* 109 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), - /* 110 */ "SorterData" OpHelp("r[P2]=data"), - /* 111 */ "RowData" OpHelp("r[P2]=data"), - /* 112 */ "NullRow" OpHelp(""), - /* 113 */ "SorterInsert" OpHelp("key=r[P2]"), - /* 114 */ "IdxReplace" OpHelp("key=r[P2]"), + /* 76 */ "SCopy" OpHelp("r[P2]=r[P1]"), + /* 77 */ "IntCopy" OpHelp("r[P2]=r[P1]"), + /* 78 */ "ResultRow" OpHelp("output=r[P1@P2]"), + /* 79 */ "CollSeq" OpHelp(""), + /* 80 */ "Function0" OpHelp("r[P3]=func(r[P2@P5])"), + /* 81 */ "Function" OpHelp("r[P3]=func(r[P2@P5])"), + /* 82 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), + /* 83 */ "RealAffinity" OpHelp(""), + /* 84 */ "Cast" OpHelp("affinity(r[P1])"), + /* 85 */ "Permutation" OpHelp(""), + /* 86 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), + /* 87 */ "Column" OpHelp("r[P3]=PX"), + /* 88 */ "Affinity" OpHelp("affinity(r[P1@P2])"), + /* 89 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), + /* 90 */ "Count" OpHelp("r[P2]=count()"), + /* 91 */ "FkCheckCommit" OpHelp(""), + /* 92 */ "TTransaction" OpHelp(""), + /* 93 */ "ReadCookie" OpHelp(""), + /* 94 */ "SetCookie" OpHelp(""), + /* 95 */ "ReopenIdx" OpHelp("root=P2"), + /* 96 */ "OpenRead" OpHelp("root=P2"), + /* 97 */ "OpenWrite" OpHelp("root=P2"), + /* 98 */ "OpenTEphemeral" OpHelp("nColumn = P2"), + /* 99 */ "SorterOpen" OpHelp(""), + /* 100 */ "SequenceTest" OpHelp("if (cursor[P1].ctr++) pc = P2"), + /* 101 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), + /* 102 */ "Close" OpHelp(""), + /* 103 */ "ColumnsUsed" OpHelp(""), + /* 104 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), + /* 105 */ "NextId" OpHelp("r[P3]=get_max(space_index[P1]{Column[P2]})"), + /* 106 */ "NextIdEphemeral" OpHelp("r[P3]=get_max(space_index[P1]{Column[P2]})"), + /* 107 */ "FCopy" OpHelp("reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)]"), + /* 108 */ "Delete" OpHelp(""), + /* 109 */ "ResetCount" OpHelp(""), + /* 110 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), + /* 111 */ "SorterData" OpHelp("r[P2]=data"), + /* 112 */ "RowData" OpHelp("r[P2]=data"), + /* 113 */ "NullRow" OpHelp(""), + /* 114 */ "SorterInsert" OpHelp("key=r[P2]"), /* 115 */ "Real" OpHelp("r[P2]=P4"), - /* 116 */ "IdxInsert" OpHelp("key=r[P2]"), - /* 117 */ "IdxDelete" OpHelp("key=r[P2@P3]"), - /* 118 */ "Clear" OpHelp("space id = P1"), - /* 119 */ "ResetSorter" OpHelp(""), - /* 120 */ "ParseSchema2" OpHelp("rows=r[P1@P2]"), - /* 121 */ "ParseSchema3" OpHelp("name=r[P1] sql=r[P1+1]"), - /* 122 */ "RenameTable" OpHelp("P1 = root, P4 = name"), - /* 123 */ "LoadAnalysis" OpHelp(""), - /* 124 */ "DropTable" OpHelp(""), - /* 125 */ "DropIndex" OpHelp(""), - /* 126 */ "DropTrigger" OpHelp(""), - /* 127 */ "Param" OpHelp(""), - /* 128 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), - /* 129 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), - /* 130 */ "AggStep0" OpHelp("accum=r[P3] step(r[P2@P5])"), - /* 131 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), - /* 132 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), - /* 133 */ "Expire" OpHelp(""), - /* 134 */ "IncMaxid" OpHelp(""), - /* 135 */ "Noop" OpHelp(""), - /* 136 */ "Explain" OpHelp(""), + /* 116 */ "IdxReplace" OpHelp("key=r[P2]"), + /* 117 */ "IdxInsert" OpHelp("key=r[P2]"), + /* 118 */ "IdxDelete" OpHelp("key=r[P2@P3]"), + /* 119 */ "Clear" OpHelp("space id = P1"), + /* 120 */ "ResetSorter" OpHelp(""), + /* 121 */ "ParseSchema2" OpHelp("rows=r[P1@P2]"), + /* 122 */ "ParseSchema3" OpHelp("name=r[P1] sql=r[P1+1]"), + /* 123 */ "RenameTable" OpHelp("P1 = root, P4 = name"), + /* 124 */ "LoadAnalysis" OpHelp(""), + /* 125 */ "DropTable" OpHelp(""), + /* 126 */ "DropIndex" OpHelp(""), + /* 127 */ "DropTrigger" OpHelp(""), + /* 128 */ "Param" OpHelp(""), + /* 129 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), + /* 130 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), + /* 131 */ "AggStep0" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 132 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 133 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), + /* 134 */ "Expire" OpHelp(""), + /* 135 */ "IncMaxid" OpHelp(""), + /* 136 */ "Noop" OpHelp(""), + /* 137 */ "Explain" OpHelp(""), }; return azName[i]; } diff --git a/src/box/sql/opcodes.h b/src/box/sql/opcodes.h index af2ba1963..7b62f6d80 100644 --- a/src/box/sql/opcodes.h +++ b/src/box/sql/opcodes.h @@ -67,76 +67,77 @@ #define OP_Integer 64 /* synopsis: r[P2]=P1 */ #define OP_Bool 65 /* synopsis: r[P2]=P1 */ #define OP_Int64 66 /* synopsis: r[P2]=P4 */ -#define OP_String 67 /* synopsis: r[P2]='P4' (len=P1) */ -#define OP_Null 68 /* synopsis: r[P2..P3]=NULL */ -#define OP_SoftNull 69 /* synopsis: r[P1]=NULL */ -#define OP_Blob 70 /* synopsis: r[P2]=P4 (len=P1, subtype=P3) */ -#define OP_Variable 71 /* synopsis: r[P2]=parameter(P1,P4) */ -#define OP_Move 72 /* synopsis: r[P2@P3]=r[P1@P3] */ -#define OP_Copy 73 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ -#define OP_SCopy 74 /* synopsis: r[P2]=r[P1] */ +#define OP_LoadPtr 67 /* synopsis: r[P2] = P4 */ +#define OP_String 68 /* synopsis: r[P2]='P4' (len=P1) */ +#define OP_Null 69 /* synopsis: r[P2..P3]=NULL */ +#define OP_SoftNull 70 /* synopsis: r[P1]=NULL */ +#define OP_Blob 71 /* synopsis: r[P2]=P4 (len=P1, subtype=P3) */ +#define OP_Variable 72 /* synopsis: r[P2]=parameter(P1,P4) */ +#define OP_Move 73 /* synopsis: r[P2@P3]=r[P1@P3] */ +#define OP_Copy 74 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ #define OP_String8 75 /* same as TK_STRING, synopsis: r[P2]='P4' */ -#define OP_IntCopy 76 /* synopsis: r[P2]=r[P1] */ -#define OP_ResultRow 77 /* synopsis: output=r[P1@P2] */ -#define OP_CollSeq 78 -#define OP_Function0 79 /* synopsis: r[P3]=func(r[P2@P5]) */ -#define OP_Function 80 /* synopsis: r[P3]=func(r[P2@P5]) */ -#define OP_AddImm 81 /* synopsis: r[P1]=r[P1]+P2 */ -#define OP_RealAffinity 82 -#define OP_Cast 83 /* synopsis: affinity(r[P1]) */ -#define OP_Permutation 84 -#define OP_Compare 85 /* synopsis: r[P1@P3] <-> r[P2@P3] */ -#define OP_Column 86 /* synopsis: r[P3]=PX */ -#define OP_Affinity 87 /* synopsis: affinity(r[P1@P2]) */ -#define OP_MakeRecord 88 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ -#define OP_Count 89 /* synopsis: r[P2]=count() */ -#define OP_FkCheckCommit 90 -#define OP_TTransaction 91 -#define OP_ReadCookie 92 -#define OP_SetCookie 93 -#define OP_ReopenIdx 94 /* synopsis: root=P2 */ -#define OP_OpenRead 95 /* synopsis: root=P2 */ -#define OP_OpenWrite 96 /* synopsis: root=P2 */ -#define OP_OpenTEphemeral 97 /* synopsis: nColumn = P2 */ -#define OP_SorterOpen 98 -#define OP_SequenceTest 99 /* synopsis: if (cursor[P1].ctr++) pc = P2 */ -#define OP_OpenPseudo 100 /* synopsis: P3 columns in r[P2] */ -#define OP_Close 101 -#define OP_ColumnsUsed 102 -#define OP_Sequence 103 /* synopsis: r[P2]=cursor[P1].ctr++ */ -#define OP_NextId 104 /* synopsis: r[P3]=get_max(space_index[P1]{Column[P2]}) */ -#define OP_NextIdEphemeral 105 /* synopsis: r[P3]=get_max(space_index[P1]{Column[P2]}) */ -#define OP_FCopy 106 /* synopsis: reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)] */ -#define OP_Delete 107 -#define OP_ResetCount 108 -#define OP_SorterCompare 109 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ -#define OP_SorterData 110 /* synopsis: r[P2]=data */ -#define OP_RowData 111 /* synopsis: r[P2]=data */ -#define OP_NullRow 112 -#define OP_SorterInsert 113 /* synopsis: key=r[P2] */ -#define OP_IdxReplace 114 /* synopsis: key=r[P2] */ +#define OP_SCopy 76 /* synopsis: r[P2]=r[P1] */ +#define OP_IntCopy 77 /* synopsis: r[P2]=r[P1] */ +#define OP_ResultRow 78 /* synopsis: output=r[P1@P2] */ +#define OP_CollSeq 79 +#define OP_Function0 80 /* synopsis: r[P3]=func(r[P2@P5]) */ +#define OP_Function 81 /* synopsis: r[P3]=func(r[P2@P5]) */ +#define OP_AddImm 82 /* synopsis: r[P1]=r[P1]+P2 */ +#define OP_RealAffinity 83 +#define OP_Cast 84 /* synopsis: affinity(r[P1]) */ +#define OP_Permutation 85 +#define OP_Compare 86 /* synopsis: r[P1@P3] <-> r[P2@P3] */ +#define OP_Column 87 /* synopsis: r[P3]=PX */ +#define OP_Affinity 88 /* synopsis: affinity(r[P1@P2]) */ +#define OP_MakeRecord 89 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ +#define OP_Count 90 /* synopsis: r[P2]=count() */ +#define OP_FkCheckCommit 91 +#define OP_TTransaction 92 +#define OP_ReadCookie 93 +#define OP_SetCookie 94 +#define OP_ReopenIdx 95 /* synopsis: root=P2 */ +#define OP_OpenRead 96 /* synopsis: root=P2 */ +#define OP_OpenWrite 97 /* synopsis: root=P2 */ +#define OP_OpenTEphemeral 98 /* synopsis: nColumn = P2 */ +#define OP_SorterOpen 99 +#define OP_SequenceTest 100 /* synopsis: if (cursor[P1].ctr++) pc = P2 */ +#define OP_OpenPseudo 101 /* synopsis: P3 columns in r[P2] */ +#define OP_Close 102 +#define OP_ColumnsUsed 103 +#define OP_Sequence 104 /* synopsis: r[P2]=cursor[P1].ctr++ */ +#define OP_NextId 105 /* synopsis: r[P3]=get_max(space_index[P1]{Column[P2]}) */ +#define OP_NextIdEphemeral 106 /* synopsis: r[P3]=get_max(space_index[P1]{Column[P2]}) */ +#define OP_FCopy 107 /* synopsis: reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)] */ +#define OP_Delete 108 +#define OP_ResetCount 109 +#define OP_SorterCompare 110 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ +#define OP_SorterData 111 /* synopsis: r[P2]=data */ +#define OP_RowData 112 /* synopsis: r[P2]=data */ +#define OP_NullRow 113 +#define OP_SorterInsert 114 /* synopsis: key=r[P2] */ #define OP_Real 115 /* same as TK_FLOAT, synopsis: r[P2]=P4 */ -#define OP_IdxInsert 116 /* synopsis: key=r[P2] */ -#define OP_IdxDelete 117 /* synopsis: key=r[P2@P3] */ -#define OP_Clear 118 /* synopsis: space id = P1 */ -#define OP_ResetSorter 119 -#define OP_ParseSchema2 120 /* synopsis: rows=r[P1@P2] */ -#define OP_ParseSchema3 121 /* synopsis: name=r[P1] sql=r[P1+1] */ -#define OP_RenameTable 122 /* synopsis: P1 = root, P4 = name */ -#define OP_LoadAnalysis 123 -#define OP_DropTable 124 -#define OP_DropIndex 125 -#define OP_DropTrigger 126 -#define OP_Param 127 -#define OP_FkCounter 128 /* synopsis: fkctr[P1]+=P2 */ -#define OP_OffsetLimit 129 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ -#define OP_AggStep0 130 /* synopsis: accum=r[P3] step(r[P2@P5]) */ -#define OP_AggStep 131 /* synopsis: accum=r[P3] step(r[P2@P5]) */ -#define OP_AggFinal 132 /* synopsis: accum=r[P1] N=P2 */ -#define OP_Expire 133 -#define OP_IncMaxid 134 -#define OP_Noop 135 -#define OP_Explain 136 +#define OP_IdxReplace 116 /* synopsis: key=r[P2] */ +#define OP_IdxInsert 117 /* synopsis: key=r[P2] */ +#define OP_IdxDelete 118 /* synopsis: key=r[P2@P3] */ +#define OP_Clear 119 /* synopsis: space id = P1 */ +#define OP_ResetSorter 120 +#define OP_ParseSchema2 121 /* synopsis: rows=r[P1@P2] */ +#define OP_ParseSchema3 122 /* synopsis: name=r[P1] sql=r[P1+1] */ +#define OP_RenameTable 123 /* synopsis: P1 = root, P4 = name */ +#define OP_LoadAnalysis 124 +#define OP_DropTable 125 +#define OP_DropIndex 126 +#define OP_DropTrigger 127 +#define OP_Param 128 +#define OP_FkCounter 129 /* synopsis: fkctr[P1]+=P2 */ +#define OP_OffsetLimit 130 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ +#define OP_AggStep0 131 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggStep 132 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggFinal 133 /* synopsis: accum=r[P1] N=P2 */ +#define OP_Expire 134 +#define OP_IncMaxid 135 +#define OP_Noop 136 +#define OP_Explain 137 /* Properties such as "out2" or "jump" that are specified in ** comments following the "case" for each opcode in the vdbe.c @@ -157,16 +158,16 @@ /* 40 */ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x01, 0x01,\ /* 48 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\ /* 56 */ 0x03, 0x03, 0x03, 0x01, 0x02, 0x02, 0x08, 0x00,\ -/* 64 */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x10,\ -/* 72 */ 0x00, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00,\ -/* 80 */ 0x00, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00,\ -/* 88 */ 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\ -/* 96 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\ -/* 104 */ 0x20, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 112 */ 0x00, 0x04, 0x00, 0x10, 0x04, 0x00, 0x00, 0x00,\ -/* 120 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\ -/* 128 */ 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 136 */ 0x00,} +/* 64 */ 0x10, 0x10, 0x10, 0x00, 0x10, 0x10, 0x00, 0x10,\ +/* 72 */ 0x10, 0x00, 0x00, 0x10, 0x10, 0x10, 0x00, 0x00,\ +/* 80 */ 0x00, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00,\ +/* 88 */ 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00,\ +/* 96 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ +/* 104 */ 0x10, 0x20, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,\ +/* 112 */ 0x00, 0x00, 0x04, 0x10, 0x00, 0x04, 0x00, 0x00,\ +/* 120 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ +/* 128 */ 0x10, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00,\ +/* 136 */ 0x00, 0x00,} /* The sqlite3P2Values() routine is able to run faster if it knows ** the value of the largest JUMP opcode. The smaller the maximum diff --git a/src/box/sql/select.c b/src/box/sql/select.c index 2a8c83d06..39c1be53d 100644 --- a/src/box/sql/select.c +++ b/src/box/sql/select.c @@ -35,7 +35,9 @@ */ #include #include "sqliteInt.h" +#include "tarantoolInt.h" #include "box/session.h" +#include "box/schema.h" /* * Trace output macros @@ -6187,8 +6189,15 @@ sqlite3Select(Parse * pParse, /* The parser context */ } /* Open a read-only cursor, execute the OP_Count, close the cursor. */ + struct space *space = + space_by_id(SQLITE_PAGENO_TO_SPACEID(iRoot)); + assert(space != NULL); + int space_ptr_reg = ++pParse->nMem; + sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, + space_ptr_reg, 0, + (void *) space); sqlite3VdbeAddOp4Int(v, OP_OpenRead, iCsr, - iRoot, 0, 1); + iRoot, space_ptr_reg, 1); if (pKeyInfo) { sqlite3VdbeChangeP4(v, -1, (char *)pKeyInfo, diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index 9929dfb96..a44a17062 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -1072,6 +1072,19 @@ case OP_Int64: { /* out2 */ break; } +/* Opcode: LoadPtr * P2 * P4 * + * Synopsis: r[P2] = P4 + * + * P4 is a generic pointer. Copy it into register P2. + */ +case OP_LoadPtr: { + pOut = out2Prerelease(p, pOp); + assert(pOp->p4type == P4_PTR); + pOut->u.p = pOp->p4.p; + pOut->flags = MEM_Ptr; + break; +} + #ifndef SQLITE_OMIT_FLOATING_POINT /* Opcode: Real * P2 * P4 * * Synopsis: r[P2]=P4 diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h index 7241963e4..340ddc766 100644 --- a/src/box/sql/vdbe.h +++ b/src/box/sql/vdbe.h @@ -144,6 +144,7 @@ typedef struct VdbeOpList VdbeOpList; #define P4_INDEX (-15) /* P4 is a pointer to a Index structure */ #define P4_FUNCCTX (-16) /* P4 is a pointer to an sqlite3_context object */ #define P4_BOOL (-17) /* P4 is a bool value */ +#define P4_PTR (-18) /* P4 is a generic pointer */ /* Error message codes for OP_Halt */ @@ -200,6 +201,7 @@ int sqlite3VdbeAddOp3(Vdbe *, int, int, int, int); int sqlite3VdbeAddOp4(Vdbe *, int, int, int, int, const char *zP4, int); int sqlite3VdbeAddOp4Dup8(Vdbe *, int, int, int, int, const u8 *, int); int sqlite3VdbeAddOp4Int(Vdbe *, int, int, int, int, int); +int sqlite3VdbeAddOp4Ptr(Vdbe *, int, int, int, int, void *); void sqlite3VdbeEndCoroutine(Vdbe *, int); #if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS) void sqlite3VdbeVerifyNoMallocRequired(Vdbe * p, int N); diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h index 8b622de5b..fcb45c8a8 100644 --- a/src/box/sql/vdbeInt.h +++ b/src/box/sql/vdbeInt.h @@ -196,6 +196,7 @@ struct Mem { i64 i; /* Integer value used when MEM_Int is set in flags */ bool b; /* Boolean value used when MEM_Bool is set in flags */ int nZero; /* Used when bit MEM_Zero is set in flags */ + void *p; /* Generic pointer */ FuncDef *pDef; /* Used only when flags==MEM_Agg */ VdbeFrame *pFrame; /* Used when flags==MEM_Frame */ } u; @@ -239,6 +240,7 @@ struct Mem { #define MEM_Real 0x0008 /* Value is a real number */ #define MEM_Blob 0x0010 /* Value is a BLOB */ #define MEM_Bool 0x0020 /* Value is a bool */ +#define MEM_Ptr 0x0040 /* Value is a generic pointer */ #define MEM_AffMask 0x003f /* Mask of affinity bits */ #define MEM_Frame 0x0080 /* Value is a VdbeFrame object */ #define MEM_Undefined 0x0100 /* Value is undefined */ @@ -378,6 +380,7 @@ struct Vdbe { i64 nFkConstraint; /* Number of imm. FK constraints this VM */ i64 nStmtDefCons; /* Number of def. constraints when stmt started */ i64 nStmtDefImmCons; /* Number of def. imm constraints when stmt started */ + uint32_t schema_ver; /* Schema version at the moment of VDBE creation. */ /* * In recursive triggers we can execute INSERT/UPDATE OR IGNORE diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c index 92bf9943b..37a3c90d2 100644 --- a/src/box/sql/vdbeaux.c +++ b/src/box/sql/vdbeaux.c @@ -66,6 +66,7 @@ sqlite3VdbeCreate(Parse * pParse) p->magic = VDBE_MAGIC_INIT; p->pParse = pParse; p->autoCommit = (char)box_txn() == 0 ? 1 : 0; + p->schema_ver = box_schema_version(); if (!p->autoCommit) { p->psql_txn = in_txn()->psql_txn; p->nDeferredCons = p->psql_txn->nDeferredConsSave; @@ -413,6 +414,16 @@ sqlite3VdbeAddOp4Int(Vdbe * p, /* Add the opcode to this VM */ return addr; } +int +sqlite3VdbeAddOp4Ptr(Vdbe *p, int op, int p1, int p2, int p3, void *ptr) +{ + int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3); + VdbeOp *pOp = &p->aOp[addr]; + pOp->p4type = P4_PTR; + pOp->p4.p = ptr; + return addr; +} + /* Insert the end of a co-routine */ void diff --git a/src/box/sql/where.c b/src/box/sql/where.c index 2f1c627e5..47da3c84c 100644 --- a/src/box/sql/where.c +++ b/src/box/sql/where.c @@ -42,6 +42,8 @@ #include "vdbeInt.h" #include "whereInt.h" #include "box/session.h" +#include "box/schema.h" +#include "tarantoolInt.h" /* Forward declaration of methods */ static int whereLoopResize(sqlite3 *, WhereLoop *, int); @@ -4606,7 +4608,15 @@ sqlite3WhereBegin(Parse * pParse, /* The parser context */ assert(pIx->pSchema == pTab->pSchema); assert(iIndexCur >= 0); if (op) { - sqlite3VdbeAddOp2(v, op, iIndexCur, pIx->tnum); + struct space *space = + space_by_id(SQLITE_PAGENO_TO_SPACEID(pIx->tnum)); + assert(space != NULL); + int space_ptr_reg = ++pParse->nMem; + sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, + space_ptr_reg, 0, + (void *) space); + sqlite3VdbeAddOp3(v, op, iIndexCur, pIx->tnum, + space_ptr_reg); sqlite3VdbeSetP4KeyInfo(pParse, pIx); if ((pLoop->wsFlags & WHERE_CONSTRAINT) != 0 && (pLoop->