On 21 Mar 2018, at 16:14, Kirill Yukhin <kyukhin@tarantool.org> 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 <box/coll.h>
 #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 <box/coll.h>
 #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 <box/coll.h>
 #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->