diff --git a/src/box/index.cc b/src/box/index.cc
index 3c644f0a8ecae2b382c3cad4c86c138386845bf9..dfddeb32be3bcc0c06112220013eada45b506a34 100644
--- a/src/box/index.cc
+++ b/src/box/index.cc
@@ -449,6 +449,9 @@ int
 iterator_next(struct iterator *it, struct tuple **ret)
 {
 	assert(it->next != NULL);
+	/* In case of ephemeral space there is no need to check schema version */
+	if (it->space_id == 0)
+		return it->next(it, ret);
 	if (unlikely(it->schema_version != schema_version)) {
 		struct space *space = space_by_id(it->space_id);
 		if (space == NULL)
diff --git a/src/box/sql.c b/src/box/sql.c
index d4b343b4abed03c42435b419047354ba0d108c82..53a56764871fecdf165e6d818ec889296c406c69 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -48,12 +48,15 @@
 #include "schema.h"
 #include "box.h"
 #include "txn.h"
+#include "space.h"
 #include "space_def.h"
 #include "index_def.h"
 #include "tuple.h"
 #include "fiber.h"
 #include "small/region.h"
 #include "session.h"
+#include "xrow.h"
+#include "iproto_constants.h"
 
 static sqlite3 *db;
 
@@ -160,6 +163,8 @@ struct ta_cursor {
 	box_iterator_t    *iter;
 	struct tuple      *tuple_last;
 	enum iterator_type type;
+	/* Used only by ephemeral spaces, for ordinary space == NULL. */
+	struct space      *ephem_space;
 	char               key[1];
 };
 
@@ -173,6 +178,13 @@ cursor_seek(BtCursor *pCur, int *pRes, enum iterator_type type,
 static int
 cursor_advance(BtCursor *pCur, int *pRes);
 
+static int
+cursor_ephemeral_seek(BtCursor *pCur, int *pRes, enum iterator_type type,
+		      const char *key, const char *key_end);
+
+static int
+cursor_ephemeral_advance(BtCursor *pCur, int *pRes);
+
 const char *tarantoolErrorMessage()
 {
 	return box_error_message(box_error_last());
@@ -180,7 +192,8 @@ const char *tarantoolErrorMessage()
 
 int tarantoolSqlite3CloseCursor(BtCursor *pCur)
 {
-	assert(pCur->curFlags & BTCF_TaCursor);
+	assert(pCur->curFlags & BTCF_TaCursor ||
+	       pCur->curFlags & BTCF_TEphemCursor);
 
 	struct ta_cursor *c = pCur->pTaCursor;
 
@@ -196,7 +209,8 @@ int tarantoolSqlite3CloseCursor(BtCursor *pCur)
 
 const void *tarantoolSqlite3PayloadFetch(BtCursor *pCur, u32 *pAmt)
 {
-	assert(pCur->curFlags & BTCF_TaCursor);
+	assert(pCur->curFlags & BTCF_TaCursor ||
+	       pCur->curFlags & BTCF_TEphemCursor);
 
 	struct ta_cursor *c = pCur->pTaCursor;
 
@@ -226,6 +240,16 @@ tarantoolSqlite3TupleColumnFast(BtCursor *pCur, u32 fieldno, u32 *field_size)
 	return field;
 }
 
+/*
+ * Set cursor to the first tuple in ephemeral space.
+ * It is a simple wrapper around cursor_ephemeral_seek.
+ */
+int tarantoolSqlite3EphemeralFirst(BtCursor *pCur, int *pRes)
+{
+	return cursor_ephemeral_seek(pCur, pRes, ITER_GE,
+				     nil_key, nil_key + sizeof(nil_key));
+}
+
 int tarantoolSqlite3First(BtCursor *pCur, int *pRes)
 {
 	return cursor_seek(pCur, pRes, ITER_GE,
@@ -238,6 +262,24 @@ int tarantoolSqlite3Last(BtCursor *pCur, int *pRes)
 			   nil_key, nil_key + sizeof(nil_key));
 }
 
+/*
+ * Set cursor to the next entry in ephemeral space.
+ * If state of cursor is invalid (e.g. it is still under construction,
+ * or already destroyed), it immediately returns.
+ */
+int tarantoolSqlite3EphemeralNext(BtCursor *pCur, int *pRes)
+{
+	assert(pCur->curFlags & BTCF_TEphemCursor);
+	if (pCur->eState == CURSOR_INVALID) {
+		*pRes = 1;
+		return SQLITE_OK;
+	}
+	assert(pCur->pTaCursor);
+	assert(iterator_direction(
+		((struct ta_cursor *)pCur->pTaCursor)->type) > 0);
+	return cursor_ephemeral_advance(pCur, pRes);
+}
+
 int tarantoolSqlite3Next(BtCursor *pCur, int *pRes)
 {
 	assert(pCur->curFlags & BTCF_TaCursor);
@@ -346,6 +388,103 @@ 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. 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.
+ *
+ * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
+ */
+int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count)
+{
+	assert(pCur);
+	assert(pCur->curFlags & BTCF_TEphemCursor);
+
+	struct space_def *ephemer_space_def =
+		space_def_new(0 /* space id */, 0 /* user id */, field_count,
+			      "ephemeral", strlen("ephemeral"),
+			      "memtx", strlen("memtx"),
+			      &space_opts_default, &field_def_default,
+			      0 /* length of field_def */);
+
+	struct key_def *ephemer_key_def = key_def_new(field_count);
+	assert(ephemer_key_def);
+	for (uint32_t part = 0; part < field_count; ++part) {
+		key_def_set_part(ephemer_key_def, part /* part no */,
+				 part /* filed no */,
+				 FIELD_TYPE_SCALAR, true /* is_nullable */,
+				 NULL /* coll */);
+	}
+
+	struct index_def *ephemer_index_def =
+		index_def_new(0 /*space id */, 0 /* index id */, "ephemer_idx",
+			      strlen("ephemer_idx"), TREE, &index_opts_default,
+			      ephemer_key_def, NULL /* pk def */);
+
+	struct rlist key_list;
+	rlist_create(&key_list);
+	rlist_add_entry(&key_list, ephemer_index_def, link);
+
+	struct space *ephemer_new_space = space_new_ephemeral(ephemer_space_def,
+							      &key_list);
+	struct ta_cursor *c = NULL;
+	c = cursor_create(c, field_count /* key size */);
+	if (!c)
+		return SQLITE_NOMEM;
+
+	c->ephem_space = ephemer_new_space;
+	pCur->pTaCursor = c;
+
+	int unused;
+	return tarantoolSqlite3EphemeralFirst(pCur, &unused);
+}
+
+/*
+ * Insert tuple which is contained in pX into ephemeral space. In contrast to
+ * ordinary spaces, there is no need to create and fill request or handle
+ * transaction routine.
+ *
+ * @param pCur Cursor pointing to ephemeral space.
+ * @param pX Payload containing tuple to insert.
+ *
+ * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
+ */
+int tarantoolSqlite3EphemeralInsert(BtCursor *pCur, const BtreePayload *pX)
+{
+	assert(pCur);
+	assert(pCur->curFlags & BTCF_TEphemCursor);
+	struct ta_cursor *c = pCur->pTaCursor;
+	assert(c);
+	assert(c->ephem_space);
+	mp_tuple_assert(pX->pKey, pX->pKey + pX->nKey);
+
+	struct space *space = c->ephem_space;
+	if (space_ephemeral_replace(space, pX->pKey,
+				    pX->pKey + pX->nKey) != 0) {
+		diag_log();
+		return SQLITE_TARANTOOL_ERROR;
+	}
+	return SQLITE_OK;
+}
+
+/* Simply delete ephemeral space calling space_delete(). */
+int tarantoolSqlite3EphemeralDrop(BtCursor *pCur)
+{
+	assert(pCur);
+	assert(pCur->curFlags & BTCF_TEphemCursor);
+
+	struct ta_cursor *c = pCur->pTaCursor;
+	assert(c->ephem_space);
+	space_delete(c->ephem_space);
+
+	return SQLITE_OK;
+}
+
 int tarantoolSqlite3Insert(BtCursor *pCur, const BtreePayload *pX)
 {
 	assert(pCur->curFlags & BTCF_TaCursor);
@@ -968,13 +1107,16 @@ cursor_create(struct ta_cursor *c, size_t key_size)
 {
 	size_t             size;
 	struct ta_cursor  *res;
+	struct space *ephem_space;
 
 	if (c) {
 		size = c->size;
+		ephem_space = c->ephem_space;
 		if (size - offsetof(struct ta_cursor, key) >= key_size)
 			return c;
 	} else {
 		size = sizeof(*c);
+		ephem_space = NULL;
 	}
 
 	while (size - offsetof(struct ta_cursor, key) < key_size)
@@ -983,6 +1125,7 @@ cursor_create(struct ta_cursor *c, size_t key_size)
 	res = realloc(c, size);
 	if (res) {
 		res->size = size;
+		res->ephem_space = ephem_space;
 		if (!c) {
 			res->iter = NULL;
 			res->tuple_last = NULL;
@@ -991,6 +1134,68 @@ cursor_create(struct ta_cursor *c, size_t key_size)
 	return res;
 }
 
+/*
+ * Create new Tarantool iterator and set it to the first entry found by
+ * given key. If cursor already contains iterator, it will be freed.
+ *
+ * @param pCur Cursor which points to ephemeral space.
+ * @param pRes Flag which is == 0 if reached end of space, == 1 otherwise.
+ * @param type Type of iterator.
+ * @param key Start of buffer containing key.
+ * @param key_end End of key.
+ *
+ * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
+ */
+static int
+cursor_ephemeral_seek(BtCursor *pCur, int *pRes, enum iterator_type type,
+		      const char *key, const char *key_end)
+{
+	assert(pCur->curFlags & BTCF_TEphemCursor);
+	assert(key != NULL && key_end != NULL);
+	assert(type >= 0 && type < iterator_type_MAX);
+	mp_tuple_assert(key, key_end);
+
+	struct ta_cursor *c = pCur->pTaCursor;
+	assert(c->ephem_space);
+
+	size_t key_size = 0;
+	if (c && c->iter) {
+		box_iterator_free(c->iter);
+		c->iter = NULL;
+	}
+
+	if (type == ITER_EQ || type == ITER_REQ) {
+		key_size = (size_t)(key_end - key);
+	}
+	c = cursor_create(c, key_size);
+	if (!c) {
+		*pRes = 1;
+		return SQLITE_NOMEM;
+	}
+	pCur->pTaCursor = c;
+
+	uint32_t part_count = mp_decode_array(&key);
+	struct space *ephem_space = c->ephem_space;
+	struct index *index = *ephem_space->index;
+	if (key_validate(index->def, type, key, part_count)) {
+		diag_log();
+		return SQLITE_TARANTOOL_ERROR;
+	}
+
+	struct iterator *it = index_create_iterator(*ephem_space->index, type,
+						    key, part_count);
+	if (it == NULL) {
+		pCur->eState = CURSOR_INVALID;
+		return SQLITE_TARANTOOL_ERROR;
+	}
+	c->iter = it;
+	c->type = type;
+	pCur->eState = CURSOR_VALID;
+	pCur->curIntKey = 0;
+
+	return cursor_ephemeral_advance(pCur, pRes);
+}
+
 /* Cursor positioning. */
 static int
 cursor_seek(BtCursor *pCur, int *pRes, enum iterator_type type,
@@ -1038,6 +1243,41 @@ cursor_seek(BtCursor *pCur, int *pRes, enum iterator_type type,
 	return cursor_advance(pCur, pRes);
 }
 
+/*
+ * Move cursor to the next entry in ephemeral space.
+ * New tuple is refed and saved in cursord.
+ * Tuple from previous call is unrefed.
+ *
+ * @param pCur Cursor which will point to the new ephemeral space.
+ * @param pRes Flag which is == 0 if reached end of space, == 1 otherwise.
+ *
+ * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
+ */
+static int
+cursor_ephemeral_advance(BtCursor *pCur, int *pRes)
+{
+	assert(pCur->curFlags & BTCF_TEphemCursor);
+	struct ta_cursor *c = pCur->pTaCursor;
+	assert(c);
+	assert(c->iter);
+
+	struct tuple *tuple;
+	if (iterator_next(c->iter, &tuple) != 0)
+		return SQLITE_TARANTOOL_ERROR;
+	if (tuple != NULL && tuple_bless(tuple) == NULL)
+		return SQLITE_TARANTOOL_ERROR;
+	if (c->tuple_last) box_tuple_unref(c->tuple_last);
+	if (tuple) {
+		box_tuple_ref(tuple);
+		*pRes = 0;
+	} else {
+		pCur->eState = CURSOR_INVALID;
+		*pRes = 1;
+	}
+	c->tuple_last = tuple;
+	return SQLITE_OK;
+}
+
 static int
 cursor_advance(BtCursor *pCur, int *pRes)
 {
diff --git a/src/box/sql/btree.c b/src/box/sql/btree.c
index ca14ae6cd82460491b495e2121115a3797783a01..af735a3a2e1b6b13f90ce6afd30a15b48f2abb6a 100644
--- a/src/box/sql/btree.c
+++ b/src/box/sql/btree.c
@@ -2967,6 +2967,17 @@ sqlite3BtreeCursor(Btree * p,	/* The btree */
 	return rc;
 }
 
+int
+sqlite3BtreeCursorEphemeral(Btree* p, int iTable, int wrFlag,
+			    struct KeyInfo *pKeyInfo, BtCursor * pCur)
+{
+	btreeCursor(p, iTable, wrFlag, pKeyInfo, pCur);
+	pCur->curFlags |= BTCF_TEphemCursor;
+	pCur->pTaCursor = 0;
+
+	return SQLITE_OK;
+}
+
 /*
  * Return the size of a BtCursor object in bytes.
  *
@@ -3027,6 +3038,9 @@ sqlite3BtreeCloseCursor(BtCursor * pCur)
 		sqlite3_free(pCur->aOverflow);
 		if (pCur->curFlags & BTCF_TaCursor) {
 			tarantoolSqlite3CloseCursor(pCur);
+		} else if (pCur->curFlags & BTCF_TEphemCursor) {
+			tarantoolSqlite3EphemeralDrop(pCur);
+			tarantoolSqlite3CloseCursor(pCur);
 		}
 		/* sqlite3_free(pCur); */
 	}
@@ -3118,7 +3132,8 @@ sqlite3BtreePayloadSize(BtCursor * pCur)
 {
 	assert(cursorHoldsMutex(pCur));
 	assert(pCur->eState == CURSOR_VALID);
-	if (pCur->curFlags & BTCF_TaCursor) {
+	if (pCur->curFlags & BTCF_TaCursor ||
+	    pCur->curFlags & BTCF_TEphemCursor) {
 		u32 sz;
 		tarantoolSqlite3PayloadFetch(pCur, &sz);
 		return sz;
@@ -3246,7 +3261,8 @@ accessPayload(BtCursor * pCur,	/* Cursor pointing to entry to read from */
 	      int eOp		/* zero to read. non-zero to write. */
     )
 {
-	if (pCur->curFlags & BTCF_TaCursor) {
+	if (pCur->curFlags & BTCF_TaCursor ||
+	    pCur->curFlags & BTCF_TEphemCursor) {
 		const void *pPayload;
 		u32 sz;
 		pPayload = tarantoolSqlite3PayloadFetch(pCur, &sz);
@@ -3488,8 +3504,10 @@ sqlite3BtreePayload(BtCursor * pCur, u32 offset, u32 amt, void *pBuf)
 	assert(cursorHoldsMutex(pCur));
 	assert(pCur->eState == CURSOR_VALID);
 	assert((pCur->curFlags & BTCF_TaCursor) ||
+	       (pCur->curFlags & BTCF_TEphemCursor) ||
 	       (pCur->iPage >= 0 && pCur->apPage[pCur->iPage]));
 	assert((pCur->curFlags & BTCF_TaCursor) ||
+	       (pCur->curFlags & BTCF_TEphemCursor) ||
 	       pCur->aiIdx[pCur->iPage] < pCur->apPage[pCur->iPage]->nCell);
 	return accessPayload(pCur, offset, amt, (unsigned char *)pBuf, 0);
 }
@@ -3834,6 +3852,9 @@ sqlite3BtreeFirst(BtCursor * pCur, int *pRes)
 	if (pCur->curFlags & BTCF_TaCursor) {
 		return tarantoolSqlite3First(pCur, pRes);
 	}
+	if (pCur->curFlags & BTCF_TEphemCursor) {
+		return tarantoolSqlite3EphemeralFirst(pCur, pRes);
+	}
 	rc = moveToRoot(pCur);
 	if (rc == SQLITE_OK) {
 		if (pCur->eState == CURSOR_INVALID) {
@@ -4319,13 +4340,16 @@ sqlite3BtreeNext(BtCursor * pCur, int *pRes)
 	pCur->info.nSize = 0;
 	pCur->curFlags &= ~(BTCF_ValidNKey | BTCF_ValidOvfl);
 	*pRes = 0;
-	if (pCur->curFlags & BTCF_TaCursor) {
+	if (pCur->curFlags & BTCF_TaCursor ||
+	    pCur->curFlags & BTCF_TEphemCursor) {
 		if (pCur->eState != CURSOR_VALID) {
 			int rc = restoreCursorPosition(pCur);
 			if (rc != SQLITE_OK) {
 				return rc;
 			}
 		}
+		if (pCur->curFlags & BTCF_TEphemCursor)
+			return tarantoolSqlite3EphemeralNext(pCur, pRes);
 		return tarantoolSqlite3Next(pCur, pRes);
 	}
 	if (pCur->eState != CURSOR_VALID)
@@ -6797,6 +6821,9 @@ sqlite3BtreeInsert(BtCursor * pCur,	/* Insert data into the table of this cursor
 		return tarantoolSqlite3Insert(pCur, pX);
 	}
 
+	if (pCur->curFlags & BTCF_TEphemCursor) {
+		return tarantoolSqlite3EphemeralInsert(pCur, pX);
+	}
 	/* Save the positions of any other cursors open on this table.
 	 *
 	 * In some cases, the call to btreeMoveto() below is a no-op. For
diff --git a/src/box/sql/btree.h b/src/box/sql/btree.h
index 2b5b687cdb1d4e58c2a466dc122b407d805ae485..df0f24f06935472d4edeab93db5319908a84ed92 100644
--- a/src/box/sql/btree.h
+++ b/src/box/sql/btree.h
@@ -221,6 +221,7 @@ int sqlite3BtreeCursor(Btree *,	/* BTree containing table to open */
 		       struct KeyInfo *,	/* First argument to compare function */
 		       BtCursor * pCursor	/* Space to write cursor structure */
     );
+int sqlite3BtreeCursorEphemeral(Btree *, int, int, struct KeyInfo *, BtCursor *);
 int sqlite3BtreeCursorSize(void);
 void sqlite3BtreeCursorZero(BtCursor *);
 void sqlite3BtreeCursorHintFlags(BtCursor *, unsigned);
diff --git a/src/box/sql/btreeInt.h b/src/box/sql/btreeInt.h
index fc4a6ca66e6dd19205a0476b5f605398bcd59931..4e6331c3b949692bea41cef49687c0eb29c52b7f 100644
--- a/src/box/sql/btreeInt.h
+++ b/src/box/sql/btreeInt.h
@@ -539,6 +539,7 @@ struct BtCursor {
 #define BTCF_Incrblob     0x10	/* True if an incremental I/O handle */
 #define BTCF_Multiple     0x20	/* Maybe another cursor on the same btree */
 #define BTCF_TaCursor     0x80	/* Tarantool cursor, pTaCursor valid */
+#define BTCF_TEphemCursor 0x40	/* Tarantool cursor to ephemeral table  */
 
 /*
  * Potential values for BtCursor.eState.
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 4ee5ac1d60fb01e961c5b5e057039575c6b01f5f..172509b10e383312c2368cb944e7d402ca929442 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -421,7 +421,7 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
 			pParse->nMem += nPk;
 			iEphCur = pParse->nTab++;
 			addrEphOpen =
-			    sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEphCur,
+			    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEphCur,
 					      nPk);
 			sqlite3VdbeSetP4KeyInfo(pParse, pPk);
 		}
diff --git a/src/box/sql/opcodes.c b/src/box/sql/opcodes.c
index 84fae8e70fa16ddaae2273a4da74fccbd68995a4..f27568be34bce182a5038bbaa36a1319a0ce5fe8 100644
--- a/src/box/sql/opcodes.c
+++ b/src/box/sql/opcodes.c
@@ -117,58 +117,59 @@ const char *sqlite3OpcodeName(int i){
     /* 103 */ "OpenWrite"        OpHelp("root=P2"),
     /* 104 */ "OpenAutoindex"    OpHelp("nColumn=P2"),
     /* 105 */ "OpenEphemeral"    OpHelp("nColumn=P2"),
-    /* 106 */ "SorterOpen"       OpHelp(""),
-    /* 107 */ "SequenceTest"     OpHelp("if (cursor[P1].ctr++) pc = P2"),
-    /* 108 */ "OpenPseudo"       OpHelp("P3 columns in r[P2]"),
-    /* 109 */ "Close"            OpHelp(""),
-    /* 110 */ "ColumnsUsed"      OpHelp(""),
-    /* 111 */ "Sequence"         OpHelp("r[P2]=cursor[P1].ctr++"),
-    /* 112 */ "NextId"           OpHelp("r[P3]=get_max(space_index[P1]{Column[P2]})"),
-    /* 113 */ "FCopy"            OpHelp("reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)]"),
-    /* 114 */ "NewRowid"         OpHelp("r[P2]=rowid"),
+    /* 106 */ "OpenTEphemeral"   OpHelp("nColumn = P2"),
+    /* 107 */ "SorterOpen"       OpHelp(""),
+    /* 108 */ "SequenceTest"     OpHelp("if (cursor[P1].ctr++) pc = P2"),
+    /* 109 */ "OpenPseudo"       OpHelp("P3 columns in r[P2]"),
+    /* 110 */ "Close"            OpHelp(""),
+    /* 111 */ "ColumnsUsed"      OpHelp(""),
+    /* 112 */ "Sequence"         OpHelp("r[P2]=cursor[P1].ctr++"),
+    /* 113 */ "NextId"           OpHelp("r[P3]=get_max(space_index[P1]{Column[P2]})"),
+    /* 114 */ "FCopy"            OpHelp("reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)]"),
     /* 115 */ "Real"             OpHelp("r[P2]=P4"),
-    /* 116 */ "Insert"           OpHelp("intkey=r[P3] data=r[P2]"),
-    /* 117 */ "InsertInt"        OpHelp("intkey=P3 data=r[P2]"),
-    /* 118 */ "Delete"           OpHelp(""),
-    /* 119 */ "ResetCount"       OpHelp(""),
-    /* 120 */ "SorterCompare"    OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"),
-    /* 121 */ "SorterData"       OpHelp("r[P2]=data"),
-    /* 122 */ "RowData"          OpHelp("r[P2]=data"),
-    /* 123 */ "Rowid"            OpHelp("r[P2]=rowid"),
-    /* 124 */ "NullRow"          OpHelp(""),
-    /* 125 */ "SorterInsert"     OpHelp("key=r[P2]"),
-    /* 126 */ "IdxInsert"        OpHelp("key=r[P2]"),
-    /* 127 */ "IdxDelete"        OpHelp("key=r[P2@P3]"),
-    /* 128 */ "Seek"             OpHelp("Move P3 to P1.rowid"),
-    /* 129 */ "IdxRowid"         OpHelp("r[P2]=rowid"),
-    /* 130 */ "Destroy"          OpHelp(""),
-    /* 131 */ "Clear"            OpHelp(""),
-    /* 132 */ "ResetSorter"      OpHelp(""),
-    /* 133 */ "CreateIndex"      OpHelp("r[P2]=root"),
-    /* 134 */ "CreateTable"      OpHelp("r[P2]=root"),
-    /* 135 */ "ParseSchema2"     OpHelp("rows=r[P1@P2]"),
-    /* 136 */ "ParseSchema3"     OpHelp("name=r[P1] sql=r[P1+1]"),
-    /* 137 */ "RenameTable"      OpHelp("P1 = root, P4 = name"),
-    /* 138 */ "LoadAnalysis"     OpHelp(""),
-    /* 139 */ "DropTable"        OpHelp(""),
-    /* 140 */ "DropIndex"        OpHelp(""),
-    /* 141 */ "DropTrigger"      OpHelp(""),
-    /* 142 */ "IntegrityCk"      OpHelp(""),
-    /* 143 */ "RowSetAdd"        OpHelp("rowset(P1)=r[P2]"),
-    /* 144 */ "Param"            OpHelp(""),
-    /* 145 */ "FkCounter"        OpHelp("fkctr[P1]+=P2"),
-    /* 146 */ "MemMax"           OpHelp("r[P1]=max(r[P1],r[P2])"),
-    /* 147 */ "OffsetLimit"      OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"),
-    /* 148 */ "AggStep0"         OpHelp("accum=r[P3] step(r[P2@P5])"),
-    /* 149 */ "AggStep"          OpHelp("accum=r[P3] step(r[P2@P5])"),
-    /* 150 */ "AggFinal"         OpHelp("accum=r[P1] N=P2"),
-    /* 151 */ "Expire"           OpHelp(""),
-    /* 152 */ "Pagecount"        OpHelp(""),
-    /* 153 */ "MaxPgcnt"         OpHelp(""),
-    /* 154 */ "CursorHint"       OpHelp(""),
-    /* 155 */ "IncMaxid"         OpHelp(""),
-    /* 156 */ "Noop"             OpHelp(""),
-    /* 157 */ "Explain"          OpHelp(""),
+    /* 116 */ "NewRowid"         OpHelp("r[P2]=rowid"),
+    /* 117 */ "Insert"           OpHelp("intkey=r[P3] data=r[P2]"),
+    /* 118 */ "InsertInt"        OpHelp("intkey=P3 data=r[P2]"),
+    /* 119 */ "Delete"           OpHelp(""),
+    /* 120 */ "ResetCount"       OpHelp(""),
+    /* 121 */ "SorterCompare"    OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"),
+    /* 122 */ "SorterData"       OpHelp("r[P2]=data"),
+    /* 123 */ "RowData"          OpHelp("r[P2]=data"),
+    /* 124 */ "Rowid"            OpHelp("r[P2]=rowid"),
+    /* 125 */ "NullRow"          OpHelp(""),
+    /* 126 */ "SorterInsert"     OpHelp("key=r[P2]"),
+    /* 127 */ "IdxInsert"        OpHelp("key=r[P2]"),
+    /* 128 */ "IdxDelete"        OpHelp("key=r[P2@P3]"),
+    /* 129 */ "Seek"             OpHelp("Move P3 to P1.rowid"),
+    /* 130 */ "IdxRowid"         OpHelp("r[P2]=rowid"),
+    /* 131 */ "Destroy"          OpHelp(""),
+    /* 132 */ "Clear"            OpHelp(""),
+    /* 133 */ "ResetSorter"      OpHelp(""),
+    /* 134 */ "CreateIndex"      OpHelp("r[P2]=root"),
+    /* 135 */ "CreateTable"      OpHelp("r[P2]=root"),
+    /* 136 */ "ParseSchema2"     OpHelp("rows=r[P1@P2]"),
+    /* 137 */ "ParseSchema3"     OpHelp("name=r[P1] sql=r[P1+1]"),
+    /* 138 */ "RenameTable"      OpHelp("P1 = root, P4 = name"),
+    /* 139 */ "LoadAnalysis"     OpHelp(""),
+    /* 140 */ "DropTable"        OpHelp(""),
+    /* 141 */ "DropIndex"        OpHelp(""),
+    /* 142 */ "DropTrigger"      OpHelp(""),
+    /* 143 */ "IntegrityCk"      OpHelp(""),
+    /* 144 */ "RowSetAdd"        OpHelp("rowset(P1)=r[P2]"),
+    /* 145 */ "Param"            OpHelp(""),
+    /* 146 */ "FkCounter"        OpHelp("fkctr[P1]+=P2"),
+    /* 147 */ "MemMax"           OpHelp("r[P1]=max(r[P1],r[P2])"),
+    /* 148 */ "OffsetLimit"      OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"),
+    /* 149 */ "AggStep0"         OpHelp("accum=r[P3] step(r[P2@P5])"),
+    /* 150 */ "AggStep"          OpHelp("accum=r[P3] step(r[P2@P5])"),
+    /* 151 */ "AggFinal"         OpHelp("accum=r[P1] N=P2"),
+    /* 152 */ "Expire"           OpHelp(""),
+    /* 153 */ "Pagecount"        OpHelp(""),
+    /* 154 */ "MaxPgcnt"         OpHelp(""),
+    /* 155 */ "CursorHint"       OpHelp(""),
+    /* 156 */ "IncMaxid"         OpHelp(""),
+    /* 157 */ "Noop"             OpHelp(""),
+    /* 158 */ "Explain"          OpHelp(""),
   };
   return azName[i];
 }
diff --git a/src/box/sql/opcodes.h b/src/box/sql/opcodes.h
index dfa19b75413dcf730d4a0388205def95b700df49..28cf70b7a26e8057637d6224382f6a2391ece3f9 100644
--- a/src/box/sql/opcodes.h
+++ b/src/box/sql/opcodes.h
@@ -106,58 +106,59 @@
 #define OP_OpenWrite     103 /* synopsis: root=P2                          */
 #define OP_OpenAutoindex 104 /* synopsis: nColumn=P2                       */
 #define OP_OpenEphemeral 105 /* synopsis: nColumn=P2                       */
-#define OP_SorterOpen    106
-#define OP_SequenceTest  107 /* synopsis: if (cursor[P1].ctr++) pc = P2    */
-#define OP_OpenPseudo    108 /* synopsis: P3 columns in r[P2]              */
-#define OP_Close         109
-#define OP_ColumnsUsed   110
-#define OP_Sequence      111 /* synopsis: r[P2]=cursor[P1].ctr++           */
-#define OP_NextId        112 /* synopsis: r[P3]=get_max(space_index[P1]{Column[P2]}) */
-#define OP_FCopy         113 /* synopsis: reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)] */
-#define OP_NewRowid      114 /* synopsis: r[P2]=rowid                      */
+#define OP_OpenTEphemeral 106 /* synopsis: nColumn = P2                     */
+#define OP_SorterOpen    107
+#define OP_SequenceTest  108 /* synopsis: if (cursor[P1].ctr++) pc = P2    */
+#define OP_OpenPseudo    109 /* synopsis: P3 columns in r[P2]              */
+#define OP_Close         110
+#define OP_ColumnsUsed   111
+#define OP_Sequence      112 /* synopsis: r[P2]=cursor[P1].ctr++           */
+#define OP_NextId        113 /* synopsis: r[P3]=get_max(space_index[P1]{Column[P2]}) */
+#define OP_FCopy         114 /* synopsis: reg[P2@cur_frame]= reg[P1@root_frame(OPFLAG_SAME_FRAME)] */
 #define OP_Real          115 /* same as TK_FLOAT, synopsis: r[P2]=P4       */
-#define OP_Insert        116 /* synopsis: intkey=r[P3] data=r[P2]          */
-#define OP_InsertInt     117 /* synopsis: intkey=P3 data=r[P2]             */
-#define OP_Delete        118
-#define OP_ResetCount    119
-#define OP_SorterCompare 120 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */
-#define OP_SorterData    121 /* synopsis: r[P2]=data                       */
-#define OP_RowData       122 /* synopsis: r[P2]=data                       */
-#define OP_Rowid         123 /* synopsis: r[P2]=rowid                      */
-#define OP_NullRow       124
-#define OP_SorterInsert  125 /* synopsis: key=r[P2]                        */
-#define OP_IdxInsert     126 /* synopsis: key=r[P2]                        */
-#define OP_IdxDelete     127 /* synopsis: key=r[P2@P3]                     */
-#define OP_Seek          128 /* synopsis: Move P3 to P1.rowid              */
-#define OP_IdxRowid      129 /* synopsis: r[P2]=rowid                      */
-#define OP_Destroy       130
-#define OP_Clear         131
-#define OP_ResetSorter   132
-#define OP_CreateIndex   133 /* synopsis: r[P2]=root                       */
-#define OP_CreateTable   134 /* synopsis: r[P2]=root                       */
-#define OP_ParseSchema2  135 /* synopsis: rows=r[P1@P2]                    */
-#define OP_ParseSchema3  136 /* synopsis: name=r[P1] sql=r[P1+1]           */
-#define OP_RenameTable   137 /* synopsis: P1 = root, P4 = name             */
-#define OP_LoadAnalysis  138
-#define OP_DropTable     139
-#define OP_DropIndex     140
-#define OP_DropTrigger   141
-#define OP_IntegrityCk   142
-#define OP_RowSetAdd     143 /* synopsis: rowset(P1)=r[P2]                 */
-#define OP_Param         144
-#define OP_FkCounter     145 /* synopsis: fkctr[P1]+=P2                    */
-#define OP_MemMax        146 /* synopsis: r[P1]=max(r[P1],r[P2])           */
-#define OP_OffsetLimit   147 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */
-#define OP_AggStep0      148 /* synopsis: accum=r[P3] step(r[P2@P5])       */
-#define OP_AggStep       149 /* synopsis: accum=r[P3] step(r[P2@P5])       */
-#define OP_AggFinal      150 /* synopsis: accum=r[P1] N=P2                 */
-#define OP_Expire        151
-#define OP_Pagecount     152
-#define OP_MaxPgcnt      153
-#define OP_CursorHint    154
-#define OP_IncMaxid      155
-#define OP_Noop          156
-#define OP_Explain       157
+#define OP_NewRowid      116 /* synopsis: r[P2]=rowid                      */
+#define OP_Insert        117 /* synopsis: intkey=r[P3] data=r[P2]          */
+#define OP_InsertInt     118 /* synopsis: intkey=P3 data=r[P2]             */
+#define OP_Delete        119
+#define OP_ResetCount    120
+#define OP_SorterCompare 121 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */
+#define OP_SorterData    122 /* synopsis: r[P2]=data                       */
+#define OP_RowData       123 /* synopsis: r[P2]=data                       */
+#define OP_Rowid         124 /* synopsis: r[P2]=rowid                      */
+#define OP_NullRow       125
+#define OP_SorterInsert  126 /* synopsis: key=r[P2]                        */
+#define OP_IdxInsert     127 /* synopsis: key=r[P2]                        */
+#define OP_IdxDelete     128 /* synopsis: key=r[P2@P3]                     */
+#define OP_Seek          129 /* synopsis: Move P3 to P1.rowid              */
+#define OP_IdxRowid      130 /* synopsis: r[P2]=rowid                      */
+#define OP_Destroy       131
+#define OP_Clear         132
+#define OP_ResetSorter   133
+#define OP_CreateIndex   134 /* synopsis: r[P2]=root                       */
+#define OP_CreateTable   135 /* synopsis: r[P2]=root                       */
+#define OP_ParseSchema2  136 /* synopsis: rows=r[P1@P2]                    */
+#define OP_ParseSchema3  137 /* synopsis: name=r[P1] sql=r[P1+1]           */
+#define OP_RenameTable   138 /* synopsis: P1 = root, P4 = name             */
+#define OP_LoadAnalysis  139
+#define OP_DropTable     140
+#define OP_DropIndex     141
+#define OP_DropTrigger   142
+#define OP_IntegrityCk   143
+#define OP_RowSetAdd     144 /* synopsis: rowset(P1)=r[P2]                 */
+#define OP_Param         145
+#define OP_FkCounter     146 /* synopsis: fkctr[P1]+=P2                    */
+#define OP_MemMax        147 /* synopsis: r[P1]=max(r[P1],r[P2])           */
+#define OP_OffsetLimit   148 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */
+#define OP_AggStep0      149 /* synopsis: accum=r[P3] step(r[P2@P5])       */
+#define OP_AggStep       150 /* synopsis: accum=r[P3] step(r[P2@P5])       */
+#define OP_AggFinal      151 /* synopsis: accum=r[P1] N=P2                 */
+#define OP_Expire        152
+#define OP_Pagecount     153
+#define OP_MaxPgcnt      154
+#define OP_CursorHint    155
+#define OP_IncMaxid      156
+#define OP_Noop          157
+#define OP_Explain       158
 
 /* Properties such as "out2" or "jump" that are specified in
 ** comments following the "case" for each opcode in the vdbe.c
@@ -183,13 +184,13 @@
 /*  80 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,\
 /*  88 */ 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,\
 /*  96 */ 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,\
-/* 104 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\
-/* 112 */ 0x20, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,\
-/* 120 */ 0x00, 0x00, 0x00, 0x10, 0x00, 0x04, 0x04, 0x00,\
-/* 128 */ 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00,\
-/* 136 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,\
-/* 144 */ 0x10, 0x00, 0x04, 0x1a, 0x00, 0x00, 0x00, 0x00,\
-/* 152 */ 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,}
+/* 104 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+/* 112 */ 0x10, 0x20, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00,\
+/* 120 */ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x04, 0x04,\
+/* 128 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10,\
+/* 136 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+/* 144 */ 0x06, 0x10, 0x00, 0x04, 0x1a, 0x00, 0x00, 0x00,\
+/* 152 */ 0x00, 0x10, 0x10, 0x00, 0x00, 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/tarantoolInt.h b/src/box/sql/tarantoolInt.h
index acbadc5a1c8812bda91937976b947604e50946ac..933bf25b81b31f51fdc90e4896323d611aa518be 100644
--- a/src/box/sql/tarantoolInt.h
+++ b/src/box/sql/tarantoolInt.h
@@ -85,6 +85,13 @@ int tarantoolSqlite3RenameTrigger(const char *zTriggerName,
 int tarantoolSqlite3RenameParentTable(int iTab, const char *zOldParentName,
 				      const char *zNewParentName);
 
+/* Interface for ephemeral tables. */
+int tarantoolSqlite3EphemeralCreate(BtCursor * pCur, uint32_t filed_count);
+int tarantoolSqlite3EphemeralInsert(BtCursor * pCur, const BtreePayload * pX);
+int tarantoolSqlite3EphemeralFirst(BtCursor * pCur, int * pRes);
+int tarantoolSqlite3EphemeralNext(BtCursor * pCur, int * pRes);
+int tarantoolSqlite3EphemeralDrop(BtCursor * pCur);
+
 /* Compare against the index key under a cursor -
  * the key may span non-adjacent fields in a random order,
  * ex: [4]-[1]-[2]
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index bd3d27e24f64317d885441616a4a3125c69cdb91..497425a166b6d9f60ff506fa303c90ed5b2d2b4a 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -3451,6 +3451,55 @@ case OP_OpenEphemeral: {
 	break;
 }
 
+/* Opcode: OpenTEphemeral P1 P2 * * *
+ * Synopsis: nColumn = P2
+ *
+ * This opcode creates Tarantool's ephemeral table and sets cursor P1 to it.
+ */
+case OP_OpenTEphemeral: {
+	VdbeCursor *pCx;
+	KeyInfo *pKeyInfo;
+	static const int vfsFlags =
+		SQLITE_OPEN_READWRITE |
+		SQLITE_OPEN_CREATE |
+		SQLITE_OPEN_EXCLUSIVE |
+		SQLITE_OPEN_DELETEONCLOSE |
+		SQLITE_OPEN_TRANSIENT_DB |
+		SQLITE_OPEN_MEMORY;
+	assert(pOp->p1 >= 0);
+	assert(pOp->p2 > 0);
+
+	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_BTREE);
+	if (pCx == 0) goto no_mem;
+	pCx->isEphemeral = 1;
+	pCx->nullRow = 1;
+	rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBtx,
+			      BTREE_OMIT_JOURNAL | BTREE_SINGLE, vfsFlags);
+	if (rc) goto abort_due_to_error;
+	rc = sqlite3BtreeBeginTrans(pCx->pBtx, 0, 1);
+	if (rc) goto abort_due_to_error;
+	if ((pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo) !=0) {
+		int pgno;
+		assert(pOp->p4type == P4_KEYINFO);
+		rc = sqlite3BtreeCreateTable(pCx->pBtx, &pgno, BTREE_BLOBKEY | pOp->p5);
+		if (rc == SQLITE_OK) {
+			assert(pgno == 2);
+			assert(pKeyInfo->db==db);
+			sqlite3BtreeCursorEphemeral(pCx->pBtx, pgno, BTREE_WRCSR, pKeyInfo,
+						    pCx->uc.pCursor);
+		}
+		pCx->isTable = 0;
+	} else {
+		sqlite3BtreeCursorEphemeral(pCx->pBtx, 1, BTREE_WRCSR, 0,
+					    pCx->uc.pCursor);
+		pCx->isTable = 1;
+	}
+
+	rc = tarantoolSqlite3EphemeralCreate(pCx->uc.pCursor, pOp->p2);
+	if (rc) goto abort_due_to_error;
+	break;
+}
+
 /* Opcode: SorterOpen P1 P2 P3 P4 *
  *
  * This opcode works like OP_OpenEphemeral except that it opens