Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH] sql: refactor SQL cursor to remove write ones
@ 2018-10-01 10:31 Kirill Yukhin
  2018-10-02 11:56 ` [tarantool-patches] " n.pettik
  2018-10-04 12:00 ` Kirill Yukhin
  0 siblings, 2 replies; 9+ messages in thread
From: Kirill Yukhin @ 2018-10-01 10:31 UTC (permalink / raw)
  To: korablev; +Cc: tarantool-patches, Kirill Yukhin

In Tarantool opening and positioning cursor for writing
have no sense. So refactor SQL code, to perform:
  - Creation of ephemeral table w/o any cursors machinery.
    No op-code returns register which contains plain pointer
    to the ephemeral space.
  - OpenRead/OpenWrite opcodes were replaced with single
    CursorOpen op-code, which establishes new cursor w/
    intention to sebsequent read from the space. This opcode
    accepts both plain pointer (in P4 operand) and register
    which contains pointer (inn P3) to the ephemeral space.
This query scheduler and DML routines thoroughly.

Closes #3182
Part of #2362
---
Branch: https://github.com/tarantool/tarantool/commits/kyukhin/gh-3182-repair-cursors-2
Issue: https://github.com/tarantool/tarantool/issues/3182

 src/box/sql.c              |  65 ++++-------
 src/box/sql/analyze.c      |  64 ++++-------
 src/box/sql/build.c        |   4 +-
 src/box/sql/delete.c       |  25 +++--
 src/box/sql/expr.c         |  39 ++++---
 src/box/sql/insert.c       |  40 ++++---
 src/box/sql/parse.y        |   2 +-
 src/box/sql/select.c       | 174 ++++++++++++++++++++----------
 src/box/sql/sqliteInt.h    |  21 ++--
 src/box/sql/tarantoolInt.h |  32 +++++-
 src/box/sql/trigger.c      |  12 +--
 src/box/sql/update.c       |  19 ++--
 src/box/sql/vdbe.c         | 264 ++++++++++++++++++++++-----------------------
 src/box/sql/where.c        |  13 +--
 src/box/sql/whereInt.h     |   2 +-
 src/box/sql/wherecode.c    |  12 ++-
 16 files changed, 411 insertions(+), 377 deletions(-)

diff --git a/src/box/sql.c b/src/box/sql.c
index ab4a587..c5461ad 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -355,31 +355,15 @@ int tarantoolSqlite3Count(BtCursor *pCur, i64 *pnEntry)
 	return SQLITE_OK;
 }
 
-/**
- * Create ephemeral space and set cursor to the first entry. Features of
- * ephemeral spaces: id == 0, name == "ephemeral", memtx engine (in future it
- * can be changed, but now only memtx engine is supported), primary index
- * which covers all fields and no secondary indexes, given field number and
- * collation sequence. All fields are scalar and nullable.
- *
- * @param pCur Cursor which will point to the new ephemeral space.
- * @param field_count Number of fields in ephemeral space.
- * @param key_info Keys description for new ephemeral space.
- *
- * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
- */
-int
-tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
+struct space *
+tarantoolSqlite3EphemeralCreate(uint32_t field_count,
 				struct sql_key_info *key_info)
 {
-	assert(pCur);
-	assert(pCur->curFlags & BTCF_TEphemCursor);
-
 	struct key_def *def = NULL;
 	if (key_info != NULL) {
 		def = sql_key_info_to_key_def(key_info);
 		if (def == NULL)
-			return SQL_TARANTOOL_ERROR;
+			return NULL;
 	}
 
 	struct key_part_def *ephemer_key_parts = region_alloc(&fiber()->gc,
@@ -387,7 +371,7 @@ tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
 	if (ephemer_key_parts == NULL) {
 		diag_set(OutOfMemory, sizeof(*ephemer_key_parts) * field_count,
 			 "region", "key parts");
-		return SQL_TARANTOOL_ERROR;
+		return NULL;
 	}
 	for (uint32_t i = 0; i < field_count; ++i) {
 		struct key_part_def *part = &ephemer_key_parts[i];
@@ -404,14 +388,14 @@ tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
 	struct key_def *ephemer_key_def = key_def_new(ephemer_key_parts,
 						      field_count);
 	if (ephemer_key_def == NULL)
-		return SQL_TARANTOOL_ERROR;
+		return NULL;
 
 	struct index_def *ephemer_index_def =
 		index_def_new(0, 0, "ephemer_idx", strlen("ephemer_idx"), TREE,
 			      &index_opts_default, ephemer_key_def, NULL);
 	key_def_delete(ephemer_key_def);
 	if (ephemer_index_def == NULL)
-		return SQL_TARANTOOL_ERROR;
+		return NULL;
 
 	struct rlist key_list;
 	rlist_create(&key_list);
@@ -425,24 +409,15 @@ tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
 			      0 /* length of field_def */);
 	if (ephemer_space_def == NULL) {
 		index_def_delete(ephemer_index_def);
-		return SQL_TARANTOOL_ERROR;
+		return NULL;
 	}
 
 	struct space *ephemer_new_space = space_new_ephemeral(ephemer_space_def,
 							      &key_list);
 	index_def_delete(ephemer_index_def);
 	space_def_delete(ephemer_space_def);
-	if (ephemer_new_space == NULL)
-		return SQL_TARANTOOL_ERROR;
-	if (key_alloc(pCur, field_count) != 0) {
-		space_delete(ephemer_new_space);
-		return SQL_TARANTOOL_ERROR;
-	}
-	pCur->space = ephemer_new_space;
-	pCur->index = *ephemer_new_space->index;
 
-	int unused;
-	return tarantoolSqlite3First(pCur, &unused);
+	return ephemer_new_space;
 }
 
 int tarantoolSqlite3EphemeralInsert(struct space *space, const char *tuple,
@@ -1461,35 +1436,31 @@ sql_debug_info(struct info_handler *h)
 	info_end(h);
 }
 
-/*
+/**
  * Extract maximum integer value from ephemeral space.
  * If index is empty - return 0 in max_id and success status.
  *
- * @param pCur Cursor pointing to ephemeral space.
+ * @param space Pointer to ephemeral space.
  * @param fieldno Number of field from fetching tuple.
  * @param[out] max_id Fetched max value.
  *
  * @retval 0 on success, -1 otherwise.
  */
-int tarantoolSqlite3EphemeralGetMaxId(BtCursor *pCur, uint32_t fieldno,
-				       uint64_t *max_id)
+int tarantoolSqlite3EphemeralGetMaxId(struct space *space, uint32_t fieldno,
+				      uint64_t *max_id)
 {
-	struct space *ephem_space = pCur->space;
-	assert(ephem_space);
-	struct index *primary_index = *ephem_space->index;
-
+	struct index *primary_index = *space->index;
 	struct tuple *tuple;
-	if (index_max(primary_index, NULL, 0, &tuple) != 0) {
-		return SQL_TARANTOOL_ERROR;
-	}
+	if (index_max(primary_index, NULL, 0, &tuple) != 0)
+		return -1;
 	if (tuple == NULL) {
 		*max_id = 0;
-		return SQLITE_OK;
+		return 0;
 	}
 	if (tuple_field_u64(tuple, fieldno, max_id) == -1)
-		return SQL_TARANTOOL_ERROR;
+		return -1;
 
-	return SQLITE_OK;
+	return 0;
 }
 
 int
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index 01e2ad1..36bbcff 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -122,13 +122,10 @@
  * created.
  *
  * @param parse Parsing context.
- * @param stat_cursor Open the _sql_stat1 table on this cursor.
- *        you should allocate |stat_names| cursors before call.
  * @param table_name Delete records of this table if specified.
  */
 static void
-vdbe_emit_stat_space_open(struct Parse *parse, int stat_cursor,
-			  const char *table_name)
+vdbe_emit_stat_space_open(struct Parse *parse, const char *table_name)
 {
 	const char *stat_names[] = {"_sql_stat1", "_sql_stat4"};
 	const uint32_t stat_ids[] = {BOX_SQL_STAT1_ID, BOX_SQL_STAT4_ID};
@@ -144,14 +141,6 @@ vdbe_emit_stat_space_open(struct Parse *parse, int stat_cursor,
 			sqlite3VdbeAddOp1(v, OP_Clear, stat_ids[i]);
 		}
 	}
-
-	/* Open the sql_stat tables for writing. */
-	for (uint i = 0; i < lengthof(stat_names); ++i) {
-		uint32_t id = stat_ids[i];
-		vdbe_emit_open_cursor(parse, stat_cursor + i, 0,
-				      space_by_id(id));
-		VdbeComment((v, stat_names[i]));
-	}
 }
 
 /*
@@ -770,15 +759,16 @@ callStatGet(Vdbe * v, int regStat4, int iParam, int regOut)
  *
  * @param parse Current parsing context.
  * @param space Space to be analyzed.
- * @param stat_cursor Cursor pointing to spaces containing
- *        statistics: _sql_stat1 (stat_cursor) and
- *        _sql_stat4 (stat_cursor + 1).
  */
 static void
-vdbe_emit_analyze_space(struct Parse *parse, struct space *space,
-			int stat_cursor)
+vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
 {
 	assert(space != NULL);
+	struct space *stat1 = space_by_id(BOX_SQL_STAT1_ID);
+	assert(stat1 != NULL);
+	struct space *stat4 = space_by_id(BOX_SQL_STAT4_ID);
+	assert(stat4 != NULL);
+
 	/* Register to hold Stat4Accum object. */
 	int stat4_reg = ++parse->nMem;
 	/* Index of changed index field. */
@@ -808,7 +798,7 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space,
 	struct Vdbe *v = sqlite3GetVdbe(parse);
 	assert(v != NULL);
 	const char *tab_name = space_name(space);
-	sqlite3VdbeAddOp4(v, OP_OpenRead, tab_cursor, 0, 0, (void *) space,
+	sqlite3VdbeAddOp4(v, OP_CursorOpen, tab_cursor, 0, 0, (void *) space,
 			  P4_SPACEPTR);
 	sqlite3VdbeLoadString(v, tab_name_reg, space->def->name);
 	for (uint32_t j = 0; j < space->index_count; ++j) {
@@ -871,7 +861,7 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space,
 		int idx_cursor;
 		if (j != 0) {
 			idx_cursor = parse->nTab - 1;
-			sqlite3VdbeAddOp4(v, OP_OpenRead, idx_cursor,
+			sqlite3VdbeAddOp4(v, OP_CursorOpen, idx_cursor,
 					  idx->def->iid, 0,
 					  (void *) space, P4_SPACEPTR);
 			VdbeComment((v, "%s", idx->def->name));
@@ -1003,7 +993,8 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space,
 		assert("BBB"[0] == AFFINITY_TEXT);
 		sqlite3VdbeAddOp4(v, OP_MakeRecord, tab_name_reg, 3, tmp_reg,
 				  "BBB", 0);
-		sqlite3VdbeAddOp2(v, OP_IdxInsert, stat_cursor, tmp_reg);
+		sqlite3VdbeAddOp4(v, OP_IdxInsert, tmp_reg, 0, 0,
+				  (char *)stat1, P4_SPACEPTR);
 		/* Add the entries to the stat4 table. */
 		int eq_reg = stat1_reg;
 		int lt_reg = stat1_reg + 1;
@@ -1036,7 +1027,8 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space,
 		sqlite3VdbeAddOp3(v, OP_MakeRecord, col_reg, part_count,
 				  sample_reg);
 		sqlite3VdbeAddOp3(v, OP_MakeRecord, tab_name_reg, 6, tmp_reg);
-		sqlite3VdbeAddOp2(v, OP_IdxReplace, stat_cursor + 1, tmp_reg);
+		sqlite3VdbeAddOp4(v, OP_IdxReplace, tmp_reg, 0, 0,
+				  (char *)stat4, P4_SPACEPTR);
 		/* P1==1 for end-of-loop. */
 		sqlite3VdbeAddOp2(v, OP_Goto, 1, next_addr);
 		sqlite3VdbeJumpHere(v, is_null_addr);
@@ -1058,27 +1050,12 @@ loadAnalysis(Parse * pParse)
 	}
 }
 
-/**
- * Wrapper to pass args to space_foreach callback.
- */
-struct analyze_data {
-	struct Parse *parse_context;
-	/**
-	 * Cursor numbers pointing to stat spaces:
-	 * stat_cursor is opened on _sql_stat1 and
-	 * stat_cursor + 1 - on _sql_stat4.
-	 */
-	int stat_cursor;
-};
-
 static int
 sql_space_foreach_analyze(struct space *space, void *data)
 {
 	if (space->def->opts.sql == NULL || space->def->opts.is_view)
 		return 0;
-	struct analyze_data *anal_data = (struct analyze_data *) data;
-	vdbe_emit_analyze_space(anal_data->parse_context, space,
-				anal_data->stat_cursor);
+	vdbe_emit_analyze_space((struct Parse*)data, space);
 	return 0;
 }
 
@@ -1090,11 +1067,8 @@ static void
 sql_analyze_database(struct Parse *parser)
 {
 	sql_set_multi_write(parser, false);
-	int stat_cursor = parser->nTab;
-	parser->nTab += 2;
-	vdbe_emit_stat_space_open(parser, stat_cursor, NULL);
-	struct analyze_data anal_data = { parser, stat_cursor };
-	space_foreach(sql_space_foreach_analyze, (void *) &anal_data);
+	vdbe_emit_stat_space_open(parser, NULL);
+	space_foreach(sql_space_foreach_analyze, (void *)parser);
 	loadAnalysis(parser);
 }
 
@@ -1114,10 +1088,8 @@ vdbe_emit_analyze_table(struct Parse *parse, struct space *space)
 	 * There are two system spaces for statistics: _sql_stat1
 	 * and _sql_stat4.
 	 */
-	int stat_cursor = parse->nTab;
-	parse->nTab += 2;
-	vdbe_emit_stat_space_open(parse, stat_cursor, space->def->name);
-	vdbe_emit_analyze_space(parse, space, stat_cursor);
+	vdbe_emit_stat_space_open(parse, space->def->name);
+	vdbe_emit_analyze_space(parse, space);
 	loadAnalysis(parse);
 }
 
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 43be777..0ae4c7d 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -883,7 +883,7 @@ vdbe_emit_open_cursor(struct Parse *parse_context, int cursor, int index_id,
 		      struct space *space)
 {
 	assert(space != NULL);
-	return sqlite3VdbeAddOp4(parse_context->pVdbe, OP_OpenWrite, cursor,
+	return sqlite3VdbeAddOp4(parse_context->pVdbe, OP_CursorOpen, cursor,
 				 index_id, 0, (void *) space, P4_SPACEPTR);
 }
 
@@ -2693,7 +2693,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
 			goto exit_create_index;
 
 		sql_set_multi_write(parse, true);
-		sqlite3VdbeAddOp4(vdbe, OP_OpenWrite, cursor, 0, 0,
+		sqlite3VdbeAddOp4(vdbe, OP_CursorOpen, cursor, 0, 0,
 				  (void *)space_by_id(BOX_INDEX_ID),
 				  P4_SPACEPTR);
 		sqlite3VdbeChangeP5(vdbe, OPFLAG_SEEKEQ);
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 9aa7058..ebde698 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -69,7 +69,7 @@ sql_lookup_table(struct Parse *parse, struct SrcList_item *tbl_name)
 
 void
 sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
-		     int cursor)
+		     int cursor, int reg_eph)
 {
 	struct sqlite3 *db = parse->db;
 	where = sqlite3ExprDup(db, where, 0);
@@ -83,7 +83,7 @@ sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
 	struct Select *select = sqlite3SelectNew(parse, NULL, from, where, NULL,
 						 NULL, NULL, 0, NULL, NULL);
 	struct SelectDest dest;
-	sqlite3SelectDestInit(&dest, SRT_EphemTab, cursor);
+	sqlite3SelectDestInit(&dest, SRT_EphemTab, cursor, reg_eph);
 	sqlite3Select(parse, select, &dest);
 	sql_select_delete(db, select);
 }
@@ -194,9 +194,10 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 	/* If we are trying to delete from a view, realize that
 	 * view into an ephemeral table.
 	 */
+	int reg_eph = ++parse->nMem;
 	if (is_view) {
 		sql_materialize_view(parse, space->def->name, where,
-				     tab_cursor);
+				     tab_cursor, reg_eph);
 	}
 
 	/* Initialize the counter of the number of rows deleted,
@@ -241,6 +242,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 		 * it, so columns should be loaded manually.
 		 */
 		struct sql_key_info *pk_info = NULL;
+		int reg_eph = ++parse->nMem;
 		int reg_pk = parse->nMem + 1;
 		int pk_len;
 		int eph_cursor = parse->nTab++;
@@ -248,8 +250,8 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 		if (is_view) {
 			pk_len = table->def->field_count;
 			parse->nMem += pk_len;
-			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
-					  eph_cursor, pk_len);
+			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, reg_eph,
+					  pk_len);
 		} else {
                         assert(space->index_count > 0);
                         pk_info = sql_key_info_new_from_key_def(db,
@@ -258,9 +260,9 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
                                 goto delete_from_cleanup;
                         pk_len = pk_info->part_count;
                         parse->nMem += pk_len;
-                        sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, eph_cursor,
-                                          pk_len, 0,
-                                          (char *)pk_info, P4_KEYINFO);
+			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, reg_eph,
+					  pk_len, 0,
+					  (char *)pk_info, P4_KEYINFO);
 		}
 
 		/* Construct a query to find the primary key for
@@ -345,7 +347,8 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 			 * by malloc.
 			 */
 			sqlite3VdbeChangeP5(v, 1);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, eph_cursor, reg_key);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, reg_key, reg_eph);
+			sqlite3VdbeChangeP5(v, OPFLAG_EPH_INSERT);
 		}
 
 		/* If this DELETE cannot use the ONEPASS strategy,
@@ -369,7 +372,7 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 				iAddrOnce = sqlite3VdbeAddOp0(v, OP_Once);
 				VdbeCoverage(v);
 			}
-			sqlite3VdbeAddOp4(v, OP_OpenWrite, tab_cursor, 0, 0,
+			sqlite3VdbeAddOp4(v, OP_CursorOpen, tab_cursor, 0, 0,
 					  (void *) space, P4_SPACEPTR);
 			VdbeComment((v, "%s", space->index[0]->def->name));
 
@@ -390,6 +393,8 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 
 			VdbeCoverage(v);
 		} else {
+			sqlite3VdbeAddOp3(v, OP_CursorOpen,
+						      eph_cursor, 0, reg_eph);
 			addr_loop = sqlite3VdbeAddOp1(v, OP_Rewind, eph_cursor);
 			VdbeCoverage(v);
 			sqlite3VdbeAddOp2(v, OP_RowData, eph_cursor, reg_key);
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index a13de4f..aa26342 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -2714,8 +2714,10 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 			 */
 			pExpr->iTable = pParse->nTab++;
 			pExpr->is_ephemeral = 1;
+			int reg_eph = ++pParse->nMem;
 			addr = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
-						 pExpr->iTable, nVal);
+						 reg_eph, nVal);
+			sqlite3VdbeAddOp3(v, OP_CursorOpen, pExpr->iTable, 0, reg_eph);
 			struct sql_key_info *key_info = sql_key_info_new(pParse->db, nVal);
 			if (key_info == NULL)
 				return 0;
@@ -2736,7 +2738,7 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 					SelectDest dest;
 					int i;
 					sqlite3SelectDestInit(&dest, SRT_Set,
-							      pExpr->iTable);
+							      pExpr->iTable, reg_eph);
 					dest.zAffSdst =
 					    exprINAffinity(pParse, pExpr);
 					assert((pExpr->iTable & 0x0000FFFF) ==
@@ -2808,8 +2810,10 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 							  1, r2, &affinity, 1);
 					sqlite3ExprCacheAffinityChange(pParse,
 								       r3, 1);
-					sqlite3VdbeAddOp2(v, OP_IdxInsert,
-							  pExpr->iTable, r2);
+					sqlite3VdbeAddOp2(v, OP_IdxInsert, r2,
+							  reg_eph);
+					sqlite3VdbeChangeP5(v,
+							    OPFLAG_EPH_INSERT);
 				}
 				sqlite3ReleaseTempReg(pParse, r1);
 				sqlite3ReleaseTempReg(pParse, r2);
@@ -2847,7 +2851,7 @@ sqlite3CodeSubselect(Parse * pParse,	/* Parsing context */
 
 			pSel = pExpr->x.pSelect;
 			nReg = pExpr->op == TK_SELECT ? pSel->pEList->nExpr : 1;
-			sqlite3SelectDestInit(&dest, 0, pParse->nMem + 1);
+			sqlite3SelectDestInit(&dest, 0, pParse->nMem + 1, 0);
 			pParse->nMem += nReg;
 			if (pExpr->op == TK_SELECT) {
 				dest.eDest = SRT_Mem;
@@ -5361,24 +5365,17 @@ analyzeAggregate(Walker * pWalker, Expr * pExpr)
 						pItem->iMem = ++pParse->nMem;
 						assert(!ExprHasProperty
 						       (pExpr, EP_IntValue));
-						pItem->pFunc =
-						    sqlite3FindFunction(pParse->
-									db,
-									pExpr->
-									u.
-									zToken,
-									pExpr->
-									x.
-									pList ?
-									pExpr->
-									x.
-									pList->
-									nExpr :
-									0,
-									0);
+						pItem->pFunc = sqlite3FindFunction(
+							pParse->db,
+							pExpr->u.zToken,
+							pExpr->x.pList ?
+							pExpr->x.pList->nExpr : 0,
+							0);
 						if (pExpr->flags & EP_Distinct) {
 							pItem->iDistinct =
-							    pParse->nTab++;
+								pParse->nTab++;
+							pItem->reg_eph =
+								++pParse->nMem;
 						} else {
 							pItem->iDistinct = -1;
 						}
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 03f4e1b..c9b4846 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -46,12 +46,10 @@
 void
 sqlite3OpenTable(Parse * pParse,	/* Generate code into this VDBE */
 		 int iCur,	/* The cursor number of the table */
-		 struct space *space,	/* The table to be opened */
-		 int opcode)	/* OP_OpenRead or OP_OpenWrite */
+		 struct space *space)	/* The table to be opened */
 {
 	Vdbe *v;
 	v = sqlite3GetVdbe(pParse);
-	assert(opcode == OP_OpenWrite || opcode == OP_OpenRead);
 	assert(space->index_count > 0);
 	vdbe_emit_open_cursor(pParse, iCur, 0, space);
 	VdbeComment((v, "%s", space->def->name));
@@ -139,9 +137,12 @@ vdbe_has_table_read(struct Parse *parser, const struct Table *table)
 		 * Currently, there is no difference between Read
 		 * and Write cursors.
 		 */
-		if (op->opcode == OP_OpenRead || op->opcode == OP_OpenWrite) {
-			assert(op->p4type == P4_SPACEPTR);
-			struct space *space = op->p4.space;
+		if (op->opcode == OP_CursorOpen) {
+			struct space *space = NULL;
+			if (op->p4type == P4_SPACEPTR)
+				space = op->p4.space;
+			else
+				continue;
 			if (space->def->id == table->def->id)
 				return true;
 		}
@@ -418,6 +419,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 		}
 	}
 
+	int reg_eph;
 	/* Figure out how many columns of data are supplied.  If the data
 	 * is coming from a SELECT statement, then generate a co-routine that
 	 * produces a single row of the SELECT on each invocation.  The
@@ -434,7 +436,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 		regYield = ++pParse->nMem;
 		addrTop = sqlite3VdbeCurrentAddr(v) + 1;
 		sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop);
-		sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield);
+		sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield, -1);
 		dest.iSdst = bIdListInOrder ? regData : 0;
 		dest.nSdst = def->field_count;
 		rc = sqlite3Select(pParse, pSelect, &dest);
@@ -480,10 +482,13 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 			int64_t initial_pk = 0;
 
 			srcTab = pParse->nTab++;
+			reg_eph = ++pParse->nMem;
 			regRec = sqlite3GetTempReg(pParse);
 			regCopy = sqlite3GetTempRange(pParse, nColumn);
 			regTempId = sqlite3GetTempReg(pParse);
-			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, srcTab, nColumn+1);
+			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, reg_eph,
+					  nColumn + 1);
+
 			/* Create counter for rowid */
 			sqlite3VdbeAddOp4Dup8(v, OP_Int64,
 					      0 /* unused */,
@@ -499,7 +504,8 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 					  nColumn + 1, regRec);
 			/* Set flag to save memory allocating one by malloc. */
 			sqlite3VdbeChangeP5(v, 1);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, srcTab, regRec);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, regRec, reg_eph);
+			sqlite3VdbeChangeP5(v, OPFLAG_EPH_INSERT);
 
 			sqlite3VdbeGoto(v, addrL);
 			sqlite3VdbeJumpHere(v, addrL);
@@ -515,6 +521,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 		memset(&sNC, 0, sizeof(sNC));
 		sNC.pParse = pParse;
 		srcTab = -1;
+		reg_eph = -1;
 		assert(useTempTable == 0);
 		if (pList) {
 			nColumn = pList->nExpr;
@@ -555,6 +562,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 		 *         end loop
 		 *      D: ...
 		 */
+		sqlite3VdbeAddOp3(v, OP_CursorOpen, srcTab, 0, reg_eph);
 		addrInsTop = sqlite3VdbeAddOp1(v, OP_Rewind, srcTab);
 		VdbeCoverage(v);
 		addrCont = sqlite3VdbeCurrentAddr(v);
@@ -746,9 +754,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 		vdbe_emit_constraint_checks(pParse, pTab, regIns + 1,
 					    on_error, endOfLoop, 0);
 		fkey_emit_check(pParse, pTab, 0, regIns, 0);
-		int pk_cursor = pParse->nTab++;
-		vdbe_emit_open_cursor(pParse, pk_cursor, 0, space);
-		vdbe_emit_insertion_completion(v, pk_cursor, regIns + 1,
+		vdbe_emit_insertion_completion(v, space, regIns + 1,
 					       pTab->def->field_count,
 					       on_error);
 	}
@@ -1063,8 +1069,8 @@ process_index:  ;
 }
 
 void
-vdbe_emit_insertion_completion(struct Vdbe *v, int cursor_id, int raw_data_reg,
-			       uint32_t tuple_len,
+vdbe_emit_insertion_completion(struct Vdbe *v, struct space *space,
+			       int raw_data_reg, uint32_t tuple_len,
 			       enum on_conflict_action on_conflict)
 {
 	assert(v != NULL);
@@ -1077,7 +1083,8 @@ vdbe_emit_insertion_completion(struct Vdbe *v, int cursor_id, int raw_data_reg,
 		pik_flags |= OPFLAG_OE_ROLLBACK;
 	sqlite3VdbeAddOp3(v, OP_MakeRecord, raw_data_reg, tuple_len,
 			  raw_data_reg + tuple_len);
-	sqlite3VdbeAddOp2(v, OP_IdxInsert, cursor_id, raw_data_reg + tuple_len);
+	sqlite3VdbeAddOp1(v, OP_IdxInsert, raw_data_reg + tuple_len);
+	sqlite3VdbeChangeP4(v, -1, (char *)space, P4_SPACEPTR);
 	sqlite3VdbeChangeP5(v, pik_flags);
 }
 
@@ -1327,7 +1334,8 @@ xferOptimization(Parse * pParse,	/* Parser context */
 	sqlite3VdbeChangeP5(v, OPFLAG_XFER_OPT);
 #endif
 
-	sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
+	sqlite3VdbeAddOp4(v, OP_IdxInsert, regData, 0, 0,
+			  (char *)dest, P4_SPACEPTR);
 	switch (onError) {
 	case ON_CONFLICT_ACTION_IGNORE:
 		sqlite3VdbeChangeP5(v, OPFLAG_OE_IGNORE | OPFLAG_NCHANGE);
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 473cf89..6b5f19d 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -390,7 +390,7 @@ cmd ::= DROP VIEW ifexists(E) fullname(X). {
 //////////////////////// The SELECT statement /////////////////////////////////
 //
 cmd ::= select(X).  {
-  SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0};
+	SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0, 0};
   if(!pParse->parse_only)
 	  sqlite3Select(pParse, X, &dest);
   else
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 02c0a5d..0370327 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -66,6 +66,8 @@ struct DistinctCtx {
 	u8 eTnctType;		/* One of the WHERE_DISTINCT_* operators */
 	int tabTnct;		/* Ephemeral table used for DISTINCT processing */
 	int addrTnct;		/* Address of OP_OpenEphemeral opcode for tabTnct */
+	/* Register, containing a pointer to ephemeral space. */
+	int reg_eph;
 };
 
 /*
@@ -77,6 +79,8 @@ struct SortCtx {
 	ExprList *pOrderBy;	/* The ORDER BY (or GROUP BY clause) */
 	int nOBSat;		/* Number of ORDER BY terms satisfied by indices */
 	int iECursor;		/* Cursor number for the sorter */
+	/* Register, containing pointer to ephemeral space. */
+	int reg_eph;
 	int regReturn;		/* Register holding block-output return address */
 	int labelBkOut;		/* Start label for the block-output subroutine */
 	int addrSortIndex;	/* Address of the OP_SorterOpen or OP_OpenEphemeral */
@@ -117,10 +121,11 @@ clearSelect(sqlite3 * db, Select * p, int bFree)
  * Initialize a SelectDest structure.
  */
 void
-sqlite3SelectDestInit(SelectDest * pDest, int eDest, int iParm)
+sqlite3SelectDestInit(SelectDest * pDest, int eDest, int iParm, int reg_eph)
 {
 	pDest->eDest = (u8) eDest;
 	pDest->iSDParm = iParm;
+	pDest->reg_eph = reg_eph;
 	pDest->zAffSdst = 0;
 	pDest->iSdst = 0;
 	pDest->nSdst = 0;
@@ -687,7 +692,6 @@ pushOntoSorter(Parse * pParse,		/* Parser context */
 	int regBase;		/* Regs for sorter record */
 	int regRecord = ++pParse->nMem;	/* Assembled sorter record */
 	int nOBSat = pSort->nOBSat;	/* ORDER BY terms to skip */
-	int op;			/* Opcode to add sorter record to sorter */
 	int iLimit;		/* LIMIT counter */
 
 	assert(bSeq == 0 || bSeq == 1);
@@ -764,11 +768,14 @@ pushOntoSorter(Parse * pParse,		/* Parser context */
 		sqlite3ExprCodeMove(pParse, regBase, regPrevKey, pSort->nOBSat);
 		sqlite3VdbeJumpHere(v, addrJmp);
 	}
-	if (pSort->sortFlags & SORTFLAG_UseSorter)
-		op = OP_SorterInsert;
-	else
-		op = OP_IdxInsert;
-	sqlite3VdbeAddOp2(v, op, pSort->iECursor, regRecord);
+	if (pSort->sortFlags & SORTFLAG_UseSorter) {
+		sqlite3VdbeAddOp2(v, OP_SorterInsert, pSort->iECursor,
+				  regRecord);
+	} else {
+		sqlite3VdbeAddOp2(v, OP_IdxInsert, regRecord, pSort->reg_eph);
+		sqlite3VdbeChangeP5(v, OPFLAG_EPH_INSERT);
+	}
+
 	if (iLimit) {
 		int addr;
 		int r1 = 0;
@@ -823,7 +830,7 @@ codeOffset(Vdbe * v,		/* Generate code into this VM */
 	}
 }
 
-/*
+/**
  * Add code that will check to make sure the N registers starting at iMem
  * form a distinct entry.  iTab is a sorting index that holds previously
  * seen combinations of the N values.  A new entry is made in iTab
@@ -831,24 +838,32 @@ codeOffset(Vdbe * v,		/* Generate code into this VM */
  *
  * A jump to addrRepeat is made and the N+1 values are popped from the
  * stack if the top N elements are not distinct.
+ *
+ * @param parse Parsing and code generating context.
+ * @param cursor A sorting index cursor used to test for distinctness.
+ * @param reg_eph Register holding ephemeral's space pointer.
+ * @param addr_repeat Jump to here if not distinct.
+ * @param n Number of elements.
+ * @param reg_data First register holding the data.
  */
 static void
-codeDistinct(Parse * pParse,	/* Parsing and code generating context */
-	     int iTab,		/* A sorting index used to test for distinctness */
-	     int addrRepeat,	/* Jump to here if not distinct */
-	     int N,		/* Number of elements */
-	     int iMem)		/* First element */
+codeDistinct(struct Parse * parse,
+	     int cursor,
+	     int reg_eph,
+	     int addr_repeat,
+	     int n,
+	     int reg_data)
 {
-	Vdbe *v;
+	struct Vdbe *v = parse->pVdbe;
 	int r1;
 
-	v = pParse->pVdbe;
-	r1 = sqlite3GetTempReg(pParse);
-	sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, iMem, N);
+	r1 = sqlite3GetTempReg(parse);
+	sqlite3VdbeAddOp4Int(v, OP_Found, cursor, addr_repeat, reg_data, n);
 	VdbeCoverage(v);
-	sqlite3VdbeAddOp3(v, OP_MakeRecord, iMem, N, r1);
-	sqlite3VdbeAddOp2(v, OP_IdxInsert, iTab, r1);
-	sqlite3ReleaseTempReg(pParse, r1);
+	sqlite3VdbeAddOp3(v, OP_MakeRecord, reg_data, n, r1);
+	sqlite3VdbeAddOp2(v, OP_IdxInsert, r1, reg_eph);
+	sqlite3VdbeChangeP5(v, OPFLAG_EPH_INSERT);
+	sqlite3ReleaseTempReg(parse, r1);
 }
 
 /*
@@ -998,7 +1013,8 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 				 * row is all NULLs.
 				 */
 				sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct);
-				pOp = sqlite3VdbeGetOp(v, pDistinct->addrTnct);
+				sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct + 1);
+				pOp = sqlite3VdbeGetOp(v, pDistinct->addrTnct + 1);
 				pOp->opcode = OP_Null;
 				pOp->p1 = 1;
 				pOp->p2 = regPrev;
@@ -1040,13 +1056,14 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 
 		case WHERE_DISTINCT_UNIQUE:{
 				sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct);
+				sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct + 1);
 				break;
 			}
 
 		default:{
 				assert(pDistinct->eTnctType ==
 				       WHERE_DISTINCT_UNORDERED);
-				codeDistinct(pParse, pDistinct->tabTnct,
+				codeDistinct(pParse, pDistinct->tabTnct, pDistinct->reg_eph,
 					     iContinue, nResultCol, regResult);
 				break;
 			}
@@ -1066,7 +1083,8 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 			r1 = sqlite3GetTempReg(pParse);
 			sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult,
 					  nResultCol, r1);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert,  r1, pDest->reg_eph);
+			sqlite3VdbeChangeP5(v, OPFLAG_EPH_INSERT);
 			sqlite3ReleaseTempReg(pParse, r1);
 			break;
 		}
@@ -1109,8 +1127,9 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 				sqlite3VdbeAddOp4Int(v, OP_Found, iParm + 1,
 						     addr, r1, 0);
 				VdbeCoverage(v);
-				sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm + 1,
-						  r1);
+				sqlite3VdbeAddOp2(v, OP_IdxInsert, r1,
+						  pDest->reg_eph + 1);
+				sqlite3VdbeChangeP5(v, OPFLAG_EPH_INSERT);
 				assert(pSort == 0);
 			}
 #endif
@@ -1122,7 +1141,7 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 				int regRec = sqlite3GetTempReg(pParse);
 				/* Last column is required for ID. */
 				int regCopy = sqlite3GetTempRange(pParse, nResultCol + 1);
-				sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, iParm,
+				sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, pDest->reg_eph,
 						  nResultCol, regCopy + nResultCol);
 				/* Positioning ID column to be last in inserted tuple.
 				 * NextId -> regCopy + n + 1
@@ -1134,7 +1153,8 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 				sqlite3VdbeAddOp3(v, OP_MakeRecord, regCopy, nResultCol + 1, regRec);
 				/* Set flag to save memory allocating one by malloc. */
 				sqlite3VdbeChangeP5(v, 1);
-				sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, regRec);
+				sqlite3VdbeAddOp2(v, OP_IdxInsert, regRec, pDest->reg_eph);
+				sqlite3VdbeChangeP5(v, OPFLAG_EPH_INSERT);
 				sqlite3ReleaseTempReg(pParse, regRec);
 				sqlite3ReleaseTempRange(pParse, regCopy, nResultCol + 1);
 			}
@@ -1164,7 +1184,8 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 				sqlite3ExprCacheAffinityChange(pParse,
 							       regResult,
 							       nResultCol);
-				sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1);
+				sqlite3VdbeAddOp2(v, OP_IdxInsert, r1, pDest->reg_eph);
+				sqlite3VdbeChangeP5(v, OPFLAG_EPH_INSERT);
 				sqlite3ReleaseTempReg(pParse, r1);
 			}
 			break;
@@ -1247,8 +1268,8 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 			sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult,
 					  nResultCol, r3);
 			if (eDest == SRT_DistQueue) {
-				sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm + 1,
-						  r3);
+				sqlite3VdbeAddOp2(v, OP_IdxInsert, r3,
+						  pDest->reg_eph + 1);
 			}
 			for (i = 0; i < nKey; i++) {
 				sqlite3VdbeAddOp2(v, OP_SCopy,
@@ -1258,7 +1279,8 @@ selectInnerLoop(Parse * pParse,		/* The parser context */
 			}
 			sqlite3VdbeAddOp2(v, OP_Sequence, iParm, r2 + nKey);
 			sqlite3VdbeAddOp3(v, OP_MakeRecord, r2, nKey + 2, r1);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, r1, pDest->reg_eph);
+			sqlite3VdbeChangeP5(v, OPFLAG_EPH_INSERT);
 			if (addrTest)
 				sqlite3VdbeJumpHere(v, addrTest);
 			sqlite3ReleaseTempReg(pParse, r1);
@@ -1498,7 +1520,6 @@ generateSortTail(Parse * pParse,	/* Parsing context */
 	int iTab;
 	ExprList *pOrderBy = pSort->pOrderBy;
 	int eDest = pDest->eDest;
-	int iParm = pDest->iSDParm;
 	int regRow;
 	int regTupleid;
 	int iCol;
@@ -1570,10 +1591,12 @@ generateSortTail(Parse * pParse,	/* Parsing context */
 	case SRT_Table:
 	case SRT_EphemTab: {
 			int regCopy = sqlite3GetTempRange(pParse,  nColumn);
-			sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, iParm, nColumn, regTupleid);
+			sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, pDest->reg_eph,
+					  nColumn, regTupleid);
 			sqlite3VdbeAddOp3(v, OP_Copy, regRow, regCopy, nSortData - 1);
 			sqlite3VdbeAddOp3(v, OP_MakeRecord, regCopy, nColumn + 1, regRow);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, regRow);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, regRow, pDest->reg_eph);
+			sqlite3VdbeChangeP5(v, OPFLAG_EPH_INSERT);
 			sqlite3ReleaseTempReg(pParse, regCopy);
 			break;
 		}
@@ -1583,7 +1606,8 @@ generateSortTail(Parse * pParse,	/* Parsing context */
 			sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, nColumn,
 					  regTupleid, pDest->zAffSdst, nColumn);
 			sqlite3ExprCacheAffinityChange(pParse, regRow, nColumn);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, regTupleid);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, regTupleid, pDest->reg_eph);
+			sqlite3VdbeChangeP5(v, OPFLAG_EPH_INSERT);
 			break;
 		}
 	case SRT_Mem:{
@@ -2409,13 +2433,16 @@ generateWithRecursiveQuery(Parse * pParse,	/* Parsing context */
 	 * for the SRT_DistFifo and SRT_DistQueue destinations to work.
 	 */
 	iQueue = pParse->nTab++;
+	int reg_queue = ++pParse->nMem;
+	int reg_dist = 0;
 	if (p->op == TK_UNION) {
 		eDest = pOrderBy ? SRT_DistQueue : SRT_DistFifo;
 		iDistinct = pParse->nTab++;
+		reg_dist = ++pParse->nMem;
 	} else {
 		eDest = pOrderBy ? SRT_Queue : SRT_Fifo;
 	}
-	sqlite3SelectDestInit(&destQueue, eDest, iQueue);
+	sqlite3SelectDestInit(&destQueue, eDest, iQueue, reg_queue);
 
 	/* Allocate cursors for Current, Queue, and Distinct. */
 	regCurrent = ++pParse->nMem;
@@ -2423,18 +2450,20 @@ generateWithRecursiveQuery(Parse * pParse,	/* Parsing context */
 	if (pOrderBy) {
 		struct sql_key_info *key_info =
 			sql_multiselect_orderby_to_key_info(pParse, p, 1);
-		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iQueue,
+		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, reg_queue,
 				  pOrderBy->nExpr + 2, 0, (char *)key_info,
 				  P4_KEYINFO);
 		VdbeComment((v, "Orderby table"));
 		destQueue.pOrderBy = pOrderBy;
 	} else {
-		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iQueue, nCol + 1);
+		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, reg_queue, nCol + 1);
 		VdbeComment((v, "Queue table"));
 	}
+	sqlite3VdbeAddOp3(v, OP_CursorOpen, iQueue, 0, reg_queue);
 	if (iDistinct) {
 		p->addrOpenEphm[0] =
-		    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iDistinct, 1);
+		    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, reg_dist, 1);
+		sqlite3VdbeAddOp3(v, OP_CursorOpen, iDistinct, 0, reg_dist);
 		p->selFlags |= SF_UsesEphemeral;
 		VdbeComment((v, "Distinct table"));
 	}
@@ -2630,7 +2659,8 @@ multiSelect(Parse * pParse,	/* Parsing context */
 	if (dest.eDest == SRT_EphemTab) {
 		assert(p->pEList);
 		int nCols = p->pEList->nExpr;
-		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, dest.iSDParm, nCols + 1);
+		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, dest.reg_eph, nCols + 1);
+		sqlite3VdbeAddOp3(v, OP_CursorOpen, dest.iSDParm, 0, dest.reg_eph);
 		VdbeComment((v, "Destination temp"));
 		dest.eDest = SRT_Table;
 	}
@@ -2721,6 +2751,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
 		case TK_EXCEPT:
 		case TK_UNION:{
 				int unionTab;	/* Cursor number of the temporary table holding result */
+				int reg_union;
 				u8 op = 0;	/* One of the SRT_ operations to apply to self */
 				int priorOp;	/* The SRT_ operation to apply to prior selects */
 				Expr *pLimit, *pOffset;	/* Saved values of p->nLimit and p->nOffset */
@@ -2737,16 +2768,19 @@ multiSelect(Parse * pParse,	/* Parsing context */
 					assert(p->pLimit == 0);	/* Not allowed on leftward elements */
 					assert(p->pOffset == 0);	/* Not allowed on leftward elements */
 					unionTab = dest.iSDParm;
+					reg_union = dest.reg_eph;
 				} else {
 					/* We will need to create our own temporary table to hold the
 					 * intermediate results.
 					 */
 					unionTab = pParse->nTab++;
+					reg_union = ++pParse->nMem;
 					assert(p->pOrderBy == 0);
 					addr =
 					    sqlite3VdbeAddOp2(v,
 							      OP_OpenTEphemeral,
-							      unionTab, 0);
+							      reg_union, 0);
+					sqlite3VdbeAddOp3(v, OP_CursorOpen, unionTab, 0, reg_union);
 					assert(p->addrOpenEphm[0] == -1);
 					p->addrOpenEphm[0] = addr;
 					findRightmost(p)->selFlags |=
@@ -2758,7 +2792,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
 				 */
 				assert(!pPrior->pOrderBy);
 				sqlite3SelectDestInit(&uniondest, priorOp,
-						      unionTab);
+						      unionTab, reg_union);
 				iSub1 = pParse->iNextSelectId;
 				rc = sqlite3Select(pParse, pPrior, &uniondest);
 				if (rc) {
@@ -2841,6 +2875,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
 		default:
 			assert(p->op == TK_INTERSECT); {
 				int tab1, tab2;
+				int reg_eph1, reg_eph2;
 				int iCont, iBreak, iStart;
 				Expr *pLimit, *pOffset;
 				int addr;
@@ -2852,12 +2887,15 @@ multiSelect(Parse * pParse,	/* Parsing context */
 				 * by allocating the tables we will need.
 				 */
 				tab1 = pParse->nTab++;
+				reg_eph1 = ++pParse->nMem;
 				tab2 = pParse->nTab++;
+				reg_eph2 = ++pParse->nMem;
 				assert(p->pOrderBy == 0);
 
 				addr =
-				    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, tab1,
+				    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, reg_eph1,
 						      0);
+				sqlite3VdbeAddOp3(v, OP_CursorOpen, tab1, 0, reg_eph1);
 				assert(p->addrOpenEphm[0] == -1);
 				p->addrOpenEphm[0] = addr;
 				findRightmost(p)->selFlags |= SF_UsesEphemeral;
@@ -2866,7 +2904,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
 				/* Code the SELECTs to our left into temporary table "tab1".
 				 */
 				sqlite3SelectDestInit(&intersectdest, SRT_Union,
-						      tab1);
+						      tab1, reg_eph1);
 				iSub1 = pParse->iNextSelectId;
 				rc = sqlite3Select(pParse, pPrior,
 						   &intersectdest);
@@ -2877,8 +2915,9 @@ multiSelect(Parse * pParse,	/* Parsing context */
 				/* Code the current SELECT into temporary table "tab2"
 				 */
 				addr =
-				    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, tab2,
+				    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, reg_eph2,
 						      0);
+				sqlite3VdbeAddOp3(v, OP_CursorOpen, tab2, 0, reg_eph2);
 				assert(p->addrOpenEphm[1] == -1);
 				p->addrOpenEphm[1] = addr;
 				p->pPrior = 0;
@@ -2887,6 +2926,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
 				pOffset = p->pOffset;
 				p->pOffset = 0;
 				intersectdest.iSDParm = tab2;
+				intersectdest.reg_eph = reg_eph2;
 				iSub2 = pParse->iNextSelectId;
 				rc = sqlite3Select(pParse, p, &intersectdest);
 				testcase(rc != SQLITE_OK);
@@ -3075,7 +3115,7 @@ generateOutputSubroutine(struct Parse *parse, struct Select *p,
 	case SRT_EphemTab:{
 			int regRec = sqlite3GetTempReg(parse);
 			int regCopy = sqlite3GetTempRange(parse, in->nSdst + 1);
-			sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, dest->iSDParm,
+			sqlite3VdbeAddOp3(v, OP_NextIdEphemeral, dest->reg_eph,
 					  in->nSdst, regCopy + in->nSdst);
 			sqlite3VdbeAddOp3(v, OP_Copy, in->iSdst, regCopy,
 					  in->nSdst - 1);
@@ -3083,7 +3123,8 @@ generateOutputSubroutine(struct Parse *parse, struct Select *p,
 					  in->nSdst + 1, regRec);
 			/* Set flag to save memory allocating one by malloc. */
 			sqlite3VdbeChangeP5(v, 1);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, dest->iSDParm, regRec);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, regRec, dest->reg_eph);
+			sqlite3VdbeChangeP5(v, OPFLAG_EPH_INSERT);
 			sqlite3ReleaseTempRange(parse, regCopy, in->nSdst + 1);
 			sqlite3ReleaseTempReg(parse, regRec);
 			break;
@@ -3099,7 +3140,8 @@ generateOutputSubroutine(struct Parse *parse, struct Select *p,
 					  in->nSdst);
 			sqlite3ExprCacheAffinityChange(parse, in->iSdst,
 						       in->nSdst);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, dest->iSDParm, r1);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, r1, dest->reg_eph);
+			sqlite3VdbeChangeP5(v, OPFLAG_EPH_INSERT);
 			sqlite3ReleaseTempReg(parse, r1);
 			break;
 		}
@@ -3417,8 +3459,8 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 	regAddrB = ++pParse->nMem;
 	regOutA = ++pParse->nMem;
 	regOutB = ++pParse->nMem;
-	sqlite3SelectDestInit(&destA, SRT_Coroutine, regAddrA);
-	sqlite3SelectDestInit(&destB, SRT_Coroutine, regAddrB);
+	sqlite3SelectDestInit(&destA, SRT_Coroutine, regAddrA, 0);
+	sqlite3SelectDestInit(&destB, SRT_Coroutine, regAddrB, 0);
 
 	/* Generate a coroutine to evaluate the SELECT statement to the
 	 * left of the compound operator - the "A" select.
@@ -5261,7 +5303,7 @@ resetAccumulator(Parse * pParse, AggInfo * pAggInfo)
 	/* Verify that all AggInfo registers are within the range specified by
 	 * AggInfo.mnReg..AggInfo.mxReg
 	 */
-	assert(nReg == pAggInfo->mxReg - pAggInfo->mnReg + 1);
+	assert(nReg <= pAggInfo->mxReg - pAggInfo->mnReg + 1);
 	for (i = 0; i < pAggInfo->nColumn; i++) {
 		assert(pAggInfo->aCol[i].iMem >= pAggInfo->mnReg
 		       && pAggInfo->aCol[i].iMem <= pAggInfo->mxReg);
@@ -5287,8 +5329,10 @@ resetAccumulator(Parse * pParse, AggInfo * pAggInfo)
 								  pE->x.pList,
 								  0);
 				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
-						  pFunc->iDistinct, 1, 0,
+						  pFunc->reg_eph, 1, 0,
 						  (char *)key_info, P4_KEYINFO);
+				sqlite3VdbeAddOp3(v, OP_CursorOpen,
+						  pFunc->iDistinct, 0, pFunc->reg_eph);
 			}
 		}
 	}
@@ -5347,8 +5391,8 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 			addrNext = sqlite3VdbeMakeLabel(v);
 			testcase(nArg == 0);	/* Error condition */
 			testcase(nArg > 1);	/* Also an error */
-			codeDistinct(pParse, pF->iDistinct, addrNext, 1,
-				     regAgg);
+			codeDistinct(pParse, pF->iDistinct, pF->reg_eph,
+				     addrNext, 1, regAgg);
 		}
 		if (pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) {
 			struct coll *coll = NULL;
@@ -5679,7 +5723,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 			VdbeComment((v, "%s", pItem->pTab->def->name));
 			pItem->addrFillSub = addrTop;
 			sqlite3SelectDestInit(&dest, SRT_Coroutine,
-					      pItem->regReturn);
+					      pItem->regReturn, 0);
 			pItem->iSelectId = pParse->iNextSelectId;
 			sqlite3Select(pParse, pSub, &dest);
 			pItem->pTab->tuple_log_count = pSub->nSelectRow;
@@ -5699,6 +5743,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 			int retAddr;
 			assert(pItem->addrFillSub == 0);
 			pItem->regReturn = ++pParse->nMem;
+			pItem->reg = ++pParse->nMem;
 			topAddr =
 			    sqlite3VdbeAddOp2(v, OP_Integer, 0,
 					      pItem->regReturn);
@@ -5717,7 +5762,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 						 pItem->pTab->def->name));
 			}
 			sqlite3SelectDestInit(&dest, SRT_EphemTab,
-					      pItem->iCursor);
+					      pItem->iCursor, pItem->reg);
 			pItem->iSelectId = pParse->iNextSelectId;
 			sqlite3Select(pParse, pSub, &dest);
 			pItem->pTab->tuple_log_count = pSub->nSelectRow;
@@ -5796,6 +5841,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	if (sSort.pOrderBy) {
 		struct sql_key_info *key_info =
 			sql_expr_list_to_key_info(pParse, sSort.pOrderBy, 0);
+		sSort.reg_eph = ++pParse->nMem;
 		sSort.iECursor = pParse->nTab++;
 		/* Number of columns in transient table equals to number of columns in
 		 * SELECT statement plus number of columns in ORDER BY statement
@@ -5807,9 +5853,10 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 		}
 		sSort.addrSortIndex =
 		    sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
-				      sSort.iECursor,
+				      sSort.reg_eph,
 				      nCols,
 				      0, (char *)key_info, P4_KEYINFO);
+		sqlite3VdbeAddOp3(v, OP_CursorOpen, sSort.iECursor, 0, sSort.reg_eph);
 		VdbeComment((v, "Sort table"));
 	} else {
 		sSort.addrSortIndex = -1;
@@ -5818,8 +5865,10 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	/* If the output is destined for a temporary table, open that table.
 	 */
 	if (pDest->eDest == SRT_EphemTab) {
-		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, pDest->iSDParm,
+		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, pDest->reg_eph,
 				  pEList->nExpr + 1);
+		sqlite3VdbeAddOp3(v, OP_CursorOpen, pDest->iSDParm, 0,
+				  pDest->reg_eph);
 
 		VdbeComment((v, "Output table"));
 	}
@@ -5833,6 +5882,8 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	computeLimitRegisters(pParse, p, iEnd);
 	if (p->iLimit == 0 && sSort.addrSortIndex >= 0) {
 		sqlite3VdbeChangeOpcode(v, sSort.addrSortIndex, OP_SorterOpen);
+		sqlite3VdbeChangeP1(v, sSort.addrSortIndex, sSort.iECursor);
+		sqlite3VdbeChangeToNoop(v, sSort.addrSortIndex + 1);
 		sSort.sortFlags |= SORTFLAG_UseSorter;
 	}
 
@@ -5840,13 +5891,16 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	 */
 	if (p->selFlags & SF_Distinct) {
 		sDistinct.tabTnct = pParse->nTab++;
+		sDistinct.reg_eph = ++pParse->nMem;
 		struct sql_key_info *key_info =
 			sql_expr_list_to_key_info(pParse, p->pEList, 0);
 		sDistinct.addrTnct = sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
-						       sDistinct.tabTnct,
+						       sDistinct.reg_eph,
 						       key_info->part_count,
 						       0, (char *)key_info,
 						       P4_KEYINFO);
+		sqlite3VdbeAddOp3(v, OP_CursorOpen, sDistinct.tabTnct, 0,
+				  sDistinct.reg_eph);
 		VdbeComment((v, "Distinct table"));
 		sDistinct.eTnctType = WHERE_DISTINCT_UNORDERED;
 	} else {
@@ -5886,6 +5940,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 		 */
 		if (sSort.addrSortIndex >= 0 && sSort.pOrderBy == 0) {
 			sqlite3VdbeChangeToNoop(v, sSort.addrSortIndex);
+			sqlite3VdbeChangeToNoop(v, sSort.addrSortIndex + 1);
 		}
 
 		/* Use the standard inner loop. */
@@ -6130,6 +6185,7 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 			    ) {
 				sSort.pOrderBy = 0;
 				sqlite3VdbeChangeToNoop(v, sSort.addrSortIndex);
+				sqlite3VdbeChangeToNoop(v, sSort.addrSortIndex + 1);
 			}
 
 			/* Evaluate the current GROUP BY terms and store in b0, b1, b2...
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 53188e7..51110aa 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2000,6 +2000,8 @@ struct AggInfo {
 		FuncDef *pFunc;	/* The aggregate function implementation */
 		int iMem;	/* Memory location that acts as accumulator */
 		int iDistinct;	/* Ephemeral table used to enforce DISTINCT */
+		/* Register, holding ephemeral's spacepointer. */
+		int reg_eph;
 	} *aFunc;
 	int nFunc;		/* Number of entries in aFunc[] */
 };
@@ -2327,6 +2329,8 @@ struct SrcList {
 		} fg;
 		u8 iSelectId;	/* If pSelect!=0, the id of the sub-select in EQP */
 		int iCursor;	/* The VDBE cursor number used to access this table */
+		/* Register, containing pointer to the space. */
+		int reg;
 		Expr *pOn;	/* The ON clause of a join */
 		IdList *pUsing;	/* The USING clause of a join */
 		Bitmask colUsed;	/* Bit N (1<<N) set if column N of pTab is used */
@@ -2591,6 +2595,8 @@ struct SelectDest {
 	u8 eDest;		/* How to dispose of the results.  On of SRT_* above. */
 	char *zAffSdst;		/* Affinity used when eDest==SRT_Set */
 	int iSDParm;		/* A parameter used by the eDest disposal method */
+	/* Register containing ephemeral's space pointer. */
+	int reg_eph;
 	int iSdst;		/* Base register where results are written */
 	int nSdst;		/* Number of registers allocated */
 	ExprList *pOrderBy;	/* Key columns for SRT_Queue and SRT_DistQueue */
@@ -2806,6 +2812,8 @@ struct Parse {
 #define OPFLAG_PERMUTE       0x01	/* OP_Compare: use the permutation */
 #define OPFLAG_SAVEPOSITION  0x02	/* OP_Delete: keep cursor position */
 #define OPFLAG_AUXDELETE     0x04	/* OP_Delete: index in a DELETE op */
+#define OPFLAG_EPH_INSERT    0x02       /* OP_IdxInsert2: index's space is
+					   ephemeral */
 
 #define OPFLAG_SAME_FRAME    0x01	/* OP_FCopy: use same frame for source
 					 * register
@@ -3508,7 +3516,7 @@ Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
 struct Table *
 sql_lookup_table(struct Parse *parse, struct SrcList_item *tbl_name);
 
-void sqlite3OpenTable(Parse *, int iCur, struct space *, int);
+void sqlite3OpenTable(Parse *, int iCur, struct space *);
 /**
  * Generate code for a DELETE FROM statement.
  *
@@ -3828,14 +3836,14 @@ vdbe_emit_constraint_checks(struct Parse *parse_context,
  * @cursor_id.
  *
  * @param v Virtual database engine.
- * @param cursor_id Primary index cursor.
+ * @param space Pointer to space object.
  * @param raw_data_reg Register with raw data to insert.
  * @param tuple_len Number of registers to hold the tuple.
  * @param on_conflict On conflict action.
  */
 void
-vdbe_emit_insertion_completion(struct Vdbe *v, int cursor_id, int raw_data_reg,
-			       uint32_t tuple_len,
+vdbe_emit_insertion_completion(struct Vdbe *v, struct space *space,
+			       int raw_data_reg, uint32_t tuple_len,
 			       enum on_conflict_action on_conflict);
 
 void
@@ -3870,10 +3878,11 @@ int sqlite3SafetyCheckSickOrOk(sqlite3 *);
  * @param name View name.
  * @param where Option WHERE clause to be added.
  * @param cursor Cursor number for ephemeral table.
+ * @param reg_eph Register holding pointer to ephemeral table.
  */
 void
 sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
-		     int cursor);
+		     int cursor, int reg_eph);
 
 /**
  * This is called by the parser when it sees a CREATE TRIGGER
@@ -4529,7 +4538,7 @@ void sqlite3StrAccumAppendAll(StrAccum *, const char *);
 void sqlite3AppendChar(StrAccum *, int, char);
 char *sqlite3StrAccumFinish(StrAccum *);
 void sqlite3StrAccumReset(StrAccum *);
-void sqlite3SelectDestInit(SelectDest *, int, int);
+void sqlite3SelectDestInit(SelectDest *, int, int, int);
 Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int);
 
 int sqlite3ExprCheckIN(Parse *, Expr *);
diff --git a/src/box/sql/tarantoolInt.h b/src/box/sql/tarantoolInt.h
index 8017742..50a989a 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -97,9 +97,22 @@ sql_index_update_table_name(struct index_def *idef, const char *new_tbl_name,
 int tarantoolSqlite3RenameTrigger(const char *zTriggerName,
 				  const char *zOldName, const char *zNewName);
 
-/* Interface for ephemeral tables. */
-int tarantoolSqlite3EphemeralCreate(BtCursor * pCur, uint32_t filed_count,
-				    struct sql_key_info *key_info);
+/**
+ * Create ephemeral space. Features of ephemeral spaces: id == 0,
+ * name == "ephemeral", memtx engine (in future it can be changed,
+ * but now only memtx engine is supported), primary index which
+ * covers all fields and no secondary indexes, given field number
+ * and collation sequence. All fields are scalar and nullable.
+ *
+ * @param field_count Number of fields in ephemeral space.
+ * @param key_info Keys description for new ephemeral space.
+ *
+ * @retval Pointer to created space, NULL if error.
+ */
+struct space *
+tarantoolSqlite3EphemeralCreate(uint32_t filed_count,
+				struct sql_key_info *key_info);
+
 /**
  * Insert tuple into ephemeral space.
  * In contrast to ordinary spaces, there is no need to create and
@@ -117,7 +130,18 @@ int tarantoolSqlite3EphemeralDelete(BtCursor * pCur);
 int tarantoolSqlite3EphemeralCount(BtCursor * pCur, i64 * pnEntry);
 int tarantoolSqlite3EphemeralDrop(BtCursor * pCur);
 int tarantoolSqlite3EphemeralClearTable(BtCursor * pCur);
-int tarantoolSqlite3EphemeralGetMaxId(BtCursor * pCur, uint32_t fieldno,
+
+/**
+ * Extract maximum integer value from ephemeral space.
+ * If index is empty - return 0 in max_id and success status.
+ *
+ * @param space Pointer to ephemeral space.
+ * @param fieldno Number of field from fetching tuple.
+ * @param[out] max_id Fetched max value.
+ *
+ * @retval 0 on success, -1 otherwise.
+ */
+int tarantoolSqlite3EphemeralGetMaxId(struct space *space, uint32_t fieldno,
 				       uint64_t * max_id);
 
 /**
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index c6bd15b..271f40a 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -205,15 +205,9 @@ sql_trigger_finish(struct Parse *parse, struct TriggerStep *step_list,
 		if (db->mallocFailed)
 			goto cleanup;
 
-		int cursor = parse->nTab++;
 		struct space *_trigger = space_by_id(BOX_TRIGGER_ID);
 		assert(_trigger != NULL);
-		vdbe_emit_open_cursor(parse, cursor, 0, _trigger);
 
-		/*
-		 * makerecord(cursor(iRecord),
-		 * [reg(first_col), reg(first_col+1)]).
-		 */
 		int first_col = parse->nMem + 1;
 		parse->nMem += 3;
 		int record = ++parse->nMem;
@@ -245,10 +239,10 @@ sql_trigger_finish(struct Parse *parse, struct TriggerStep *step_list,
 		sqlite3VdbeAddOp4(v, OP_Blob, opts_buff_sz, first_col + 2,
 				  SQL_SUBTYPE_MSGPACK, opts_buff, P4_DYNAMIC);
 		sqlite3VdbeAddOp3(v, OP_MakeRecord, first_col, 3, record);
-		sqlite3VdbeAddOp2(v, OP_IdxInsert, cursor, record);
+		sqlite3VdbeAddOp4(v, OP_IdxInsert, record, 0, 0,
+				  (char *)_trigger, P4_SPACEPTR);
 
 		sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
-		sqlite3VdbeAddOp1(v, OP_Close, cursor);
 
 		sql_set_multi_write(parse, false);
 	} else {
@@ -694,7 +688,7 @@ codeTriggerProgram(Parse * pParse,	/* The parser context */
 				SelectDest sDest;
 				Select *pSelect =
 				    sqlite3SelectDup(db, pStep->pSelect, 0);
-				sqlite3SelectDestInit(&sDest, SRT_Discard, 0);
+				sqlite3SelectDestInit(&sDest, SRT_Discard, 0, 0);
 				sqlite3Select(pParse, pSelect, &sDest);
 				sql_select_delete(db, pSelect);
 				break;
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 730872f..71bd71d 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -222,13 +222,16 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	 * an ephemeral table.
 	 */
 	uint32_t pk_part_count;
+	struct space *space;
 	if (is_view) {
-		sql_materialize_view(pParse, def->name, pWhere, pk_cursor);
+		int reg_eph = ++pParse->nMem;
+		sql_materialize_view(pParse, def->name, pWhere, pk_cursor, reg_eph);
 		/* Number of columns from SELECT plus ID.*/
 		pk_part_count = nKey = def->field_count + 1;
 	} else {
-		vdbe_emit_open_cursor(pParse, pk_cursor, 0,
-				      space_by_id(pTab->def->id));
+		space = space_by_id(pTab->def->id);
+		assert(space != NULL);
+		vdbe_emit_open_cursor(pParse, pk_cursor, 0, space);
 		pk_part_count = pPk->def->key_def->part_count;
 	}
 
@@ -242,11 +245,12 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	int iPk = pParse->nMem + 1;
 	pParse->nMem += pk_part_count;
 	regKey = ++pParse->nMem;
+	int reg_eph = ++pParse->nMem;
 	iEph = pParse->nTab++;
 	sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
 
 	/* Address of the OpenEphemeral instruction. */
-	int addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph,
+	int addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, reg_eph,
 					 pk_part_count);
 	pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0,
 				   WHERE_ONEPASS_DESIRED, pk_cursor);
@@ -276,9 +280,9 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 								pPk->def);
 		sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, pk_part_count,
 				  regKey, zAff, pk_part_count);
-		sqlite3VdbeAddOp2(v, OP_IdxInsert, iEph, regKey);
+		sqlite3VdbeAddOp2(v, OP_IdxInsert, regKey, reg_eph);
 		/* Set flag to save memory allocating one by malloc. */
-		sqlite3VdbeChangeP5(v, 1);
+		sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE | OPFLAG_EPH_INSERT);
 	}
 	/* End the database scan loop.
 	 */
@@ -304,6 +308,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 		}
 	} else {
 		labelContinue = sqlite3VdbeMakeLabel(v);
+		sqlite3VdbeAddOp3(v, OP_CursorOpen, iEph, 0, reg_eph);
 		sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak);
 		addrTop = sqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey);
 		sqlite3VdbeAddOp4Int(v, OP_NotFound, pk_cursor, labelContinue,
@@ -437,7 +442,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 		sqlite3VdbeJumpHere(v, addr1);
 		if (hasFK)
 			fkey_emit_check(pParse, pTab, 0, regNewPk, aXRef);
-		vdbe_emit_insertion_completion(v, pk_cursor, regNew,
+		vdbe_emit_insertion_completion(v, space, regNew,
 					       pTab->def->field_count,
 					       on_error);
 		/*
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 00ffb0c..7df491b 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -3054,51 +3054,37 @@ case OP_TTransaction: {
 	break;
 }
 
-/* Opcode: OpenRead P1 P2 P3 P4 P5
+/* Opcode: CursorReopen P1 P2 P3 P4 P5
  * Synopsis: index id = P2, space ptr = P4
  *
- * Open a cursor for a space specified by pointer in P4 and index
- * id in P2. Give the new cursor an identifier of P1. The P1
- * values need not be contiguous but all P1 values should be
- * small integers. It is an error for P1 to be negative.
- */
-/* Opcode: ReopenIdx P1 P2 P3 P4 P5
- * Synopsis: index id = P2, space ptr = P4
- *
- * The ReopenIdx opcode works exactly like OpenRead except that
- * it first checks to see if the cursor on P1 is already open
+ * The CursorReopen opcode works exactly like CursorOpen except
+ * that it first checks to see if the cursor on P1 is already open
  * with the same index and if it is this opcode becomes a no-op.
- * In other words, if the cursor is already open, do not reopen it.
+ * In other words, if the cursor is already open, do not reopen
+ * it.
  *
- * The ReopenIdx opcode may only be used with P5 == 0.
+ * The CursorReopen opcode may only be used with P5 == 0.
  */
-/* Opcode: OpenWrite P1 P2 P3 P4 P5
- * Synopsis: index id = P2, space ptr = P4
+/* Opcode: CursorOpen P1 P2 P3 P4 P5
+ * Synopsis: index id = P2, space ptr = P4 or reg[P3]
  *
- * For now, OpenWrite is an alias for OpenRead.
- * It exists just due legacy reasons and should be removed:
- * it isn't neccessary to open cursor to make insertion or
- * deletion.
- */
-case OP_ReopenIdx: {
-	int nField;
-	int p2;
-	VdbeCursor *pCur;
-	BtCursor *pBtCur;
-
-	assert(pOp->p5==0 || pOp->p5==OPFLAG_SEEKEQ);
-	pCur = p->apCsr[pOp->p1];
-	p2 = pOp->p2;
-	if (pCur && pCur->uc.pCursor->space == pOp->p4.space &&
-	    pCur->uc.pCursor->index->def->iid == (uint32_t)p2)
+ * Open a cursor for a space specified by pointer in P4 and index
+ * id in P2. Give the new cursor an identifier of P1. The P1
+ * values need not be contiguous but all P1 values should be
+ * small integers. It is an error for P1 to be negative.
+ * If P4 was not set, then P3 supposed to be the register
+ * containing space pointer.
+ */
+case OP_CursorReopen: {
+	assert(pOp->p5 == 0);
+	struct VdbeCursor *cur = p->apCsr[pOp->p1];
+	if (cur != NULL && cur->uc.pCursor->space == pOp->p4.space &&
+	    cur->uc.pCursor->index->def->iid == (uint32_t)pOp->p2)
 		goto open_cursor_set_hints;
 	/* If the cursor is not currently open or is open on a different
-	 * index, then fall through into OP_OpenRead to force a reopen
+	 * index, then fall through into OP_OpenCursor to force a reopen
 	 */
-case OP_OpenRead:
-case OP_OpenWrite:
-
-	assert(pOp->opcode==OP_OpenWrite || pOp->p5==0 || pOp->p5==OPFLAG_SEEKEQ);
+case OP_CursorOpen:
 	if (box_schema_version() != p->schema_ver &&
 	    (pOp->p5 & OPFLAG_SYSTEMSP) == 0) {
 		p->expired = 1;
@@ -3107,66 +3093,60 @@ case OP_OpenWrite:
 				    "need to re-compile SQL statement");
 		goto abort_due_to_error;
 	}
-	p2 = pOp->p2;
-	struct space *space = pOp->p4.space;
+	struct space *space;
+	if (pOp->p4type == P4_SPACEPTR)
+		space = pOp->p4.space;
+	else
+		space = aMem[pOp->p3].u.p;
 	assert(space != NULL);
-	struct index *index = space_index(space, p2);
+	struct index *index = space_index(space, pOp->p2);
 	assert(index != NULL);
-	/*
-	 * Since Tarantool iterator provides the full tuple,
-	 * we need a number of fields as wide as the table itself.
-	 * Otherwise, not enough slots for row parser cache are
-	 * allocated in VdbeCursor object.
-	 */
-	nField = space->def->field_count;
-	assert(pOp->p1>=0);
-	assert(nField>=0);
-	pCur = allocateCursor(p, pOp->p1, nField, CURTYPE_TARANTOOL);
-	if (pCur==0) goto no_mem;
-	pCur->nullRow = 1;
-	pBtCur = pCur->uc.pCursor;
-	pBtCur->curFlags |= BTCF_TaCursor;
-	pBtCur->space = space;
-	pBtCur->index = index;
-	pBtCur->eState = CURSOR_INVALID;
+	assert(pOp->p1 >= 0);	
+	cur = allocateCursor(p, pOp->p1,
+			     space->def->exact_field_count == 0 ?
+			     space->def->field_count :
+			     space->def->exact_field_count,
+			     CURTYPE_TARANTOOL);
+	if (cur == NULL)
+		goto no_mem;
+	struct BtCursor *bt_cur = cur->uc.pCursor;
+	bt_cur->curFlags |= space->def->id == 0 ? BTCF_TEphemCursor :
+				BTCF_TaCursor;
+	bt_cur->space = space;
+	bt_cur->index = index;
+	bt_cur->eState = CURSOR_INVALID;
 	/* Key info still contains sorter order and collation. */
-	pCur->key_def = index->def->key_def;
-
+	cur->key_def = index->def->key_def;
+	cur->nullRow = 1;
 open_cursor_set_hints:
-	pCur->uc.pCursor->hints = pOp->p5 & OPFLAG_SEEKEQ;
-	if (rc) goto abort_due_to_error;
+	cur->uc.pCursor->hints = pOp->p5 & OPFLAG_SEEKEQ;
+	if (rc != 0)
+		goto abort_due_to_error;
 	break;
 }
 
 /**
  * Opcode: OpenTEphemeral P1 P2 * P4 *
  * Synopsis:
- * @param P1 index of new cursor to be created.
+ * @param P1 register, where pointer to new space is stored.
  * @param P2 number of columns in a new table.
  * @param P4 key def for new table, NULL is allowed.
  *
- * This opcode creates Tarantool's ephemeral table and sets cursor P1 to it.
+ * This opcode creates Tarantool's ephemeral table and stores pointer
+ * to it into P1 register.
  */
 case OP_OpenTEphemeral: {
-	VdbeCursor *pCx;
-	BtCursor *pBtCur;
 	assert(pOp->p1 >= 0);
 	assert(pOp->p2 > 0);
 	assert(pOp->p4type != P4_KEYINFO || pOp->p4.key_info != NULL);
 
-	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
-	if (pCx == 0) goto no_mem;
-	pCx->nullRow = 1;
+	struct space *space = tarantoolSqlite3EphemeralCreate(pOp->p2,
+							      pOp->p4.key_info);
 
-	pBtCur = pCx->uc.pCursor;
-	/* Ephemeral spaces don't have space_id */
-	pBtCur->eState = CURSOR_INVALID;
-	pBtCur->curFlags = BTCF_TEphemCursor;
-
-	rc = tarantoolSqlite3EphemeralCreate(pCx->uc.pCursor, pOp->p2,
-					     pOp->p4.key_info);
-	pCx->key_def = pCx->uc.pCursor->index->def->key_def;
-	if (rc) goto abort_due_to_error;
+	if (space == NULL)
+		goto abort_due_to_error;
+	aMem[pOp->p1].u.p = space;
+	aMem[pOp->p1].flags = MEM_Ptr;
 	break;
 }
 
@@ -3708,23 +3688,21 @@ case OP_NextSequenceId: {
 /* Opcode: NextIdEphemeral P1 P2 P3 * *
  * Synopsis: r[P3]=get_max(space_index[P1]{Column[P2]})
  *
- * This opcode works in the same way as OP_NextId does, except it is
- * only applied for ephemeral tables. The difference is in the fact that
- * all ephemeral tables don't have space_id (to be more precise it equals to zero).
+ * This opcode works in the same way as OP_NextId does, except it
+ * is only applied for ephemeral tables. The difference is in the
+ * fact that all ephemeral tables don't have space_id (to be more
+ * precise it equals to zero). This opcode uses register P1 to
+ * fetch pointer to epehemeral space.
  */
 case OP_NextIdEphemeral: {
-	VdbeCursor *pC;
-	int p2;
-	pC = p->apCsr[pOp->p1];
-	p2 = pOp->p2;
+	struct space *space = (struct space*)p->aMem[pOp->p1].u.p;
+	int p2 = pOp->p2;
+	assert(space != NULL);
 	pOut = &aMem[pOp->p3];
-
-	assert(pC->uc.pCursor->curFlags & BTCF_TEphemCursor);
-
-	rc = tarantoolSqlite3EphemeralGetMaxId(pC->uc.pCursor, p2,
+	rc = tarantoolSqlite3EphemeralGetMaxId(space, p2,
 					       (uint64_t *) &pOut->u.i);
-	if (rc) goto abort_due_to_error;
-
+	if (rc != 0)
+		goto abort_due_to_error;
 	pOut->u.i += 1;
 	pOut->flags = MEM_Int;
 	break;
@@ -4242,83 +4220,95 @@ case OP_Next:          /* jump */
 	goto check_for_interrupt;
 }
 
-/* Opcode: IdxInsert P1 P2 * * P5
+/* Opcode: SorterInsert P1 P2 * * *
  * Synopsis: key=r[P2]
  *
- * @param P1 Index of a space cursor.
- * @param P2 Index of a register with MessagePack data to insert.
+ * Register P2 holds an SQL index key made using the
+ * MakeRecord instructions.  This opcode writes that key
+ * into the sorter P1.  Data for the entry is nil.
+ */
+case OP_SorterInsert: {      /* in2 */
+	assert(pOp->p1 >= 0 && pOp->p1 < p->nCursor);
+	struct VdbeCursor *cursor = p->apCsr[pOp->p1];
+	assert(cursor != NULL);
+	assert(isSorter(cursor));
+	pIn2 = &aMem[pOp->p2];
+	assert((pIn2->flags & MEM_Blob) != 0);
+	rc = ExpandBlob(pIn2);
+	if (rc != 0)
+		goto abort_due_to_error;
+	rc = sqlite3VdbeSorterWrite(cursor, pIn2);
+	if (rc != 0)
+		goto abort_due_to_error;
+	break;
+}
+
+/* Opcode: IdxInsert2 P1 * * P4 P5
+ * Synopsis: key=r[P1]
+ *
+ * @param P1 Index of a register with MessagePack data to insert.
+ * @param P2 If P4 is not set, then P2 is register containing pointer
+ *           to space to insert into.
+ * @param P4 Pointer to the struct space to insert to.
  * @param P5 Flags. If P5 contains OPFLAG_NCHANGE, then VDBE
  *        accounts the change in a case of successful insertion in
  *        nChange counter. If P5 contains OPFLAG_OE_IGNORE, then
  *        we are processing INSERT OR INGORE statement. Thus, in
  *        case of conflict we don't raise an error.
  */
-/* Opcode: IdxReplace P1 P2 * * P5
- * Synopsis: key=r[P2]
+/* Opcode: IdxReplace2 P1 * * P4 P5
+ * Synopsis: key=r[P1]
  *
  * This opcode works exactly as IdxInsert does, but in Tarantool
  * internals it invokes box_replace() instead of box_insert().
  */
-/* Opcode: SorterInsert P1 P2 * * *
- * Synopsis: key=r[P2]
- *
- * Register P2 holds an SQL index key made using the
- * MakeRecord instructions.  This opcode writes that key
- * into the sorter P1.  Data for the entry is nil.
- */
-case OP_SorterInsert:       /* in2 */
 case OP_IdxReplace:
-case OP_IdxInsert: {        /* in2 */
-	VdbeCursor *pC;
-
-	assert(pOp->p1>=0 && pOp->p1<p->nCursor);
-	pC = p->apCsr[pOp->p1];
-	assert(pC!=0);
-	assert(isSorter(pC)==(pOp->opcode==OP_SorterInsert));
-	pIn2 = &aMem[pOp->p2];
-	assert(pIn2->flags & MEM_Blob);
-	if (pOp->p5 & OPFLAG_NCHANGE) p->nChange++;
-	assert(pC->eCurType==CURTYPE_TARANTOOL || pOp->opcode==OP_SorterInsert);
+case OP_IdxInsert: {
+	pIn2 = &aMem[pOp->p1];
+	assert((pIn2->flags & MEM_Blob) != 0);
+	if (pOp->p5 & OPFLAG_NCHANGE)
+	  p->nChange++;
 	rc = ExpandBlob(pIn2);
-	if (rc) goto abort_due_to_error;
-	if (pOp->opcode==OP_SorterInsert) {
-		rc = sqlite3VdbeSorterWrite(pC, pIn2);
+	if (rc != 0)
+	  goto abort_due_to_error;
+	struct space *space;
+	if (pOp->p4type == P4_SPACEPTR) {
+		space = pOp->p4.space;
 	} else {
-		BtCursor *pBtCur = pC->uc.pCursor;
-		if (pBtCur->curFlags & BTCF_TaCursor) {
-			/* Make sure that memory has been allocated on region. */
-			assert(aMem[pOp->p2].flags & MEM_Ephem);
-			if (pOp->opcode == OP_IdxInsert)
-				rc = tarantoolSqlite3Insert(pBtCur->space,
-							    pIn2->z,
-							    pIn2->z + pIn2->n);
-			else
-				rc = tarantoolSqlite3Replace(pBtCur->space,
-							     pIn2->z,
-							     pIn2->z + pIn2->n);
-		} else if (pBtCur->curFlags & BTCF_TEphemCursor) {
-			rc = tarantoolSqlite3EphemeralInsert(pBtCur->space,
-							     pIn2->z,
-							     pIn2->z + pIn2->n);
+		space = aMem[pOp->p2].u.p;
+	}
+	assert(space != NULL);
+	if ((pOp->p5 & OPFLAG_EPH_INSERT) == 0) {
+		/* Make sure that memory has been allocated on region. */
+		assert(aMem[pOp->p1].flags & MEM_Ephem);
+		if (pOp->opcode == OP_IdxInsert) {
+			rc = tarantoolSqlite3Insert(space,
+						    pIn2->z,
+						    pIn2->z + pIn2->n);
 		} else {
-			unreachable();
+			rc = tarantoolSqlite3Replace(space,
+						     pIn2->z,
+						     pIn2->z + pIn2->n);
 		}
-		pC->cacheStatus = CACHE_STALE;
+	} else {
+		rc = tarantoolSqlite3EphemeralInsert(space,
+						     pIn2->z,
+						     pIn2->z + pIn2->n);
 	}
 
 	if (pOp->p5 & OPFLAG_OE_IGNORE) {
 		/* Ignore any kind of failes and do not raise error message */
 		rc = SQLITE_OK;
 		/* If we are in trigger, increment ignore raised counter */
-		if (p->pFrame) {
+		if (p->pFrame)
 			p->ignoreRaised++;
-		}
 	} else if (pOp->p5 & OPFLAG_OE_FAIL) {
 		p->errorAction = ON_CONFLICT_ACTION_FAIL;
 	} else if (pOp->p5 & OPFLAG_OE_ROLLBACK) {
 		p->errorAction = ON_CONFLICT_ACTION_ROLLBACK;
 	}
-	if (rc) goto abort_due_to_error;
+	if (rc != 0)
+	  goto abort_due_to_error;
 	break;
 }
 
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index 01dcc49..237a549 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -4569,12 +4569,9 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 			/* Do nothing */
 		} else if ((pLoop->wsFlags & WHERE_IDX_ONLY) == 0 &&
 			   (wctrlFlags & WHERE_OR_SUBCLAUSE) == 0) {
-			int op = OP_OpenRead;
-			if (pWInfo->eOnePass != ONEPASS_OFF) {
-				op = OP_OpenWrite;
+			if (pWInfo->eOnePass != ONEPASS_OFF)
 				pWInfo->aiCurOnePass[0] = pTabItem->iCursor;
-			};
-			sqlite3OpenTable(pParse, pTabItem->iCursor, space, op);
+			sqlite3OpenTable(pParse, pTabItem->iCursor, space);
 			assert(pTabItem->iCursor == pLevel->iTabCur);
 			testcase(pWInfo->eOnePass == ONEPASS_OFF
 				 && pTab->nCol == BMS - 1);
@@ -4591,7 +4588,7 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 		if (pLoop->wsFlags & WHERE_INDEXED) {
 			struct index_def *idx_def = pLoop->index_def;
 			int iIndexCur;
-			int op = OP_OpenRead;
+			int op = OP_CursorOpen;
 			/* Check if index is primary. Either of
 			 * points should be true:
 			 * 1. struct Index is non-NULL and is
@@ -4636,12 +4633,12 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 					}
 				}
 				assert(wctrlFlags & WHERE_ONEPASS_DESIRED);
-				op = OP_OpenWrite;
+				op = OP_CursorOpen;
 				pWInfo->aiCurOnePass[1] = iIndexCur;
 			} else if (iAuxArg
 				   && (wctrlFlags & WHERE_OR_SUBCLAUSE) != 0) {
 				iIndexCur = iAuxArg;
-				op = OP_ReopenIdx;
+				op = OP_CursorReopen;
 			} else {
 				iIndexCur = pParse->nTab++;
 			}
diff --git a/src/box/sql/whereInt.h b/src/box/sql/whereInt.h
index 8a3f2ac..1aeb360 100644
--- a/src/box/sql/whereInt.h
+++ b/src/box/sql/whereInt.h
@@ -416,7 +416,7 @@ struct WhereInfo {
 	ExprList *pOrderBy;	/* The ORDER BY clause or NULL */
 	ExprList *pDistinctSet;	/* DISTINCT over all these values */
 	LogEst iLimit;		/* LIMIT if wctrlFlags has WHERE_USE_LIMIT */
-	int aiCurOnePass[2];	/* OP_OpenWrite cursors for the ONEPASS opt */
+	int aiCurOnePass[2];	/* OP_CursorOpen cursors for the ONEPASS opt */
 	int iContinue;		/* Jump here to continue with next record */
 	int iBreak;		/* Jump here to break out of the loop */
 	int savedNQueryLoop;	/* pParse->nQueryLoop outside the WHERE loop */
diff --git a/src/box/sql/wherecode.c b/src/box/sql/wherecode.c
index e2aaca3..14278f0 100644
--- a/src/box/sql/wherecode.c
+++ b/src/box/sql/wherecode.c
@@ -1341,7 +1341,8 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		int iCovCur = pParse->nTab++;	/* Cursor used for index scans (if any) */
 
 		int regReturn = ++pParse->nMem;	/* Register used with OP_Gosub */
-		int regRowset = 0;	/* Register for RowSet object */
+		int regRowset = 0;	/* Cursor for RowSet object */
+		int reg_row_set = 0;
 		int regPk = 0;	/* Register holding PK */
 		int iLoopBody = sqlite3VdbeMakeLabel(v);	/* Start of loop body */
 		int iRetInit;	/* Address of regReturn init */
@@ -1400,8 +1401,11 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 		 */
 		if ((pWInfo->wctrlFlags & WHERE_DUPLICATES_OK) == 0) {
 			regRowset = pParse->nTab++;
+			reg_row_set = ++pParse->nMem;
 			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
-					  regRowset, pk_part_count);
+					  reg_row_set, pk_part_count);
+			sqlite3VdbeAddOp3(v, OP_CursorOpen, regRowset, 0,
+					  reg_row_set);
 			sql_vdbe_set_p4_key_def(pParse, pk_key_def);
 			regPk = ++pParse->nMem;
 		}
@@ -1545,7 +1549,9 @@ sqlite3WhereCodeOneLoopStart(WhereInfo * pWInfo,	/* Complete information about t
 								 r, pk_part_count, regPk);
 							sqlite3VdbeAddOp2
 								(v, OP_IdxInsert,
-								 regRowset, regPk);
+								 regPk, reg_row_set);
+							sqlite3VdbeChangeP5(v,
+									    OPFLAG_EPH_INSERT);
 						}
 
 						/* Release the array of temp registers */
-- 
2.16.2

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2018-10-05  4:39 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-10-01 10:31 [tarantool-patches] [PATCH] sql: refactor SQL cursor to remove write ones Kirill Yukhin
2018-10-02 11:56 ` [tarantool-patches] " n.pettik
2018-10-03  9:57   ` Kirill Yukhin
2018-10-04  2:21     ` n.pettik
2018-10-04  7:16       ` Kirill Yukhin
2018-10-04 10:45         ` n.pettik
2018-10-04 11:48           ` Kirill Yukhin
2018-10-04 12:00 ` Kirill Yukhin
2018-10-05  4:39   ` Kirill Yukhin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox