Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH 0/2] sql: refactor DELETE STMT translation
@ 2018-05-16 15:24 Kirill Yukhin
  2018-05-16 15:24 ` [tarantool-patches] [PATCH 1/2] sql: allow key_def to be NULL for ephemeral create Kirill Yukhin
                   ` (2 more replies)
  0 siblings, 3 replies; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-16 15:24 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches, Kirill Yukhin

Issue: https://github.com/tarantool/tarantool/issues/3235
Branch: https://github.com/tarantool/tarantool/tree/kyukhin/gh-3235-refactor-delete-3

This two-patch set refactors SQL DELETE statements translation. It allows
not to create dummy key_defs and passing NULL instead to OP_OpenTEphemeral.
It also make box/sql/delete.c obey Tarantool coding style.

Part of #3235

Kirill Yukhin (2):
  sql: allow key_def to be NULL for ephemeral create
  sql: refactor delete routines

 src/box/sql.c           |    7 +-
 src/box/sql/build.c     |    6 +-
 src/box/sql/delete.c    | 1046 ++++++++++++++++++-----------------------------
 src/box/sql/fkey.c      |    3 +-
 src/box/sql/insert.c    |   33 +-
 src/box/sql/parse.y     |    2 +-
 src/box/sql/select.c    |   19 +-
 src/box/sql/sqliteInt.h |  202 ++++++++-
 src/box/sql/trigger.c   |   12 +-
 src/box/sql/update.c    |   16 +-
 src/box/sql/vdbe.c      |   11 +-
 src/box/sql/vdbeaux.c   |    7 +-
 12 files changed, 630 insertions(+), 734 deletions(-)

-- 
2.16.2

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

* [tarantool-patches] [PATCH 1/2] sql: allow key_def to be NULL for ephemeral create
  2018-05-16 15:24 [tarantool-patches] [PATCH 0/2] sql: refactor DELETE STMT translation Kirill Yukhin
@ 2018-05-16 15:24 ` Kirill Yukhin
  2018-05-17 15:49   ` [tarantool-patches] " Kirill Yukhin
  2018-05-16 15:24 ` [tarantool-patches] [PATCH 2/2] sql: refactor delete routines Kirill Yukhin
  2018-05-18 11:01 ` [tarantool-patches] Re: [PATCH 0/2] sql: refactor DELETE STMT translation Kirill Yukhin
  2 siblings, 1 reply; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-16 15:24 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches, Kirill Yukhin

There're many cases in SQL FE, where exact key def passed to
doesn't ephemeral table matter. It is used to store data only.
Only field count makes sense in such a case. Hoewever it is
passed separately (in P2). So, allow P4 field to be NULL for
OP_OpenTEphemeral. Update delte from routine from sql/delete.c
accordingly.

Part of #3235
---
 src/box/sql.c        |  7 ++++---
 src/box/sql/insert.c |  7 +------
 src/box/sql/select.c | 19 +++----------------
 src/box/sql/update.c |  7 +------
 src/box/sql/vdbe.c   |  9 ++++-----
 5 files changed, 13 insertions(+), 36 deletions(-)

diff --git a/src/box/sql.c b/src/box/sql.c
index 6104a6d..e502f22 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -370,8 +370,9 @@ int tarantoolSqlite3Count(BtCursor *pCur, i64 *pnEntry)
  *
  * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
  */
-int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
-				    struct key_def *def)
+int
+tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
+				struct key_def *def)
 {
 	assert(pCur);
 	assert(pCur->curFlags & BTCF_TEphemCursor);
@@ -381,7 +382,7 @@ int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
 		return SQL_TARANTOOL_ERROR;
 	for (uint32_t part = 0; part < field_count; ++part) {
 		struct coll *coll;
-		if (part < def->part_count)
+		if (def != NULL && part < def->part_count)
 			coll = def->parts[part].coll;
 		else
 			coll = NULL;
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 22130ef..1ad7469 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -566,13 +566,8 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 			regRec = sqlite3GetTempReg(pParse);
 			regCopy = sqlite3GetTempRange(pParse, nColumn);
 			regTempId = sqlite3GetTempReg(pParse);
-			struct key_def *def = key_def_new(nColumn + 1);
-			if (def == NULL) {
-				sqlite3OomFault(db);
-				goto insert_cleanup;
-			}
 			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, srcTab, nColumn+1,
-					  0, (char*)def, P4_KEYDEF);
+					  0, (char*)NULL, P4_KEYDEF);
 			/* Create counter for rowid */
 			sqlite3VdbeAddOp4Dup8(v, OP_Int64,
 					      0 /* unused */,
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index bd895bc..da4d816 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -2215,13 +2215,8 @@ generateWithRecursiveQuery(Parse * pParse,	/* Parsing context */
 		VdbeComment((v, "Orderby table"));
 		destQueue.pOrderBy = pOrderBy;
 	} else {
-		struct key_def *def = key_def_new(nCol + 1);
-		if (def == NULL) {
-			sqlite3OomFault(pParse->db);
-			goto end_of_recursive_query;
-		}
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iQueue, nCol + 1, 0,
-				  (char*)def, P4_KEYDEF);
+				  (char*)NULL, P4_KEYDEF);
 		VdbeComment((v, "Queue table"));
 	}
 	if (iDistinct) {
@@ -2422,13 +2417,8 @@ multiSelect(Parse * pParse,	/* Parsing context */
 	if (dest.eDest == SRT_EphemTab) {
 		assert(p->pEList);
 		int nCols = p->pEList->nExpr;
-		struct key_def *def = key_def_new(nCols + 1);
-		if (def == NULL) {
-			sqlite3OomFault(db);
-			goto multi_select_end;
-		}
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, dest.iSDParm, nCols + 1,
-				  0, (char*)def, P4_KEYDEF);
+				  0, (char*)NULL, P4_KEYDEF);
 		VdbeComment((v, "Destination temp"));
 		dest.eDest = SRT_Table;
 	}
@@ -5608,11 +5598,8 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	/* If the output is destined for a temporary table, open that table.
 	 */
 	if (pDest->eDest == SRT_EphemTab) {
-		struct key_def *def = key_def_new(pEList->nExpr + 1);
-		if (def == NULL)
-			sqlite3OomFault(db);
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, pDest->iSDParm,
-				  pEList->nExpr + 1, 0, (char*)def, P4_KEYDEF);
+				  pEList->nExpr + 1, 0, (char*)NULL, P4_KEYDEF);
 
 		VdbeComment((v, "Output table"));
 	}
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index f3db92c..de1349b 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -358,13 +358,8 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
 
 	if (isView) {
-		struct key_def *def = key_def_new(nKey);
-		if (def == NULL) {
-			sqlite3OomFault(db);
-			goto update_cleanup;
-		}
 		addrOpen = sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEph,
-					     nKey, 0, (char*)def, P4_KEYDEF);
+					     nKey, 0, (char*)NULL, P4_KEYDEF);
 	} else {
 		addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph, nPk);
 		sql_vdbe_set_p4_key_def(pParse, pPk);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index b4b59da..b8d4c1a 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -3225,9 +3225,9 @@ open_cursor_set_hints:
 /**
  * Opcode: OpenTEphemeral P1 P2 * P4 *
  * Synopsis:
- * @param P1 index of new cursor to be created
- * @param P2 number of columns in a new table
- * @param P4 key def for new table
+ * @param P1 index of new cursor to be created.
+ * @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.
  */
@@ -3236,8 +3236,7 @@ case OP_OpenTEphemeral: {
 	BtCursor *pBtCur;
 	assert(pOp->p1 >= 0);
 	assert(pOp->p2 > 0);
-	assert(pOp->p4.key_def != NULL);
-	assert(pOp->p4type == P4_KEYDEF);
+	assert(pOp->p4type != P4_KEYDEF || pOp->p4.key_def != NULL);
 
 	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
 	if (pCx == 0) goto no_mem;
-- 
2.16.2

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

* [tarantool-patches] [PATCH 2/2] sql: refactor delete routines
  2018-05-16 15:24 [tarantool-patches] [PATCH 0/2] sql: refactor DELETE STMT translation Kirill Yukhin
  2018-05-16 15:24 ` [tarantool-patches] [PATCH 1/2] sql: allow key_def to be NULL for ephemeral create Kirill Yukhin
@ 2018-05-16 15:24 ` Kirill Yukhin
  2018-05-16 16:29   ` [tarantool-patches] " Kirill Yukhin
  2018-05-18 11:01 ` [tarantool-patches] Re: [PATCH 0/2] sql: refactor DELETE STMT translation Kirill Yukhin
  2 siblings, 1 reply; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-16 15:24 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches, Kirill Yukhin

Refactor DELETE FROM statements translation, update
all live routines according to Tarantool's coding style.
Use key_def instead of Table* where possible.
Remove useless index delete routine as well.

Part of #3235
---
 src/box/sql/build.c     |    6 +-
 src/box/sql/delete.c    | 1046 ++++++++++++++++++-----------------------------
 src/box/sql/fkey.c      |    3 +-
 src/box/sql/insert.c    |   26 +-
 src/box/sql/parse.y     |    2 +-
 src/box/sql/sqliteInt.h |  202 ++++++++-
 src/box/sql/trigger.c   |   12 +-
 src/box/sql/update.c    |    9 +-
 src/box/sql/vdbe.c      |    6 +-
 src/box/sql/vdbeaux.c   |    7 +-
 10 files changed, 619 insertions(+), 700 deletions(-)

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index c96d351..e3aadab 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -2664,10 +2664,10 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
 	VdbeCoverage(v);
 	regRecord = sqlite3GetTempReg(pParse);
 
-	sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord,
-				&iPartIdxLabel, 0, 0);
+	sql_generate_index_key(pParse, pIndex, iTab, regRecord,
+			       &iPartIdxLabel, 0, 0);
 	sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord);
-	sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
+	sql_resolve_part_idx_label(pParse, iPartIdxLabel);
 	sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1 + 1);
 	VdbeCoverage(v);
 	sqlite3VdbeJumpHere(v, addr1);
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 3455f52..ea4f36a 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -29,103 +29,47 @@
  * SUCH DAMAGE.
  */
 
-/*
- * This file contains C code routines that are called by the parser
- * in order to generate code for DELETE FROM statements.
- */
-#include "sqliteInt.h"
 #include "box/session.h"
+#include "box/schema.h"
+#include "sqliteInt.h"
+#include "tarantoolInt.h"
 
-/*
- * While a SrcList can in general represent multiple tables and subqueries
- * (as in the FROM clause of a SELECT statement) in this case it contains
- * the name of a single table, as one might find in an INSERT, DELETE,
- * or UPDATE statement.  Look up that table in the symbol table and
- * return a pointer.  Set an error message and return NULL if the table
- * name is not found or if any other error occurs.
- *
- * The following fields are initialized appropriate in pSrc:
- *
- *    pSrc->a[0].pTab       Pointer to the Table object
- *    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if there is one
- *
- */
-Table *
-sqlite3SrcListLookup(Parse * pParse, SrcList * pSrc)
+struct Table *
+sql_list_lookup_table(struct Parse *parse, SrcList *src_list)
 {
-	struct SrcList_item *pItem = pSrc->a;
-	Table *pTab;
-	assert(pItem && pSrc->nSrc == 1);
-	pTab = sqlite3LocateTable(pParse, 0, pItem->zName);
-	sqlite3DeleteTable(pParse->db, pItem->pTab);
-	pItem->pTab = pTab;
-	if (pTab != NULL)
-		pTab->nTabRef++;
-	if (sqlite3IndexedByLookup(pParse, pItem))
-		pTab = NULL;
-	return pTab;
+	struct SrcList_item *item = src_list->a;
+	struct Table *table;
+	assert(item != NULL && src_list->nSrc == 1);
+	table = sqlite3LocateTable(parse, 0, item->zName);
+	sqlite3DeleteTable(parse->db, item->pTab);
+	item->pTab = table;
+	if (table != NULL)
+		table->nTabRef++;
+	if (sqlite3IndexedByLookup(parse, item))
+		table = NULL;
+	return table;
 }
 
-/*
- * Check to make sure the given table is writable.  If it is not
- * writable, generate an error message and return 1.  If it is
- * writable return 0;
- */
-int
-sqlite3IsReadOnly(Parse * pParse, Table * pTab, int viewOk)
-{
-	/*
-	 * A table is not writable if it is a system table
-	 * (i.e. _sql_stat1), this call is not part of a
-	 * nested parse. In either case leave an error message in
-	 * pParse and return non-zero.
-	 */
-	if ((pTab->tabFlags & TF_Readonly) != 0 && pParse->nested == 0) {
-		sqlite3ErrorMsg(pParse, "table %s may not be modified",
-				pTab->zName);
-		return 1;
-	}
-#ifndef SQLITE_OMIT_VIEW
-	if (!viewOk && space_is_view(pTab)) {
-		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
-				pTab->zName);
-		return 1;
-	}
-#endif
-	return 0;
-}
-
-#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-/*
- * Evaluate a view and store its result in an ephemeral table.  The
- * pWhere argument is an optional WHERE clause that restricts the
- * set of rows in the view that are to be added to the ephemeral table.
- */
 void
-sqlite3MaterializeView(Parse * pParse,	/* Parsing context */
-		       Table * pView,	/* View definition */
-		       Expr * pWhere,	/* Optional WHERE clause to be added */
-		       int iCur)	/* Cursor number for ephemeral table */
+sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
+		     int cursor)
 {
-	SelectDest dest;
-	Select *pSel;
-	SrcList *pFrom;
-	sqlite3 *db = pParse->db;
-	pWhere = sqlite3ExprDup(db, pWhere, 0);
-	pFrom = sqlite3SrcListAppend(db, 0, 0);
-	if (pFrom) {
-		assert(pFrom->nSrc == 1);
-		pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName);
-		assert(pFrom->a[0].pOn == 0);
-		assert(pFrom->a[0].pUsing == 0);
+	struct sqlite3 *db = parse->db;
+	where = sqlite3ExprDup(db, where, 0);
+	struct SrcList *from = sqlite3SrcListAppend(db, NULL, NULL);
+	if (from != NULL) {
+		assert(from->nSrc == 1);
+		from->a[0].zName = sqlite3DbStrDup(db, name);
+		assert(from->a[0].pOn == NULL);
+		assert(from->a[0].pUsing == NULL);
 	}
-	pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0,
-				0, 0, 0);
-	sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur);
-	sqlite3Select(pParse, pSel, &dest);
-	sqlite3SelectDelete(db, pSel);
+	struct Select *select = sqlite3SelectNew(parse, NULL, from, where, NULL,
+						 NULL, NULL, 0, NULL, NULL);
+	struct SelectDest dest;
+	sqlite3SelectDestInit(&dest, SRT_EphemTab, cursor);
+	sqlite3Select(parse, select, &dest);
+	sqlite3SelectDelete(db, select);
 }
-#endif				/* !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */
 
 #if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
 /*
@@ -217,405 +161,338 @@ sqlite3LimitWhere(Parse * pParse,	/* The parser context */
 }
 #endif				/* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) */
 
-/*
- * Generate code for a DELETE FROM statement.
- *
- *     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
- *                 \________/       \________________/
- *                  pTabList              pWhere
- */
 void
-sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
-		  SrcList * pTabList,	/* The table from which we should delete things */
-		  Expr * pWhere)	/* The WHERE clause.  May be null */
+sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
+		      struct Expr *where)
 {
-	Vdbe *v;		/* The virtual database engine */
-	Table *pTab;		/* The table from which records will be deleted */
-	int i;			/* Loop counter */
-	WhereInfo *pWInfo;	/* Information about the WHERE clause */
-	Index *pIdx;		/* For looping over indices of the table */
-	int iTabCur;		/* Cursor number for the table */
-	int iDataCur = 0;	/* VDBE cursor for the canonical data source */
-	int iIdxCur = 0;	/* Cursor number of the first index */
-	int nIdx;		/* Number of indices */
-	sqlite3 *db;		/* Main database structure */
-	NameContext sNC;	/* Name context to resolve expressions in */
-	int memCnt = -1;	/* Memory cell used for change counting */
-	int eOnePass;		/* ONEPASS_OFF or _SINGLE or _MULTI */
-	int aiCurOnePass[2];	/* The write cursors opened by WHERE_ONEPASS */
-	u8 *aToOpen = 0;	/* Open cursor iTabCur+j if aToOpen[j] is true */
-	Index *pPk = 0;		/* The PRIMARY KEY index on the table */
-	int iPk = 0;		/* First of nPk registers holding PRIMARY KEY value */
-	i16 nPk;		/* Number of columns in the PRIMARY KEY */
-	int iKey;		/* Memory cell holding key of row to be deleted */
-	i16 nKey;		/* Number of memory cells in the row key */
-	int iEphCur = 0;	/* Ephemeral table holding all primary key values */
-	int addrBypass = 0;	/* Address of jump over the delete logic */
-	int addrLoop = 0;	/* Top of the delete loop */
-	int addrEphOpen = 0;	/* Instruction to open the Ephemeral table */
-	int bComplex;		/* True if there are triggers or FKs or
-				 * subqueries in the WHERE clause
-				 */
-	struct session *user_session = current_session();
+	struct sqlite3 *db = parse->db;
+	if (parse->nErr || db->mallocFailed)
+		goto delete_from_cleanup;
 
-#ifndef SQLITE_OMIT_TRIGGER
-	int isView;		/* True if attempting to delete from a view */
-	Trigger *pTrigger;	/* List of table triggers, if required */
-#endif
+	assert(tab_list->nSrc == 1);
 
-	db = pParse->db;
-	if (pParse->nErr || db->mallocFailed) {
+	/* Locate the table which we want to delete.  This table
+	 * has to be put in an SrcList structure because some of
+	 * the subroutines we will be calling are designed to work
+	 * with multiple tables and expect an SrcList* parameter
+	 * instead of just a Table* parameter.
+	 */
+	struct Table *table = sql_list_lookup_table(parse, tab_list);
+	if (table == NULL)
 		goto delete_from_cleanup;
-	}
-	assert(pTabList->nSrc == 1);
 
-	/* Locate the table which we want to delete.  This table has to be
-	 * put in an SrcList structure because some of the subroutines we
-	 * will be calling are designed to work with multiple tables and expect
-	 * an SrcList* parameter instead of just a Table* parameter.
+	struct space *space = space_by_id(SQLITE_PAGENO_TO_SPACEID(table->tnum));
+	assert(space != NULL);
+	/* Figure out if we have any triggers and if the table
+	 * being deleted from is a view.
 	 */
-	pTab = sqlite3SrcListLookup(pParse, pTabList);
-	if (pTab == 0)
-		goto delete_from_cleanup;
+	struct Trigger *trigger_list = sqlite3TriggersExist(table, TK_DELETE,
+							    NULL, NULL);
 
-	/* Figure out if we have any triggers and if the table being
-	 * deleted from is a view
+	bool is_view = space->def->opts.is_view;
+	/* True if there are triggers or FKs or subqueries in the
+	 * WHERE clause.
 	 */
-#ifndef SQLITE_OMIT_TRIGGER
-	pTrigger = sqlite3TriggersExist(pTab, TK_DELETE, 0, 0);
-	isView = pTab->pSelect != 0;
-	bComplex = pTrigger || sqlite3FkRequired(pTab, 0);
-#else
-#define pTrigger 0
-#define isView 0
-#endif
-#ifdef SQLITE_OMIT_VIEW
-#undef isView
-#define isView 0
-#endif
-
-	/* If pTab is really a view, make sure it has been initialized.
+	bool is_complex = (trigger_list != NULL) ||
+			   sqlite3FkRequired(table, 0);
+
+	/* If table is really a view, make sure it has been
+	 * initialized.
 	 */
-	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
+	if (sqlite3ViewGetColumnNames(parse, table))
 		goto delete_from_cleanup;
-	}
 
-	if (sqlite3IsReadOnly(pParse, pTab, (pTrigger ? 1 : 0))) {
+	if (is_view && trigger_list == NULL) {
+		sqlite3ErrorMsg(parse, "cannot modify %s because it is a view",
+				space->def->name);
 		goto delete_from_cleanup;
 	}
-	assert(!isView || pTrigger);
 
 	/* Assign cursor numbers to the table and all its indices.
 	 */
-	assert(pTabList->nSrc == 1);
-	iTabCur = pTabList->a[0].iCursor = pParse->nTab++;
-	for (nIdx = 0, pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext, nIdx++) {
-		pParse->nTab++;
-	}
+	int tab_cursor;
+	tab_cursor = tab_list->a[0].iCursor = parse->nTab++;
+	parse->nTab += space->index_count;
 
-	/* Begin generating code.
-	 */
-	v = sqlite3GetVdbe(pParse);
-	if (v == 0) {
+	/* Begin generating code.  */
+	struct Vdbe *v = sqlite3GetVdbe(parse);
+	if (v == NULL)
 		goto delete_from_cleanup;
-	}
-	if (pParse->nested == 0)
-		sqlite3VdbeCountChanges(v);
-	sql_set_multi_write(pParse, true);
 
-	/* If we are trying to delete from a view, realize that view into
-	 * an ephemeral table.
-	 */
-#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-	if (isView) {
-		sqlite3MaterializeView(pParse, pTab, pWhere, iTabCur);
-		iDataCur = iIdxCur = iTabCur;
-	}
-#endif
+	if (parse->nested == 0)
+		sqlite3VdbeCountChanges(v);
+	sql_set_multi_write(parse, true);
 
-	/* Resolve the column names in the WHERE clause.
+	/* If we are trying to delete from a view, realize that
+	 * view into an ephemeral table.
 	 */
-	memset(&sNC, 0, sizeof(sNC));
-	sNC.pParse = pParse;
-	sNC.pSrcList = pTabList;
-	if (sqlite3ResolveExprNames(&sNC, pWhere)) {
+	if (is_view)
+		sql_materialize_view(parse, space->def->name, where, tab_cursor);
+
+	/* Resolve the column names in the WHERE clause. */
+	struct NameContext nc;
+	memset(&nc, 0, sizeof(nc));
+	nc.pParse = parse;
+	nc.pSrcList = tab_list;
+	if (sqlite3ResolveExprNames(&nc, where))
 		goto delete_from_cleanup;
-	}
 
-	/* Initialize the counter of the number of rows deleted, if
-	 * we are counting rows.
+	/* Initialize the counter of the number of rows deleted,
+	 * if we are counting rows.
 	 */
+	int reg_count = -1;
+	struct session *user_session = current_session();
 	if (user_session->sql_flags & SQLITE_CountRows) {
-		memCnt = ++pParse->nMem;
-		sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt);
+		reg_count = ++parse->nMem;
+		sqlite3VdbeAddOp2(v, OP_Integer, 0, reg_count);
 	}
-#ifndef SQLITE_OMIT_TRUNCATE_OPTIMIZATION
-	/* Special case: A DELETE without a WHERE clause deletes everything.
-	 * It is easier just to erase the whole table. Prior to version 3.6.5,
-	 * this optimization caused the row change count (the value returned by
-	 * API function sqlite3_count_changes) to be set incorrectly.
+	/* Special case: A DELETE without a WHERE clause deletes
+	 * everything. It is easier just to erase the whole table.
 	 */
-	if (pWhere == 0 && !bComplex
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-	    && db->xPreUpdateCallback == 0
-#endif
-	    ) {
-		assert(!isView);
-
-		sqlite3VdbeAddOp1(v, OP_Clear, pTab->tnum);
-
-		/* Do not start Tarantool's transaction in case of truncate optimization.
-		   This is workaround until system tables cannot be changes inside a
-		   transaction (_truncate).  */
-		pParse->initiateTTrans = false;
-	} else
-#endif				/* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */
-	{
-		u16 wcf =
-		    WHERE_ONEPASS_DESIRED | WHERE_DUPLICATES_OK |
-		    WHERE_SEEK_TABLE;
-		if (sNC.ncFlags & NC_VarSelect)
-			bComplex = 1;
-		wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW);
-		/* Create an ephemeral table used to hold all primary keys for
-		 * rows to be deleted. Since VIEW is held in ephemeral table,
-		 * there is no PK for it, so columns should be loaded manually.
+	if (where == NULL && !is_complex) {
+		assert(!is_view);
+
+		sqlite3VdbeAddOp1(v, OP_Clear, table->tnum);
+
+		/* Do not start Tarantool's transaction in case of
+		 * truncate optimization. This is workaround until
+		 * system tables cannot be changes inside a
+		 * transaction (_truncate).
 		 */
-		if (isView) {
-			nPk = pTab->nCol;
-			iPk = pParse->nMem + 1;
-			pParse->nMem += nPk;
-			iEphCur = pParse->nTab++;
-			struct key_def *def = key_def_new(nPk);
-			if (def == NULL) {
-				sqlite3OomFault(db);
-				goto delete_from_cleanup;
-			}
-			addrEphOpen =
-				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
-						  nPk, 0, (char*)def, P4_KEYDEF);
+		parse->initiateTTrans = false;
+	} else {
+		uint16_t wcf =
+			WHERE_ONEPASS_DESIRED | WHERE_DUPLICATES_OK |
+			WHERE_SEEK_TABLE;
+		if (nc.ncFlags & NC_VarSelect)
+			is_complex = true;
+		wcf |= (is_complex ? 0 : WHERE_ONEPASS_MULTIROW);
+		/* Create an ephemeral table used to hold all
+		 * primary keys for rows to be deleted. Since VIEW
+		 * is held in ephemeral table, there is no PK for
+		 * it, so columns should be loaded manually.
+		 */
+		struct Index *pk = NULL;
+		int reg_pk = parse->nMem + 1;
+		int pk_len;
+		int eph_cursor = parse->nTab++;
+		int addr_eph_open = sqlite3VdbeCurrentAddr(v);
+		if (is_view) {
+			pk_len = table->nCol;
+			parse->nMem += pk_len;
+			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
+					  eph_cursor, pk_len, 0,
+					  (char*)NULL, P4_KEYDEF);
 		} else {
-			pPk = sqlite3PrimaryKeyIndex(pTab);
-			assert(pPk != 0);
-			nPk = index_column_count(pPk);
-			iPk = pParse->nMem + 1;
-			pParse->nMem += nPk;
-			iEphCur = pParse->nTab++;
-			addrEphOpen =
-			    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEphCur,
-					      nPk);
-			sql_vdbe_set_p4_key_def(pParse, pPk);
+			pk = sqlite3PrimaryKeyIndex(table);
+			assert(pk != NULL);
+			pk_len = index_column_count(pk);
+			parse->nMem += pk_len;
+			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, eph_cursor,
+					  pk_len);
+			sql_vdbe_set_p4_key_def(parse, pk);
 		}
 
-		/* Construct a query to find the primary key for every row
-		 * to be deleted, based on the WHERE clause. Set variable eOnePass
-		 * to indicate the strategy used to implement this delete:
+		/* Construct a query to find the primary key for
+		 * every row to be deleted, based on the WHERE
+		 * clause. Set variable one_pass to indicate the
+		 * strategy used to implement this delete:
 		 *
-		 *  ONEPASS_OFF:    Two-pass approach - use a FIFO for PK values.
-		 *  ONEPASS_SINGLE: One-pass approach - at most one row deleted.
-		 *  ONEPASS_MULTI:  One-pass approach - any number of rows may be deleted.
+		 * ONEPASS_OFF:    Two-pass approach - use a FIFO
+		 * for PK values.
+		 * ONEPASS_SINGLE: One-pass approach - at most one
+		 * row deleted.
+		 * ONEPASS_MULTI:  One-pass approach - any number
+		 * of rows may be deleted.
 		 */
-		pWInfo =
-		    sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf,
-				      iTabCur + 1);
-		if (pWInfo == 0)
+		struct WhereInfo *winfo =
+		    sqlite3WhereBegin(parse, tab_list, where, NULL, NULL, wcf,
+				      tab_cursor + 1);
+		if (winfo == NULL)
 			goto delete_from_cleanup;
-		eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
-		assert(eOnePass != ONEPASS_MULTI);
-		/* Tarantool workaround: see comment in sqlite3WhereBegin.  */
-		/* assert( bComplex || eOnePass!=ONEPASS_OFF ); */
-
-		/* Keep track of the number of rows to be deleted */
-		if (user_session->sql_flags & SQLITE_CountRows) {
-			sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1);
-		}
+
+		/* The write cursors opened by WHERE_ONEPASS */
+		int one_pass_cur[2];
+		int one_pass = sqlite3WhereOkOnePass(winfo, one_pass_cur);
+		assert(one_pass != ONEPASS_MULTI);
+		/* Tarantool: see comment in
+		 * sqlite3WhereOkOnePass.
+		 */
+		/* assert(is_complex || one_pass != ONEPASS_OFF); */
+
+		/* Keep track of the number of rows to be
+		 * deleted.
+		 */
+		if (user_session->sql_flags & SQLITE_CountRows)
+			sqlite3VdbeAddOp2(v, OP_AddImm, reg_count, 1);
 
 		/* Extract the primary key for the current row */
-		if (!isView) {
-			for (i = 0; i < nPk; i++) {
-				assert(pPk->aiColumn[i] >= 0);
-				sqlite3ExprCodeGetColumnOfTable(v, pTab,
-								iTabCur,
-								pPk->
+		if (!is_view) {
+			for (int i = 0; i < pk_len; i++) {
+				assert(pk->aiColumn[i] >= 0);
+				sqlite3ExprCodeGetColumnOfTable(v, table,
+								tab_cursor,
+								pk->
 								aiColumn[i],
-								iPk + i);
+								reg_pk + i);
 			}
 		} else {
-			for (i = 0; i < nPk; i++) {
-				sqlite3VdbeAddOp3(v, OP_Column, iDataCur,
-						  i, iPk + i);
+			for (int i = 0; i < pk_len; i++) {
+				sqlite3VdbeAddOp3(v, OP_Column, tab_cursor,
+						  i, reg_pk + i);
 			}
 		}
-		iKey = iPk;
 
-		if (eOnePass != ONEPASS_OFF) {
-			/* For ONEPASS, no need to store the primary-key. There is only
-			 * one, so just keep it in its register(s) and fall through to the
-			 * delete code.
+		int reg_key;
+		int key_len;
+		if (one_pass != ONEPASS_OFF) {
+			/* For ONEPASS, no need to store the
+			 * primary-key. There is only one, so just
+			 * keep it in its register(s) and fall
+			 * through to the delete code.
 			 */
-			nKey = nPk;	/* OP_Found will use an unpacked key */
-			aToOpen = sqlite3DbMallocRawNN(db, nIdx + 2);
-			if (aToOpen == 0) {
-				sqlite3WhereEnd(pWInfo);
-				goto delete_from_cleanup;
-			}
-			memset(aToOpen, 1, nIdx + 1);
-			aToOpen[nIdx + 1] = 0;
-			if (aiCurOnePass[0] >= 0)
-				aToOpen[aiCurOnePass[0] - iTabCur] = 0;
-			if (aiCurOnePass[1] >= 0)
-				aToOpen[aiCurOnePass[1] - iTabCur] = 0;
-			if (addrEphOpen)
-				sqlite3VdbeChangeToNoop(v, addrEphOpen);
+			reg_key = reg_pk;
+			/* OP_Found will use an unpacked key */
+			key_len = pk_len;
+			sqlite3VdbeChangeToNoop(v, addr_eph_open);
 		} else {
-			/* Add the PK key for this row to the temporary table */
-			iKey = ++pParse->nMem;
-			nKey = 0;	/* Zero tells OP_Found to use a composite key */
-			const char *zAff = isView ? 0 :
-					  sqlite3IndexAffinityStr(pParse->db, pPk);
-			sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey, zAff, nPk);
-			/* Set flag to save memory allocating one by malloc. */
+			/* Add the PK key for this row to the
+			 * temporary table.
+			 */
+			reg_key = ++parse->nMem;
+			/* Zero tells OP_Found to use a composite
+			 * key.
+			 */
+			key_len = 0;
+			const char *zAff = is_view ? NULL :
+					  sqlite3IndexAffinityStr(parse->db, pk);
+			sqlite3VdbeAddOp4(v, OP_MakeRecord, reg_pk, pk_len,
+					  reg_key, zAff, pk_len);
+			/* Set flag to save memory allocating one
+			 * by malloc.
+			 */
 			sqlite3VdbeChangeP5(v, 1);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, eph_cursor, reg_key);
 		}
 
-		/* If this DELETE cannot use the ONEPASS strategy, this is the
-		 * end of the WHERE loop
+		/* If this DELETE cannot use the ONEPASS strategy,
+		 * this is the end of the WHERE loop.
 		 */
-		if (eOnePass != ONEPASS_OFF) {
-			addrBypass = sqlite3VdbeMakeLabel(v);
-		} else {
-			sqlite3WhereEnd(pWInfo);
-		}
-
-		/* Unless this is a view, open cursors for the table we are
-		 * deleting from and all its indices. If this is a view, then the
-		 * only effect this statement has is to fire the INSTEAD OF
+		int addr_bypass = 0;
+		if (one_pass != ONEPASS_OFF)
+			addr_bypass = sqlite3VdbeMakeLabel(v);
+		else
+			sqlite3WhereEnd(winfo);
+
+		/* Unless this is a view, open cursors for the
+		 * table we are deleting from and all its indices.
+		 * If this is a view, then the only effect this
+		 * statement has is to fire the INSTEAD OF
 		 * triggers.
 		 */
-		if (!isView) {
+		if (!is_view) {
 			int iAddrOnce = 0;
-			if (eOnePass == ONEPASS_MULTI) {
+			if (one_pass == ONEPASS_MULTI) {
 				iAddrOnce = sqlite3VdbeAddOp0(v, OP_Once);
 				VdbeCoverage(v);
 			}
-			sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite,
-						   OPFLAG_FORDELETE, iTabCur,
-						   aToOpen, &iDataCur,
-						   &iIdxCur,
-						   ON_CONFLICT_ACTION_NONE, 0);
-			assert(pPk || iDataCur == iTabCur);
-			assert(pPk || iIdxCur == iDataCur + 1);
-			if (eOnePass == ONEPASS_MULTI)
+
+			int space_ptr_reg = ++parse->nMem;
+			sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0,
+					     (void *) space);
+			sqlite3VdbeAddOp3(v, OP_OpenWrite, tab_cursor,
+					  table->tnum, space_ptr_reg);
+			sql_vdbe_set_p4_key_def(parse, pk);
+			VdbeComment((v, "%s", pk->zName));
+
+			if (one_pass == ONEPASS_MULTI)
 				sqlite3VdbeJumpHere(v, iAddrOnce);
 		}
 
-		/* Set up a loop over the primary-keys that were found in the
-		 * where-clause loop above.
+		/* Set up a loop over the primary-keys that were
+		 * found in the where-clause loop above.
 		 */
-		if (eOnePass != ONEPASS_OFF) {
-			assert(nKey == nPk);	/* OP_Found will use an unpacked key */
-			if (aToOpen[iDataCur - iTabCur]) {
-				assert(pPk != 0 || pTab->pSelect != 0);
-				sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur,
-						     addrBypass, iKey, nKey);
+		int addr_loop = 0;
+		if (one_pass != ONEPASS_OFF) {
+			/* OP_Found will use an unpacked key. */
+			assert(key_len == pk_len);
+			assert(pk != NULL || table->pSelect != NULL);
+			sqlite3VdbeAddOp4Int(v, OP_NotFound, tab_cursor,
+					     addr_bypass, reg_key, key_len);
 
-				VdbeCoverage(v);
-			}
+			VdbeCoverage(v);
 		} else {
-			addrLoop = sqlite3VdbeAddOp1(v, OP_Rewind, iEphCur);
+			addr_loop = sqlite3VdbeAddOp1(v, OP_Rewind, eph_cursor);
 			VdbeCoverage(v);
-			sqlite3VdbeAddOp2(v, OP_RowData, iEphCur, iKey);
+			sqlite3VdbeAddOp2(v, OP_RowData, eph_cursor, reg_key);
 		}
 
 		/* Delete the row */
-		{
-			int count = (pParse->nested == 0);	/* True to count changes */
-			int iIdxNoSeek = -1;
-			if (bComplex == 0 && aiCurOnePass[1] != iDataCur
-			    /* Tarantool: as far as ONEPASS is disabled, there's no index
-			       w/o need of seeking.  */
-			    && eOnePass != ONEPASS_OFF) {
-				iIdxNoSeek = aiCurOnePass[1];
-			}
-			sqlite3GenerateRowDelete(pParse, pTab, pTrigger,
-						 iDataCur, iIdxCur, iKey, nKey,
-						 count,
-						 ON_CONFLICT_ACTION_DEFAULT,
-						 eOnePass,
-						 iIdxNoSeek);
-		}
+		int idx_noseek = -1;
+		if (!is_complex && one_pass_cur[1] != tab_cursor
+		    /* Tarantool: as far as ONEPASS is disabled,
+		     * there's no index w/o need of seeking.
+		     */
+		    && one_pass != ONEPASS_OFF)
+			idx_noseek = one_pass_cur[1];
+
+		sql_generate_row_delete(parse, table, trigger_list, tab_cursor,
+					reg_key, key_len, parse->nested == 0,
+					ON_CONFLICT_ACTION_DEFAULT, one_pass,
+					idx_noseek);
 
 		/* End of the loop over all primary-keys. */
-		if (eOnePass != ONEPASS_OFF) {
-			sqlite3VdbeResolveLabel(v, addrBypass);
-			sqlite3WhereEnd(pWInfo);
+		if (one_pass != ONEPASS_OFF) {
+			sqlite3VdbeResolveLabel(v, addr_bypass);
+			sqlite3WhereEnd(winfo);
 		} else {
-			sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop + 1);
+			sqlite3VdbeAddOp2(v, OP_Next, eph_cursor,
+					  addr_loop + 1);
 			VdbeCoverage(v);
-			sqlite3VdbeJumpHere(v, addrLoop);
+			sqlite3VdbeJumpHere(v, addr_loop);
 		}
-	}			/* End non-truncate path */
+	}
 
-	/* Return the number of rows that were deleted. If this routine is
-	 * generating code because of a call to sqlite3NestedParse(), do not
-	 * invoke the callback function.
+	/* Return the number of rows that were deleted. If this
+	 * routine is generating code because of a call to
+	 * sqlite3NestedParse(), do not invoke the callback
+	 * function.
 	 */
 	if ((user_session->sql_flags & SQLITE_CountRows) &&
-	    !pParse->nested && !pParse->pTriggerTab) {
-		sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1);
+	    parse->nested == 0 && !parse->pTriggerTab) {
+		sqlite3VdbeAddOp2(v, OP_ResultRow, reg_count, 1);
 		sqlite3VdbeSetNumCols(v, 1);
 		sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted",
 				      SQLITE_STATIC);
 	}
 
  delete_from_cleanup:
-	sqlite3SrcListDelete(db, pTabList);
-	sql_expr_free(db, pWhere, false);
-	sqlite3DbFree(db, aToOpen);
-	return;
+	sqlite3SrcListDelete(db, tab_list);
+	sql_expr_free(db, where, false);
 }
 
-/* Generate VDBE code for
- * DELETE FROM <pTab.z> WHERE
- *        <columns[0]> = <values[0]>
- *    AND  ...
- *    AND <columns[nPairs - 1]> = <values[nPairs - 1]>;
- *
- * This function does not increment the nested counter and is
- * faster than nested parsing of the request above.
- * @param pParse Parser context.
- * @param pTab Table name.
- * @param columns Column names array.
- * @param values Column values array.
- * @param nPairs Length of @columns and @values.
- *
- * In case of error the @values elements are deleted.
- */
 void
-sqlite3DeleteByKey(Parse *pParse, char *zTab, const char **columns,
-		   Expr **values, int nPairs)
+sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
+		    struct Expr **values, int pairs_count)
 {
-	Expr *where = NULL;
-	SrcList *src;
-
-		assert(nPairs > 0);
-	if (pParse->nErr > 0 || pParse->db->mallocFailed)
+	if (parse->nErr > 0 || parse->db->mallocFailed)
 		goto error;
-	src = sql_alloc_src_list(pParse->db);
-	src->a[0].zName = sqlite3DbStrDup(pParse->db, zTab);
+	struct SrcList *src = sql_alloc_src_list(parse->db);
+	src->a[0].zName = sqlite3DbStrDup(parse->db, t_name);
 	if (src == NULL)
 		goto error;
 	/* Dummy init of INDEXED BY clause. */
-	Token t = { NULL, 0, false };
-	sqlite3SrcListIndexedBy(pParse, src, &t);
+	struct Token t = {NULL, 0, false};
+	sqlite3SrcListIndexedBy(parse, src, &t);
 
-	for (int i = 0; i < nPairs; ++i) {
-		Expr *col_expr = sqlite3Expr(pParse->db, TK_ID, columns[i]);
+	struct Expr *where = NULL;
+	assert(pairs_count > 0);
+	for (int i = 0; i < pairs_count; ++i) {
+		struct Expr *col_expr = sqlite3Expr(parse->db, TK_ID, columns[i]);
 		if (col_expr == NULL || values[i] == NULL)
 			goto error;
-		Expr *eq_expr =
-		    sqlite3PExpr(pParse, TK_EQ, col_expr, values[i]);
+		struct Expr *eq_expr =
+		    sqlite3PExpr(parse, TK_EQ, col_expr, values[i]);
 		/* In case of error the values[i] had been deleted in
 		 * sqlite3PExpr already. Do not delete it second time in the
 		 * cycle below.
@@ -626,311 +503,175 @@ sqlite3DeleteByKey(Parse *pParse, char *zTab, const char **columns,
 		if (i == 0) {
 			where = eq_expr;
 		} else {
-			where = sqlite3ExprAnd(pParse->db, where, eq_expr);
+			where = sqlite3ExprAnd(parse->db, where, eq_expr);
 			if (where == NULL)
 				goto error;
 		}
 	}
 	/* DeleteFrom frees the src and exprs in case of error. */
-	sqlite3DeleteFrom(pParse, src, where);
+	sql_table_delete_from(parse, src, where);
 	return;
 
  error:
-	sql_expr_free(pParse->db, where, false);
-	for (int i = 0; i < nPairs; ++i)
-		sql_expr_free(pParse->db, values[i], false);
+	sql_expr_free(parse->db, where, false);
+	for (int i = 0; i < pairs_count; ++i)
+		sql_expr_free(parse->db, values[i], false);
 }
 
-/* Make sure "isView" and other macros defined above are undefined. Otherwise
- * they may interfere with compilation of other functions in this file
- * (or in another file, if this file becomes part of the amalgamation).
- */
-#ifdef isView
-#undef isView
-#endif
-#ifdef pTrigger
-#undef pTrigger
-#endif
-
-/*
- * This routine generates VDBE code that causes a single row of a
- * single table to be deleted.  Both the original table entry and
- * all indices are removed.
- *
- * Preconditions:
- *
- *   1.  iDataCur is an open cursor on the btree that is the canonical data
- *       store for the table.  (This will be the PRIMARY KEY index)
- *
- *   2.  Read/write cursors for all indices of pTab must be open as
- *       cursor number iIdxCur+i for the i-th index.
- *
- *   3.  The primary key for the row to be deleted must be stored in a
- *       sequence of nPk memory cells starting at iPk.  If nPk==0 that means
- *       that a search record formed from OP_MakeRecord is contained in the
- *       single memory location iPk.
- *
- * eMode:
- *   Parameter eMode may be passed either ONEPASS_OFF (0), ONEPASS_SINGLE, or
- *   ONEPASS_MULTI.  If eMode is not ONEPASS_OFF, then the cursor
- *   iDataCur already points to the row to delete. If eMode is ONEPASS_OFF
- *   then this function must seek iDataCur to the entry identified by iPk
- *   and nPk before reading from it.
- *
- *   If eMode is ONEPASS_MULTI, then this call is being made as part
- *   of a ONEPASS delete that affects multiple rows. In this case, if
- *   iIdxNoSeek is a valid cursor number (>=0), then its position should
- *   be preserved following the delete operation. Or, if iIdxNoSeek is not
- *   a valid cursor number, the position of iDataCur should be preserved
- *   instead.
- *
- * iIdxNoSeek:
- *   If iIdxNoSeek is a valid cursor number (>=0), then it identifies an
- *   index cursor (from within array of cursors starting at iIdxCur) that
- *   already points to the index entry to be deleted.
- */
 void
-sqlite3GenerateRowDelete(Parse * pParse,	/* Parsing context */
-			 Table * pTab,		/* Table containing the row to be deleted */
-			 Trigger * pTrigger,	/* List of triggers to (potentially) fire */
-			 int iDataCur,		/* Cursor from which column data is extracted */
-			 int iIdxCur,		/* First index cursor */
-			 int iPk,		/* First memory cell containing the PRIMARY KEY */
-			 i16 nPk,		/* Number of PRIMARY KEY memory cells */
-			 u8 count,		/* If non-zero, increment the row change counter */
-			 enum on_conflict_action onconf,		/* Default ON CONFLICT policy for triggers */
-			 u8 eMode,		/* ONEPASS_OFF, _SINGLE, or _MULTI.  See above */
-			 int iIdxNoSeek)	/* Cursor number of cursor that does not need seeking */
+sql_generate_row_delete(struct Parse *parse, struct Table *table,
+			struct Trigger *trigger_list, int cursor, int reg_pk,
+			short npk, bool is_count,
+			enum on_conflict_action onconf, u8 mode,
+			int idx_noseek)
 {
-	Vdbe *v = pParse->pVdbe;	/* Vdbe */
-	int iOld = 0;		/* First register in OLD.* array */
-	int iLabel;		/* Label resolved to end of generated code */
-
-	/* Vdbe is guaranteed to have been allocated by this stage. */
-	assert(v);
-	(void)iIdxCur;
+	struct Vdbe *v = parse->pVdbe;
+	/* Vdbe is guaranteed to have been allocated by this
+	 * stage.
+	 */
+	assert(v != NULL);
 	VdbeModuleComment((v, "BEGIN: GenRowDel(%d,%d,%d,%d)",
-			   iDataCur, iIdxCur, iPk, (int)nPk));
+			   cursor, iIdxCur, reg_pk, (int)nPk));
 
-	/* Seek cursor iCur to the row to delete. If this row no longer exists
-	 * (this can happen if a trigger program has already deleted it), do
-	 * not attempt to delete it or fire any DELETE triggers.
+	/* Seek cursor iCur to the row to delete. If this row no
+	 * longer exists (this can happen if a trigger program has
+	 * already deleted it), do not attempt to delete it or
+	 * fire any DELETE triggers.
 	 */
-	iLabel = sqlite3VdbeMakeLabel(v);
-	if (eMode == ONEPASS_OFF) {
-		sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, iLabel, iPk, nPk);
+	int label = sqlite3VdbeMakeLabel(v);
+	if (mode == ONEPASS_OFF) {
+		sqlite3VdbeAddOp4Int(v, OP_NotFound, cursor, label, reg_pk, npk);
 		VdbeCoverageIf(v, opSeek == OP_NotFound);
 	}
 
+	int first_old_reg = 0;
 	/* If there are any triggers to fire, allocate a range of registers to
 	 * use for the old.* references in the triggers.
 	 */
-	if (sqlite3FkRequired(pTab, 0) || pTrigger) {
-		u32 mask;	/* Mask of OLD.* columns in use */
-		int iCol;	/* Iterator used while populating OLD.* */
-		int addrStart;	/* Start of BEFORE trigger programs */
-
-		/* TODO: Could use temporary registers here.
-		 */
-		mask =
-		    sqlite3TriggerColmask(pParse, pTrigger, 0, 0,
-					  TRIGGER_BEFORE | TRIGGER_AFTER, pTab,
+	if (sqlite3FkRequired(table, 0) || trigger_list != NULL) {
+		/* Mask of OLD.* columns in use */
+		/* TODO: Could use temporary registers here. */
+		uint32_t mask =
+		    sqlite3TriggerColmask(parse, trigger_list, 0, 0,
+					  TRIGGER_BEFORE | TRIGGER_AFTER, table,
 					  onconf);
-		mask |= sqlite3FkOldmask(pParse, pTab);
-		iOld = pParse->nMem + 1;
-		pParse->nMem += (1 + pTab->nCol);
+		mask |= sqlite3FkOldmask(parse, table);
+		first_old_reg = parse->nMem + 1;
+		parse->nMem += (1 + table->nCol);
 
-		/* Populate the OLD.* pseudo-table register array. These values will be
-		 * used by any BEFORE and AFTER triggers that exist.
+		/* Populate the OLD.* pseudo-table register array.
+		 * These values will be used by any BEFORE and
+		 * AFTER triggers that exist.
 		 */
-		sqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld);
-		for (iCol = 0; iCol < pTab->nCol; iCol++) {
+		sqlite3VdbeAddOp2(v, OP_Copy, reg_pk, first_old_reg);
+		for (int i = 0; i < table->nCol; i++) {
 			testcase(mask != 0xffffffff && iCol == 31);
 			testcase(mask != 0xffffffff && iCol == 32);
 			if (mask == 0xffffffff
-			    || (iCol <= 31 && (mask & MASKBIT32(iCol)) != 0)) {
-				sqlite3ExprCodeGetColumnOfTable(v, pTab,
-								iDataCur, iCol,
-								iOld + iCol +
-								1);
+			    || (i <= 31 && (mask & MASKBIT32(i)) != 0)) {
+				sqlite3ExprCodeGetColumnOfTable(v, table,
+								cursor, i,
+								first_old_reg +
+								i + 1);
 			}
 		}
 
 		/* Invoke BEFORE DELETE trigger programs. */
-		addrStart = sqlite3VdbeCurrentAddr(v);
-		sqlite3CodeRowTrigger(pParse, pTrigger,
-				      TK_DELETE, 0, TRIGGER_BEFORE, pTab, iOld,
-				      onconf, iLabel);
-
-		/* If any BEFORE triggers were coded, then seek the cursor to the
-		 * row to be deleted again. It may be that the BEFORE triggers moved
-		 * the cursor or of already deleted the row that the cursor was
-		 * pointing to.
+		int addr_start = sqlite3VdbeCurrentAddr(v);
+		sqlite3CodeRowTrigger(parse, trigger_list,
+				      TK_DELETE, 0, TRIGGER_BEFORE, table,
+				      first_old_reg, onconf, label);
+
+		/* If any BEFORE triggers were coded, then seek
+		 * the cursor to the row to be deleted again. It
+		 * may be that the BEFORE triggers moved the
+		 * cursor or of already deleted the row that the
+		 * cursor was pointing to.
 		 */
-		if (addrStart < sqlite3VdbeCurrentAddr(v)) {
-			sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, iLabel, iPk,
-					     nPk);
+		if (addr_start < sqlite3VdbeCurrentAddr(v)) {
+			sqlite3VdbeAddOp4Int(v, OP_NotFound, cursor, label,
+					     reg_pk, npk);
 			VdbeCoverageIf(v, opSeek == OP_NotFound);
 		}
 
-		/* Do FK processing. This call checks that any FK constraints that
-		 * refer to this table (i.e. constraints attached to other tables)
-		 * are not violated by deleting this row.
+		/* Do FK processing. This call checks that any FK
+		 * constraints that refer to this table (i.e.
+		 * constraints attached to other tables) are not
+		 * violated by deleting this row.
 		 */
-		sqlite3FkCheck(pParse, pTab, iOld, 0, 0);
+		sqlite3FkCheck(parse, table, first_old_reg, 0, NULL);
 	}
 
-	/* Delete the index and table entries. Skip this step if pTab is really
-	 * a view (in which case the only effect of the DELETE statement is to
-	 * fire the INSTEAD OF triggers).
-	 *
-	 * If variable 'count' is non-zero, then this OP_Delete instruction should
-	 * invoke the update-hook. The pre-update-hook, on the other hand should
-	 * be invoked unless table pTab is a system table. The difference is that
-	 * the update-hook is not invoked for rows removed by REPLACE, but the
-	 * pre-update-hook is.
+	/* Delete the index and table entries. Skip this step if
+	 * table is really a view (in which case the only effect
+	 * of the DELETE statement is to fire the INSTEAD OF
+	 * triggers).
 	 */
-	if (pTab->pSelect == 0) {
-		u8 p5 = 0;
-		/* kyukhin: Tarantool handles indices uypdate automatically.  */
-		/* sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek);  */
-		sqlite3VdbeAddOp2(v, OP_Delete, iDataCur,
-				  (count ? OPFLAG_NCHANGE : 0));
-		if (eMode != ONEPASS_OFF) {
+	if (table->pSelect == NULL) {
+		uint8_t p5 = 0;
+		sqlite3VdbeAddOp2(v, OP_Delete, cursor,
+				  (is_count ? OPFLAG_NCHANGE : 0));
+		if (mode != ONEPASS_OFF)
 			sqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE);
-		}
-		if (iIdxNoSeek >= 0) {
-			sqlite3VdbeAddOp1(v, OP_Delete, iIdxNoSeek);
-		}
-		if (eMode == ONEPASS_MULTI)
+
+		if (idx_noseek >= 0)
+			sqlite3VdbeAddOp1(v, OP_Delete, idx_noseek);
+
+		if (mode == ONEPASS_MULTI)
 			p5 |= OPFLAG_SAVEPOSITION;
 		sqlite3VdbeChangeP5(v, p5);
 	}
 
-	/* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
-	 * handle rows (possibly in other tables) that refer via a foreign key
-	 * to the row just deleted.
+	/* Do any ON CASCADE, SET NULL or SET DEFAULT operations
+	 * required to handle rows (possibly in other tables) that
+	 * refer via a foreign key to the row just deleted.
 	 */
-	sqlite3FkActions(pParse, pTab, 0, iOld, 0);
+	sqlite3FkActions(parse, table, 0, first_old_reg, 0);
 
 	/* Invoke AFTER DELETE trigger programs. */
-	sqlite3CodeRowTrigger(pParse, pTrigger,
-			      TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf,
-			      iLabel);
+	sqlite3CodeRowTrigger(parse, trigger_list,
+			      TK_DELETE, 0, TRIGGER_AFTER, table, first_old_reg,
+			      onconf, label);
 
-	/* Jump here if the row had already been deleted before any BEFORE
-	 * trigger programs were invoked. Or if a trigger program throws a
-	 * RAISE(IGNORE) exception.
+	/* Jump here if the row had already been deleted before
+	 * any BEFORE trigger programs were invoked. Or if a trigger program
+	 * throws a RAISE(IGNORE) exception.
 	 */
-	sqlite3VdbeResolveLabel(v, iLabel);
+	sqlite3VdbeResolveLabel(v, label);
 	VdbeModuleComment((v, "END: GenRowDel()"));
 }
 
-/*
- * This routine generates VDBE code that causes the deletion of all
- * index entries associated with a single row of a single table, pTab
- *
- * Preconditions:
- *
- *   1.  A read/write cursor "iDataCur" must be open on the canonical storage
- *       btree for the table pTab.  (This will be primary key index)
- *
- *   2.  Read/write cursor for primary index of pTab must be open as
- *       cursor number iIdxCur.  (The pTab->pIndex index is the 0-th index.)
- *
- *   3.  The "iDataCur" cursor must be already positioned on the row
- *       that is to be deleted.
- */
-void
-sqlite3GenerateRowIndexDelete(Parse * pParse,	/* Parsing and code generating context */
-			      Table * pTab,	/* Table containing the row to be deleted */
-			      int iDataCur,	/* Cursor of table holding data. */
-			      int iIdxCur)	/* Primary index cursor */
-{
-	int r1 = -1;		/* Register holding an index key */
-	int iPartIdxLabel;	/* Jump destination for skipping partial index entries */
-	Vdbe *v;		/* The prepared statement under construction */
-	Index *pPk;		/* PRIMARY KEY index */
-
-	v = pParse->pVdbe;
-	pPk = sqlite3PrimaryKeyIndex(pTab);
-	/* In Tarantool it is enough to delete row just from pk */
-	VdbeModuleComment((v, "GenRowIdxDel for %s", pPk->zName));
-	r1 = sqlite3GenerateIndexKey(pParse, pPk, iDataCur, 0, &iPartIdxLabel,
-				     NULL, r1);
-	sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur, r1, index_column_count(pPk));
-	sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
-}
-
-/*
- * Generate code that will assemble an index key and stores it in register
- * regOut.  The key with be for index pIdx which is an index on pTab.
- * iCur is the index of a cursor open on the pTab table and pointing to
- * the entry that needs indexing.
- * iCur must be the cursor of the PRIMARY KEY index.
- *
- * Return a register number which is the first in a block of
- * registers that holds the elements of the index key.  The
- * block of registers has already been deallocated by the time
- * this routine returns.
- *
- * If *piPartIdxLabel is not NULL, fill it in with a label and jump
- * to that label if pIdx is a partial index that should be skipped.
- * The label should be resolved using sqlite3ResolvePartIdxLabel().
- * A partial index should be skipped if its WHERE clause evaluates
- * to false or null.  If pIdx is not a partial index, *piPartIdxLabel
- * will be set to zero which is an empty label that is ignored by
- * sqlite3ResolvePartIdxLabel().
- *
- * The pPrior and regPrior parameters are used to implement a cache to
- * avoid unnecessary register loads.  If pPrior is not NULL, then it is
- * a pointer to a different index for which an index key has just been
- * computed into register regPrior.  If the current pIdx index is generating
- * its key into the same sequence of registers and if pPrior and pIdx share
- * a column in common, then the register corresponding to that column already
- * holds the correct value and the loading of that register is skipped.
- * This optimization is helpful when doing a DELETE or an INTEGRITY_CHECK
- * on a table with multiple indices, and especially with
- * the PRIMARY KEY columns of the index.
- */
 int
-sqlite3GenerateIndexKey(Parse * pParse,	/* Parsing context */
-			Index * pIdx,	/* The index for which to generate a key */
-			int iDataCur,	/* Cursor number from which to take column data */
-			int regOut,	/* Put the new key into this register if not 0 */
-			int *piPartIdxLabel,	/* OUT: Jump to this label to skip partial index */
-			Index * pPrior,	/* Previously generated index key */
-			int regPrior)	/* Register holding previous generated key */
+sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
+		       int reg_out, int *part_idx_label, struct Index *prev,
+		       int reg_prev)
 {
-	Vdbe *v = pParse->pVdbe;
-	int j;
-	int regBase;
-	int nCol;
-
-	if (piPartIdxLabel) {
-		if (pIdx->pPartIdxWhere) {
-			*piPartIdxLabel = sqlite3VdbeMakeLabel(v);
-			pParse->iSelfTab = iDataCur;
-			sqlite3ExprCachePush(pParse);
-			sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere,
-					      *piPartIdxLabel,
+	Vdbe *v = parse->pVdbe;
+
+	if (part_idx_label != NULL) {
+		if (index->pPartIdxWhere) {
+			*part_idx_label = sqlite3VdbeMakeLabel(v);
+			parse->iSelfTab = cursor;
+			sqlite3ExprCachePush(parse);
+			sqlite3ExprIfFalseDup(parse, index->pPartIdxWhere,
+					      *part_idx_label,
 					      SQLITE_JUMPIFNULL);
 		} else {
-			*piPartIdxLabel = 0;
+			*part_idx_label = 0;
 		}
 	}
-	nCol = index_column_count(pIdx);
-	regBase = sqlite3GetTempRange(pParse, nCol);
-	if (pPrior && (regBase != regPrior || pPrior->pPartIdxWhere))
-		pPrior = 0;
-	for (j = 0; j < nCol; j++) {
-		if (pPrior && pPrior->aiColumn[j] == pIdx->aiColumn[j]
-		    && pPrior->aiColumn[j] != XN_EXPR) {
+	int col_cnt = index_column_count(index);
+	int reg_base = sqlite3GetTempRange(parse, col_cnt);
+	if (prev != NULL && (reg_base != reg_prev || prev->pPartIdxWhere))
+		prev = NULL;
+	for (int j = 0; j < col_cnt; j++) {
+		if (prev != NULL && prev->aiColumn[j] == index->aiColumn[j]
+		    && prev->aiColumn[j] != XN_EXPR) {
 			/* This column was already computed by the previous index */
 			continue;
 		}
-		sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j,
-					       regBase + j);
+		sqlite3ExprCodeLoadIndexColumn(parse, index, cursor, j,
+					       reg_base + j);
 		/* If the column affinity is REAL but the number is an integer, then it
 		 * might be stored in the table as an integer (using a compact
 		 * representation) then converted to REAL by an OP_RealAffinity opcode.
@@ -940,23 +681,18 @@ sqlite3GenerateIndexKey(Parse * pParse,	/* Parsing context */
 		 */
 		sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity);
 	}
-	if (regOut) {
-		sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut);
-	}
-	sqlite3ReleaseTempRange(pParse, regBase, nCol);
-	return regBase;
+	if (reg_out)
+		sqlite3VdbeAddOp3(v, OP_MakeRecord, reg_base, col_cnt, reg_out);
+
+	sqlite3ReleaseTempRange(parse, reg_base, col_cnt);
+	return reg_base;
 }
 
-/*
- * If a prior call to sqlite3GenerateIndexKey() generated a jump-over label
- * because it was a partial index, then this routine should be called to
- * resolve that label.
- */
 void
-sqlite3ResolvePartIdxLabel(Parse * pParse, int iLabel)
+sql_resolve_part_idx_label(struct Parse *parse, int label)
 {
-	if (iLabel) {
-		sqlite3VdbeResolveLabel(pParse->pVdbe, iLabel);
-		sqlite3ExprCachePop(pParse);
+	if (label != 0) {
+		sqlite3VdbeResolveLabel(parse->pVdbe, label);
+		sqlite3ExprCachePop(parse);
 	}
 }
diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index 60b4786..a21dd2d 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -784,7 +784,8 @@ sqlite3FkDropTable(Parse * pParse, SrcList * pName, Table * pTab)
 		pParse->disableTriggers = 1;
 		/* Staring new transaction before DELETE FROM <tbl> */
 		sqlite3VdbeAddOp0(v, OP_TTransaction);
-		sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0);
+		sql_table_delete_from(pParse, sqlite3SrcListDup(db, pName, 0),
+				      NULL);
 		pParse->disableTriggers = 0;
 
 		/* If the DELETE has generated immediate foreign key constraint
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 1ad7469..2d49296 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -379,7 +379,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 	zTab = pTabList->a[0].zName;
 	if (NEVER(zTab == 0))
 		goto insert_cleanup;
-	pTab = sqlite3SrcListLookup(pParse, pTabList);
+	pTab = sql_list_lookup_table(pParse, pTabList);
 	if (pTab == 0) {
 		goto insert_cleanup;
 	}
@@ -406,13 +406,13 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 	/* If pTab is really a view, make sure it has been initialized.
 	 * ViewGetColumnNames() is a no-op if pTab is not a view.
 	 */
-	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
+	if (sqlite3ViewGetColumnNames(pParse, pTab))
 		goto insert_cleanup;
-	}
 
-	/* Cannot insert into a read-only table.
-	 */
-	if (sqlite3IsReadOnly(pParse, pTab, tmask)) {
+	/* Cannot insert into a read-only table. */
+	if (isView && tmask == 0) {
+		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
+				pTab->zName);
 		goto insert_cleanup;
 	}
 
@@ -1511,13 +1511,13 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 								 TK_DELETE, 0,
 								 0);
 				}
-				sqlite3GenerateRowDelete(pParse, pTab, pTrigger,
-							 iDataCur, iIdxCur,
-							 regR, nPkField, 0,
-							 ON_CONFLICT_ACTION_REPLACE,
-							 (pIdx ==
-							  pPk ? ONEPASS_SINGLE :
-							  ONEPASS_OFF), -1);
+				sql_generate_row_delete(pParse, pTab, pTrigger,
+							iDataCur,
+							regR, nPkField, 0,
+							ON_CONFLICT_ACTION_REPLACE,
+							(pIdx ==
+							 pPk ? ONEPASS_SINGLE :
+							 ONEPASS_OFF), -1);
 				seenReplace = 1;
 				break;
 			}
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 872647d..9343c0e 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -738,7 +738,7 @@ cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W). {
   sqlSubProgramsRemaining = SQL_MAX_COMPILING_TRIGGERS;
   /* Instruct SQL to initate Tarantool's transaction.  */
   pParse->initiateTTrans = true;
-  sqlite3DeleteFrom(pParse,X,W);
+  sql_table_delete_from(pParse,X,W);
 }
 %endif
 
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index e056d63..eb12ebd 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1994,7 +1994,6 @@ sql_space_tuple_log_count(struct Table *tab);
 /*
  * Allowed values for Table.tabFlags.
  */
-#define TF_Readonly        0x01	/* Read-only system table */
 #define TF_Ephemeral       0x02	/* An ephemeral table */
 #define TF_HasPrimaryKey   0x04	/* Table has a primary key */
 #define TF_Autoincrement   0x08	/* Integer primary key is autoincrement */
@@ -3671,15 +3670,73 @@ int sqlite3Select(Parse *, Select *, SelectDest *);
 Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
 			 Expr *, ExprList *, u32, Expr *, Expr *);
 void sqlite3SelectDelete(sqlite3 *, Select *);
-Table *sqlite3SrcListLookup(Parse *, SrcList *);
+
+/**
+ * While a SrcList can in general represent multiple tables and
+ * subqueries (as in the FROM clause of a SELECT statement) in
+ * this case it contains the name of a single table, as one might
+ * find in an INSERT, DELETE, or UPDATE statement.  Look up that
+ * table in the symbol table and return a pointer.  Set an error
+ * message and return NULL if the table name is not found or if
+ * any other error occurs.
+ *
+ * The following fields are initialized appropriate in src_list:
+ *
+ *    pSrc->a[0].pTab       Pointer to the Table object.
+ *    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if
+ *                          there is one.
+ *
+ * @param parse Parsing context.
+ * @param src_list List containing single table element.
+ * @retval Table object if found, NULL oterwise.
+ */
+struct Table *
+sql_list_lookup_table(struct Parse *parse, SrcList *src_list);
+
 int sqlite3IsReadOnly(Parse *, Table *, int);
 void sqlite3OpenTable(Parse *, int iCur, Table *, int);
 #if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
 Expr *sqlite3LimitWhere(Parse *, SrcList *, Expr *, ExprList *, Expr *, Expr *,
 			char *);
 #endif
-void sqlite3DeleteFrom(Parse *, SrcList *, Expr *);
-void sqlite3DeleteByKey(Parse *, char *, const char **, Expr **, int);
+/**
+ * Generate code for a DELETE FROM statement.
+ *
+ *     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
+ *                 \________/       \________________/
+ *                  pTabList              pWhere
+ *
+ * @param parse Parsing context.
+ * @param tab_list List of single element which table from which
+ * deletetion if performed.
+ * @param where The WHERE clause.  May be NULL.
+ */
+void
+sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
+		      struct Expr *where);
+
+/**
+ * Generate VDBE code for
+ * DELETE FROM <t_name> WHERE
+ *        <columns[0]> = <values[0]>
+ *    AND  ...
+ *    AND <columns[nPairs - 1]> = <values[nPairs - 1]>;
+ *
+ * This function does not increment the nested counter and is
+ * faster than nested parsing of the request above.
+ *
+ * @param parse Parser context.
+ * @param t_name Table name.
+ * @param columns Column names array.
+ * @param values Column values array.
+ * @param pairs_count Length of @columns and @values.
+ *
+ * In case of error the @values elements are deleted.
+ */
+void
+sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
+		    struct Expr **values, int pairs_count);
+
 void sqlite3Update(Parse *, SrcList *, ExprList *, Expr *,
 		   enum on_conflict_action);
 WhereInfo *sqlite3WhereBegin(Parse *, SrcList *, Expr *, ExprList *, ExprList *,
@@ -3782,11 +3839,121 @@ int sqlite3ExprContainsSubquery(Expr *);
 int sqlite3ExprIsInteger(Expr *, int *);
 int sqlite3ExprCanBeNull(const Expr *);
 int sqlite3ExprNeedsNoAffinityChange(const Expr *, char);
-void sqlite3GenerateRowDelete(Parse *, Table *, Trigger *, int, int, int, i16,
-			      u8, enum on_conflict_action, u8, int);
-void sqlite3GenerateRowIndexDelete(Parse *, Table *, int, int);
-int sqlite3GenerateIndexKey(Parse *, Index *, int, int, int *, Index *, int);
-void sqlite3ResolvePartIdxLabel(Parse *, int);
+
+/**
+ * This routine generates VDBE code that causes a single row of a
+ * single table to be deleted.  Both the original table entry and
+ * all indices are removed.
+ *
+ * Preconditions:
+ *
+ *   1.  cursor is an open cursor on the btree that is the
+ *       canonical data store for the table.  (This will be the
+ *       PRIMARY KEY index)
+ *
+ *   2.  The primary key for the row to be deleted must be stored
+ *       in a sequence of nPk memory cells starting at iPk. If
+ *       nPk==0 that means that a search record formed from
+ *       OP_MakeRecord is contained in the single memory location
+ *       iPk.
+ *
+ * mode:
+ *   Parameter mode may be passed either ONEPASS_OFF (0),
+ *   ONEPASS_SINGLE, or ONEPASS_MULTI.  If eMode is not
+ *   ONEPASS_OFF, then the cursor already points to the row to
+ *   delete. If eMode is ONEPASS_OFF then this function must seek
+ *   cursor to the entry identified by ipk and npk before reading
+ *   from it.
+ *
+ *   If mode is ONEPASS_MULTI, then this call is being made as
+ *   part of a ONEPASS delete that affects multiple rows. In this
+ *   case, if idx_noseek is a valid cursor number (>=0), then its
+ *   position should be preserved following the delete operation.
+ *   Or, if iIdxNoSeek is not a valid cursor number, the position
+ *   of cursor should be preserved instead.
+ *
+ * idx_noseek:
+ *   If idx_noseek is a valid cursor number (>=0), then it
+ *   identifies an index cursor that already points to the index
+ *   entry to be deleted.
+ *
+ * @param parse Parsing context.
+ * @param table Table containing the row to be deleted.
+ * @param trigger_list List of triggers to (potentially) fire.
+ * @param cursor Cursor from which column data is extracted/
+ * @param ipk First memory cell containing the PRIMARY KEY.
+ * @param npk umber of PRIMARY KEY memory cells.
+ * @param is_count If non-zero, increment the row change counter.
+ * @param onconf Default ON CONFLICT policy for triggers.
+ * @param mode ONEPASS_OFF, _SINGLE, or _MULTI.  See above.
+ * @param idx_noseek Cursor number of cursor that does not need
+          seeking.
+ */
+void
+sql_generate_row_delete(struct Parse *parse, struct Table *table,
+			struct Trigger *trigger_list, int cursor, int ipk,
+			short npk, bool is_count,
+			enum on_conflict_action onconf, u8 eMode,
+			int idx_noseek);
+
+/**
+ * Generate code that will assemble an index key and stores it in
+ * register reg_out.  The key with be for index pIdx which is an
+ * index on table. cursor is the index of a cursor open on the
+ * table table and pointing to the entry that needs indexing.
+ * cursor must be the cursor of the PRIMARY KEY index.
+ *
+ * Return a register number which is the first in a block of
+ * registers that holds the elements of the index key.  The
+ * block of registers has already been deallocated by the time
+ * this routine returns.
+ *
+ * If *part_idx_label is not NULL, fill it in with a label and
+ * jump to that label if pIdx is a partial index that should be
+ * skipped. The label should be resolved using
+ * sql_resolve_part_idx_label(). A partial index should be skipped
+ * if its WHERE clause evaluates to false or null.  If index is
+ * not a partial index, *piPartIdxLabel will be set to zero which
+ * is an empty label that is ignored by sql_resolve_part_idx_label().
+ *
+ * The pPrior and regPrior parameters are used to implement a
+ * cache to avoid unnecessary register loads.  If prev is not
+ * NULL, then it is a pointer to a different index for which an
+ * index key has just been computed into register reg_prev. If the
+ * current pIdx index is generating its key into the same
+ * sequence of registers and if prev and index share a column in
+ * common, then the register corresponding to that column already
+ * holds the correct value and the loading of that register is
+ * skipped. This optimization is helpful when doing a DELETE or
+ * an INTEGRITY_CHECK on a table with multiple indices, and
+ * especially with the PRIMARY KEY columns of the index.
+ *
+ * @param parse Parsing context.
+ * @param index The index for which to generate a key.
+ * @param cursor Cursor number from which to take column data.
+ * @param reg_out Put the new key into this register if not NULL.
+ * @param[out] part_idx_label Jump to this label to skip partial
+ *        index.
+ * @param prev Previously generated index key
+ * @param reg_prev Register holding previous generated key.
+ * @retval Register containing new record
+ */
+int
+sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
+		       int reg_out, int *part_idx_label, struct Index *prev,
+		       int reg_prev);
+
+/**
+ * If a prior call to sqlite3GenerateIndexKey() generated a
+ * jump-over label because it was a partial index, then this
+ * routine should be called to resolve that label.
+ *
+ * @param parse Parsing context.
+ * @param label Label to resolve.
+ */
+void
+sql_resolve_part_idx_label(struct Parse *parse, int label);
+
 void sqlite3GenerateConstraintChecks(Parse *, Table *, int *, int, int, int,
 				     int, u8, struct on_conflict *, int, int *,
 				     int *);
@@ -3831,9 +3998,20 @@ int sqlite3SafetyCheckOk(sqlite3 *);
 int sqlite3SafetyCheckSickOrOk(sqlite3 *);
 void sqlite3ChangeCookie(Parse *);
 
-#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-void sqlite3MaterializeView(Parse *, Table *, Expr *, int);
-#endif
+/**
+ * Evaluate a view and store its result in an ephemeral table.
+ * The where argument is an optional WHERE clause that restricts
+ * the set of rows in the view that are to be added to the
+ * ephemeral table.
+ *
+ * @param parse Parsing context.
+ * @param name View name.
+ * @param where Option WHERE clause to be added.
+ * @param cursor Cursor number for ephemeral table.
+ */
+void
+sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
+		     int cursor);
 
 #ifndef SQLITE_OMIT_TRIGGER
 void sqlite3BeginTrigger(Parse *, Token *, int, int, IdList *, SrcList *,
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index 28c56db..e6d9925 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -111,7 +111,7 @@ sqlite3BeginTrigger(Parse * pParse,	/* The parse context of the CREATE TRIGGER s
 	if (sqlite3FixSrcList(&sFix, pTableName)) {
 		goto trigger_cleanup;
 	}
-	pTab = sqlite3SrcListLookup(pParse, pTableName);
+	pTab = sql_list_lookup_table(pParse, pTableName);
 	if (!pTab) {
 		goto trigger_cleanup;
 	}
@@ -744,11 +744,11 @@ codeTriggerProgram(Parse * pParse,	/* The parser context */
 				break;
 			}
 		case TK_DELETE:{
-				sqlite3DeleteFrom(pParse,
-						  targetSrcList(pParse, pStep),
-						  sqlite3ExprDup(db,
-								 pStep->pWhere,
-								 0)
+				sql_table_delete_from(pParse,
+						      targetSrcList(pParse, pStep),
+						      sqlite3ExprDup(db,
+								     pStep->pWhere,
+								     0)
 				    );
 				break;
 			}
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index de1349b..60cb229 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -163,7 +163,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 
 	/* Locate the table which we want to update.
 	 */
-	pTab = sqlite3SrcListLookup(pParse, pTabList);
+	pTab = sql_list_lookup_table(pParse, pTabList);
 	if (pTab == 0)
 		goto update_cleanup;
 
@@ -187,7 +187,9 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
 		goto update_cleanup;
 	}
-	if (sqlite3IsReadOnly(pParse, pTab, tmask)) {
+	if (isView && tmask == 0) {
+		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
+				pTab->zName);
 		goto update_cleanup;
 	}
 
@@ -324,7 +326,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	 */
 #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
 	if (isView) {
-		sqlite3MaterializeView(pParse, pTab, pWhere, iDataCur);
+		sql_materialize_view(pParse, pTab->zName, pWhere, iDataCur);
 		/* Number of columns from SELECT plus ID.*/
 		nKey = pTab->nCol + 1;
 	}
@@ -603,7 +605,6 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 						     nKey);
 			VdbeCoverageNeverTaken(v);
 		}
-		sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur);
 
 		/* If changing the PK value, or if there are foreign key constraints
 		 * to process, delete the old record. Otherwise, add a noop OP_Delete
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index b8d4c1a..2ad6f3e 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -3236,20 +3236,20 @@ case OP_OpenTEphemeral: {
 	BtCursor *pBtCur;
 	assert(pOp->p1 >= 0);
 	assert(pOp->p2 > 0);
-	assert(pOp->p4type != P4_KEYDEF || pOp->p4.key_def != NULL);
+	assert(pOp->p4type == P4_KEYDEF);
 
 	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
 	if (pCx == 0) goto no_mem;
 	pCx->nullRow = 1;
 
-	pCx->key_def  = pOp->p4.key_def;
 	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,
-					     pCx->key_def);
+					     pOp->p4.key_def);
+	pCx->key_def = pCx->uc.pCursor->index->def->key_def;
 	if (rc) goto abort_due_to_error;
 	break;
 }
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index e67fcae..4a8a9d2 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1134,7 +1134,7 @@ sqlite3VdbeChangeP4(Vdbe * p, int addr, const char *zP4, int n)
 	} if (n == P4_BOOL) {
 		pOp->p4.b = *(bool*)zP4;
 		pOp->p4type = P4_BOOL;
-	} else if (zP4 != 0) {
+	} else {
 		assert(n < 0);
 		pOp->p4.p = (void *)zP4;
 		pOp->p4type = (signed char)n;
@@ -1571,7 +1571,10 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
 #endif
 	case P4_COLLSEQ:{
 			struct coll *pColl = pOp->p4.pColl;
-			sqlite3XPrintf(&x, "(%.20s)", pColl->name);
+			if (pColl != NULL)
+				sqlite3XPrintf(&x, "(%.20s)", pColl->name);
+			else
+				sqlite3XPrintf(&x, "(binary)");
 			break;
 		}
 	case P4_FUNCDEF:{
-- 
2.16.2

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

* [tarantool-patches] Re: [PATCH 2/2] sql: refactor delete routines
  2018-05-16 15:24 ` [tarantool-patches] [PATCH 2/2] sql: refactor delete routines Kirill Yukhin
@ 2018-05-16 16:29   ` Kirill Yukhin
  2018-05-17 14:23     ` Vladislav Shpilevoy
  2018-05-17 15:18     ` Vladislav Shpilevoy
  0 siblings, 2 replies; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-16 16:29 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

Hi Vlad,
On 16 мая 18:24, Kirill Yukhin wrote:
> Refactor DELETE FROM statements translation, update
I've removed one more dead routine. Patch is in the bottom.

--
Regards, Kirill Yukhin

sql: refactor SQL delete routines

Refactor DELETE FROM statements translation, update
all live routines according to Tarantool's coding style.
Use key_def instead of Table* where possible.
Remove useless index delete routine as well.

Part of #3235
---
 src/box/sql/build.c     |    6 +-
 src/box/sql/delete.c    | 1136 ++++++++++++++++-------------------------------
 src/box/sql/fkey.c      |    3 +-
 src/box/sql/insert.c    |   26 +-
 src/box/sql/parse.y     |    2 +-
 src/box/sql/sqliteInt.h |  206 ++++++++-
 src/box/sql/trigger.c   |   12 +-
 src/box/sql/update.c    |    9 +-
 src/box/sql/vdbe.c      |    6 +-
 src/box/sql/vdbeaux.c   |    7 +-
 10 files changed, 619 insertions(+), 794 deletions(-)

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index c96d351..e3aadab 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -2664,10 +2664,10 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
 	VdbeCoverage(v);
 	regRecord = sqlite3GetTempReg(pParse);
 
-	sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord,
-				&iPartIdxLabel, 0, 0);
+	sql_generate_index_key(pParse, pIndex, iTab, regRecord,
+			       &iPartIdxLabel, 0, 0);
 	sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord);
-	sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
+	sql_resolve_part_idx_label(pParse, iPartIdxLabel);
 	sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1 + 1);
 	VdbeCoverage(v);
 	sqlite3VdbeJumpHere(v, addr1);
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 3455f52..0c77057 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -29,593 +29,380 @@
  * SUCH DAMAGE.
  */
 
-/*
- * This file contains C code routines that are called by the parser
- * in order to generate code for DELETE FROM statements.
- */
-#include "sqliteInt.h"
 #include "box/session.h"
+#include "box/schema.h"
+#include "sqliteInt.h"
+#include "tarantoolInt.h"
 
-/*
- * While a SrcList can in general represent multiple tables and subqueries
- * (as in the FROM clause of a SELECT statement) in this case it contains
- * the name of a single table, as one might find in an INSERT, DELETE,
- * or UPDATE statement.  Look up that table in the symbol table and
- * return a pointer.  Set an error message and return NULL if the table
- * name is not found or if any other error occurs.
- *
- * The following fields are initialized appropriate in pSrc:
- *
- *    pSrc->a[0].pTab       Pointer to the Table object
- *    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if there is one
- *
- */
-Table *
-sqlite3SrcListLookup(Parse * pParse, SrcList * pSrc)
+struct Table *
+sql_list_lookup_table(struct Parse *parse, SrcList *src_list)
 {
-	struct SrcList_item *pItem = pSrc->a;
-	Table *pTab;
-	assert(pItem && pSrc->nSrc == 1);
-	pTab = sqlite3LocateTable(pParse, 0, pItem->zName);
-	sqlite3DeleteTable(pParse->db, pItem->pTab);
-	pItem->pTab = pTab;
-	if (pTab != NULL)
-		pTab->nTabRef++;
-	if (sqlite3IndexedByLookup(pParse, pItem))
-		pTab = NULL;
-	return pTab;
+	struct SrcList_item *item = src_list->a;
+	struct Table *table;
+	assert(item != NULL && src_list->nSrc == 1);
+	table = sqlite3LocateTable(parse, 0, item->zName);
+	sqlite3DeleteTable(parse->db, item->pTab);
+	item->pTab = table;
+	if (table != NULL)
+		table->nTabRef++;
+	if (sqlite3IndexedByLookup(parse, item))
+		table = NULL;
+	return table;
 }
 
-/*
- * Check to make sure the given table is writable.  If it is not
- * writable, generate an error message and return 1.  If it is
- * writable return 0;
- */
-int
-sqlite3IsReadOnly(Parse * pParse, Table * pTab, int viewOk)
-{
-	/*
-	 * A table is not writable if it is a system table
-	 * (i.e. _sql_stat1), this call is not part of a
-	 * nested parse. In either case leave an error message in
-	 * pParse and return non-zero.
-	 */
-	if ((pTab->tabFlags & TF_Readonly) != 0 && pParse->nested == 0) {
-		sqlite3ErrorMsg(pParse, "table %s may not be modified",
-				pTab->zName);
-		return 1;
-	}
-#ifndef SQLITE_OMIT_VIEW
-	if (!viewOk && space_is_view(pTab)) {
-		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
-				pTab->zName);
-		return 1;
-	}
-#endif
-	return 0;
-}
-
-#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-/*
- * Evaluate a view and store its result in an ephemeral table.  The
- * pWhere argument is an optional WHERE clause that restricts the
- * set of rows in the view that are to be added to the ephemeral table.
- */
 void
-sqlite3MaterializeView(Parse * pParse,	/* Parsing context */
-		       Table * pView,	/* View definition */
-		       Expr * pWhere,	/* Optional WHERE clause to be added */
-		       int iCur)	/* Cursor number for ephemeral table */
+sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
+		     int cursor)
 {
-	SelectDest dest;
-	Select *pSel;
-	SrcList *pFrom;
-	sqlite3 *db = pParse->db;
-	pWhere = sqlite3ExprDup(db, pWhere, 0);
-	pFrom = sqlite3SrcListAppend(db, 0, 0);
-	if (pFrom) {
-		assert(pFrom->nSrc == 1);
-		pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName);
-		assert(pFrom->a[0].pOn == 0);
-		assert(pFrom->a[0].pUsing == 0);
-	}
-	pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0,
-				0, 0, 0);
-	sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur);
-	sqlite3Select(pParse, pSel, &dest);
-	sqlite3SelectDelete(db, pSel);
-}
-#endif				/* !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */
-
-#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
-/*
- * Generate an expression tree to implement the WHERE, ORDER BY,
- * and LIMIT/OFFSET portion of DELETE and UPDATE statements.
- *
- *     DELETE FROM table_wxyz WHERE a<5 ORDER BY a LIMIT 1;
- *                            \__________________________/
- *                               pLimitWhere (pInClause)
- */
-Expr *
-sqlite3LimitWhere(Parse * pParse,	/* The parser context */
-		  SrcList * pSrc,	/* the FROM clause -- which tables to scan */
-		  Expr * pWhere,	/* The WHERE clause.  May be null */
-		  ExprList * pOrderBy,	/* The ORDER BY clause.  May be null */
-		  Expr * pLimit,	/* The LIMIT clause.  May be null */
-		  Expr * pOffset,	/* The OFFSET clause.  May be null */
-		  char *zStmtType	/* Either DELETE or UPDATE.  For err msgs. */
-    )
-{
-	Expr *pWhereRowid = NULL;	/* WHERE rowid .. */
-	Expr *pInClause = NULL;	/* WHERE rowid IN ( select ) */
-	Expr *pSelectRowid = NULL;	/* SELECT rowid ... */
-	ExprList *pEList = NULL;	/* Expression list contaning only pSelectRowid */
-	SrcList *pSelectSrc = NULL;	/* SELECT rowid FROM x ... (dup of pSrc) */
-	Select *pSelect = NULL;	/* Complete SELECT tree */
-
-	/* Check that there isn't an ORDER BY without a LIMIT clause.
-	 */
-	if (pOrderBy && (pLimit == 0)) {
-		sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s",
-				zStmtType);
-		goto limit_where_cleanup;
-	}
-
-	/* We only need to generate a select expression if there
-	 * is a limit/offset term to enforce.
-	 */
-	if (pLimit == 0) {
-		/* if pLimit is null, pOffset will always be null as well. */
-		assert(pOffset == 0);
-		return pWhere;
+	struct sqlite3 *db = parse->db;
+	where = sqlite3ExprDup(db, where, 0);
+	struct SrcList *from = sqlite3SrcListAppend(db, NULL, NULL);
+	if (from != NULL) {
+		assert(from->nSrc == 1);
+		from->a[0].zName = sqlite3DbStrDup(db, name);
+		assert(from->a[0].pOn == NULL);
+		assert(from->a[0].pUsing == NULL);
 	}
-
-	/* Generate a select expression tree to enforce the limit/offset
-	 * term for the DELETE or UPDATE statement.  For example:
-	 *   DELETE FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
-	 * becomes:
-	 *   DELETE FROM table_a WHERE rowid IN (
-	 *     SELECT rowid FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
-	 *   );
-	 */
-
-	pSelectRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0);
-	if (pSelectRowid == 0)
-		goto limit_where_cleanup;
-	pEList = sqlite3ExprListAppend(pParse, 0, pSelectRowid);
-	if (pEList == 0)
-		goto limit_where_cleanup;
-
-	/* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree
-	 * and the SELECT subtree.
-	 */
-	pSelectSrc = sqlite3SrcListDup(pParse->db, pSrc, 0);
-	if (pSelectSrc == 0) {
-		sqlite3ExprListDelete(pParse->db, pEList);
-		goto limit_where_cleanup;
-	}
-
-	/* generate the SELECT expression tree. */
-	pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0, 0,
-				   pOrderBy, 0, pLimit, pOffset);
-	if (pSelect == 0)
-		return 0;
-
-	/* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */
-	pWhereRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0);
-	pInClause =
-	    pWhereRowid ? sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0) : 0;
-	sqlite3PExprAddSelect(pParse, pInClause, pSelect);
-	return pInClause;
-
- limit_where_cleanup:
-	sql_expr_free(pParse->db, pWhere);
-	sqlite3ExprListDelete(pParse->db, pOrderBy);
-	sql_expr_free(pParse->db, pLimit);
-	sql_expr_free(pParse->db, pOffset);
-	return 0;
+	struct Select *select = sqlite3SelectNew(parse, NULL, from, where, NULL,
+						 NULL, NULL, 0, NULL, NULL);
+	struct SelectDest dest;
+	sqlite3SelectDestInit(&dest, SRT_EphemTab, cursor);
+	sqlite3Select(parse, select, &dest);
+	sqlite3SelectDelete(db, select);
 }
-#endif				/* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) */
 
-/*
- * Generate code for a DELETE FROM statement.
- *
- *     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
- *                 \________/       \________________/
- *                  pTabList              pWhere
- */
 void
-sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
-		  SrcList * pTabList,	/* The table from which we should delete things */
-		  Expr * pWhere)	/* The WHERE clause.  May be null */
+sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
+		      struct Expr *where)
 {
-	Vdbe *v;		/* The virtual database engine */
-	Table *pTab;		/* The table from which records will be deleted */
-	int i;			/* Loop counter */
-	WhereInfo *pWInfo;	/* Information about the WHERE clause */
-	Index *pIdx;		/* For looping over indices of the table */
-	int iTabCur;		/* Cursor number for the table */
-	int iDataCur = 0;	/* VDBE cursor for the canonical data source */
-	int iIdxCur = 0;	/* Cursor number of the first index */
-	int nIdx;		/* Number of indices */
-	sqlite3 *db;		/* Main database structure */
-	NameContext sNC;	/* Name context to resolve expressions in */
-	int memCnt = -1;	/* Memory cell used for change counting */
-	int eOnePass;		/* ONEPASS_OFF or _SINGLE or _MULTI */
-	int aiCurOnePass[2];	/* The write cursors opened by WHERE_ONEPASS */
-	u8 *aToOpen = 0;	/* Open cursor iTabCur+j if aToOpen[j] is true */
-	Index *pPk = 0;		/* The PRIMARY KEY index on the table */
-	int iPk = 0;		/* First of nPk registers holding PRIMARY KEY value */
-	i16 nPk;		/* Number of columns in the PRIMARY KEY */
-	int iKey;		/* Memory cell holding key of row to be deleted */
-	i16 nKey;		/* Number of memory cells in the row key */
-	int iEphCur = 0;	/* Ephemeral table holding all primary key values */
-	int addrBypass = 0;	/* Address of jump over the delete logic */
-	int addrLoop = 0;	/* Top of the delete loop */
-	int addrEphOpen = 0;	/* Instruction to open the Ephemeral table */
-	int bComplex;		/* True if there are triggers or FKs or
-				 * subqueries in the WHERE clause
-				 */
-	struct session *user_session = current_session();
+	struct sqlite3 *db = parse->db;
+	if (parse->nErr || db->mallocFailed)
+		goto delete_from_cleanup;
 
-#ifndef SQLITE_OMIT_TRIGGER
-	int isView;		/* True if attempting to delete from a view */
-	Trigger *pTrigger;	/* List of table triggers, if required */
-#endif
+	assert(tab_list->nSrc == 1);
 
-	db = pParse->db;
-	if (pParse->nErr || db->mallocFailed) {
+	/* Locate the table which we want to delete.  This table
+	 * has to be put in an SrcList structure because some of
+	 * the subroutines we will be calling are designed to work
+	 * with multiple tables and expect an SrcList* parameter
+	 * instead of just a Table* parameter.
+	 */
+	struct Table *table = sql_list_lookup_table(parse, tab_list);
+	if (table == NULL)
 		goto delete_from_cleanup;
-	}
-	assert(pTabList->nSrc == 1);
 
-	/* Locate the table which we want to delete.  This table has to be
-	 * put in an SrcList structure because some of the subroutines we
-	 * will be calling are designed to work with multiple tables and expect
-	 * an SrcList* parameter instead of just a Table* parameter.
+	struct space *space = space_by_id(SQLITE_PAGENO_TO_SPACEID(table->tnum));
+	assert(space != NULL);
+	/* Figure out if we have any triggers and if the table
+	 * being deleted from is a view.
 	 */
-	pTab = sqlite3SrcListLookup(pParse, pTabList);
-	if (pTab == 0)
-		goto delete_from_cleanup;
+	struct Trigger *trigger_list = sqlite3TriggersExist(table, TK_DELETE,
+							    NULL, NULL);
 
-	/* Figure out if we have any triggers and if the table being
-	 * deleted from is a view
+	bool is_view = space->def->opts.is_view;
+	/* True if there are triggers or FKs or subqueries in the
+	 * WHERE clause.
 	 */
-#ifndef SQLITE_OMIT_TRIGGER
-	pTrigger = sqlite3TriggersExist(pTab, TK_DELETE, 0, 0);
-	isView = pTab->pSelect != 0;
-	bComplex = pTrigger || sqlite3FkRequired(pTab, 0);
-#else
-#define pTrigger 0
-#define isView 0
-#endif
-#ifdef SQLITE_OMIT_VIEW
-#undef isView
-#define isView 0
-#endif
-
-	/* If pTab is really a view, make sure it has been initialized.
+	bool is_complex = (trigger_list != NULL) ||
+			   sqlite3FkRequired(table, 0);
+
+	/* If table is really a view, make sure it has been
+	 * initialized.
 	 */
-	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
+	if (sqlite3ViewGetColumnNames(parse, table))
 		goto delete_from_cleanup;
-	}
 
-	if (sqlite3IsReadOnly(pParse, pTab, (pTrigger ? 1 : 0))) {
+	if (is_view && trigger_list == NULL) {
+		sqlite3ErrorMsg(parse, "cannot modify %s because it is a view",
+				space->def->name);
 		goto delete_from_cleanup;
 	}
-	assert(!isView || pTrigger);
 
 	/* Assign cursor numbers to the table and all its indices.
 	 */
-	assert(pTabList->nSrc == 1);
-	iTabCur = pTabList->a[0].iCursor = pParse->nTab++;
-	for (nIdx = 0, pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext, nIdx++) {
-		pParse->nTab++;
-	}
+	int tab_cursor;
+	tab_cursor = tab_list->a[0].iCursor = parse->nTab++;
+	parse->nTab += space->index_count;
 
-	/* Begin generating code.
-	 */
-	v = sqlite3GetVdbe(pParse);
-	if (v == 0) {
+	/* Begin generating code.  */
+	struct Vdbe *v = sqlite3GetVdbe(parse);
+	if (v == NULL)
 		goto delete_from_cleanup;
-	}
-	if (pParse->nested == 0)
-		sqlite3VdbeCountChanges(v);
-	sql_set_multi_write(pParse, true);
 
-	/* If we are trying to delete from a view, realize that view into
-	 * an ephemeral table.
-	 */
-#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-	if (isView) {
-		sqlite3MaterializeView(pParse, pTab, pWhere, iTabCur);
-		iDataCur = iIdxCur = iTabCur;
-	}
-#endif
+	if (parse->nested == 0)
+		sqlite3VdbeCountChanges(v);
+	sql_set_multi_write(parse, true);
 
-	/* Resolve the column names in the WHERE clause.
+	/* If we are trying to delete from a view, realize that
+	 * view into an ephemeral table.
 	 */
-	memset(&sNC, 0, sizeof(sNC));
-	sNC.pParse = pParse;
-	sNC.pSrcList = pTabList;
-	if (sqlite3ResolveExprNames(&sNC, pWhere)) {
+	if (is_view)
+		sql_materialize_view(parse, space->def->name, where, tab_cursor);
+
+	/* Resolve the column names in the WHERE clause. */
+	struct NameContext nc;
+	memset(&nc, 0, sizeof(nc));
+	nc.pParse = parse;
+	nc.pSrcList = tab_list;
+	if (sqlite3ResolveExprNames(&nc, where))
 		goto delete_from_cleanup;
-	}
 
-	/* Initialize the counter of the number of rows deleted, if
-	 * we are counting rows.
+	/* Initialize the counter of the number of rows deleted,
+	 * if we are counting rows.
 	 */
+	int reg_count = -1;
+	struct session *user_session = current_session();
 	if (user_session->sql_flags & SQLITE_CountRows) {
-		memCnt = ++pParse->nMem;
-		sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt);
+		reg_count = ++parse->nMem;
+		sqlite3VdbeAddOp2(v, OP_Integer, 0, reg_count);
 	}
-#ifndef SQLITE_OMIT_TRUNCATE_OPTIMIZATION
-	/* Special case: A DELETE without a WHERE clause deletes everything.
-	 * It is easier just to erase the whole table. Prior to version 3.6.5,
-	 * this optimization caused the row change count (the value returned by
-	 * API function sqlite3_count_changes) to be set incorrectly.
+	/* Special case: A DELETE without a WHERE clause deletes
+	 * everything. It is easier just to erase the whole table.
 	 */
-	if (pWhere == 0 && !bComplex
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-	    && db->xPreUpdateCallback == 0
-#endif
-	    ) {
-		assert(!isView);
-
-		sqlite3VdbeAddOp1(v, OP_Clear, pTab->tnum);
-
-		/* Do not start Tarantool's transaction in case of truncate optimization.
-		   This is workaround until system tables cannot be changes inside a
-		   transaction (_truncate).  */
-		pParse->initiateTTrans = false;
-	} else
-#endif				/* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */
-	{
-		u16 wcf =
-		    WHERE_ONEPASS_DESIRED | WHERE_DUPLICATES_OK |
-		    WHERE_SEEK_TABLE;
-		if (sNC.ncFlags & NC_VarSelect)
-			bComplex = 1;
-		wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW);
-		/* Create an ephemeral table used to hold all primary keys for
-		 * rows to be deleted. Since VIEW is held in ephemeral table,
-		 * there is no PK for it, so columns should be loaded manually.
+	if (where == NULL && !is_complex) {
+		assert(!is_view);
+
+		sqlite3VdbeAddOp1(v, OP_Clear, table->tnum);
+
+		/* Do not start Tarantool's transaction in case of
+		 * truncate optimization. This is workaround until
+		 * system tables cannot be changes inside a
+		 * transaction (_truncate).
 		 */
-		if (isView) {
-			nPk = pTab->nCol;
-			iPk = pParse->nMem + 1;
-			pParse->nMem += nPk;
-			iEphCur = pParse->nTab++;
-			struct key_def *def = key_def_new(nPk);
-			if (def == NULL) {
-				sqlite3OomFault(db);
-				goto delete_from_cleanup;
-			}
-			addrEphOpen =
-				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
-						  nPk, 0, (char*)def, P4_KEYDEF);
+		parse->initiateTTrans = false;
+	} else {
+		uint16_t wcf =
+			WHERE_ONEPASS_DESIRED | WHERE_DUPLICATES_OK |
+			WHERE_SEEK_TABLE;
+		if (nc.ncFlags & NC_VarSelect)
+			is_complex = true;
+		wcf |= (is_complex ? 0 : WHERE_ONEPASS_MULTIROW);
+		/* Create an ephemeral table used to hold all
+		 * primary keys for rows to be deleted. Since VIEW
+		 * is held in ephemeral table, there is no PK for
+		 * it, so columns should be loaded manually.
+		 */
+		struct Index *pk = NULL;
+		int reg_pk = parse->nMem + 1;
+		int pk_len;
+		int eph_cursor = parse->nTab++;
+		int addr_eph_open = sqlite3VdbeCurrentAddr(v);
+		if (is_view) {
+			pk_len = table->nCol;
+			parse->nMem += pk_len;
+			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
+					  eph_cursor, pk_len, 0,
+					  (char*)NULL, P4_KEYDEF);
 		} else {
-			pPk = sqlite3PrimaryKeyIndex(pTab);
-			assert(pPk != 0);
-			nPk = index_column_count(pPk);
-			iPk = pParse->nMem + 1;
-			pParse->nMem += nPk;
-			iEphCur = pParse->nTab++;
-			addrEphOpen =
-			    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEphCur,
-					      nPk);
-			sql_vdbe_set_p4_key_def(pParse, pPk);
+			pk = sqlite3PrimaryKeyIndex(table);
+			assert(pk != NULL);
+			pk_len = index_column_count(pk);
+			parse->nMem += pk_len;
+			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, eph_cursor,
+					  pk_len);
+			sql_vdbe_set_p4_key_def(parse, pk);
 		}
 
-		/* Construct a query to find the primary key for every row
-		 * to be deleted, based on the WHERE clause. Set variable eOnePass
-		 * to indicate the strategy used to implement this delete:
+		/* Construct a query to find the primary key for
+		 * every row to be deleted, based on the WHERE
+		 * clause. Set variable one_pass to indicate the
+		 * strategy used to implement this delete:
 		 *
-		 *  ONEPASS_OFF:    Two-pass approach - use a FIFO for PK values.
-		 *  ONEPASS_SINGLE: One-pass approach - at most one row deleted.
-		 *  ONEPASS_MULTI:  One-pass approach - any number of rows may be deleted.
+		 * ONEPASS_OFF:    Two-pass approach - use a FIFO
+		 * for PK values.
+		 * ONEPASS_SINGLE: One-pass approach - at most one
+		 * row deleted.
+		 * ONEPASS_MULTI:  One-pass approach - any number
+		 * of rows may be deleted.
 		 */
-		pWInfo =
-		    sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf,
-				      iTabCur + 1);
-		if (pWInfo == 0)
+		struct WhereInfo *winfo =
+		    sqlite3WhereBegin(parse, tab_list, where, NULL, NULL, wcf,
+				      tab_cursor + 1);
+		if (winfo == NULL)
 			goto delete_from_cleanup;
-		eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
-		assert(eOnePass != ONEPASS_MULTI);
-		/* Tarantool workaround: see comment in sqlite3WhereBegin.  */
-		/* assert( bComplex || eOnePass!=ONEPASS_OFF ); */
-
-		/* Keep track of the number of rows to be deleted */
-		if (user_session->sql_flags & SQLITE_CountRows) {
-			sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1);
-		}
+
+		/* The write cursors opened by WHERE_ONEPASS */
+		int one_pass_cur[2];
+		int one_pass = sqlite3WhereOkOnePass(winfo, one_pass_cur);
+		assert(one_pass != ONEPASS_MULTI);
+		/* Tarantool: see comment in
+		 * sqlite3WhereOkOnePass.
+		 */
+		/* assert(is_complex || one_pass != ONEPASS_OFF); */
+
+		/* Keep track of the number of rows to be
+		 * deleted.
+		 */
+		if (user_session->sql_flags & SQLITE_CountRows)
+			sqlite3VdbeAddOp2(v, OP_AddImm, reg_count, 1);
 
 		/* Extract the primary key for the current row */
-		if (!isView) {
-			for (i = 0; i < nPk; i++) {
-				assert(pPk->aiColumn[i] >= 0);
-				sqlite3ExprCodeGetColumnOfTable(v, pTab,
-								iTabCur,
-								pPk->
+		if (!is_view) {
+			for (int i = 0; i < pk_len; i++) {
+				assert(pk->aiColumn[i] >= 0);
+				sqlite3ExprCodeGetColumnOfTable(v, table,
+								tab_cursor,
+								pk->
 								aiColumn[i],
-								iPk + i);
+								reg_pk + i);
 			}
 		} else {
-			for (i = 0; i < nPk; i++) {
-				sqlite3VdbeAddOp3(v, OP_Column, iDataCur,
-						  i, iPk + i);
+			for (int i = 0; i < pk_len; i++) {
+				sqlite3VdbeAddOp3(v, OP_Column, tab_cursor,
+						  i, reg_pk + i);
 			}
 		}
-		iKey = iPk;
 
-		if (eOnePass != ONEPASS_OFF) {
-			/* For ONEPASS, no need to store the primary-key. There is only
-			 * one, so just keep it in its register(s) and fall through to the
-			 * delete code.
+		int reg_key;
+		int key_len;
+		if (one_pass != ONEPASS_OFF) {
+			/* For ONEPASS, no need to store the
+			 * primary-key. There is only one, so just
+			 * keep it in its register(s) and fall
+			 * through to the delete code.
 			 */
-			nKey = nPk;	/* OP_Found will use an unpacked key */
-			aToOpen = sqlite3DbMallocRawNN(db, nIdx + 2);
-			if (aToOpen == 0) {
-				sqlite3WhereEnd(pWInfo);
-				goto delete_from_cleanup;
-			}
-			memset(aToOpen, 1, nIdx + 1);
-			aToOpen[nIdx + 1] = 0;
-			if (aiCurOnePass[0] >= 0)
-				aToOpen[aiCurOnePass[0] - iTabCur] = 0;
-			if (aiCurOnePass[1] >= 0)
-				aToOpen[aiCurOnePass[1] - iTabCur] = 0;
-			if (addrEphOpen)
-				sqlite3VdbeChangeToNoop(v, addrEphOpen);
+			reg_key = reg_pk;
+			/* OP_Found will use an unpacked key */
+			key_len = pk_len;
+			sqlite3VdbeChangeToNoop(v, addr_eph_open);
 		} else {
-			/* Add the PK key for this row to the temporary table */
-			iKey = ++pParse->nMem;
-			nKey = 0;	/* Zero tells OP_Found to use a composite key */
-			const char *zAff = isView ? 0 :
-					  sqlite3IndexAffinityStr(pParse->db, pPk);
-			sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey, zAff, nPk);
-			/* Set flag to save memory allocating one by malloc. */
+			/* Add the PK key for this row to the
+			 * temporary table.
+			 */
+			reg_key = ++parse->nMem;
+			/* Zero tells OP_Found to use a composite
+			 * key.
+			 */
+			key_len = 0;
+			const char *zAff = is_view ? NULL :
+					  sqlite3IndexAffinityStr(parse->db, pk);
+			sqlite3VdbeAddOp4(v, OP_MakeRecord, reg_pk, pk_len,
+					  reg_key, zAff, pk_len);
+			/* Set flag to save memory allocating one
+			 * by malloc.
+			 */
 			sqlite3VdbeChangeP5(v, 1);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, eph_cursor, reg_key);
 		}
 
-		/* If this DELETE cannot use the ONEPASS strategy, this is the
-		 * end of the WHERE loop
+		/* If this DELETE cannot use the ONEPASS strategy,
+		 * this is the end of the WHERE loop.
 		 */
-		if (eOnePass != ONEPASS_OFF) {
-			addrBypass = sqlite3VdbeMakeLabel(v);
-		} else {
-			sqlite3WhereEnd(pWInfo);
-		}
-
-		/* Unless this is a view, open cursors for the table we are
-		 * deleting from and all its indices. If this is a view, then the
-		 * only effect this statement has is to fire the INSTEAD OF
+		int addr_bypass = 0;
+		if (one_pass != ONEPASS_OFF)
+			addr_bypass = sqlite3VdbeMakeLabel(v);
+		else
+			sqlite3WhereEnd(winfo);
+
+		/* Unless this is a view, open cursors for the
+		 * table we are deleting from and all its indices.
+		 * If this is a view, then the only effect this
+		 * statement has is to fire the INSTEAD OF
 		 * triggers.
 		 */
-		if (!isView) {
+		if (!is_view) {
 			int iAddrOnce = 0;
-			if (eOnePass == ONEPASS_MULTI) {
+			if (one_pass == ONEPASS_MULTI) {
 				iAddrOnce = sqlite3VdbeAddOp0(v, OP_Once);
 				VdbeCoverage(v);
 			}
-			sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite,
-						   OPFLAG_FORDELETE, iTabCur,
-						   aToOpen, &iDataCur,
-						   &iIdxCur,
-						   ON_CONFLICT_ACTION_NONE, 0);
-			assert(pPk || iDataCur == iTabCur);
-			assert(pPk || iIdxCur == iDataCur + 1);
-			if (eOnePass == ONEPASS_MULTI)
+
+			int space_ptr_reg = ++parse->nMem;
+			sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0,
+					     (void *) space);
+			sqlite3VdbeAddOp3(v, OP_OpenWrite, tab_cursor,
+					  table->tnum, space_ptr_reg);
+			sql_vdbe_set_p4_key_def(parse, pk);
+			VdbeComment((v, "%s", pk->zName));
+
+			if (one_pass == ONEPASS_MULTI)
 				sqlite3VdbeJumpHere(v, iAddrOnce);
 		}
 
-		/* Set up a loop over the primary-keys that were found in the
-		 * where-clause loop above.
+		/* Set up a loop over the primary-keys that were
+		 * found in the where-clause loop above.
 		 */
-		if (eOnePass != ONEPASS_OFF) {
-			assert(nKey == nPk);	/* OP_Found will use an unpacked key */
-			if (aToOpen[iDataCur - iTabCur]) {
-				assert(pPk != 0 || pTab->pSelect != 0);
-				sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur,
-						     addrBypass, iKey, nKey);
+		int addr_loop = 0;
+		if (one_pass != ONEPASS_OFF) {
+			/* OP_Found will use an unpacked key. */
+			assert(key_len == pk_len);
+			assert(pk != NULL || table->pSelect != NULL);
+			sqlite3VdbeAddOp4Int(v, OP_NotFound, tab_cursor,
+					     addr_bypass, reg_key, key_len);
 
-				VdbeCoverage(v);
-			}
+			VdbeCoverage(v);
 		} else {
-			addrLoop = sqlite3VdbeAddOp1(v, OP_Rewind, iEphCur);
+			addr_loop = sqlite3VdbeAddOp1(v, OP_Rewind, eph_cursor);
 			VdbeCoverage(v);
-			sqlite3VdbeAddOp2(v, OP_RowData, iEphCur, iKey);
+			sqlite3VdbeAddOp2(v, OP_RowData, eph_cursor, reg_key);
 		}
 
 		/* Delete the row */
-		{
-			int count = (pParse->nested == 0);	/* True to count changes */
-			int iIdxNoSeek = -1;
-			if (bComplex == 0 && aiCurOnePass[1] != iDataCur
-			    /* Tarantool: as far as ONEPASS is disabled, there's no index
-			       w/o need of seeking.  */
-			    && eOnePass != ONEPASS_OFF) {
-				iIdxNoSeek = aiCurOnePass[1];
-			}
-			sqlite3GenerateRowDelete(pParse, pTab, pTrigger,
-						 iDataCur, iIdxCur, iKey, nKey,
-						 count,
-						 ON_CONFLICT_ACTION_DEFAULT,
-						 eOnePass,
-						 iIdxNoSeek);
-		}
+		int idx_noseek = -1;
+		if (!is_complex && one_pass_cur[1] != tab_cursor
+		    /* Tarantool: as far as ONEPASS is disabled,
+		     * there's no index w/o need of seeking.
+		     */
+		    && one_pass != ONEPASS_OFF)
+			idx_noseek = one_pass_cur[1];
+
+		sql_generate_row_delete(parse, table, trigger_list, tab_cursor,
+					reg_key, key_len, parse->nested == 0,
+					ON_CONFLICT_ACTION_DEFAULT, one_pass,
+					idx_noseek);
 
 		/* End of the loop over all primary-keys. */
-		if (eOnePass != ONEPASS_OFF) {
-			sqlite3VdbeResolveLabel(v, addrBypass);
-			sqlite3WhereEnd(pWInfo);
+		if (one_pass != ONEPASS_OFF) {
+			sqlite3VdbeResolveLabel(v, addr_bypass);
+			sqlite3WhereEnd(winfo);
 		} else {
-			sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop + 1);
+			sqlite3VdbeAddOp2(v, OP_Next, eph_cursor,
+					  addr_loop + 1);
 			VdbeCoverage(v);
-			sqlite3VdbeJumpHere(v, addrLoop);
+			sqlite3VdbeJumpHere(v, addr_loop);
 		}
-	}			/* End non-truncate path */
+	}
 
-	/* Return the number of rows that were deleted. If this routine is
-	 * generating code because of a call to sqlite3NestedParse(), do not
-	 * invoke the callback function.
+	/* Return the number of rows that were deleted. If this
+	 * routine is generating code because of a call to
+	 * sqlite3NestedParse(), do not invoke the callback
+	 * function.
 	 */
 	if ((user_session->sql_flags & SQLITE_CountRows) &&
-	    !pParse->nested && !pParse->pTriggerTab) {
-		sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1);
+	    parse->nested == 0 && !parse->pTriggerTab) {
+		sqlite3VdbeAddOp2(v, OP_ResultRow, reg_count, 1);
 		sqlite3VdbeSetNumCols(v, 1);
 		sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted",
 				      SQLITE_STATIC);
 	}
 
  delete_from_cleanup:
-	sqlite3SrcListDelete(db, pTabList);
-	sql_expr_free(db, pWhere, false);
-	sqlite3DbFree(db, aToOpen);
-	return;
+	sqlite3SrcListDelete(db, tab_list);
+	sql_expr_free(db, where, false);
 }
 
-/* Generate VDBE code for
- * DELETE FROM <pTab.z> WHERE
- *        <columns[0]> = <values[0]>
- *    AND  ...
- *    AND <columns[nPairs - 1]> = <values[nPairs - 1]>;
- *
- * This function does not increment the nested counter and is
- * faster than nested parsing of the request above.
- * @param pParse Parser context.
- * @param pTab Table name.
- * @param columns Column names array.
- * @param values Column values array.
- * @param nPairs Length of @columns and @values.
- *
- * In case of error the @values elements are deleted.
- */
 void
-sqlite3DeleteByKey(Parse *pParse, char *zTab, const char **columns,
-		   Expr **values, int nPairs)
+sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
+		    struct Expr **values, int pairs_count)
 {
-	Expr *where = NULL;
-	SrcList *src;
-
-		assert(nPairs > 0);
-	if (pParse->nErr > 0 || pParse->db->mallocFailed)
+	if (parse->nErr > 0 || parse->db->mallocFailed)
 		goto error;
-	src = sql_alloc_src_list(pParse->db);
-	src->a[0].zName = sqlite3DbStrDup(pParse->db, zTab);
+	struct SrcList *src = sql_alloc_src_list(parse->db);
+	src->a[0].zName = sqlite3DbStrDup(parse->db, t_name);
 	if (src == NULL)
 		goto error;
 	/* Dummy init of INDEXED BY clause. */
-	Token t = { NULL, 0, false };
-	sqlite3SrcListIndexedBy(pParse, src, &t);
+	struct Token t = {NULL, 0, false};
+	sqlite3SrcListIndexedBy(parse, src, &t);
 
-	for (int i = 0; i < nPairs; ++i) {
-		Expr *col_expr = sqlite3Expr(pParse->db, TK_ID, columns[i]);
+	struct Expr *where = NULL;
+	assert(pairs_count > 0);
+	for (int i = 0; i < pairs_count; ++i) {
+		struct Expr *col_expr = sqlite3Expr(parse->db, TK_ID, columns[i]);
 		if (col_expr == NULL || values[i] == NULL)
 			goto error;
-		Expr *eq_expr =
-		    sqlite3PExpr(pParse, TK_EQ, col_expr, values[i]);
+		struct Expr *eq_expr =
+		    sqlite3PExpr(parse, TK_EQ, col_expr, values[i]);
 		/* In case of error the values[i] had been deleted in
 		 * sqlite3PExpr already. Do not delete it second time in the
 		 * cycle below.
@@ -626,311 +413,175 @@ sqlite3DeleteByKey(Parse *pParse, char *zTab, const char **columns,
 		if (i == 0) {
 			where = eq_expr;
 		} else {
-			where = sqlite3ExprAnd(pParse->db, where, eq_expr);
+			where = sqlite3ExprAnd(parse->db, where, eq_expr);
 			if (where == NULL)
 				goto error;
 		}
 	}
 	/* DeleteFrom frees the src and exprs in case of error. */
-	sqlite3DeleteFrom(pParse, src, where);
+	sql_table_delete_from(parse, src, where);
 	return;
 
  error:
-	sql_expr_free(pParse->db, where, false);
-	for (int i = 0; i < nPairs; ++i)
-		sql_expr_free(pParse->db, values[i], false);
+	sql_expr_free(parse->db, where, false);
+	for (int i = 0; i < pairs_count; ++i)
+		sql_expr_free(parse->db, values[i], false);
 }
 
-/* Make sure "isView" and other macros defined above are undefined. Otherwise
- * they may interfere with compilation of other functions in this file
- * (or in another file, if this file becomes part of the amalgamation).
- */
-#ifdef isView
-#undef isView
-#endif
-#ifdef pTrigger
-#undef pTrigger
-#endif
-
-/*
- * This routine generates VDBE code that causes a single row of a
- * single table to be deleted.  Both the original table entry and
- * all indices are removed.
- *
- * Preconditions:
- *
- *   1.  iDataCur is an open cursor on the btree that is the canonical data
- *       store for the table.  (This will be the PRIMARY KEY index)
- *
- *   2.  Read/write cursors for all indices of pTab must be open as
- *       cursor number iIdxCur+i for the i-th index.
- *
- *   3.  The primary key for the row to be deleted must be stored in a
- *       sequence of nPk memory cells starting at iPk.  If nPk==0 that means
- *       that a search record formed from OP_MakeRecord is contained in the
- *       single memory location iPk.
- *
- * eMode:
- *   Parameter eMode may be passed either ONEPASS_OFF (0), ONEPASS_SINGLE, or
- *   ONEPASS_MULTI.  If eMode is not ONEPASS_OFF, then the cursor
- *   iDataCur already points to the row to delete. If eMode is ONEPASS_OFF
- *   then this function must seek iDataCur to the entry identified by iPk
- *   and nPk before reading from it.
- *
- *   If eMode is ONEPASS_MULTI, then this call is being made as part
- *   of a ONEPASS delete that affects multiple rows. In this case, if
- *   iIdxNoSeek is a valid cursor number (>=0), then its position should
- *   be preserved following the delete operation. Or, if iIdxNoSeek is not
- *   a valid cursor number, the position of iDataCur should be preserved
- *   instead.
- *
- * iIdxNoSeek:
- *   If iIdxNoSeek is a valid cursor number (>=0), then it identifies an
- *   index cursor (from within array of cursors starting at iIdxCur) that
- *   already points to the index entry to be deleted.
- */
 void
-sqlite3GenerateRowDelete(Parse * pParse,	/* Parsing context */
-			 Table * pTab,		/* Table containing the row to be deleted */
-			 Trigger * pTrigger,	/* List of triggers to (potentially) fire */
-			 int iDataCur,		/* Cursor from which column data is extracted */
-			 int iIdxCur,		/* First index cursor */
-			 int iPk,		/* First memory cell containing the PRIMARY KEY */
-			 i16 nPk,		/* Number of PRIMARY KEY memory cells */
-			 u8 count,		/* If non-zero, increment the row change counter */
-			 enum on_conflict_action onconf,		/* Default ON CONFLICT policy for triggers */
-			 u8 eMode,		/* ONEPASS_OFF, _SINGLE, or _MULTI.  See above */
-			 int iIdxNoSeek)	/* Cursor number of cursor that does not need seeking */
+sql_generate_row_delete(struct Parse *parse, struct Table *table,
+			struct Trigger *trigger_list, int cursor, int reg_pk,
+			short npk, bool is_count,
+			enum on_conflict_action onconf, u8 mode,
+			int idx_noseek)
 {
-	Vdbe *v = pParse->pVdbe;	/* Vdbe */
-	int iOld = 0;		/* First register in OLD.* array */
-	int iLabel;		/* Label resolved to end of generated code */
-
-	/* Vdbe is guaranteed to have been allocated by this stage. */
-	assert(v);
-	(void)iIdxCur;
+	struct Vdbe *v = parse->pVdbe;
+	/* Vdbe is guaranteed to have been allocated by this
+	 * stage.
+	 */
+	assert(v != NULL);
 	VdbeModuleComment((v, "BEGIN: GenRowDel(%d,%d,%d,%d)",
-			   iDataCur, iIdxCur, iPk, (int)nPk));
+			   cursor, iIdxCur, reg_pk, (int)nPk));
 
-	/* Seek cursor iCur to the row to delete. If this row no longer exists
-	 * (this can happen if a trigger program has already deleted it), do
-	 * not attempt to delete it or fire any DELETE triggers.
+	/* Seek cursor iCur to the row to delete. If this row no
+	 * longer exists (this can happen if a trigger program has
+	 * already deleted it), do not attempt to delete it or
+	 * fire any DELETE triggers.
 	 */
-	iLabel = sqlite3VdbeMakeLabel(v);
-	if (eMode == ONEPASS_OFF) {
-		sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, iLabel, iPk, nPk);
+	int label = sqlite3VdbeMakeLabel(v);
+	if (mode == ONEPASS_OFF) {
+		sqlite3VdbeAddOp4Int(v, OP_NotFound, cursor, label, reg_pk, npk);
 		VdbeCoverageIf(v, opSeek == OP_NotFound);
 	}
 
+	int first_old_reg = 0;
 	/* If there are any triggers to fire, allocate a range of registers to
 	 * use for the old.* references in the triggers.
 	 */
-	if (sqlite3FkRequired(pTab, 0) || pTrigger) {
-		u32 mask;	/* Mask of OLD.* columns in use */
-		int iCol;	/* Iterator used while populating OLD.* */
-		int addrStart;	/* Start of BEFORE trigger programs */
-
-		/* TODO: Could use temporary registers here.
-		 */
-		mask =
-		    sqlite3TriggerColmask(pParse, pTrigger, 0, 0,
-					  TRIGGER_BEFORE | TRIGGER_AFTER, pTab,
+	if (sqlite3FkRequired(table, 0) || trigger_list != NULL) {
+		/* Mask of OLD.* columns in use */
+		/* TODO: Could use temporary registers here. */
+		uint32_t mask =
+		    sqlite3TriggerColmask(parse, trigger_list, 0, 0,
+					  TRIGGER_BEFORE | TRIGGER_AFTER, table,
 					  onconf);
-		mask |= sqlite3FkOldmask(pParse, pTab);
-		iOld = pParse->nMem + 1;
-		pParse->nMem += (1 + pTab->nCol);
+		mask |= sqlite3FkOldmask(parse, table);
+		first_old_reg = parse->nMem + 1;
+		parse->nMem += (1 + table->nCol);
 
-		/* Populate the OLD.* pseudo-table register array. These values will be
-		 * used by any BEFORE and AFTER triggers that exist.
+		/* Populate the OLD.* pseudo-table register array.
+		 * These values will be used by any BEFORE and
+		 * AFTER triggers that exist.
 		 */
-		sqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld);
-		for (iCol = 0; iCol < pTab->nCol; iCol++) {
+		sqlite3VdbeAddOp2(v, OP_Copy, reg_pk, first_old_reg);
+		for (int i = 0; i < table->nCol; i++) {
 			testcase(mask != 0xffffffff && iCol == 31);
 			testcase(mask != 0xffffffff && iCol == 32);
 			if (mask == 0xffffffff
-			    || (iCol <= 31 && (mask & MASKBIT32(iCol)) != 0)) {
-				sqlite3ExprCodeGetColumnOfTable(v, pTab,
-								iDataCur, iCol,
-								iOld + iCol +
-								1);
+			    || (i <= 31 && (mask & MASKBIT32(i)) != 0)) {
+				sqlite3ExprCodeGetColumnOfTable(v, table,
+								cursor, i,
+								first_old_reg +
+								i + 1);
 			}
 		}
 
 		/* Invoke BEFORE DELETE trigger programs. */
-		addrStart = sqlite3VdbeCurrentAddr(v);
-		sqlite3CodeRowTrigger(pParse, pTrigger,
-				      TK_DELETE, 0, TRIGGER_BEFORE, pTab, iOld,
-				      onconf, iLabel);
-
-		/* If any BEFORE triggers were coded, then seek the cursor to the
-		 * row to be deleted again. It may be that the BEFORE triggers moved
-		 * the cursor or of already deleted the row that the cursor was
-		 * pointing to.
+		int addr_start = sqlite3VdbeCurrentAddr(v);
+		sqlite3CodeRowTrigger(parse, trigger_list,
+				      TK_DELETE, 0, TRIGGER_BEFORE, table,
+				      first_old_reg, onconf, label);
+
+		/* If any BEFORE triggers were coded, then seek
+		 * the cursor to the row to be deleted again. It
+		 * may be that the BEFORE triggers moved the
+		 * cursor or of already deleted the row that the
+		 * cursor was pointing to.
 		 */
-		if (addrStart < sqlite3VdbeCurrentAddr(v)) {
-			sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, iLabel, iPk,
-					     nPk);
+		if (addr_start < sqlite3VdbeCurrentAddr(v)) {
+			sqlite3VdbeAddOp4Int(v, OP_NotFound, cursor, label,
+					     reg_pk, npk);
 			VdbeCoverageIf(v, opSeek == OP_NotFound);
 		}
 
-		/* Do FK processing. This call checks that any FK constraints that
-		 * refer to this table (i.e. constraints attached to other tables)
-		 * are not violated by deleting this row.
+		/* Do FK processing. This call checks that any FK
+		 * constraints that refer to this table (i.e.
+		 * constraints attached to other tables) are not
+		 * violated by deleting this row.
 		 */
-		sqlite3FkCheck(pParse, pTab, iOld, 0, 0);
+		sqlite3FkCheck(parse, table, first_old_reg, 0, NULL);
 	}
 
-	/* Delete the index and table entries. Skip this step if pTab is really
-	 * a view (in which case the only effect of the DELETE statement is to
-	 * fire the INSTEAD OF triggers).
-	 *
-	 * If variable 'count' is non-zero, then this OP_Delete instruction should
-	 * invoke the update-hook. The pre-update-hook, on the other hand should
-	 * be invoked unless table pTab is a system table. The difference is that
-	 * the update-hook is not invoked for rows removed by REPLACE, but the
-	 * pre-update-hook is.
+	/* Delete the index and table entries. Skip this step if
+	 * table is really a view (in which case the only effect
+	 * of the DELETE statement is to fire the INSTEAD OF
+	 * triggers).
 	 */
-	if (pTab->pSelect == 0) {
-		u8 p5 = 0;
-		/* kyukhin: Tarantool handles indices uypdate automatically.  */
-		/* sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek);  */
-		sqlite3VdbeAddOp2(v, OP_Delete, iDataCur,
-				  (count ? OPFLAG_NCHANGE : 0));
-		if (eMode != ONEPASS_OFF) {
+	if (table->pSelect == NULL) {
+		uint8_t p5 = 0;
+		sqlite3VdbeAddOp2(v, OP_Delete, cursor,
+				  (is_count ? OPFLAG_NCHANGE : 0));
+		if (mode != ONEPASS_OFF)
 			sqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE);
-		}
-		if (iIdxNoSeek >= 0) {
-			sqlite3VdbeAddOp1(v, OP_Delete, iIdxNoSeek);
-		}
-		if (eMode == ONEPASS_MULTI)
+
+		if (idx_noseek >= 0)
+			sqlite3VdbeAddOp1(v, OP_Delete, idx_noseek);
+
+		if (mode == ONEPASS_MULTI)
 			p5 |= OPFLAG_SAVEPOSITION;
 		sqlite3VdbeChangeP5(v, p5);
 	}
 
-	/* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
-	 * handle rows (possibly in other tables) that refer via a foreign key
-	 * to the row just deleted.
+	/* Do any ON CASCADE, SET NULL or SET DEFAULT operations
+	 * required to handle rows (possibly in other tables) that
+	 * refer via a foreign key to the row just deleted.
 	 */
-	sqlite3FkActions(pParse, pTab, 0, iOld, 0);
+	sqlite3FkActions(parse, table, 0, first_old_reg, 0);
 
 	/* Invoke AFTER DELETE trigger programs. */
-	sqlite3CodeRowTrigger(pParse, pTrigger,
-			      TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf,
-			      iLabel);
+	sqlite3CodeRowTrigger(parse, trigger_list,
+			      TK_DELETE, 0, TRIGGER_AFTER, table, first_old_reg,
+			      onconf, label);
 
-	/* Jump here if the row had already been deleted before any BEFORE
-	 * trigger programs were invoked. Or if a trigger program throws a
-	 * RAISE(IGNORE) exception.
+	/* Jump here if the row had already been deleted before
+	 * any BEFORE trigger programs were invoked. Or if a trigger program
+	 * throws a RAISE(IGNORE) exception.
 	 */
-	sqlite3VdbeResolveLabel(v, iLabel);
+	sqlite3VdbeResolveLabel(v, label);
 	VdbeModuleComment((v, "END: GenRowDel()"));
 }
 
-/*
- * This routine generates VDBE code that causes the deletion of all
- * index entries associated with a single row of a single table, pTab
- *
- * Preconditions:
- *
- *   1.  A read/write cursor "iDataCur" must be open on the canonical storage
- *       btree for the table pTab.  (This will be primary key index)
- *
- *   2.  Read/write cursor for primary index of pTab must be open as
- *       cursor number iIdxCur.  (The pTab->pIndex index is the 0-th index.)
- *
- *   3.  The "iDataCur" cursor must be already positioned on the row
- *       that is to be deleted.
- */
-void
-sqlite3GenerateRowIndexDelete(Parse * pParse,	/* Parsing and code generating context */
-			      Table * pTab,	/* Table containing the row to be deleted */
-			      int iDataCur,	/* Cursor of table holding data. */
-			      int iIdxCur)	/* Primary index cursor */
-{
-	int r1 = -1;		/* Register holding an index key */
-	int iPartIdxLabel;	/* Jump destination for skipping partial index entries */
-	Vdbe *v;		/* The prepared statement under construction */
-	Index *pPk;		/* PRIMARY KEY index */
-
-	v = pParse->pVdbe;
-	pPk = sqlite3PrimaryKeyIndex(pTab);
-	/* In Tarantool it is enough to delete row just from pk */
-	VdbeModuleComment((v, "GenRowIdxDel for %s", pPk->zName));
-	r1 = sqlite3GenerateIndexKey(pParse, pPk, iDataCur, 0, &iPartIdxLabel,
-				     NULL, r1);
-	sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur, r1, index_column_count(pPk));
-	sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
-}
-
-/*
- * Generate code that will assemble an index key and stores it in register
- * regOut.  The key with be for index pIdx which is an index on pTab.
- * iCur is the index of a cursor open on the pTab table and pointing to
- * the entry that needs indexing.
- * iCur must be the cursor of the PRIMARY KEY index.
- *
- * Return a register number which is the first in a block of
- * registers that holds the elements of the index key.  The
- * block of registers has already been deallocated by the time
- * this routine returns.
- *
- * If *piPartIdxLabel is not NULL, fill it in with a label and jump
- * to that label if pIdx is a partial index that should be skipped.
- * The label should be resolved using sqlite3ResolvePartIdxLabel().
- * A partial index should be skipped if its WHERE clause evaluates
- * to false or null.  If pIdx is not a partial index, *piPartIdxLabel
- * will be set to zero which is an empty label that is ignored by
- * sqlite3ResolvePartIdxLabel().
- *
- * The pPrior and regPrior parameters are used to implement a cache to
- * avoid unnecessary register loads.  If pPrior is not NULL, then it is
- * a pointer to a different index for which an index key has just been
- * computed into register regPrior.  If the current pIdx index is generating
- * its key into the same sequence of registers and if pPrior and pIdx share
- * a column in common, then the register corresponding to that column already
- * holds the correct value and the loading of that register is skipped.
- * This optimization is helpful when doing a DELETE or an INTEGRITY_CHECK
- * on a table with multiple indices, and especially with
- * the PRIMARY KEY columns of the index.
- */
 int
-sqlite3GenerateIndexKey(Parse * pParse,	/* Parsing context */
-			Index * pIdx,	/* The index for which to generate a key */
-			int iDataCur,	/* Cursor number from which to take column data */
-			int regOut,	/* Put the new key into this register if not 0 */
-			int *piPartIdxLabel,	/* OUT: Jump to this label to skip partial index */
-			Index * pPrior,	/* Previously generated index key */
-			int regPrior)	/* Register holding previous generated key */
+sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
+		       int reg_out, int *part_idx_label, struct Index *prev,
+		       int reg_prev)
 {
-	Vdbe *v = pParse->pVdbe;
-	int j;
-	int regBase;
-	int nCol;
-
-	if (piPartIdxLabel) {
-		if (pIdx->pPartIdxWhere) {
-			*piPartIdxLabel = sqlite3VdbeMakeLabel(v);
-			pParse->iSelfTab = iDataCur;
-			sqlite3ExprCachePush(pParse);
-			sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere,
-					      *piPartIdxLabel,
+	Vdbe *v = parse->pVdbe;
+
+	if (part_idx_label != NULL) {
+		if (index->pPartIdxWhere) {
+			*part_idx_label = sqlite3VdbeMakeLabel(v);
+			parse->iSelfTab = cursor;
+			sqlite3ExprCachePush(parse);
+			sqlite3ExprIfFalseDup(parse, index->pPartIdxWhere,
+					      *part_idx_label,
 					      SQLITE_JUMPIFNULL);
 		} else {
-			*piPartIdxLabel = 0;
+			*part_idx_label = 0;
 		}
 	}
-	nCol = index_column_count(pIdx);
-	regBase = sqlite3GetTempRange(pParse, nCol);
-	if (pPrior && (regBase != regPrior || pPrior->pPartIdxWhere))
-		pPrior = 0;
-	for (j = 0; j < nCol; j++) {
-		if (pPrior && pPrior->aiColumn[j] == pIdx->aiColumn[j]
-		    && pPrior->aiColumn[j] != XN_EXPR) {
+	int col_cnt = index_column_count(index);
+	int reg_base = sqlite3GetTempRange(parse, col_cnt);
+	if (prev != NULL && (reg_base != reg_prev || prev->pPartIdxWhere))
+		prev = NULL;
+	for (int j = 0; j < col_cnt; j++) {
+		if (prev != NULL && prev->aiColumn[j] == index->aiColumn[j]
+		    && prev->aiColumn[j] != XN_EXPR) {
 			/* This column was already computed by the previous index */
 			continue;
 		}
-		sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j,
-					       regBase + j);
+		sqlite3ExprCodeLoadIndexColumn(parse, index, cursor, j,
+					       reg_base + j);
 		/* If the column affinity is REAL but the number is an integer, then it
 		 * might be stored in the table as an integer (using a compact
 		 * representation) then converted to REAL by an OP_RealAffinity opcode.
@@ -940,23 +591,18 @@ sqlite3GenerateIndexKey(Parse * pParse,	/* Parsing context */
 		 */
 		sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity);
 	}
-	if (regOut) {
-		sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut);
-	}
-	sqlite3ReleaseTempRange(pParse, regBase, nCol);
-	return regBase;
+	if (reg_out)
+		sqlite3VdbeAddOp3(v, OP_MakeRecord, reg_base, col_cnt, reg_out);
+
+	sqlite3ReleaseTempRange(parse, reg_base, col_cnt);
+	return reg_base;
 }
 
-/*
- * If a prior call to sqlite3GenerateIndexKey() generated a jump-over label
- * because it was a partial index, then this routine should be called to
- * resolve that label.
- */
 void
-sqlite3ResolvePartIdxLabel(Parse * pParse, int iLabel)
+sql_resolve_part_idx_label(struct Parse *parse, int label)
 {
-	if (iLabel) {
-		sqlite3VdbeResolveLabel(pParse->pVdbe, iLabel);
-		sqlite3ExprCachePop(pParse);
+	if (label != 0) {
+		sqlite3VdbeResolveLabel(parse->pVdbe, label);
+		sqlite3ExprCachePop(parse);
 	}
 }
diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index 60b4786..a21dd2d 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -784,7 +784,8 @@ sqlite3FkDropTable(Parse * pParse, SrcList * pName, Table * pTab)
 		pParse->disableTriggers = 1;
 		/* Staring new transaction before DELETE FROM <tbl> */
 		sqlite3VdbeAddOp0(v, OP_TTransaction);
-		sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0);
+		sql_table_delete_from(pParse, sqlite3SrcListDup(db, pName, 0),
+				      NULL);
 		pParse->disableTriggers = 0;
 
 		/* If the DELETE has generated immediate foreign key constraint
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 1ad7469..2d49296 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -379,7 +379,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 	zTab = pTabList->a[0].zName;
 	if (NEVER(zTab == 0))
 		goto insert_cleanup;
-	pTab = sqlite3SrcListLookup(pParse, pTabList);
+	pTab = sql_list_lookup_table(pParse, pTabList);
 	if (pTab == 0) {
 		goto insert_cleanup;
 	}
@@ -406,13 +406,13 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 	/* If pTab is really a view, make sure it has been initialized.
 	 * ViewGetColumnNames() is a no-op if pTab is not a view.
 	 */
-	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
+	if (sqlite3ViewGetColumnNames(pParse, pTab))
 		goto insert_cleanup;
-	}
 
-	/* Cannot insert into a read-only table.
-	 */
-	if (sqlite3IsReadOnly(pParse, pTab, tmask)) {
+	/* Cannot insert into a read-only table. */
+	if (isView && tmask == 0) {
+		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
+				pTab->zName);
 		goto insert_cleanup;
 	}
 
@@ -1511,13 +1511,13 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 								 TK_DELETE, 0,
 								 0);
 				}
-				sqlite3GenerateRowDelete(pParse, pTab, pTrigger,
-							 iDataCur, iIdxCur,
-							 regR, nPkField, 0,
-							 ON_CONFLICT_ACTION_REPLACE,
-							 (pIdx ==
-							  pPk ? ONEPASS_SINGLE :
-							  ONEPASS_OFF), -1);
+				sql_generate_row_delete(pParse, pTab, pTrigger,
+							iDataCur,
+							regR, nPkField, 0,
+							ON_CONFLICT_ACTION_REPLACE,
+							(pIdx ==
+							 pPk ? ONEPASS_SINGLE :
+							 ONEPASS_OFF), -1);
 				seenReplace = 1;
 				break;
 			}
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 872647d..9343c0e 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -738,7 +738,7 @@ cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W). {
   sqlSubProgramsRemaining = SQL_MAX_COMPILING_TRIGGERS;
   /* Instruct SQL to initate Tarantool's transaction.  */
   pParse->initiateTTrans = true;
-  sqlite3DeleteFrom(pParse,X,W);
+  sql_table_delete_from(pParse,X,W);
 }
 %endif
 
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index e056d63..6f23859 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1994,7 +1994,6 @@ sql_space_tuple_log_count(struct Table *tab);
 /*
  * Allowed values for Table.tabFlags.
  */
-#define TF_Readonly        0x01	/* Read-only system table */
 #define TF_Ephemeral       0x02	/* An ephemeral table */
 #define TF_HasPrimaryKey   0x04	/* Table has a primary key */
 #define TF_Autoincrement   0x08	/* Integer primary key is autoincrement */
@@ -3671,15 +3670,69 @@ int sqlite3Select(Parse *, Select *, SelectDest *);
 Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
 			 Expr *, ExprList *, u32, Expr *, Expr *);
 void sqlite3SelectDelete(sqlite3 *, Select *);
-Table *sqlite3SrcListLookup(Parse *, SrcList *);
+
+/**
+ * While a SrcList can in general represent multiple tables and
+ * subqueries (as in the FROM clause of a SELECT statement) in
+ * this case it contains the name of a single table, as one might
+ * find in an INSERT, DELETE, or UPDATE statement.  Look up that
+ * table in the symbol table and return a pointer.  Set an error
+ * message and return NULL if the table name is not found or if
+ * any other error occurs.
+ *
+ * The following fields are initialized appropriate in src_list:
+ *
+ *    pSrc->a[0].pTab       Pointer to the Table object.
+ *    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if
+ *                          there is one.
+ *
+ * @param parse Parsing context.
+ * @param src_list List containing single table element.
+ * @retval Table object if found, NULL oterwise.
+ */
+struct Table *
+sql_list_lookup_table(struct Parse *parse, SrcList *src_list);
+
 int sqlite3IsReadOnly(Parse *, Table *, int);
 void sqlite3OpenTable(Parse *, int iCur, Table *, int);
-#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
-Expr *sqlite3LimitWhere(Parse *, SrcList *, Expr *, ExprList *, Expr *, Expr *,
-			char *);
-#endif
-void sqlite3DeleteFrom(Parse *, SrcList *, Expr *);
-void sqlite3DeleteByKey(Parse *, char *, const char **, Expr **, int);
+/**
+ * Generate code for a DELETE FROM statement.
+ *
+ *     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
+ *                 \________/       \________________/
+ *                  pTabList              pWhere
+ *
+ * @param parse Parsing context.
+ * @param tab_list List of single element which table from which
+ * deletetion if performed.
+ * @param where The WHERE clause.  May be NULL.
+ */
+void
+sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
+		      struct Expr *where);
+
+/**
+ * Generate VDBE code for
+ * DELETE FROM <t_name> WHERE
+ *        <columns[0]> = <values[0]>
+ *    AND  ...
+ *    AND <columns[nPairs - 1]> = <values[nPairs - 1]>;
+ *
+ * This function does not increment the nested counter and is
+ * faster than nested parsing of the request above.
+ *
+ * @param parse Parser context.
+ * @param t_name Table name.
+ * @param columns Column names array.
+ * @param values Column values array.
+ * @param pairs_count Length of @columns and @values.
+ *
+ * In case of error the @values elements are deleted.
+ */
+void
+sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
+		    struct Expr **values, int pairs_count);
+
 void sqlite3Update(Parse *, SrcList *, ExprList *, Expr *,
 		   enum on_conflict_action);
 WhereInfo *sqlite3WhereBegin(Parse *, SrcList *, Expr *, ExprList *, ExprList *,
@@ -3782,11 +3835,121 @@ int sqlite3ExprContainsSubquery(Expr *);
 int sqlite3ExprIsInteger(Expr *, int *);
 int sqlite3ExprCanBeNull(const Expr *);
 int sqlite3ExprNeedsNoAffinityChange(const Expr *, char);
-void sqlite3GenerateRowDelete(Parse *, Table *, Trigger *, int, int, int, i16,
-			      u8, enum on_conflict_action, u8, int);
-void sqlite3GenerateRowIndexDelete(Parse *, Table *, int, int);
-int sqlite3GenerateIndexKey(Parse *, Index *, int, int, int *, Index *, int);
-void sqlite3ResolvePartIdxLabel(Parse *, int);
+
+/**
+ * This routine generates VDBE code that causes a single row of a
+ * single table to be deleted.  Both the original table entry and
+ * all indices are removed.
+ *
+ * Preconditions:
+ *
+ *   1.  cursor is an open cursor on the btree that is the
+ *       canonical data store for the table.  (This will be the
+ *       PRIMARY KEY index)
+ *
+ *   2.  The primary key for the row to be deleted must be stored
+ *       in a sequence of nPk memory cells starting at iPk. If
+ *       nPk==0 that means that a search record formed from
+ *       OP_MakeRecord is contained in the single memory location
+ *       iPk.
+ *
+ * mode:
+ *   Parameter mode may be passed either ONEPASS_OFF (0),
+ *   ONEPASS_SINGLE, or ONEPASS_MULTI.  If eMode is not
+ *   ONEPASS_OFF, then the cursor already points to the row to
+ *   delete. If eMode is ONEPASS_OFF then this function must seek
+ *   cursor to the entry identified by ipk and npk before reading
+ *   from it.
+ *
+ *   If mode is ONEPASS_MULTI, then this call is being made as
+ *   part of a ONEPASS delete that affects multiple rows. In this
+ *   case, if idx_noseek is a valid cursor number (>=0), then its
+ *   position should be preserved following the delete operation.
+ *   Or, if iIdxNoSeek is not a valid cursor number, the position
+ *   of cursor should be preserved instead.
+ *
+ * idx_noseek:
+ *   If idx_noseek is a valid cursor number (>=0), then it
+ *   identifies an index cursor that already points to the index
+ *   entry to be deleted.
+ *
+ * @param parse Parsing context.
+ * @param table Table containing the row to be deleted.
+ * @param trigger_list List of triggers to (potentially) fire.
+ * @param cursor Cursor from which column data is extracted/
+ * @param ipk First memory cell containing the PRIMARY KEY.
+ * @param npk umber of PRIMARY KEY memory cells.
+ * @param is_count If non-zero, increment the row change counter.
+ * @param onconf Default ON CONFLICT policy for triggers.
+ * @param mode ONEPASS_OFF, _SINGLE, or _MULTI.  See above.
+ * @param idx_noseek Cursor number of cursor that does not need
+          seeking.
+ */
+void
+sql_generate_row_delete(struct Parse *parse, struct Table *table,
+			struct Trigger *trigger_list, int cursor, int ipk,
+			short npk, bool is_count,
+			enum on_conflict_action onconf, u8 eMode,
+			int idx_noseek);
+
+/**
+ * Generate code that will assemble an index key and stores it in
+ * register reg_out.  The key with be for index pIdx which is an
+ * index on table. cursor is the index of a cursor open on the
+ * table table and pointing to the entry that needs indexing.
+ * cursor must be the cursor of the PRIMARY KEY index.
+ *
+ * Return a register number which is the first in a block of
+ * registers that holds the elements of the index key.  The
+ * block of registers has already been deallocated by the time
+ * this routine returns.
+ *
+ * If *part_idx_label is not NULL, fill it in with a label and
+ * jump to that label if pIdx is a partial index that should be
+ * skipped. The label should be resolved using
+ * sql_resolve_part_idx_label(). A partial index should be skipped
+ * if its WHERE clause evaluates to false or null.  If index is
+ * not a partial index, *piPartIdxLabel will be set to zero which
+ * is an empty label that is ignored by sql_resolve_part_idx_label().
+ *
+ * The pPrior and regPrior parameters are used to implement a
+ * cache to avoid unnecessary register loads.  If prev is not
+ * NULL, then it is a pointer to a different index for which an
+ * index key has just been computed into register reg_prev. If the
+ * current pIdx index is generating its key into the same
+ * sequence of registers and if prev and index share a column in
+ * common, then the register corresponding to that column already
+ * holds the correct value and the loading of that register is
+ * skipped. This optimization is helpful when doing a DELETE or
+ * an INTEGRITY_CHECK on a table with multiple indices, and
+ * especially with the PRIMARY KEY columns of the index.
+ *
+ * @param parse Parsing context.
+ * @param index The index for which to generate a key.
+ * @param cursor Cursor number from which to take column data.
+ * @param reg_out Put the new key into this register if not NULL.
+ * @param[out] part_idx_label Jump to this label to skip partial
+ *        index.
+ * @param prev Previously generated index key
+ * @param reg_prev Register holding previous generated key.
+ * @retval Register containing new record
+ */
+int
+sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
+		       int reg_out, int *part_idx_label, struct Index *prev,
+		       int reg_prev);
+
+/**
+ * If a prior call to sqlite3GenerateIndexKey() generated a
+ * jump-over label because it was a partial index, then this
+ * routine should be called to resolve that label.
+ *
+ * @param parse Parsing context.
+ * @param label Label to resolve.
+ */
+void
+sql_resolve_part_idx_label(struct Parse *parse, int label);
+
 void sqlite3GenerateConstraintChecks(Parse *, Table *, int *, int, int, int,
 				     int, u8, struct on_conflict *, int, int *,
 				     int *);
@@ -3831,9 +3994,20 @@ int sqlite3SafetyCheckOk(sqlite3 *);
 int sqlite3SafetyCheckSickOrOk(sqlite3 *);
 void sqlite3ChangeCookie(Parse *);
 
-#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-void sqlite3MaterializeView(Parse *, Table *, Expr *, int);
-#endif
+/**
+ * Evaluate a view and store its result in an ephemeral table.
+ * The where argument is an optional WHERE clause that restricts
+ * the set of rows in the view that are to be added to the
+ * ephemeral table.
+ *
+ * @param parse Parsing context.
+ * @param name View name.
+ * @param where Option WHERE clause to be added.
+ * @param cursor Cursor number for ephemeral table.
+ */
+void
+sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
+		     int cursor);
 
 #ifndef SQLITE_OMIT_TRIGGER
 void sqlite3BeginTrigger(Parse *, Token *, int, int, IdList *, SrcList *,
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index 28c56db..e6d9925 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -111,7 +111,7 @@ sqlite3BeginTrigger(Parse * pParse,	/* The parse context of the CREATE TRIGGER s
 	if (sqlite3FixSrcList(&sFix, pTableName)) {
 		goto trigger_cleanup;
 	}
-	pTab = sqlite3SrcListLookup(pParse, pTableName);
+	pTab = sql_list_lookup_table(pParse, pTableName);
 	if (!pTab) {
 		goto trigger_cleanup;
 	}
@@ -744,11 +744,11 @@ codeTriggerProgram(Parse * pParse,	/* The parser context */
 				break;
 			}
 		case TK_DELETE:{
-				sqlite3DeleteFrom(pParse,
-						  targetSrcList(pParse, pStep),
-						  sqlite3ExprDup(db,
-								 pStep->pWhere,
-								 0)
+				sql_table_delete_from(pParse,
+						      targetSrcList(pParse, pStep),
+						      sqlite3ExprDup(db,
+								     pStep->pWhere,
+								     0)
 				    );
 				break;
 			}
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index de1349b..60cb229 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -163,7 +163,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 
 	/* Locate the table which we want to update.
 	 */
-	pTab = sqlite3SrcListLookup(pParse, pTabList);
+	pTab = sql_list_lookup_table(pParse, pTabList);
 	if (pTab == 0)
 		goto update_cleanup;
 
@@ -187,7 +187,9 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
 		goto update_cleanup;
 	}
-	if (sqlite3IsReadOnly(pParse, pTab, tmask)) {
+	if (isView && tmask == 0) {
+		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
+				pTab->zName);
 		goto update_cleanup;
 	}
 
@@ -324,7 +326,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	 */
 #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
 	if (isView) {
-		sqlite3MaterializeView(pParse, pTab, pWhere, iDataCur);
+		sql_materialize_view(pParse, pTab->zName, pWhere, iDataCur);
 		/* Number of columns from SELECT plus ID.*/
 		nKey = pTab->nCol + 1;
 	}
@@ -603,7 +605,6 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 						     nKey);
 			VdbeCoverageNeverTaken(v);
 		}
-		sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur);
 
 		/* If changing the PK value, or if there are foreign key constraints
 		 * to process, delete the old record. Otherwise, add a noop OP_Delete
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index b8d4c1a..2ad6f3e 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -3236,20 +3236,20 @@ case OP_OpenTEphemeral: {
 	BtCursor *pBtCur;
 	assert(pOp->p1 >= 0);
 	assert(pOp->p2 > 0);
-	assert(pOp->p4type != P4_KEYDEF || pOp->p4.key_def != NULL);
+	assert(pOp->p4type == P4_KEYDEF);
 
 	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
 	if (pCx == 0) goto no_mem;
 	pCx->nullRow = 1;
 
-	pCx->key_def  = pOp->p4.key_def;
 	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,
-					     pCx->key_def);
+					     pOp->p4.key_def);
+	pCx->key_def = pCx->uc.pCursor->index->def->key_def;
 	if (rc) goto abort_due_to_error;
 	break;
 }
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index e67fcae..4a8a9d2 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1134,7 +1134,7 @@ sqlite3VdbeChangeP4(Vdbe * p, int addr, const char *zP4, int n)
 	} if (n == P4_BOOL) {
 		pOp->p4.b = *(bool*)zP4;
 		pOp->p4type = P4_BOOL;
-	} else if (zP4 != 0) {
+	} else {
 		assert(n < 0);
 		pOp->p4.p = (void *)zP4;
 		pOp->p4type = (signed char)n;
@@ -1571,7 +1571,10 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
 #endif
 	case P4_COLLSEQ:{
 			struct coll *pColl = pOp->p4.pColl;
-			sqlite3XPrintf(&x, "(%.20s)", pColl->name);
+			if (pColl != NULL)
+				sqlite3XPrintf(&x, "(%.20s)", pColl->name);
+			else
+				sqlite3XPrintf(&x, "(binary)");
 			break;
 		}
 	case P4_FUNCDEF:{
-- 
2.16.2

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

* [tarantool-patches] Re: [PATCH 2/2] sql: refactor delete routines
  2018-05-16 16:29   ` [tarantool-patches] " Kirill Yukhin
@ 2018-05-17 14:23     ` Vladislav Shpilevoy
  2018-05-17 15:48       ` Kirill Yukhin
  2018-05-17 15:18     ` Vladislav Shpilevoy
  1 sibling, 1 reply; 17+ messages in thread
From: Vladislav Shpilevoy @ 2018-05-17 14:23 UTC (permalink / raw)
  To: tarantool-patches, Kirill Yukhin

Hello. Thanks for the patch! I can not build, wonder travis is ok.
Maybe Mac travis uses gcc instead of clang?

See my 20 comments below.

/Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:482:6: error: variable 'where' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized]
         if (src == NULL)
             ^~~~~~~~~~~
/Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:516:27: note: uninitialized use occurs here
         sql_expr_free(parse->db, where, false);
                                  ^~~~~
/Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:482:2: note: remove the 'if' if its condition is always false
         if (src == NULL)
         ^~~~~~~~~~~~~~~~
/Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:478:6: error: variable 'where' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized]
         if (parse->nErr > 0 || parse->db->mallocFailed)
             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:516:27: note: uninitialized use occurs here
         sql_expr_free(parse->db, where, false);
                                  ^~~~~
/Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:478:2: note: remove the 'if' if its condition is always false
         if (parse->nErr > 0 || parse->db->mallocFailed)
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:478:6: error: variable 'where' is used uninitialized whenever '||' condition is true [-Werror,-Wsometimes-uninitialized]
         if (parse->nErr > 0 || parse->db->mallocFailed)
             ^~~~~~~~~~~~~~~
/Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:516:27: note: uninitialized use occurs here
         sql_expr_free(parse->db, where, false);
                                  ^~~~~
/Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:478:6: note: remove the '||' if its condition is always false
         if (parse->nErr > 0 || parse->db->mallocFailed)
             ^~~~~~~~~~~~~~~~~~
/Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:488:2: note: variable 'where' is declared here
         struct Expr *where = NULL;
         ^

On 16/05/2018 19:29, Kirill Yukhin wrote:
> Hi Vlad,
> On 16 мая 18:24, Kirill Yukhin wrote:
>> Refactor DELETE FROM statements translation, update
> I've removed one more dead routine. Patch is in the bottom.
> 
> --
> Regards, Kirill Yukhin
> 
> sql: refactor SQL delete routines
> 
> Refactor DELETE FROM statements translation, update
> all live routines according to Tarantool's coding style.
> Use key_def instead of Table* where possible.
> Remove useless index delete routine as well.
> 
> Part of #3235
> ---
>   src/box/sql/build.c     |    6 +-
>   src/box/sql/delete.c    | 1136 ++++++++++++++++-------------------------------
>   src/box/sql/fkey.c      |    3 +-
>   src/box/sql/insert.c    |   26 +-
>   src/box/sql/parse.y     |    2 +-
>   src/box/sql/sqliteInt.h |  206 ++++++++-
>   src/box/sql/trigger.c   |   12 +-
>   src/box/sql/update.c    |    9 +-
>   src/box/sql/vdbe.c      |    6 +-
>   src/box/sql/vdbeaux.c   |    7 +-
>   10 files changed, 619 insertions(+), 794 deletions(-)
>
> diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
> index 3455f52..0c77057 100644
> --- a/src/box/sql/delete.c
> +++ b/src/box/sql/delete.c
> -Table *
> -sqlite3SrcListLookup(Parse * pParse, SrcList * pSrc)
> +struct Table *
> +sql_list_lookup_table(struct Parse *parse, SrcList *src_list)
>   {

> +	struct SrcList_item *item = src_list->a;
> +	struct Table *table;
> +	assert(item != NULL && src_list->nSrc == 1);
> +	table = sqlite3LocateTable(parse, 0, item->zName);
> +	sqlite3DeleteTable(parse->db, item->pTab);
> +	item->pTab = table;
> +	if (table != NULL)
> +		table->nTabRef++;
> +	if (sqlite3IndexedByLookup(parse, item))
> +		table = NULL;

1. What about --nTabRef; ? sqlite3IndexedByLookup does not unref
table. Looks like original SQLite bug.
> +sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
> +		    struct Expr **values, int pairs_count)
>   {
> -	Expr *where = NULL;
> -	SrcList *src;
> -
> -		assert(nPairs > 0);
> -	if (pParse->nErr > 0 || pParse->db->mallocFailed)
> +	if (parse->nErr > 0 || parse->db->mallocFailed)
>   		goto error;
> -	src = sql_alloc_src_list(pParse->db);
> -	src->a[0].zName = sqlite3DbStrDup(pParse->db, zTab);
> +	struct SrcList *src = sql_alloc_src_list(parse->db);
> +	src->a[0].zName = sqlite3DbStrDup(parse->db, t_name);
>   	if (src == NULL)

2. Maybe first check src on NULL and then use it? Looks like the second SQLite bug.

>   
> -/* Make sure "isView" and other macros defined above are undefined. Otherwise
> - * they may interfere with compilation of other functions in this file
> - * (or in another file, if this file becomes part of the amalgamation).
> - */
> -#ifdef isView
> -#undef isView
> -#endif
> -#ifdef pTrigger
> -#undef pTrigger
> -#endif
> -
> -/*
> - * This routine generates VDBE code that causes a single row of a
> - * single table to be deleted.  Both the original table entry and
> - * all indices are removed.
> - *
> - * Preconditions:
> - *
> - *   1.  iDataCur is an open cursor on the btree that is the canonical data
> - *       store for the table.  (This will be the PRIMARY KEY index)
> - *
> - *   2.  Read/write cursors for all indices of pTab must be open as
> - *       cursor number iIdxCur+i for the i-th index.
> - *
> - *   3.  The primary key for the row to be deleted must be stored in a
> - *       sequence of nPk memory cells starting at iPk.  If nPk==0 that means
> - *       that a search record formed from OP_MakeRecord is contained in the
> - *       single memory location iPk.
> - *
> - * eMode:
> - *   Parameter eMode may be passed either ONEPASS_OFF (0), ONEPASS_SINGLE, or
> - *   ONEPASS_MULTI.  If eMode is not ONEPASS_OFF, then the cursor
> - *   iDataCur already points to the row to delete. If eMode is ONEPASS_OFF
> - *   then this function must seek iDataCur to the entry identified by iPk
> - *   and nPk before reading from it.
> - *
> - *   If eMode is ONEPASS_MULTI, then this call is being made as part
> - *   of a ONEPASS delete that affects multiple rows. In this case, if
> - *   iIdxNoSeek is a valid cursor number (>=0), then its position should
> - *   be preserved following the delete operation. Or, if iIdxNoSeek is not
> - *   a valid cursor number, the position of iDataCur should be preserved
> - *   instead.
> - *
> - * iIdxNoSeek:
> - *   If iIdxNoSeek is a valid cursor number (>=0), then it identifies an
> - *   index cursor (from within array of cursors starting at iIdxCur) that
> - *   already points to the index entry to be deleted.
> - */
>   void
> -sqlite3GenerateRowDelete(Parse * pParse,	/* Parsing context */
> -			 Table * pTab,		/* Table containing the row to be deleted */
> -			 Trigger * pTrigger,	/* List of triggers to (potentially) fire */
> -			 int iDataCur,		/* Cursor from which column data is extracted */
> -			 int iIdxCur,		/* First index cursor */
> -			 int iPk,		/* First memory cell containing the PRIMARY KEY */
> -			 i16 nPk,		/* Number of PRIMARY KEY memory cells */
> -			 u8 count,		/* If non-zero, increment the row change counter */
> -			 enum on_conflict_action onconf,		/* Default ON CONFLICT policy for triggers */
> -			 u8 eMode,		/* ONEPASS_OFF, _SINGLE, or _MULTI.  See above */
> -			 int iIdxNoSeek)	/* Cursor number of cursor that does not need seeking */
> +sql_generate_row_delete(struct Parse *parse, struct Table *table,
> +			struct Trigger *trigger_list, int cursor, int reg_pk,

3. I think, it is time to invent a standard name for cursors: I see, that both 'cursor' and 'reg_pk'
are cursors, but their names differs very. My proposal: each cursor variable must end with '_cursor'.

So data_cursor and pk_cursor instead of cursor and reg_pk.

> +			short npk, bool is_count,

4. is_count -> need_update_count. This flag means, that global nChanges must be updated.

> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> index e056d63..6f23859 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -3671,15 +3670,69 @@ int sqlite3Select(Parse *, Select *, SelectDest *);
>   Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
>   			 Expr *, ExprList *, u32, Expr *, Expr *);
>   void sqlite3SelectDelete(sqlite3 *, Select *);
> -Table *sqlite3SrcListLookup(Parse *, SrcList *);
> +
> +/**
> + * While a SrcList can in general represent multiple tables and
> + * subqueries (as in the FROM clause of a SELECT statement) in
> + * this case it contains the name of a single table, as one might
> + * find in an INSERT, DELETE, or UPDATE statement.  Look up that
> + * table in the symbol table and return a pointer.  Set an error
> + * message and return NULL if the table name is not found or if
> + * any other error occurs.
> + *
> + * The following fields are initialized appropriate in src_list:
> + *
> + *    pSrc->a[0].pTab       Pointer to the Table object.

5. No pSrc anymore.

> + *    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if
> + *                          there is one.
> + *
> + * @param parse Parsing context.
> + * @param src_list List containing single table element.
> + * @retval Table object if found, NULL oterwise.
> + */
> +struct Table *
> +sql_list_lookup_table(struct Parse *parse, SrcList *src_list);
> +
>   int sqlite3IsReadOnly(Parse *, Table *, int);

6. This function has no implementation.

>   void sqlite3OpenTable(Parse *, int iCur, Table *, int);
> -#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
> -Expr *sqlite3LimitWhere(Parse *, SrcList *, Expr *, ExprList *, Expr *, Expr *,
> -			char *);
> -#endif
> -void sqlite3DeleteFrom(Parse *, SrcList *, Expr *);

7. This function still exists in parse.y, and has no implementation.

> -void sqlite3DeleteByKey(Parse *, char *, const char **, Expr **, int);
> +/**
> + * Generate code for a DELETE FROM statement.
> + *
> + *     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
> + *                 \________/       \________________/
> + *                  pTabList              pWhere

8. No pTabList, no pWhere.

> + *
> + * @param parse Parsing context.
> + * @param tab_list List of single element which table from which
> + * deletetion if performed.
> + * @param where The WHERE clause.  May be NULL.
> + */
> +void
> +sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
> +		      struct Expr *where);
> +
> +/**
> + * Generate VDBE code for
> + * DELETE FROM <t_name> WHERE
> + *        <columns[0]> = <values[0]>
> + *    AND  ...
> + *    AND <columns[nPairs - 1]> = <values[nPairs - 1]>;

9. No nPairs.

> + *
> + * This function does not increment the nested counter and is
> + * faster than nested parsing of the request above.
> + *
> + * @param parse Parser context.
> + * @param t_name Table name.
> + * @param columns Column names array.
> + * @param values Column values array.
> + * @param pairs_count Length of @columns and @values.
> + *
> + * In case of error the @values elements are deleted.
> + */
> +void
> +sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
> +		    struct Expr **values, int pairs_count);
> +
>   void sqlite3Update(Parse *, SrcList *, ExprList *, Expr *,
>   		   enum on_conflict_action);
>   WhereInfo *sqlite3WhereBegin(Parse *, SrcList *, Expr *, ExprList *, ExprList *,
> @@ -3782,11 +3835,121 @@ int sqlite3ExprContainsSubquery(Expr *);
>   int sqlite3ExprIsInteger(Expr *, int *);
>   int sqlite3ExprCanBeNull(const Expr *);
>   int sqlite3ExprNeedsNoAffinityChange(const Expr *, char);
> -void sqlite3GenerateRowDelete(Parse *, Table *, Trigger *, int, int, int, i16,
> -			      u8, enum on_conflict_action, u8, int);
> -void sqlite3GenerateRowIndexDelete(Parse *, Table *, int, int);
> -int sqlite3GenerateIndexKey(Parse *, Index *, int, int, int *, Index *, int);

10. Still exists in where.c and sqliteInt.h.

> -void sqlite3ResolvePartIdxLabel(Parse *, int);
> +
> +/**
> + * This routine generates VDBE code that causes a single row of a
> + * single table to be deleted.  Both the original table entry and
> + * all indices are removed.
> + *
> + * Preconditions:
> + *
> + *   1.  cursor is an open cursor on the btree that is the
> + *       canonical data store for the table.  (This will be the
> + *       PRIMARY KEY index)
> + *
> + *   2.  The primary key for the row to be deleted must be stored
> + *       in a sequence of nPk memory cells starting at iPk. If

11. No iPk, no nPk.

> + *       nPk==0 that means that a search record formed from
> + *       OP_MakeRecord is contained in the single memory location
> + *       iPk.
> + *
> + * mode:
> + *   Parameter mode may be passed either ONEPASS_OFF (0),
> + *   ONEPASS_SINGLE, or ONEPASS_MULTI.  If eMode is not

12. No eMode.

> + *   ONEPASS_OFF, then the cursor already points to the row to
> + *   delete. If eMode is ONEPASS_OFF then this function must seek
> + *   cursor to the entry identified by ipk and npk before reading
> + *   from it.
> + *
> + *   If mode is ONEPASS_MULTI, then this call is being made as
> + *   part of a ONEPASS delete that affects multiple rows. In this
> + *   case, if idx_noseek is a valid cursor number (>=0), then its
> + *   position should be preserved following the delete operation.
> + *   Or, if iIdxNoSeek is not a valid cursor number, the position

13. No iIdxNoSeek. Same parameter name mismatches in next functions.

> + *   of cursor should be preserved instead.
> + *
> + * idx_noseek:
> + *   If idx_noseek is a valid cursor number (>=0), then it
> + *   identifies an index cursor that already points to the index
> + *   entry to be deleted.

14. I think, it can be moved into @param idx_noseek description. (That
will be named idx_noseek_cursor).

> + *
> + * @param parse Parsing context.
> + * @param table Table containing the row to be deleted.
> + * @param trigger_list List of triggers to (potentially) fire.
> + * @param cursor Cursor from which column data is extracted/
> + * @param ipk First memory cell containing the PRIMARY KEY.
> + * @param npk umber of PRIMARY KEY memory cells.
> + * @param is_count If non-zero, increment the row change counter.

15. need_update_count.

> + * @param onconf Default ON CONFLICT policy for triggers.
> + * @param mode ONEPASS_OFF, _SINGLE, or _MULTI.  See above.
> + * @param idx_noseek Cursor number of cursor that does not need
> +          seeking.
> + */
> +void
> +sql_generate_row_delete(struct Parse *parse, struct Table *table,
> +			struct Trigger *trigger_list, int cursor, int ipk,
> +			short npk, bool is_count,
> +			enum on_conflict_action onconf, u8 eMode,
> +			int idx_noseek);
> +
> diff --git a/src/box/sql/update.c b/src/box/sql/update.c
> index de1349b..60cb229 100644
> --- a/src/box/sql/update.c
> +++ b/src/box/sql/update.c
> @@ -163,7 +163,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>   
>   	/* Locate the table which we want to update.
>   	 */
> -	pTab = sqlite3SrcListLookup(pParse, pTabList);
> +	pTab = sql_list_lookup_table(pParse, pTabList);
>   	if (pTab == 0)
>   		goto update_cleanup;
>   
> @@ -187,7 +187,9 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>   	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
>   		goto update_cleanup;
>   	}
> -	if (sqlite3IsReadOnly(pParse, pTab, tmask)) {
> +	if (isView && tmask == 0) {
> +		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
> +				pTab->zName);
>   		goto update_cleanup;
>   	}
>   
> @@ -324,7 +326,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>   	 */
>   #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
>   	if (isView) {
> -		sqlite3MaterializeView(pParse, pTab, pWhere, iDataCur);
> +		sql_materialize_view(pParse, pTab->zName, pWhere, iDataCur);
>   		/* Number of columns from SELECT plus ID.*/
>   		nKey = pTab->nCol + 1;
>   	}
> @@ -603,7 +605,6 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>   						     nKey);
>   			VdbeCoverageNeverTaken(v);
>   		}
> -		sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur);

16. Why?
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index b8d4c1a..2ad6f3e 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -3236,20 +3236,20 @@ case OP_OpenTEphemeral: {
>   	BtCursor *pBtCur;
>   	assert(pOp->p1 >= 0);
>   	assert(pOp->p2 > 0);
> -	assert(pOp->p4type != P4_KEYDEF || pOp->p4.key_def != NULL);
> +	assert(pOp->p4type == P4_KEYDEF);

17. Why was this check needed in the previous patch?

>   
>   	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
>   	if (pCx == 0) goto no_mem;
>   	pCx->nullRow = 1;
>   
> -	pCx->key_def  = pOp->p4.key_def;
>   	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,
> -					     pCx->key_def);
> +					     pOp->p4.key_def);
> +	pCx->key_def = pCx->uc.pCursor->index->def->key_def;

18. Lets merge this into the previous patch.
> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> index e67fcae..4a8a9d2 100644
> --- a/src/box/sql/vdbeaux.c
> +++ b/src/box/sql/vdbeaux.c
> @@ -1134,7 +1134,7 @@ sqlite3VdbeChangeP4(Vdbe * p, int addr, const char *zP4, int n)
>   	} if (n == P4_BOOL) {
>   		pOp->p4.b = *(bool*)zP4;
>   		pOp->p4type = P4_BOOL;
> -	} else if (zP4 != 0) {
> +	} else {

19. Why?
> @@ -1571,7 +1571,10 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
>   #endif
>   	case P4_COLLSEQ:{
>   			struct coll *pColl = pOp->p4.pColl;
> -			sqlite3XPrintf(&x, "(%.20s)", pColl->name);
> +			if (pColl != NULL)
> +				sqlite3XPrintf(&x, "(%.20s)", pColl->name);
> +			else
> +				sqlite3XPrintf(&x, "(binary)");

20. Stray change?

>   			break;
>   		}
>   	case P4_FUNCDEF:{
> 

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

* [tarantool-patches] Re: [PATCH 2/2] sql: refactor delete routines
  2018-05-16 16:29   ` [tarantool-patches] " Kirill Yukhin
  2018-05-17 14:23     ` Vladislav Shpilevoy
@ 2018-05-17 15:18     ` Vladislav Shpilevoy
  1 sibling, 0 replies; 17+ messages in thread
From: Vladislav Shpilevoy @ 2018-05-17 15:18 UTC (permalink / raw)
  To: tarantool-patches, Kirill Yukhin

I pushed some review fixes on the branch as a separate commit.

On 16/05/2018 19:29, Kirill Yukhin wrote:
> Hi Vlad,
> On 16 мая 18:24, Kirill Yukhin wrote:
>> Refactor DELETE FROM statements translation, update
> I've removed one more dead routine. Patch is in the bottom.
> 
> --
> Regards, Kirill Yukhin
> 
> sql: refactor SQL delete routines
> 
> Refactor DELETE FROM statements translation, update
> all live routines according to Tarantool's coding style.
> Use key_def instead of Table* where possible.
> Remove useless index delete routine as well.
> 
> Part of #3235
> ---
>   src/box/sql/build.c     |    6 +-
>   src/box/sql/delete.c    | 1136 ++++++++++++++++-------------------------------
>   src/box/sql/fkey.c      |    3 +-
>   src/box/sql/insert.c    |   26 +-
>   src/box/sql/parse.y     |    2 +-
>   src/box/sql/sqliteInt.h |  206 ++++++++-
>   src/box/sql/trigger.c   |   12 +-
>   src/box/sql/update.c    |    9 +-
>   src/box/sql/vdbe.c      |    6 +-
>   src/box/sql/vdbeaux.c   |    7 +-
>   10 files changed, 619 insertions(+), 794 deletions(-)
> 
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index c96d351..e3aadab 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -2664,10 +2664,10 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
>   	VdbeCoverage(v);
>   	regRecord = sqlite3GetTempReg(pParse);
>   
> -	sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord,
> -				&iPartIdxLabel, 0, 0);
> +	sql_generate_index_key(pParse, pIndex, iTab, regRecord,
> +			       &iPartIdxLabel, 0, 0);
>   	sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord);
> -	sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
> +	sql_resolve_part_idx_label(pParse, iPartIdxLabel);
>   	sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1 + 1);
>   	VdbeCoverage(v);
>   	sqlite3VdbeJumpHere(v, addr1);
> diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
> index 3455f52..0c77057 100644
> --- a/src/box/sql/delete.c
> +++ b/src/box/sql/delete.c
> @@ -29,593 +29,380 @@
>    * SUCH DAMAGE.
>    */
>   
> -/*
> - * This file contains C code routines that are called by the parser
> - * in order to generate code for DELETE FROM statements.
> - */
> -#include "sqliteInt.h"
>   #include "box/session.h"
> +#include "box/schema.h"
> +#include "sqliteInt.h"
> +#include "tarantoolInt.h"
>   
> -/*
> - * While a SrcList can in general represent multiple tables and subqueries
> - * (as in the FROM clause of a SELECT statement) in this case it contains
> - * the name of a single table, as one might find in an INSERT, DELETE,
> - * or UPDATE statement.  Look up that table in the symbol table and
> - * return a pointer.  Set an error message and return NULL if the table
> - * name is not found or if any other error occurs.
> - *
> - * The following fields are initialized appropriate in pSrc:
> - *
> - *    pSrc->a[0].pTab       Pointer to the Table object
> - *    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if there is one
> - *
> - */
> -Table *
> -sqlite3SrcListLookup(Parse * pParse, SrcList * pSrc)
> +struct Table *
> +sql_list_lookup_table(struct Parse *parse, SrcList *src_list)
>   {
> -	struct SrcList_item *pItem = pSrc->a;
> -	Table *pTab;
> -	assert(pItem && pSrc->nSrc == 1);
> -	pTab = sqlite3LocateTable(pParse, 0, pItem->zName);
> -	sqlite3DeleteTable(pParse->db, pItem->pTab);
> -	pItem->pTab = pTab;
> -	if (pTab != NULL)
> -		pTab->nTabRef++;
> -	if (sqlite3IndexedByLookup(pParse, pItem))
> -		pTab = NULL;
> -	return pTab;
> +	struct SrcList_item *item = src_list->a;
> +	struct Table *table;
> +	assert(item != NULL && src_list->nSrc == 1);
> +	table = sqlite3LocateTable(parse, 0, item->zName);
> +	sqlite3DeleteTable(parse->db, item->pTab);
> +	item->pTab = table;
> +	if (table != NULL)
> +		table->nTabRef++;
> +	if (sqlite3IndexedByLookup(parse, item))
> +		table = NULL;
> +	return table;
>   }
>   
> -/*
> - * Check to make sure the given table is writable.  If it is not
> - * writable, generate an error message and return 1.  If it is
> - * writable return 0;
> - */
> -int
> -sqlite3IsReadOnly(Parse * pParse, Table * pTab, int viewOk)
> -{
> -	/*
> -	 * A table is not writable if it is a system table
> -	 * (i.e. _sql_stat1), this call is not part of a
> -	 * nested parse. In either case leave an error message in
> -	 * pParse and return non-zero.
> -	 */
> -	if ((pTab->tabFlags & TF_Readonly) != 0 && pParse->nested == 0) {
> -		sqlite3ErrorMsg(pParse, "table %s may not be modified",
> -				pTab->zName);
> -		return 1;
> -	}
> -#ifndef SQLITE_OMIT_VIEW
> -	if (!viewOk && space_is_view(pTab)) {
> -		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
> -				pTab->zName);
> -		return 1;
> -	}
> -#endif
> -	return 0;
> -}
> -
> -#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
> -/*
> - * Evaluate a view and store its result in an ephemeral table.  The
> - * pWhere argument is an optional WHERE clause that restricts the
> - * set of rows in the view that are to be added to the ephemeral table.
> - */
>   void
> -sqlite3MaterializeView(Parse * pParse,	/* Parsing context */
> -		       Table * pView,	/* View definition */
> -		       Expr * pWhere,	/* Optional WHERE clause to be added */
> -		       int iCur)	/* Cursor number for ephemeral table */
> +sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
> +		     int cursor)
>   {
> -	SelectDest dest;
> -	Select *pSel;
> -	SrcList *pFrom;
> -	sqlite3 *db = pParse->db;
> -	pWhere = sqlite3ExprDup(db, pWhere, 0);
> -	pFrom = sqlite3SrcListAppend(db, 0, 0);
> -	if (pFrom) {
> -		assert(pFrom->nSrc == 1);
> -		pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName);
> -		assert(pFrom->a[0].pOn == 0);
> -		assert(pFrom->a[0].pUsing == 0);
> -	}
> -	pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0,
> -				0, 0, 0);
> -	sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur);
> -	sqlite3Select(pParse, pSel, &dest);
> -	sqlite3SelectDelete(db, pSel);
> -}
> -#endif				/* !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */
> -
> -#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
> -/*
> - * Generate an expression tree to implement the WHERE, ORDER BY,
> - * and LIMIT/OFFSET portion of DELETE and UPDATE statements.
> - *
> - *     DELETE FROM table_wxyz WHERE a<5 ORDER BY a LIMIT 1;
> - *                            \__________________________/
> - *                               pLimitWhere (pInClause)
> - */
> -Expr *
> -sqlite3LimitWhere(Parse * pParse,	/* The parser context */
> -		  SrcList * pSrc,	/* the FROM clause -- which tables to scan */
> -		  Expr * pWhere,	/* The WHERE clause.  May be null */
> -		  ExprList * pOrderBy,	/* The ORDER BY clause.  May be null */
> -		  Expr * pLimit,	/* The LIMIT clause.  May be null */
> -		  Expr * pOffset,	/* The OFFSET clause.  May be null */
> -		  char *zStmtType	/* Either DELETE or UPDATE.  For err msgs. */
> -    )
> -{
> -	Expr *pWhereRowid = NULL;	/* WHERE rowid .. */
> -	Expr *pInClause = NULL;	/* WHERE rowid IN ( select ) */
> -	Expr *pSelectRowid = NULL;	/* SELECT rowid ... */
> -	ExprList *pEList = NULL;	/* Expression list contaning only pSelectRowid */
> -	SrcList *pSelectSrc = NULL;	/* SELECT rowid FROM x ... (dup of pSrc) */
> -	Select *pSelect = NULL;	/* Complete SELECT tree */
> -
> -	/* Check that there isn't an ORDER BY without a LIMIT clause.
> -	 */
> -	if (pOrderBy && (pLimit == 0)) {
> -		sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s",
> -				zStmtType);
> -		goto limit_where_cleanup;
> -	}
> -
> -	/* We only need to generate a select expression if there
> -	 * is a limit/offset term to enforce.
> -	 */
> -	if (pLimit == 0) {
> -		/* if pLimit is null, pOffset will always be null as well. */
> -		assert(pOffset == 0);
> -		return pWhere;
> +	struct sqlite3 *db = parse->db;
> +	where = sqlite3ExprDup(db, where, 0);
> +	struct SrcList *from = sqlite3SrcListAppend(db, NULL, NULL);
> +	if (from != NULL) {
> +		assert(from->nSrc == 1);
> +		from->a[0].zName = sqlite3DbStrDup(db, name);
> +		assert(from->a[0].pOn == NULL);
> +		assert(from->a[0].pUsing == NULL);
>   	}
> -
> -	/* Generate a select expression tree to enforce the limit/offset
> -	 * term for the DELETE or UPDATE statement.  For example:
> -	 *   DELETE FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
> -	 * becomes:
> -	 *   DELETE FROM table_a WHERE rowid IN (
> -	 *     SELECT rowid FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
> -	 *   );
> -	 */
> -
> -	pSelectRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0);
> -	if (pSelectRowid == 0)
> -		goto limit_where_cleanup;
> -	pEList = sqlite3ExprListAppend(pParse, 0, pSelectRowid);
> -	if (pEList == 0)
> -		goto limit_where_cleanup;
> -
> -	/* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree
> -	 * and the SELECT subtree.
> -	 */
> -	pSelectSrc = sqlite3SrcListDup(pParse->db, pSrc, 0);
> -	if (pSelectSrc == 0) {
> -		sqlite3ExprListDelete(pParse->db, pEList);
> -		goto limit_where_cleanup;
> -	}
> -
> -	/* generate the SELECT expression tree. */
> -	pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0, 0,
> -				   pOrderBy, 0, pLimit, pOffset);
> -	if (pSelect == 0)
> -		return 0;
> -
> -	/* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */
> -	pWhereRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0);
> -	pInClause =
> -	    pWhereRowid ? sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0) : 0;
> -	sqlite3PExprAddSelect(pParse, pInClause, pSelect);
> -	return pInClause;
> -
> - limit_where_cleanup:
> -	sql_expr_free(pParse->db, pWhere);
> -	sqlite3ExprListDelete(pParse->db, pOrderBy);
> -	sql_expr_free(pParse->db, pLimit);
> -	sql_expr_free(pParse->db, pOffset);
> -	return 0;
> +	struct Select *select = sqlite3SelectNew(parse, NULL, from, where, NULL,
> +						 NULL, NULL, 0, NULL, NULL);
> +	struct SelectDest dest;
> +	sqlite3SelectDestInit(&dest, SRT_EphemTab, cursor);
> +	sqlite3Select(parse, select, &dest);
> +	sqlite3SelectDelete(db, select);
>   }
> -#endif				/* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) */
>   
> -/*
> - * Generate code for a DELETE FROM statement.
> - *
> - *     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
> - *                 \________/       \________________/
> - *                  pTabList              pWhere
> - */
>   void
> -sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
> -		  SrcList * pTabList,	/* The table from which we should delete things */
> -		  Expr * pWhere)	/* The WHERE clause.  May be null */
> +sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
> +		      struct Expr *where)
>   {
> -	Vdbe *v;		/* The virtual database engine */
> -	Table *pTab;		/* The table from which records will be deleted */
> -	int i;			/* Loop counter */
> -	WhereInfo *pWInfo;	/* Information about the WHERE clause */
> -	Index *pIdx;		/* For looping over indices of the table */
> -	int iTabCur;		/* Cursor number for the table */
> -	int iDataCur = 0;	/* VDBE cursor for the canonical data source */
> -	int iIdxCur = 0;	/* Cursor number of the first index */
> -	int nIdx;		/* Number of indices */
> -	sqlite3 *db;		/* Main database structure */
> -	NameContext sNC;	/* Name context to resolve expressions in */
> -	int memCnt = -1;	/* Memory cell used for change counting */
> -	int eOnePass;		/* ONEPASS_OFF or _SINGLE or _MULTI */
> -	int aiCurOnePass[2];	/* The write cursors opened by WHERE_ONEPASS */
> -	u8 *aToOpen = 0;	/* Open cursor iTabCur+j if aToOpen[j] is true */
> -	Index *pPk = 0;		/* The PRIMARY KEY index on the table */
> -	int iPk = 0;		/* First of nPk registers holding PRIMARY KEY value */
> -	i16 nPk;		/* Number of columns in the PRIMARY KEY */
> -	int iKey;		/* Memory cell holding key of row to be deleted */
> -	i16 nKey;		/* Number of memory cells in the row key */
> -	int iEphCur = 0;	/* Ephemeral table holding all primary key values */
> -	int addrBypass = 0;	/* Address of jump over the delete logic */
> -	int addrLoop = 0;	/* Top of the delete loop */
> -	int addrEphOpen = 0;	/* Instruction to open the Ephemeral table */
> -	int bComplex;		/* True if there are triggers or FKs or
> -				 * subqueries in the WHERE clause
> -				 */
> -	struct session *user_session = current_session();
> +	struct sqlite3 *db = parse->db;
> +	if (parse->nErr || db->mallocFailed)
> +		goto delete_from_cleanup;
>   
> -#ifndef SQLITE_OMIT_TRIGGER
> -	int isView;		/* True if attempting to delete from a view */
> -	Trigger *pTrigger;	/* List of table triggers, if required */
> -#endif
> +	assert(tab_list->nSrc == 1);
>   
> -	db = pParse->db;
> -	if (pParse->nErr || db->mallocFailed) {
> +	/* Locate the table which we want to delete.  This table
> +	 * has to be put in an SrcList structure because some of
> +	 * the subroutines we will be calling are designed to work
> +	 * with multiple tables and expect an SrcList* parameter
> +	 * instead of just a Table* parameter.
> +	 */
> +	struct Table *table = sql_list_lookup_table(parse, tab_list);
> +	if (table == NULL)
>   		goto delete_from_cleanup;
> -	}
> -	assert(pTabList->nSrc == 1);
>   
> -	/* Locate the table which we want to delete.  This table has to be
> -	 * put in an SrcList structure because some of the subroutines we
> -	 * will be calling are designed to work with multiple tables and expect
> -	 * an SrcList* parameter instead of just a Table* parameter.
> +	struct space *space = space_by_id(SQLITE_PAGENO_TO_SPACEID(table->tnum));
> +	assert(space != NULL);
> +	/* Figure out if we have any triggers and if the table
> +	 * being deleted from is a view.
>   	 */
> -	pTab = sqlite3SrcListLookup(pParse, pTabList);
> -	if (pTab == 0)
> -		goto delete_from_cleanup;
> +	struct Trigger *trigger_list = sqlite3TriggersExist(table, TK_DELETE,
> +							    NULL, NULL);
>   
> -	/* Figure out if we have any triggers and if the table being
> -	 * deleted from is a view
> +	bool is_view = space->def->opts.is_view;
> +	/* True if there are triggers or FKs or subqueries in the
> +	 * WHERE clause.
>   	 */
> -#ifndef SQLITE_OMIT_TRIGGER
> -	pTrigger = sqlite3TriggersExist(pTab, TK_DELETE, 0, 0);
> -	isView = pTab->pSelect != 0;
> -	bComplex = pTrigger || sqlite3FkRequired(pTab, 0);
> -#else
> -#define pTrigger 0
> -#define isView 0
> -#endif
> -#ifdef SQLITE_OMIT_VIEW
> -#undef isView
> -#define isView 0
> -#endif
> -
> -	/* If pTab is really a view, make sure it has been initialized.
> +	bool is_complex = (trigger_list != NULL) ||
> +			   sqlite3FkRequired(table, 0);
> +
> +	/* If table is really a view, make sure it has been
> +	 * initialized.
>   	 */
> -	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
> +	if (sqlite3ViewGetColumnNames(parse, table))
>   		goto delete_from_cleanup;
> -	}
>   
> -	if (sqlite3IsReadOnly(pParse, pTab, (pTrigger ? 1 : 0))) {
> +	if (is_view && trigger_list == NULL) {
> +		sqlite3ErrorMsg(parse, "cannot modify %s because it is a view",
> +				space->def->name);
>   		goto delete_from_cleanup;
>   	}
> -	assert(!isView || pTrigger);
>   
>   	/* Assign cursor numbers to the table and all its indices.
>   	 */
> -	assert(pTabList->nSrc == 1);
> -	iTabCur = pTabList->a[0].iCursor = pParse->nTab++;
> -	for (nIdx = 0, pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext, nIdx++) {
> -		pParse->nTab++;
> -	}
> +	int tab_cursor;
> +	tab_cursor = tab_list->a[0].iCursor = parse->nTab++;
> +	parse->nTab += space->index_count;
>   
> -	/* Begin generating code.
> -	 */
> -	v = sqlite3GetVdbe(pParse);
> -	if (v == 0) {
> +	/* Begin generating code.  */
> +	struct Vdbe *v = sqlite3GetVdbe(parse);
> +	if (v == NULL)
>   		goto delete_from_cleanup;
> -	}
> -	if (pParse->nested == 0)
> -		sqlite3VdbeCountChanges(v);
> -	sql_set_multi_write(pParse, true);
>   
> -	/* If we are trying to delete from a view, realize that view into
> -	 * an ephemeral table.
> -	 */
> -#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
> -	if (isView) {
> -		sqlite3MaterializeView(pParse, pTab, pWhere, iTabCur);
> -		iDataCur = iIdxCur = iTabCur;
> -	}
> -#endif
> +	if (parse->nested == 0)
> +		sqlite3VdbeCountChanges(v);
> +	sql_set_multi_write(parse, true);
>   
> -	/* Resolve the column names in the WHERE clause.
> +	/* If we are trying to delete from a view, realize that
> +	 * view into an ephemeral table.
>   	 */
> -	memset(&sNC, 0, sizeof(sNC));
> -	sNC.pParse = pParse;
> -	sNC.pSrcList = pTabList;
> -	if (sqlite3ResolveExprNames(&sNC, pWhere)) {
> +	if (is_view)
> +		sql_materialize_view(parse, space->def->name, where, tab_cursor);
> +
> +	/* Resolve the column names in the WHERE clause. */
> +	struct NameContext nc;
> +	memset(&nc, 0, sizeof(nc));
> +	nc.pParse = parse;
> +	nc.pSrcList = tab_list;
> +	if (sqlite3ResolveExprNames(&nc, where))
>   		goto delete_from_cleanup;
> -	}
>   
> -	/* Initialize the counter of the number of rows deleted, if
> -	 * we are counting rows.
> +	/* Initialize the counter of the number of rows deleted,
> +	 * if we are counting rows.
>   	 */
> +	int reg_count = -1;
> +	struct session *user_session = current_session();
>   	if (user_session->sql_flags & SQLITE_CountRows) {
> -		memCnt = ++pParse->nMem;
> -		sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt);
> +		reg_count = ++parse->nMem;
> +		sqlite3VdbeAddOp2(v, OP_Integer, 0, reg_count);
>   	}
> -#ifndef SQLITE_OMIT_TRUNCATE_OPTIMIZATION
> -	/* Special case: A DELETE without a WHERE clause deletes everything.
> -	 * It is easier just to erase the whole table. Prior to version 3.6.5,
> -	 * this optimization caused the row change count (the value returned by
> -	 * API function sqlite3_count_changes) to be set incorrectly.
> +	/* Special case: A DELETE without a WHERE clause deletes
> +	 * everything. It is easier just to erase the whole table.
>   	 */
> -	if (pWhere == 0 && !bComplex
> -#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
> -	    && db->xPreUpdateCallback == 0
> -#endif
> -	    ) {
> -		assert(!isView);
> -
> -		sqlite3VdbeAddOp1(v, OP_Clear, pTab->tnum);
> -
> -		/* Do not start Tarantool's transaction in case of truncate optimization.
> -		   This is workaround until system tables cannot be changes inside a
> -		   transaction (_truncate).  */
> -		pParse->initiateTTrans = false;
> -	} else
> -#endif				/* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */
> -	{
> -		u16 wcf =
> -		    WHERE_ONEPASS_DESIRED | WHERE_DUPLICATES_OK |
> -		    WHERE_SEEK_TABLE;
> -		if (sNC.ncFlags & NC_VarSelect)
> -			bComplex = 1;
> -		wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW);
> -		/* Create an ephemeral table used to hold all primary keys for
> -		 * rows to be deleted. Since VIEW is held in ephemeral table,
> -		 * there is no PK for it, so columns should be loaded manually.
> +	if (where == NULL && !is_complex) {
> +		assert(!is_view);
> +
> +		sqlite3VdbeAddOp1(v, OP_Clear, table->tnum);
> +
> +		/* Do not start Tarantool's transaction in case of
> +		 * truncate optimization. This is workaround until
> +		 * system tables cannot be changes inside a
> +		 * transaction (_truncate).
>   		 */
> -		if (isView) {
> -			nPk = pTab->nCol;
> -			iPk = pParse->nMem + 1;
> -			pParse->nMem += nPk;
> -			iEphCur = pParse->nTab++;
> -			struct key_def *def = key_def_new(nPk);
> -			if (def == NULL) {
> -				sqlite3OomFault(db);
> -				goto delete_from_cleanup;
> -			}
> -			addrEphOpen =
> -				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
> -						  nPk, 0, (char*)def, P4_KEYDEF);
> +		parse->initiateTTrans = false;
> +	} else {
> +		uint16_t wcf =
> +			WHERE_ONEPASS_DESIRED | WHERE_DUPLICATES_OK |
> +			WHERE_SEEK_TABLE;
> +		if (nc.ncFlags & NC_VarSelect)
> +			is_complex = true;
> +		wcf |= (is_complex ? 0 : WHERE_ONEPASS_MULTIROW);
> +		/* Create an ephemeral table used to hold all
> +		 * primary keys for rows to be deleted. Since VIEW
> +		 * is held in ephemeral table, there is no PK for
> +		 * it, so columns should be loaded manually.
> +		 */
> +		struct Index *pk = NULL;
> +		int reg_pk = parse->nMem + 1;
> +		int pk_len;
> +		int eph_cursor = parse->nTab++;
> +		int addr_eph_open = sqlite3VdbeCurrentAddr(v);
> +		if (is_view) {
> +			pk_len = table->nCol;
> +			parse->nMem += pk_len;
> +			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
> +					  eph_cursor, pk_len, 0,
> +					  (char*)NULL, P4_KEYDEF);
>   		} else {
> -			pPk = sqlite3PrimaryKeyIndex(pTab);
> -			assert(pPk != 0);
> -			nPk = index_column_count(pPk);
> -			iPk = pParse->nMem + 1;
> -			pParse->nMem += nPk;
> -			iEphCur = pParse->nTab++;
> -			addrEphOpen =
> -			    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEphCur,
> -					      nPk);
> -			sql_vdbe_set_p4_key_def(pParse, pPk);
> +			pk = sqlite3PrimaryKeyIndex(table);
> +			assert(pk != NULL);
> +			pk_len = index_column_count(pk);
> +			parse->nMem += pk_len;
> +			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, eph_cursor,
> +					  pk_len);
> +			sql_vdbe_set_p4_key_def(parse, pk);
>   		}
>   
> -		/* Construct a query to find the primary key for every row
> -		 * to be deleted, based on the WHERE clause. Set variable eOnePass
> -		 * to indicate the strategy used to implement this delete:
> +		/* Construct a query to find the primary key for
> +		 * every row to be deleted, based on the WHERE
> +		 * clause. Set variable one_pass to indicate the
> +		 * strategy used to implement this delete:
>   		 *
> -		 *  ONEPASS_OFF:    Two-pass approach - use a FIFO for PK values.
> -		 *  ONEPASS_SINGLE: One-pass approach - at most one row deleted.
> -		 *  ONEPASS_MULTI:  One-pass approach - any number of rows may be deleted.
> +		 * ONEPASS_OFF:    Two-pass approach - use a FIFO
> +		 * for PK values.
> +		 * ONEPASS_SINGLE: One-pass approach - at most one
> +		 * row deleted.
> +		 * ONEPASS_MULTI:  One-pass approach - any number
> +		 * of rows may be deleted.
>   		 */
> -		pWInfo =
> -		    sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf,
> -				      iTabCur + 1);
> -		if (pWInfo == 0)
> +		struct WhereInfo *winfo =
> +		    sqlite3WhereBegin(parse, tab_list, where, NULL, NULL, wcf,
> +				      tab_cursor + 1);
> +		if (winfo == NULL)
>   			goto delete_from_cleanup;
> -		eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
> -		assert(eOnePass != ONEPASS_MULTI);
> -		/* Tarantool workaround: see comment in sqlite3WhereBegin.  */
> -		/* assert( bComplex || eOnePass!=ONEPASS_OFF ); */
> -
> -		/* Keep track of the number of rows to be deleted */
> -		if (user_session->sql_flags & SQLITE_CountRows) {
> -			sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1);
> -		}
> +
> +		/* The write cursors opened by WHERE_ONEPASS */
> +		int one_pass_cur[2];
> +		int one_pass = sqlite3WhereOkOnePass(winfo, one_pass_cur);
> +		assert(one_pass != ONEPASS_MULTI);
> +		/* Tarantool: see comment in
> +		 * sqlite3WhereOkOnePass.
> +		 */
> +		/* assert(is_complex || one_pass != ONEPASS_OFF); */
> +
> +		/* Keep track of the number of rows to be
> +		 * deleted.
> +		 */
> +		if (user_session->sql_flags & SQLITE_CountRows)
> +			sqlite3VdbeAddOp2(v, OP_AddImm, reg_count, 1);
>   
>   		/* Extract the primary key for the current row */
> -		if (!isView) {
> -			for (i = 0; i < nPk; i++) {
> -				assert(pPk->aiColumn[i] >= 0);
> -				sqlite3ExprCodeGetColumnOfTable(v, pTab,
> -								iTabCur,
> -								pPk->
> +		if (!is_view) {
> +			for (int i = 0; i < pk_len; i++) {
> +				assert(pk->aiColumn[i] >= 0);
> +				sqlite3ExprCodeGetColumnOfTable(v, table,
> +								tab_cursor,
> +								pk->
>   								aiColumn[i],
> -								iPk + i);
> +								reg_pk + i);
>   			}
>   		} else {
> -			for (i = 0; i < nPk; i++) {
> -				sqlite3VdbeAddOp3(v, OP_Column, iDataCur,
> -						  i, iPk + i);
> +			for (int i = 0; i < pk_len; i++) {
> +				sqlite3VdbeAddOp3(v, OP_Column, tab_cursor,
> +						  i, reg_pk + i);
>   			}
>   		}
> -		iKey = iPk;
>   
> -		if (eOnePass != ONEPASS_OFF) {
> -			/* For ONEPASS, no need to store the primary-key. There is only
> -			 * one, so just keep it in its register(s) and fall through to the
> -			 * delete code.
> +		int reg_key;
> +		int key_len;
> +		if (one_pass != ONEPASS_OFF) {
> +			/* For ONEPASS, no need to store the
> +			 * primary-key. There is only one, so just
> +			 * keep it in its register(s) and fall
> +			 * through to the delete code.
>   			 */
> -			nKey = nPk;	/* OP_Found will use an unpacked key */
> -			aToOpen = sqlite3DbMallocRawNN(db, nIdx + 2);
> -			if (aToOpen == 0) {
> -				sqlite3WhereEnd(pWInfo);
> -				goto delete_from_cleanup;
> -			}
> -			memset(aToOpen, 1, nIdx + 1);
> -			aToOpen[nIdx + 1] = 0;
> -			if (aiCurOnePass[0] >= 0)
> -				aToOpen[aiCurOnePass[0] - iTabCur] = 0;
> -			if (aiCurOnePass[1] >= 0)
> -				aToOpen[aiCurOnePass[1] - iTabCur] = 0;
> -			if (addrEphOpen)
> -				sqlite3VdbeChangeToNoop(v, addrEphOpen);
> +			reg_key = reg_pk;
> +			/* OP_Found will use an unpacked key */
> +			key_len = pk_len;
> +			sqlite3VdbeChangeToNoop(v, addr_eph_open);
>   		} else {
> -			/* Add the PK key for this row to the temporary table */
> -			iKey = ++pParse->nMem;
> -			nKey = 0;	/* Zero tells OP_Found to use a composite key */
> -			const char *zAff = isView ? 0 :
> -					  sqlite3IndexAffinityStr(pParse->db, pPk);
> -			sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey, zAff, nPk);
> -			/* Set flag to save memory allocating one by malloc. */
> +			/* Add the PK key for this row to the
> +			 * temporary table.
> +			 */
> +			reg_key = ++parse->nMem;
> +			/* Zero tells OP_Found to use a composite
> +			 * key.
> +			 */
> +			key_len = 0;
> +			const char *zAff = is_view ? NULL :
> +					  sqlite3IndexAffinityStr(parse->db, pk);
> +			sqlite3VdbeAddOp4(v, OP_MakeRecord, reg_pk, pk_len,
> +					  reg_key, zAff, pk_len);
> +			/* Set flag to save memory allocating one
> +			 * by malloc.
> +			 */
>   			sqlite3VdbeChangeP5(v, 1);
> -			sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey);
> +			sqlite3VdbeAddOp2(v, OP_IdxInsert, eph_cursor, reg_key);
>   		}
>   
> -		/* If this DELETE cannot use the ONEPASS strategy, this is the
> -		 * end of the WHERE loop
> +		/* If this DELETE cannot use the ONEPASS strategy,
> +		 * this is the end of the WHERE loop.
>   		 */
> -		if (eOnePass != ONEPASS_OFF) {
> -			addrBypass = sqlite3VdbeMakeLabel(v);
> -		} else {
> -			sqlite3WhereEnd(pWInfo);
> -		}
> -
> -		/* Unless this is a view, open cursors for the table we are
> -		 * deleting from and all its indices. If this is a view, then the
> -		 * only effect this statement has is to fire the INSTEAD OF
> +		int addr_bypass = 0;
> +		if (one_pass != ONEPASS_OFF)
> +			addr_bypass = sqlite3VdbeMakeLabel(v);
> +		else
> +			sqlite3WhereEnd(winfo);
> +
> +		/* Unless this is a view, open cursors for the
> +		 * table we are deleting from and all its indices.
> +		 * If this is a view, then the only effect this
> +		 * statement has is to fire the INSTEAD OF
>   		 * triggers.
>   		 */
> -		if (!isView) {
> +		if (!is_view) {
>   			int iAddrOnce = 0;
> -			if (eOnePass == ONEPASS_MULTI) {
> +			if (one_pass == ONEPASS_MULTI) {
>   				iAddrOnce = sqlite3VdbeAddOp0(v, OP_Once);
>   				VdbeCoverage(v);
>   			}
> -			sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite,
> -						   OPFLAG_FORDELETE, iTabCur,
> -						   aToOpen, &iDataCur,
> -						   &iIdxCur,
> -						   ON_CONFLICT_ACTION_NONE, 0);
> -			assert(pPk || iDataCur == iTabCur);
> -			assert(pPk || iIdxCur == iDataCur + 1);
> -			if (eOnePass == ONEPASS_MULTI)
> +
> +			int space_ptr_reg = ++parse->nMem;
> +			sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0,
> +					     (void *) space);
> +			sqlite3VdbeAddOp3(v, OP_OpenWrite, tab_cursor,
> +					  table->tnum, space_ptr_reg);
> +			sql_vdbe_set_p4_key_def(parse, pk);
> +			VdbeComment((v, "%s", pk->zName));
> +
> +			if (one_pass == ONEPASS_MULTI)
>   				sqlite3VdbeJumpHere(v, iAddrOnce);
>   		}
>   
> -		/* Set up a loop over the primary-keys that were found in the
> -		 * where-clause loop above.
> +		/* Set up a loop over the primary-keys that were
> +		 * found in the where-clause loop above.
>   		 */
> -		if (eOnePass != ONEPASS_OFF) {
> -			assert(nKey == nPk);	/* OP_Found will use an unpacked key */
> -			if (aToOpen[iDataCur - iTabCur]) {
> -				assert(pPk != 0 || pTab->pSelect != 0);
> -				sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur,
> -						     addrBypass, iKey, nKey);
> +		int addr_loop = 0;
> +		if (one_pass != ONEPASS_OFF) {
> +			/* OP_Found will use an unpacked key. */
> +			assert(key_len == pk_len);
> +			assert(pk != NULL || table->pSelect != NULL);
> +			sqlite3VdbeAddOp4Int(v, OP_NotFound, tab_cursor,
> +					     addr_bypass, reg_key, key_len);
>   
> -				VdbeCoverage(v);
> -			}
> +			VdbeCoverage(v);
>   		} else {
> -			addrLoop = sqlite3VdbeAddOp1(v, OP_Rewind, iEphCur);
> +			addr_loop = sqlite3VdbeAddOp1(v, OP_Rewind, eph_cursor);
>   			VdbeCoverage(v);
> -			sqlite3VdbeAddOp2(v, OP_RowData, iEphCur, iKey);
> +			sqlite3VdbeAddOp2(v, OP_RowData, eph_cursor, reg_key);
>   		}
>   
>   		/* Delete the row */
> -		{
> -			int count = (pParse->nested == 0);	/* True to count changes */
> -			int iIdxNoSeek = -1;
> -			if (bComplex == 0 && aiCurOnePass[1] != iDataCur
> -			    /* Tarantool: as far as ONEPASS is disabled, there's no index
> -			       w/o need of seeking.  */
> -			    && eOnePass != ONEPASS_OFF) {
> -				iIdxNoSeek = aiCurOnePass[1];
> -			}
> -			sqlite3GenerateRowDelete(pParse, pTab, pTrigger,
> -						 iDataCur, iIdxCur, iKey, nKey,
> -						 count,
> -						 ON_CONFLICT_ACTION_DEFAULT,
> -						 eOnePass,
> -						 iIdxNoSeek);
> -		}
> +		int idx_noseek = -1;
> +		if (!is_complex && one_pass_cur[1] != tab_cursor
> +		    /* Tarantool: as far as ONEPASS is disabled,
> +		     * there's no index w/o need of seeking.
> +		     */
> +		    && one_pass != ONEPASS_OFF)
> +			idx_noseek = one_pass_cur[1];
> +
> +		sql_generate_row_delete(parse, table, trigger_list, tab_cursor,
> +					reg_key, key_len, parse->nested == 0,
> +					ON_CONFLICT_ACTION_DEFAULT, one_pass,
> +					idx_noseek);
>   
>   		/* End of the loop over all primary-keys. */
> -		if (eOnePass != ONEPASS_OFF) {
> -			sqlite3VdbeResolveLabel(v, addrBypass);
> -			sqlite3WhereEnd(pWInfo);
> +		if (one_pass != ONEPASS_OFF) {
> +			sqlite3VdbeResolveLabel(v, addr_bypass);
> +			sqlite3WhereEnd(winfo);
>   		} else {
> -			sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop + 1);
> +			sqlite3VdbeAddOp2(v, OP_Next, eph_cursor,
> +					  addr_loop + 1);
>   			VdbeCoverage(v);
> -			sqlite3VdbeJumpHere(v, addrLoop);
> +			sqlite3VdbeJumpHere(v, addr_loop);
>   		}
> -	}			/* End non-truncate path */
> +	}
>   
> -	/* Return the number of rows that were deleted. If this routine is
> -	 * generating code because of a call to sqlite3NestedParse(), do not
> -	 * invoke the callback function.
> +	/* Return the number of rows that were deleted. If this
> +	 * routine is generating code because of a call to
> +	 * sqlite3NestedParse(), do not invoke the callback
> +	 * function.
>   	 */
>   	if ((user_session->sql_flags & SQLITE_CountRows) &&
> -	    !pParse->nested && !pParse->pTriggerTab) {
> -		sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1);
> +	    parse->nested == 0 && !parse->pTriggerTab) {
> +		sqlite3VdbeAddOp2(v, OP_ResultRow, reg_count, 1);
>   		sqlite3VdbeSetNumCols(v, 1);
>   		sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted",
>   				      SQLITE_STATIC);
>   	}
>   
>    delete_from_cleanup:
> -	sqlite3SrcListDelete(db, pTabList);
> -	sql_expr_free(db, pWhere, false);
> -	sqlite3DbFree(db, aToOpen);
> -	return;
> +	sqlite3SrcListDelete(db, tab_list);
> +	sql_expr_free(db, where, false);
>   }
>   
> -/* Generate VDBE code for
> - * DELETE FROM <pTab.z> WHERE
> - *        <columns[0]> = <values[0]>
> - *    AND  ...
> - *    AND <columns[nPairs - 1]> = <values[nPairs - 1]>;
> - *
> - * This function does not increment the nested counter and is
> - * faster than nested parsing of the request above.
> - * @param pParse Parser context.
> - * @param pTab Table name.
> - * @param columns Column names array.
> - * @param values Column values array.
> - * @param nPairs Length of @columns and @values.
> - *
> - * In case of error the @values elements are deleted.
> - */
>   void
> -sqlite3DeleteByKey(Parse *pParse, char *zTab, const char **columns,
> -		   Expr **values, int nPairs)
> +sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
> +		    struct Expr **values, int pairs_count)
>   {
> -	Expr *where = NULL;
> -	SrcList *src;
> -
> -		assert(nPairs > 0);
> -	if (pParse->nErr > 0 || pParse->db->mallocFailed)
> +	if (parse->nErr > 0 || parse->db->mallocFailed)
>   		goto error;
> -	src = sql_alloc_src_list(pParse->db);
> -	src->a[0].zName = sqlite3DbStrDup(pParse->db, zTab);
> +	struct SrcList *src = sql_alloc_src_list(parse->db);
> +	src->a[0].zName = sqlite3DbStrDup(parse->db, t_name);
>   	if (src == NULL)
>   		goto error;
>   	/* Dummy init of INDEXED BY clause. */
> -	Token t = { NULL, 0, false };
> -	sqlite3SrcListIndexedBy(pParse, src, &t);
> +	struct Token t = {NULL, 0, false};
> +	sqlite3SrcListIndexedBy(parse, src, &t);
>   
> -	for (int i = 0; i < nPairs; ++i) {
> -		Expr *col_expr = sqlite3Expr(pParse->db, TK_ID, columns[i]);
> +	struct Expr *where = NULL;
> +	assert(pairs_count > 0);
> +	for (int i = 0; i < pairs_count; ++i) {
> +		struct Expr *col_expr = sqlite3Expr(parse->db, TK_ID, columns[i]);
>   		if (col_expr == NULL || values[i] == NULL)
>   			goto error;
> -		Expr *eq_expr =
> -		    sqlite3PExpr(pParse, TK_EQ, col_expr, values[i]);
> +		struct Expr *eq_expr =
> +		    sqlite3PExpr(parse, TK_EQ, col_expr, values[i]);
>   		/* In case of error the values[i] had been deleted in
>   		 * sqlite3PExpr already. Do not delete it second time in the
>   		 * cycle below.
> @@ -626,311 +413,175 @@ sqlite3DeleteByKey(Parse *pParse, char *zTab, const char **columns,
>   		if (i == 0) {
>   			where = eq_expr;
>   		} else {
> -			where = sqlite3ExprAnd(pParse->db, where, eq_expr);
> +			where = sqlite3ExprAnd(parse->db, where, eq_expr);
>   			if (where == NULL)
>   				goto error;
>   		}
>   	}
>   	/* DeleteFrom frees the src and exprs in case of error. */
> -	sqlite3DeleteFrom(pParse, src, where);
> +	sql_table_delete_from(parse, src, where);
>   	return;
>   
>    error:
> -	sql_expr_free(pParse->db, where, false);
> -	for (int i = 0; i < nPairs; ++i)
> -		sql_expr_free(pParse->db, values[i], false);
> +	sql_expr_free(parse->db, where, false);
> +	for (int i = 0; i < pairs_count; ++i)
> +		sql_expr_free(parse->db, values[i], false);
>   }
>   
> -/* Make sure "isView" and other macros defined above are undefined. Otherwise
> - * they may interfere with compilation of other functions in this file
> - * (or in another file, if this file becomes part of the amalgamation).
> - */
> -#ifdef isView
> -#undef isView
> -#endif
> -#ifdef pTrigger
> -#undef pTrigger
> -#endif
> -
> -/*
> - * This routine generates VDBE code that causes a single row of a
> - * single table to be deleted.  Both the original table entry and
> - * all indices are removed.
> - *
> - * Preconditions:
> - *
> - *   1.  iDataCur is an open cursor on the btree that is the canonical data
> - *       store for the table.  (This will be the PRIMARY KEY index)
> - *
> - *   2.  Read/write cursors for all indices of pTab must be open as
> - *       cursor number iIdxCur+i for the i-th index.
> - *
> - *   3.  The primary key for the row to be deleted must be stored in a
> - *       sequence of nPk memory cells starting at iPk.  If nPk==0 that means
> - *       that a search record formed from OP_MakeRecord is contained in the
> - *       single memory location iPk.
> - *
> - * eMode:
> - *   Parameter eMode may be passed either ONEPASS_OFF (0), ONEPASS_SINGLE, or
> - *   ONEPASS_MULTI.  If eMode is not ONEPASS_OFF, then the cursor
> - *   iDataCur already points to the row to delete. If eMode is ONEPASS_OFF
> - *   then this function must seek iDataCur to the entry identified by iPk
> - *   and nPk before reading from it.
> - *
> - *   If eMode is ONEPASS_MULTI, then this call is being made as part
> - *   of a ONEPASS delete that affects multiple rows. In this case, if
> - *   iIdxNoSeek is a valid cursor number (>=0), then its position should
> - *   be preserved following the delete operation. Or, if iIdxNoSeek is not
> - *   a valid cursor number, the position of iDataCur should be preserved
> - *   instead.
> - *
> - * iIdxNoSeek:
> - *   If iIdxNoSeek is a valid cursor number (>=0), then it identifies an
> - *   index cursor (from within array of cursors starting at iIdxCur) that
> - *   already points to the index entry to be deleted.
> - */
>   void
> -sqlite3GenerateRowDelete(Parse * pParse,	/* Parsing context */
> -			 Table * pTab,		/* Table containing the row to be deleted */
> -			 Trigger * pTrigger,	/* List of triggers to (potentially) fire */
> -			 int iDataCur,		/* Cursor from which column data is extracted */
> -			 int iIdxCur,		/* First index cursor */
> -			 int iPk,		/* First memory cell containing the PRIMARY KEY */
> -			 i16 nPk,		/* Number of PRIMARY KEY memory cells */
> -			 u8 count,		/* If non-zero, increment the row change counter */
> -			 enum on_conflict_action onconf,		/* Default ON CONFLICT policy for triggers */
> -			 u8 eMode,		/* ONEPASS_OFF, _SINGLE, or _MULTI.  See above */
> -			 int iIdxNoSeek)	/* Cursor number of cursor that does not need seeking */
> +sql_generate_row_delete(struct Parse *parse, struct Table *table,
> +			struct Trigger *trigger_list, int cursor, int reg_pk,
> +			short npk, bool is_count,
> +			enum on_conflict_action onconf, u8 mode,
> +			int idx_noseek)
>   {
> -	Vdbe *v = pParse->pVdbe;	/* Vdbe */
> -	int iOld = 0;		/* First register in OLD.* array */
> -	int iLabel;		/* Label resolved to end of generated code */
> -
> -	/* Vdbe is guaranteed to have been allocated by this stage. */
> -	assert(v);
> -	(void)iIdxCur;
> +	struct Vdbe *v = parse->pVdbe;
> +	/* Vdbe is guaranteed to have been allocated by this
> +	 * stage.
> +	 */
> +	assert(v != NULL);
>   	VdbeModuleComment((v, "BEGIN: GenRowDel(%d,%d,%d,%d)",
> -			   iDataCur, iIdxCur, iPk, (int)nPk));
> +			   cursor, iIdxCur, reg_pk, (int)nPk));
>   
> -	/* Seek cursor iCur to the row to delete. If this row no longer exists
> -	 * (this can happen if a trigger program has already deleted it), do
> -	 * not attempt to delete it or fire any DELETE triggers.
> +	/* Seek cursor iCur to the row to delete. If this row no
> +	 * longer exists (this can happen if a trigger program has
> +	 * already deleted it), do not attempt to delete it or
> +	 * fire any DELETE triggers.
>   	 */
> -	iLabel = sqlite3VdbeMakeLabel(v);
> -	if (eMode == ONEPASS_OFF) {
> -		sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, iLabel, iPk, nPk);
> +	int label = sqlite3VdbeMakeLabel(v);
> +	if (mode == ONEPASS_OFF) {
> +		sqlite3VdbeAddOp4Int(v, OP_NotFound, cursor, label, reg_pk, npk);
>   		VdbeCoverageIf(v, opSeek == OP_NotFound);
>   	}
>   
> +	int first_old_reg = 0;
>   	/* If there are any triggers to fire, allocate a range of registers to
>   	 * use for the old.* references in the triggers.
>   	 */
> -	if (sqlite3FkRequired(pTab, 0) || pTrigger) {
> -		u32 mask;	/* Mask of OLD.* columns in use */
> -		int iCol;	/* Iterator used while populating OLD.* */
> -		int addrStart;	/* Start of BEFORE trigger programs */
> -
> -		/* TODO: Could use temporary registers here.
> -		 */
> -		mask =
> -		    sqlite3TriggerColmask(pParse, pTrigger, 0, 0,
> -					  TRIGGER_BEFORE | TRIGGER_AFTER, pTab,
> +	if (sqlite3FkRequired(table, 0) || trigger_list != NULL) {
> +		/* Mask of OLD.* columns in use */
> +		/* TODO: Could use temporary registers here. */
> +		uint32_t mask =
> +		    sqlite3TriggerColmask(parse, trigger_list, 0, 0,
> +					  TRIGGER_BEFORE | TRIGGER_AFTER, table,
>   					  onconf);
> -		mask |= sqlite3FkOldmask(pParse, pTab);
> -		iOld = pParse->nMem + 1;
> -		pParse->nMem += (1 + pTab->nCol);
> +		mask |= sqlite3FkOldmask(parse, table);
> +		first_old_reg = parse->nMem + 1;
> +		parse->nMem += (1 + table->nCol);
>   
> -		/* Populate the OLD.* pseudo-table register array. These values will be
> -		 * used by any BEFORE and AFTER triggers that exist.
> +		/* Populate the OLD.* pseudo-table register array.
> +		 * These values will be used by any BEFORE and
> +		 * AFTER triggers that exist.
>   		 */
> -		sqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld);
> -		for (iCol = 0; iCol < pTab->nCol; iCol++) {
> +		sqlite3VdbeAddOp2(v, OP_Copy, reg_pk, first_old_reg);
> +		for (int i = 0; i < table->nCol; i++) {
>   			testcase(mask != 0xffffffff && iCol == 31);
>   			testcase(mask != 0xffffffff && iCol == 32);
>   			if (mask == 0xffffffff
> -			    || (iCol <= 31 && (mask & MASKBIT32(iCol)) != 0)) {
> -				sqlite3ExprCodeGetColumnOfTable(v, pTab,
> -								iDataCur, iCol,
> -								iOld + iCol +
> -								1);
> +			    || (i <= 31 && (mask & MASKBIT32(i)) != 0)) {
> +				sqlite3ExprCodeGetColumnOfTable(v, table,
> +								cursor, i,
> +								first_old_reg +
> +								i + 1);
>   			}
>   		}
>   
>   		/* Invoke BEFORE DELETE trigger programs. */
> -		addrStart = sqlite3VdbeCurrentAddr(v);
> -		sqlite3CodeRowTrigger(pParse, pTrigger,
> -				      TK_DELETE, 0, TRIGGER_BEFORE, pTab, iOld,
> -				      onconf, iLabel);
> -
> -		/* If any BEFORE triggers were coded, then seek the cursor to the
> -		 * row to be deleted again. It may be that the BEFORE triggers moved
> -		 * the cursor or of already deleted the row that the cursor was
> -		 * pointing to.
> +		int addr_start = sqlite3VdbeCurrentAddr(v);
> +		sqlite3CodeRowTrigger(parse, trigger_list,
> +				      TK_DELETE, 0, TRIGGER_BEFORE, table,
> +				      first_old_reg, onconf, label);
> +
> +		/* If any BEFORE triggers were coded, then seek
> +		 * the cursor to the row to be deleted again. It
> +		 * may be that the BEFORE triggers moved the
> +		 * cursor or of already deleted the row that the
> +		 * cursor was pointing to.
>   		 */
> -		if (addrStart < sqlite3VdbeCurrentAddr(v)) {
> -			sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, iLabel, iPk,
> -					     nPk);
> +		if (addr_start < sqlite3VdbeCurrentAddr(v)) {
> +			sqlite3VdbeAddOp4Int(v, OP_NotFound, cursor, label,
> +					     reg_pk, npk);
>   			VdbeCoverageIf(v, opSeek == OP_NotFound);
>   		}
>   
> -		/* Do FK processing. This call checks that any FK constraints that
> -		 * refer to this table (i.e. constraints attached to other tables)
> -		 * are not violated by deleting this row.
> +		/* Do FK processing. This call checks that any FK
> +		 * constraints that refer to this table (i.e.
> +		 * constraints attached to other tables) are not
> +		 * violated by deleting this row.
>   		 */
> -		sqlite3FkCheck(pParse, pTab, iOld, 0, 0);
> +		sqlite3FkCheck(parse, table, first_old_reg, 0, NULL);
>   	}
>   
> -	/* Delete the index and table entries. Skip this step if pTab is really
> -	 * a view (in which case the only effect of the DELETE statement is to
> -	 * fire the INSTEAD OF triggers).
> -	 *
> -	 * If variable 'count' is non-zero, then this OP_Delete instruction should
> -	 * invoke the update-hook. The pre-update-hook, on the other hand should
> -	 * be invoked unless table pTab is a system table. The difference is that
> -	 * the update-hook is not invoked for rows removed by REPLACE, but the
> -	 * pre-update-hook is.
> +	/* Delete the index and table entries. Skip this step if
> +	 * table is really a view (in which case the only effect
> +	 * of the DELETE statement is to fire the INSTEAD OF
> +	 * triggers).
>   	 */
> -	if (pTab->pSelect == 0) {
> -		u8 p5 = 0;
> -		/* kyukhin: Tarantool handles indices uypdate automatically.  */
> -		/* sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek);  */
> -		sqlite3VdbeAddOp2(v, OP_Delete, iDataCur,
> -				  (count ? OPFLAG_NCHANGE : 0));
> -		if (eMode != ONEPASS_OFF) {
> +	if (table->pSelect == NULL) {
> +		uint8_t p5 = 0;
> +		sqlite3VdbeAddOp2(v, OP_Delete, cursor,
> +				  (is_count ? OPFLAG_NCHANGE : 0));
> +		if (mode != ONEPASS_OFF)
>   			sqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE);
> -		}
> -		if (iIdxNoSeek >= 0) {
> -			sqlite3VdbeAddOp1(v, OP_Delete, iIdxNoSeek);
> -		}
> -		if (eMode == ONEPASS_MULTI)
> +
> +		if (idx_noseek >= 0)
> +			sqlite3VdbeAddOp1(v, OP_Delete, idx_noseek);
> +
> +		if (mode == ONEPASS_MULTI)
>   			p5 |= OPFLAG_SAVEPOSITION;
>   		sqlite3VdbeChangeP5(v, p5);
>   	}
>   
> -	/* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
> -	 * handle rows (possibly in other tables) that refer via a foreign key
> -	 * to the row just deleted.
> +	/* Do any ON CASCADE, SET NULL or SET DEFAULT operations
> +	 * required to handle rows (possibly in other tables) that
> +	 * refer via a foreign key to the row just deleted.
>   	 */
> -	sqlite3FkActions(pParse, pTab, 0, iOld, 0);
> +	sqlite3FkActions(parse, table, 0, first_old_reg, 0);
>   
>   	/* Invoke AFTER DELETE trigger programs. */
> -	sqlite3CodeRowTrigger(pParse, pTrigger,
> -			      TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf,
> -			      iLabel);
> +	sqlite3CodeRowTrigger(parse, trigger_list,
> +			      TK_DELETE, 0, TRIGGER_AFTER, table, first_old_reg,
> +			      onconf, label);
>   
> -	/* Jump here if the row had already been deleted before any BEFORE
> -	 * trigger programs were invoked. Or if a trigger program throws a
> -	 * RAISE(IGNORE) exception.
> +	/* Jump here if the row had already been deleted before
> +	 * any BEFORE trigger programs were invoked. Or if a trigger program
> +	 * throws a RAISE(IGNORE) exception.
>   	 */
> -	sqlite3VdbeResolveLabel(v, iLabel);
> +	sqlite3VdbeResolveLabel(v, label);
>   	VdbeModuleComment((v, "END: GenRowDel()"));
>   }
>   
> -/*
> - * This routine generates VDBE code that causes the deletion of all
> - * index entries associated with a single row of a single table, pTab
> - *
> - * Preconditions:
> - *
> - *   1.  A read/write cursor "iDataCur" must be open on the canonical storage
> - *       btree for the table pTab.  (This will be primary key index)
> - *
> - *   2.  Read/write cursor for primary index of pTab must be open as
> - *       cursor number iIdxCur.  (The pTab->pIndex index is the 0-th index.)
> - *
> - *   3.  The "iDataCur" cursor must be already positioned on the row
> - *       that is to be deleted.
> - */
> -void
> -sqlite3GenerateRowIndexDelete(Parse * pParse,	/* Parsing and code generating context */
> -			      Table * pTab,	/* Table containing the row to be deleted */
> -			      int iDataCur,	/* Cursor of table holding data. */
> -			      int iIdxCur)	/* Primary index cursor */
> -{
> -	int r1 = -1;		/* Register holding an index key */
> -	int iPartIdxLabel;	/* Jump destination for skipping partial index entries */
> -	Vdbe *v;		/* The prepared statement under construction */
> -	Index *pPk;		/* PRIMARY KEY index */
> -
> -	v = pParse->pVdbe;
> -	pPk = sqlite3PrimaryKeyIndex(pTab);
> -	/* In Tarantool it is enough to delete row just from pk */
> -	VdbeModuleComment((v, "GenRowIdxDel for %s", pPk->zName));
> -	r1 = sqlite3GenerateIndexKey(pParse, pPk, iDataCur, 0, &iPartIdxLabel,
> -				     NULL, r1);
> -	sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur, r1, index_column_count(pPk));
> -	sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
> -}
> -
> -/*
> - * Generate code that will assemble an index key and stores it in register
> - * regOut.  The key with be for index pIdx which is an index on pTab.
> - * iCur is the index of a cursor open on the pTab table and pointing to
> - * the entry that needs indexing.
> - * iCur must be the cursor of the PRIMARY KEY index.
> - *
> - * Return a register number which is the first in a block of
> - * registers that holds the elements of the index key.  The
> - * block of registers has already been deallocated by the time
> - * this routine returns.
> - *
> - * If *piPartIdxLabel is not NULL, fill it in with a label and jump
> - * to that label if pIdx is a partial index that should be skipped.
> - * The label should be resolved using sqlite3ResolvePartIdxLabel().
> - * A partial index should be skipped if its WHERE clause evaluates
> - * to false or null.  If pIdx is not a partial index, *piPartIdxLabel
> - * will be set to zero which is an empty label that is ignored by
> - * sqlite3ResolvePartIdxLabel().
> - *
> - * The pPrior and regPrior parameters are used to implement a cache to
> - * avoid unnecessary register loads.  If pPrior is not NULL, then it is
> - * a pointer to a different index for which an index key has just been
> - * computed into register regPrior.  If the current pIdx index is generating
> - * its key into the same sequence of registers and if pPrior and pIdx share
> - * a column in common, then the register corresponding to that column already
> - * holds the correct value and the loading of that register is skipped.
> - * This optimization is helpful when doing a DELETE or an INTEGRITY_CHECK
> - * on a table with multiple indices, and especially with
> - * the PRIMARY KEY columns of the index.
> - */
>   int
> -sqlite3GenerateIndexKey(Parse * pParse,	/* Parsing context */
> -			Index * pIdx,	/* The index for which to generate a key */
> -			int iDataCur,	/* Cursor number from which to take column data */
> -			int regOut,	/* Put the new key into this register if not 0 */
> -			int *piPartIdxLabel,	/* OUT: Jump to this label to skip partial index */
> -			Index * pPrior,	/* Previously generated index key */
> -			int regPrior)	/* Register holding previous generated key */
> +sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
> +		       int reg_out, int *part_idx_label, struct Index *prev,
> +		       int reg_prev)
>   {
> -	Vdbe *v = pParse->pVdbe;
> -	int j;
> -	int regBase;
> -	int nCol;
> -
> -	if (piPartIdxLabel) {
> -		if (pIdx->pPartIdxWhere) {
> -			*piPartIdxLabel = sqlite3VdbeMakeLabel(v);
> -			pParse->iSelfTab = iDataCur;
> -			sqlite3ExprCachePush(pParse);
> -			sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere,
> -					      *piPartIdxLabel,
> +	Vdbe *v = parse->pVdbe;
> +
> +	if (part_idx_label != NULL) {
> +		if (index->pPartIdxWhere) {
> +			*part_idx_label = sqlite3VdbeMakeLabel(v);
> +			parse->iSelfTab = cursor;
> +			sqlite3ExprCachePush(parse);
> +			sqlite3ExprIfFalseDup(parse, index->pPartIdxWhere,
> +					      *part_idx_label,
>   					      SQLITE_JUMPIFNULL);
>   		} else {
> -			*piPartIdxLabel = 0;
> +			*part_idx_label = 0;
>   		}
>   	}
> -	nCol = index_column_count(pIdx);
> -	regBase = sqlite3GetTempRange(pParse, nCol);
> -	if (pPrior && (regBase != regPrior || pPrior->pPartIdxWhere))
> -		pPrior = 0;
> -	for (j = 0; j < nCol; j++) {
> -		if (pPrior && pPrior->aiColumn[j] == pIdx->aiColumn[j]
> -		    && pPrior->aiColumn[j] != XN_EXPR) {
> +	int col_cnt = index_column_count(index);
> +	int reg_base = sqlite3GetTempRange(parse, col_cnt);
> +	if (prev != NULL && (reg_base != reg_prev || prev->pPartIdxWhere))
> +		prev = NULL;
> +	for (int j = 0; j < col_cnt; j++) {
> +		if (prev != NULL && prev->aiColumn[j] == index->aiColumn[j]
> +		    && prev->aiColumn[j] != XN_EXPR) {
>   			/* This column was already computed by the previous index */
>   			continue;
>   		}
> -		sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j,
> -					       regBase + j);
> +		sqlite3ExprCodeLoadIndexColumn(parse, index, cursor, j,
> +					       reg_base + j);
>   		/* If the column affinity is REAL but the number is an integer, then it
>   		 * might be stored in the table as an integer (using a compact
>   		 * representation) then converted to REAL by an OP_RealAffinity opcode.
> @@ -940,23 +591,18 @@ sqlite3GenerateIndexKey(Parse * pParse,	/* Parsing context */
>   		 */
>   		sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity);
>   	}
> -	if (regOut) {
> -		sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut);
> -	}
> -	sqlite3ReleaseTempRange(pParse, regBase, nCol);
> -	return regBase;
> +	if (reg_out)
> +		sqlite3VdbeAddOp3(v, OP_MakeRecord, reg_base, col_cnt, reg_out);
> +
> +	sqlite3ReleaseTempRange(parse, reg_base, col_cnt);
> +	return reg_base;
>   }
>   
> -/*
> - * If a prior call to sqlite3GenerateIndexKey() generated a jump-over label
> - * because it was a partial index, then this routine should be called to
> - * resolve that label.
> - */
>   void
> -sqlite3ResolvePartIdxLabel(Parse * pParse, int iLabel)
> +sql_resolve_part_idx_label(struct Parse *parse, int label)
>   {
> -	if (iLabel) {
> -		sqlite3VdbeResolveLabel(pParse->pVdbe, iLabel);
> -		sqlite3ExprCachePop(pParse);
> +	if (label != 0) {
> +		sqlite3VdbeResolveLabel(parse->pVdbe, label);
> +		sqlite3ExprCachePop(parse);
>   	}
>   }
> diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
> index 60b4786..a21dd2d 100644
> --- a/src/box/sql/fkey.c
> +++ b/src/box/sql/fkey.c
> @@ -784,7 +784,8 @@ sqlite3FkDropTable(Parse * pParse, SrcList * pName, Table * pTab)
>   		pParse->disableTriggers = 1;
>   		/* Staring new transaction before DELETE FROM <tbl> */
>   		sqlite3VdbeAddOp0(v, OP_TTransaction);
> -		sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0);
> +		sql_table_delete_from(pParse, sqlite3SrcListDup(db, pName, 0),
> +				      NULL);
>   		pParse->disableTriggers = 0;
>   
>   		/* If the DELETE has generated immediate foreign key constraint
> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index 1ad7469..2d49296 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -379,7 +379,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
>   	zTab = pTabList->a[0].zName;
>   	if (NEVER(zTab == 0))
>   		goto insert_cleanup;
> -	pTab = sqlite3SrcListLookup(pParse, pTabList);
> +	pTab = sql_list_lookup_table(pParse, pTabList);
>   	if (pTab == 0) {
>   		goto insert_cleanup;
>   	}
> @@ -406,13 +406,13 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
>   	/* If pTab is really a view, make sure it has been initialized.
>   	 * ViewGetColumnNames() is a no-op if pTab is not a view.
>   	 */
> -	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
> +	if (sqlite3ViewGetColumnNames(pParse, pTab))
>   		goto insert_cleanup;
> -	}
>   
> -	/* Cannot insert into a read-only table.
> -	 */
> -	if (sqlite3IsReadOnly(pParse, pTab, tmask)) {
> +	/* Cannot insert into a read-only table. */
> +	if (isView && tmask == 0) {
> +		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
> +				pTab->zName);
>   		goto insert_cleanup;
>   	}
>   
> @@ -1511,13 +1511,13 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
>   								 TK_DELETE, 0,
>   								 0);
>   				}
> -				sqlite3GenerateRowDelete(pParse, pTab, pTrigger,
> -							 iDataCur, iIdxCur,
> -							 regR, nPkField, 0,
> -							 ON_CONFLICT_ACTION_REPLACE,
> -							 (pIdx ==
> -							  pPk ? ONEPASS_SINGLE :
> -							  ONEPASS_OFF), -1);
> +				sql_generate_row_delete(pParse, pTab, pTrigger,
> +							iDataCur,
> +							regR, nPkField, 0,
> +							ON_CONFLICT_ACTION_REPLACE,
> +							(pIdx ==
> +							 pPk ? ONEPASS_SINGLE :
> +							 ONEPASS_OFF), -1);
>   				seenReplace = 1;
>   				break;
>   			}
> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
> index 872647d..9343c0e 100644
> --- a/src/box/sql/parse.y
> +++ b/src/box/sql/parse.y
> @@ -738,7 +738,7 @@ cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W). {
>     sqlSubProgramsRemaining = SQL_MAX_COMPILING_TRIGGERS;
>     /* Instruct SQL to initate Tarantool's transaction.  */
>     pParse->initiateTTrans = true;
> -  sqlite3DeleteFrom(pParse,X,W);
> +  sql_table_delete_from(pParse,X,W);
>   }
>   %endif
>   
> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> index e056d63..6f23859 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -1994,7 +1994,6 @@ sql_space_tuple_log_count(struct Table *tab);
>   /*
>    * Allowed values for Table.tabFlags.
>    */
> -#define TF_Readonly        0x01	/* Read-only system table */
>   #define TF_Ephemeral       0x02	/* An ephemeral table */
>   #define TF_HasPrimaryKey   0x04	/* Table has a primary key */
>   #define TF_Autoincrement   0x08	/* Integer primary key is autoincrement */
> @@ -3671,15 +3670,69 @@ int sqlite3Select(Parse *, Select *, SelectDest *);
>   Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
>   			 Expr *, ExprList *, u32, Expr *, Expr *);
>   void sqlite3SelectDelete(sqlite3 *, Select *);
> -Table *sqlite3SrcListLookup(Parse *, SrcList *);
> +
> +/**
> + * While a SrcList can in general represent multiple tables and
> + * subqueries (as in the FROM clause of a SELECT statement) in
> + * this case it contains the name of a single table, as one might
> + * find in an INSERT, DELETE, or UPDATE statement.  Look up that
> + * table in the symbol table and return a pointer.  Set an error
> + * message and return NULL if the table name is not found or if
> + * any other error occurs.
> + *
> + * The following fields are initialized appropriate in src_list:
> + *
> + *    pSrc->a[0].pTab       Pointer to the Table object.
> + *    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if
> + *                          there is one.
> + *
> + * @param parse Parsing context.
> + * @param src_list List containing single table element.
> + * @retval Table object if found, NULL oterwise.
> + */
> +struct Table *
> +sql_list_lookup_table(struct Parse *parse, SrcList *src_list);
> +
>   int sqlite3IsReadOnly(Parse *, Table *, int);
>   void sqlite3OpenTable(Parse *, int iCur, Table *, int);
> -#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
> -Expr *sqlite3LimitWhere(Parse *, SrcList *, Expr *, ExprList *, Expr *, Expr *,
> -			char *);
> -#endif
> -void sqlite3DeleteFrom(Parse *, SrcList *, Expr *);
> -void sqlite3DeleteByKey(Parse *, char *, const char **, Expr **, int);
> +/**
> + * Generate code for a DELETE FROM statement.
> + *
> + *     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
> + *                 \________/       \________________/
> + *                  pTabList              pWhere
> + *
> + * @param parse Parsing context.
> + * @param tab_list List of single element which table from which
> + * deletetion if performed.
> + * @param where The WHERE clause.  May be NULL.
> + */
> +void
> +sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
> +		      struct Expr *where);
> +
> +/**
> + * Generate VDBE code for
> + * DELETE FROM <t_name> WHERE
> + *        <columns[0]> = <values[0]>
> + *    AND  ...
> + *    AND <columns[nPairs - 1]> = <values[nPairs - 1]>;
> + *
> + * This function does not increment the nested counter and is
> + * faster than nested parsing of the request above.
> + *
> + * @param parse Parser context.
> + * @param t_name Table name.
> + * @param columns Column names array.
> + * @param values Column values array.
> + * @param pairs_count Length of @columns and @values.
> + *
> + * In case of error the @values elements are deleted.
> + */
> +void
> +sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
> +		    struct Expr **values, int pairs_count);
> +
>   void sqlite3Update(Parse *, SrcList *, ExprList *, Expr *,
>   		   enum on_conflict_action);
>   WhereInfo *sqlite3WhereBegin(Parse *, SrcList *, Expr *, ExprList *, ExprList *,
> @@ -3782,11 +3835,121 @@ int sqlite3ExprContainsSubquery(Expr *);
>   int sqlite3ExprIsInteger(Expr *, int *);
>   int sqlite3ExprCanBeNull(const Expr *);
>   int sqlite3ExprNeedsNoAffinityChange(const Expr *, char);
> -void sqlite3GenerateRowDelete(Parse *, Table *, Trigger *, int, int, int, i16,
> -			      u8, enum on_conflict_action, u8, int);
> -void sqlite3GenerateRowIndexDelete(Parse *, Table *, int, int);
> -int sqlite3GenerateIndexKey(Parse *, Index *, int, int, int *, Index *, int);
> -void sqlite3ResolvePartIdxLabel(Parse *, int);
> +
> +/**
> + * This routine generates VDBE code that causes a single row of a
> + * single table to be deleted.  Both the original table entry and
> + * all indices are removed.
> + *
> + * Preconditions:
> + *
> + *   1.  cursor is an open cursor on the btree that is the
> + *       canonical data store for the table.  (This will be the
> + *       PRIMARY KEY index)
> + *
> + *   2.  The primary key for the row to be deleted must be stored
> + *       in a sequence of nPk memory cells starting at iPk. If
> + *       nPk==0 that means that a search record formed from
> + *       OP_MakeRecord is contained in the single memory location
> + *       iPk.
> + *
> + * mode:
> + *   Parameter mode may be passed either ONEPASS_OFF (0),
> + *   ONEPASS_SINGLE, or ONEPASS_MULTI.  If eMode is not
> + *   ONEPASS_OFF, then the cursor already points to the row to
> + *   delete. If eMode is ONEPASS_OFF then this function must seek
> + *   cursor to the entry identified by ipk and npk before reading
> + *   from it.
> + *
> + *   If mode is ONEPASS_MULTI, then this call is being made as
> + *   part of a ONEPASS delete that affects multiple rows. In this
> + *   case, if idx_noseek is a valid cursor number (>=0), then its
> + *   position should be preserved following the delete operation.
> + *   Or, if iIdxNoSeek is not a valid cursor number, the position
> + *   of cursor should be preserved instead.
> + *
> + * idx_noseek:
> + *   If idx_noseek is a valid cursor number (>=0), then it
> + *   identifies an index cursor that already points to the index
> + *   entry to be deleted.
> + *
> + * @param parse Parsing context.
> + * @param table Table containing the row to be deleted.
> + * @param trigger_list List of triggers to (potentially) fire.
> + * @param cursor Cursor from which column data is extracted/
> + * @param ipk First memory cell containing the PRIMARY KEY.
> + * @param npk umber of PRIMARY KEY memory cells.
> + * @param is_count If non-zero, increment the row change counter.
> + * @param onconf Default ON CONFLICT policy for triggers.
> + * @param mode ONEPASS_OFF, _SINGLE, or _MULTI.  See above.
> + * @param idx_noseek Cursor number of cursor that does not need
> +          seeking.
> + */
> +void
> +sql_generate_row_delete(struct Parse *parse, struct Table *table,
> +			struct Trigger *trigger_list, int cursor, int ipk,
> +			short npk, bool is_count,
> +			enum on_conflict_action onconf, u8 eMode,
> +			int idx_noseek);
> +
> +/**
> + * Generate code that will assemble an index key and stores it in
> + * register reg_out.  The key with be for index pIdx which is an
> + * index on table. cursor is the index of a cursor open on the
> + * table table and pointing to the entry that needs indexing.
> + * cursor must be the cursor of the PRIMARY KEY index.
> + *
> + * Return a register number which is the first in a block of
> + * registers that holds the elements of the index key.  The
> + * block of registers has already been deallocated by the time
> + * this routine returns.
> + *
> + * If *part_idx_label is not NULL, fill it in with a label and
> + * jump to that label if pIdx is a partial index that should be
> + * skipped. The label should be resolved using
> + * sql_resolve_part_idx_label(). A partial index should be skipped
> + * if its WHERE clause evaluates to false or null.  If index is
> + * not a partial index, *piPartIdxLabel will be set to zero which
> + * is an empty label that is ignored by sql_resolve_part_idx_label().
> + *
> + * The pPrior and regPrior parameters are used to implement a
> + * cache to avoid unnecessary register loads.  If prev is not
> + * NULL, then it is a pointer to a different index for which an
> + * index key has just been computed into register reg_prev. If the
> + * current pIdx index is generating its key into the same
> + * sequence of registers and if prev and index share a column in
> + * common, then the register corresponding to that column already
> + * holds the correct value and the loading of that register is
> + * skipped. This optimization is helpful when doing a DELETE or
> + * an INTEGRITY_CHECK on a table with multiple indices, and
> + * especially with the PRIMARY KEY columns of the index.
> + *
> + * @param parse Parsing context.
> + * @param index The index for which to generate a key.
> + * @param cursor Cursor number from which to take column data.
> + * @param reg_out Put the new key into this register if not NULL.
> + * @param[out] part_idx_label Jump to this label to skip partial
> + *        index.
> + * @param prev Previously generated index key
> + * @param reg_prev Register holding previous generated key.
> + * @retval Register containing new record
> + */
> +int
> +sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
> +		       int reg_out, int *part_idx_label, struct Index *prev,
> +		       int reg_prev);
> +
> +/**
> + * If a prior call to sqlite3GenerateIndexKey() generated a
> + * jump-over label because it was a partial index, then this
> + * routine should be called to resolve that label.
> + *
> + * @param parse Parsing context.
> + * @param label Label to resolve.
> + */
> +void
> +sql_resolve_part_idx_label(struct Parse *parse, int label);
> +
>   void sqlite3GenerateConstraintChecks(Parse *, Table *, int *, int, int, int,
>   				     int, u8, struct on_conflict *, int, int *,
>   				     int *);
> @@ -3831,9 +3994,20 @@ int sqlite3SafetyCheckOk(sqlite3 *);
>   int sqlite3SafetyCheckSickOrOk(sqlite3 *);
>   void sqlite3ChangeCookie(Parse *);
>   
> -#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
> -void sqlite3MaterializeView(Parse *, Table *, Expr *, int);
> -#endif
> +/**
> + * Evaluate a view and store its result in an ephemeral table.
> + * The where argument is an optional WHERE clause that restricts
> + * the set of rows in the view that are to be added to the
> + * ephemeral table.
> + *
> + * @param parse Parsing context.
> + * @param name View name.
> + * @param where Option WHERE clause to be added.
> + * @param cursor Cursor number for ephemeral table.
> + */
> +void
> +sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
> +		     int cursor);
>   
>   #ifndef SQLITE_OMIT_TRIGGER
>   void sqlite3BeginTrigger(Parse *, Token *, int, int, IdList *, SrcList *,
> diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
> index 28c56db..e6d9925 100644
> --- a/src/box/sql/trigger.c
> +++ b/src/box/sql/trigger.c
> @@ -111,7 +111,7 @@ sqlite3BeginTrigger(Parse * pParse,	/* The parse context of the CREATE TRIGGER s
>   	if (sqlite3FixSrcList(&sFix, pTableName)) {
>   		goto trigger_cleanup;
>   	}
> -	pTab = sqlite3SrcListLookup(pParse, pTableName);
> +	pTab = sql_list_lookup_table(pParse, pTableName);
>   	if (!pTab) {
>   		goto trigger_cleanup;
>   	}
> @@ -744,11 +744,11 @@ codeTriggerProgram(Parse * pParse,	/* The parser context */
>   				break;
>   			}
>   		case TK_DELETE:{
> -				sqlite3DeleteFrom(pParse,
> -						  targetSrcList(pParse, pStep),
> -						  sqlite3ExprDup(db,
> -								 pStep->pWhere,
> -								 0)
> +				sql_table_delete_from(pParse,
> +						      targetSrcList(pParse, pStep),
> +						      sqlite3ExprDup(db,
> +								     pStep->pWhere,
> +								     0)
>   				    );
>   				break;
>   			}
> diff --git a/src/box/sql/update.c b/src/box/sql/update.c
> index de1349b..60cb229 100644
> --- a/src/box/sql/update.c
> +++ b/src/box/sql/update.c
> @@ -163,7 +163,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>   
>   	/* Locate the table which we want to update.
>   	 */
> -	pTab = sqlite3SrcListLookup(pParse, pTabList);
> +	pTab = sql_list_lookup_table(pParse, pTabList);
>   	if (pTab == 0)
>   		goto update_cleanup;
>   
> @@ -187,7 +187,9 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>   	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
>   		goto update_cleanup;
>   	}
> -	if (sqlite3IsReadOnly(pParse, pTab, tmask)) {
> +	if (isView && tmask == 0) {
> +		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
> +				pTab->zName);
>   		goto update_cleanup;
>   	}
>   
> @@ -324,7 +326,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>   	 */
>   #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
>   	if (isView) {
> -		sqlite3MaterializeView(pParse, pTab, pWhere, iDataCur);
> +		sql_materialize_view(pParse, pTab->zName, pWhere, iDataCur);
>   		/* Number of columns from SELECT plus ID.*/
>   		nKey = pTab->nCol + 1;
>   	}
> @@ -603,7 +605,6 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>   						     nKey);
>   			VdbeCoverageNeverTaken(v);
>   		}
> -		sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur);
>   
>   		/* If changing the PK value, or if there are foreign key constraints
>   		 * to process, delete the old record. Otherwise, add a noop OP_Delete
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index b8d4c1a..2ad6f3e 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -3236,20 +3236,20 @@ case OP_OpenTEphemeral: {
>   	BtCursor *pBtCur;
>   	assert(pOp->p1 >= 0);
>   	assert(pOp->p2 > 0);
> -	assert(pOp->p4type != P4_KEYDEF || pOp->p4.key_def != NULL);
> +	assert(pOp->p4type == P4_KEYDEF);
>   
>   	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
>   	if (pCx == 0) goto no_mem;
>   	pCx->nullRow = 1;
>   
> -	pCx->key_def  = pOp->p4.key_def;
>   	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,
> -					     pCx->key_def);
> +					     pOp->p4.key_def);
> +	pCx->key_def = pCx->uc.pCursor->index->def->key_def;
>   	if (rc) goto abort_due_to_error;
>   	break;
>   }
> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> index e67fcae..4a8a9d2 100644
> --- a/src/box/sql/vdbeaux.c
> +++ b/src/box/sql/vdbeaux.c
> @@ -1134,7 +1134,7 @@ sqlite3VdbeChangeP4(Vdbe * p, int addr, const char *zP4, int n)
>   	} if (n == P4_BOOL) {
>   		pOp->p4.b = *(bool*)zP4;
>   		pOp->p4type = P4_BOOL;
> -	} else if (zP4 != 0) {
> +	} else {
>   		assert(n < 0);
>   		pOp->p4.p = (void *)zP4;
>   		pOp->p4type = (signed char)n;
> @@ -1571,7 +1571,10 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
>   #endif
>   	case P4_COLLSEQ:{
>   			struct coll *pColl = pOp->p4.pColl;
> -			sqlite3XPrintf(&x, "(%.20s)", pColl->name);
> +			if (pColl != NULL)
> +				sqlite3XPrintf(&x, "(%.20s)", pColl->name);
> +			else
> +				sqlite3XPrintf(&x, "(binary)");
>   			break;
>   		}
>   	case P4_FUNCDEF:{
> 

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

* [tarantool-patches] Re: [PATCH 2/2] sql: refactor delete routines
  2018-05-17 14:23     ` Vladislav Shpilevoy
@ 2018-05-17 15:48       ` Kirill Yukhin
  2018-05-17 16:47         ` Vladislav Shpilevoy
  0 siblings, 1 reply; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-17 15:48 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi Vlad,
Thanks for review.

My answers are inlined. Updated patch is in the bottom.

On 17 мая 17:23, Vladislav Shpilevoy wrote:
> Hello. Thanks for the patch! I can not build, wonder travis is ok.
> Maybe Mac travis uses gcc instead of clang?
> 
> See my 20 comments below.
> 
> /Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:482:6: error: variable 'where' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized]
>         if (src == NULL)
>             ^~~~~~~~~~~
> /Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:516:27: note: uninitialized use occurs here
>         sql_expr_free(parse->db, where, false);
>                                  ^~~~~
> /Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:482:2: note: remove the 'if' if its condition is always false
>         if (src == NULL)
>         ^~~~~~~~~~~~~~~~
> /Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:478:6: error: variable 'where' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized]
>         if (parse->nErr > 0 || parse->db->mallocFailed)
>             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> /Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:516:27: note: uninitialized use occurs here
>         sql_expr_free(parse->db, where, false);
>                                  ^~~~~
> /Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:478:2: note: remove the 'if' if its condition is always false
>         if (parse->nErr > 0 || parse->db->mallocFailed)
>         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> /Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:478:6: error: variable 'where' is used uninitialized whenever '||' condition is true [-Werror,-Wsometimes-uninitialized]
>         if (parse->nErr > 0 || parse->db->mallocFailed)
>             ^~~~~~~~~~~~~~~
> /Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:516:27: note: uninitialized use occurs here
>         sql_expr_free(parse->db, where, false);
>                                  ^~~~~
> /Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:478:6: note: remove the '||' if its condition is always false
>         if (parse->nErr > 0 || parse->db->mallocFailed)
>             ^~~~~~~~~~~~~~~~~~
> /Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:488:2: note: variable 'where' is declared here
>         struct Expr *where = NULL;
>         ^
I have no idea, what'a wrong here. Fixed all warnings you mentioned thoroughly.

> On 16/05/2018 19:29, Kirill Yukhin wrote:
> > Hi Vlad,
> > On 16 мая 18:24, Kirill Yukhin wrote:
> > --- a/src/box/sql/delete.c
> > +++ b/src/box/sql/delete.c
> > -Table *
> > -sqlite3SrcListLookup(Parse * pParse, SrcList * pSrc)
> > +struct Table *
> > +sql_list_lookup_table(struct Parse *parse, SrcList *src_list)
> >   {
> 
> > +	struct SrcList_item *item = src_list->a;
> > +	struct Table *table;
> > +	assert(item != NULL && src_list->nSrc == 1);
> > +	table = sqlite3LocateTable(parse, 0, item->zName);
> > +	sqlite3DeleteTable(parse->db, item->pTab);
> > +	item->pTab = table;
> > +	if (table != NULL)
> > +		table->nTabRef++;
> > +	if (sqlite3IndexedByLookup(parse, item))
> > +		table = NULL;
> 
> 1. What about --nTabRef; ? sqlite3IndexedByLookup does not unref
> table. Looks like original SQLite bug.
I'll submit a question to SQLite author.

> > +sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
> > +		    struct Expr **values, int pairs_count)
> >   {
> > -	Expr *where = NULL;
> > -	SrcList *src;
> > -
> > -		assert(nPairs > 0);
> > -	if (pParse->nErr > 0 || pParse->db->mallocFailed)
> > +	if (parse->nErr > 0 || parse->db->mallocFailed)
> >   		goto error;
> > -	src = sql_alloc_src_list(pParse->db);
> > -	src->a[0].zName = sqlite3DbStrDup(pParse->db, zTab);
> > +	struct SrcList *src = sql_alloc_src_list(parse->db);
> > +	src->a[0].zName = sqlite3DbStrDup(parse->db, t_name);
> >   	if (src == NULL)
> 2. Maybe first check src on NULL and then use it? Looks like the second SQLite bug.
Fixed.

> > +sql_generate_row_delete(struct Parse *parse, struct Table *table,
> > +			struct Trigger *trigger_list, int cursor, int reg_pk,
> 
> 3. I think, it is time to invent a standard name for cursors: I see, that both 'cursor' and 'reg_pk'
> are cursors, but their names differs very. My proposal: each cursor variable must end with '_cursor'.
> So data_cursor and pk_cursor instead of cursor and reg_pk.
Well, actualy no. Cursor is a cursor, this is immediate value in VDBE. Register (reg_pk) is a memory
cell. They're not the same thing for sure.

> > +			short npk, bool is_count,
> 
> 4. is_count -> need_update_count. This flag means, that global nChanges must be updated.
Fixed.

> > diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> > --- a/src/box/sql/sqliteInt.h
> > +++ b/src/box/sql/sqliteInt.h
> > @@ -3671,15 +3670,69 @@ int sqlite3Select(Parse *, Select *, SelectDest *);
> >   Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
> >   			 Expr *, ExprList *, u32, Expr *, Expr *);
> >   void sqlite3SelectDelete(sqlite3 *, Select *);
> > -Table *sqlite3SrcListLookup(Parse *, SrcList *);
> > +
> > +/**
> > + * While a SrcList can in general represent multiple tables and
> > + * subqueries (as in the FROM clause of a SELECT statement) in
> > + * this case it contains the name of a single table, as one might
> > + * find in an INSERT, DELETE, or UPDATE statement.  Look up that
> > + * table in the symbol table and return a pointer.  Set an error
> > + * message and return NULL if the table name is not found or if
> > + * any other error occurs.
> > + *
> > + * The following fields are initialized appropriate in src_list:
> > + *
> > + *    pSrc->a[0].pTab       Pointer to the Table object.
> 5. No pSrc anymore.
Fixed.

> > + *    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if
> > + *                          there is one.
> > + *
> > + * @param parse Parsing context.
> > + * @param src_list List containing single table element.
> > + * @retval Table object if found, NULL oterwise.
> > + */
> > +struct Table *
> > +sql_list_lookup_table(struct Parse *parse, SrcList *src_list);
> > +
> >   int sqlite3IsReadOnly(Parse *, Table *, int);
> 6. This function has no implementation.
Removed.

> >   void sqlite3OpenTable(Parse *, int iCur, Table *, int);
> > -#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
> > -Expr *sqlite3LimitWhere(Parse *, SrcList *, Expr *, ExprList *, Expr *, Expr *,
> > -			char *);
> > -#endif
> > -void sqlite3DeleteFrom(Parse *, SrcList *, Expr *);
> 7. This function still exists in parse.y, and has no implementation.
This is dead code for a long time. Removed.

> > -void sqlite3DeleteByKey(Parse *, char *, const char **, Expr **, int);
> > +/**
> > + * Generate code for a DELETE FROM statement.
> > + *
> > + *     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
> > + *                 \________/       \________________/
> > + *                  pTabList              pWhere
> 
> 8. No pTabList, no pWhere.
Fixed.

> > + *
> > + * @param parse Parsing context.
> > + * @param tab_list List of single element which table from which
> > + * deletetion if performed.
> > + * @param where The WHERE clause.  May be NULL.
> > + */
> > +void
> > +sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
> > +		      struct Expr *where);
> > +
> > +/**
> > + * Generate VDBE code for
> > + * DELETE FROM <t_name> WHERE
> > + *        <columns[0]> = <values[0]>
> > + *    AND  ...
> > + *    AND <columns[nPairs - 1]> = <values[nPairs - 1]>;
> 9. No nPairs.
Fixed.

> > @@ -3782,11 +3835,121 @@ int sqlite3ExprContainsSubquery(Expr *);
> >   int sqlite3ExprIsInteger(Expr *, int *);
> >   int sqlite3ExprCanBeNull(const Expr *);
> >   int sqlite3ExprNeedsNoAffinityChange(const Expr *, char);
> > -void sqlite3GenerateRowDelete(Parse *, Table *, Trigger *, int, int, int, i16,
> > -			      u8, enum on_conflict_action, u8, int);
> > -void sqlite3GenerateRowIndexDelete(Parse *, Table *, int, int);
> > -int sqlite3GenerateIndexKey(Parse *, Index *, int, int, int *, Index *, int);
> 10. Still exists in where.c and sqliteInt.h.
Fixed. Although this code is disabled so far (see #2583 for details).

> > -void sqlite3ResolvePartIdxLabel(Parse *, int);
> > +
> > +/**
> > + * This routine generates VDBE code that causes a single row of a
> > + * single table to be deleted.  Both the original table entry and
> > + * all indices are removed.
> > + *
> > + * Preconditions:
> > + *
> > + *   1.  cursor is an open cursor on the btree that is the
> > + *       canonical data store for the table.  (This will be the
> > + *       PRIMARY KEY index)
> > + *
> > + *   2.  The primary key for the row to be deleted must be stored
> > + *       in a sequence of nPk memory cells starting at iPk. If
> 11. No iPk, no nPk.
Fixed.

> > + *       nPk==0 that means that a search record formed from
> > + *       OP_MakeRecord is contained in the single memory location
> > + *       iPk.
> > + *
> > + * mode:
> > + *   Parameter mode may be passed either ONEPASS_OFF (0),
> > + *   ONEPASS_SINGLE, or ONEPASS_MULTI.  If eMode is not
> 12. No eMode.
Fixed.

> > + *   ONEPASS_OFF, then the cursor already points to the row to
> > + *   delete. If eMode is ONEPASS_OFF then this function must seek
> > + *   cursor to the entry identified by ipk and npk before reading
> > + *   from it.
> > + *
> > + *   If mode is ONEPASS_MULTI, then this call is being made as
> > + *   part of a ONEPASS delete that affects multiple rows. In this
> > + *   case, if idx_noseek is a valid cursor number (>=0), then its
> > + *   position should be preserved following the delete operation.
> > + *   Or, if iIdxNoSeek is not a valid cursor number, the position
> 13. No iIdxNoSeek. Same parameter name mismatches in next functions.
Fixed.

> > + *   of cursor should be preserved instead.
> > + *
> > + * idx_noseek:
> > + *   If idx_noseek is a valid cursor number (>=0), then it
> > + *   identifies an index cursor that already points to the index
> > + *   entry to be deleted.
> 14. I think, it can be moved into @param idx_noseek description. (That
> will be named idx_noseek_cursor).
Done.

> > + * @param parse Parsing context.
> > + * @param table Table containing the row to be deleted.
> > + * @param trigger_list List of triggers to (potentially) fire.
> > + * @param cursor Cursor from which column data is extracted/
> > + * @param ipk First memory cell containing the PRIMARY KEY.
> > + * @param npk umber of PRIMARY KEY memory cells.
> > + * @param is_count If non-zero, increment the row change counter.
> 15. need_update_count.
Fixed.

> > diff --git a/src/box/sql/update.c b/src/box/sql/update.c
> > index de1349b..60cb229 100644
> > --- a/src/box/sql/update.c
> > +++ b/src/box/sql/update.c
> > @@ -603,7 +605,6 @@ sqlite3Update(Parse * pParse,		/* The parser context */
> >   						     nKey);
> >   			VdbeCoverageNeverTaken(v);
> >   		}
> > -		sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur);
> 16. Why?
Tarantool remove index entries automatically, no need to do that explicitly.
Actual delete is emitted few lines later: by OP_Delete instruction.

> > diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> > index b8d4c1a..2ad6f3e 100644
> > --- a/src/box/sql/vdbe.c
> > +++ b/src/box/sql/vdbe.c
> > @@ -3236,20 +3236,20 @@ case OP_OpenTEphemeral: {
> >   	BtCursor *pBtCur;
> >   	assert(pOp->p1 >= 0);
> >   	assert(pOp->p2 > 0);
> > -	assert(pOp->p4type != P4_KEYDEF || pOp->p4.key_def != NULL);
> > +	assert(pOp->p4type == P4_KEYDEF);
> 17. Why was this check needed in the previous patch?
Since previously P4 is expected to be non-NULL pointer to key_def.
Now, constraint relaxed: it may be pointer to key_def or NULL, w/o type.

> >   	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
> >   	if (pCx == 0) goto no_mem;
> >   	pCx->nullRow = 1;
> > -	pCx->key_def  = pOp->p4.key_def;
> >   	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,
> > -					     pCx->key_def);
> > +					     pOp->p4.key_def);
> > +	pCx->key_def = pCx->uc.pCursor->index->def->key_def; 
> 18. Lets merge this into the previous patch.
Done.

> > diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> > index e67fcae..4a8a9d2 100644
> > --- a/src/box/sql/vdbeaux.c
> > +++ b/src/box/sql/vdbeaux.c
> > @@ -1134,7 +1134,7 @@ sqlite3VdbeChangeP4(Vdbe * p, int addr, const char *zP4, int n)
> >   	} if (n == P4_BOOL) {
> >   		pOp->p4.b = *(bool*)zP4;
> >   		pOp->p4type = P4_BOOL;
> > -	} else if (zP4 != 0) {
> > +	} else {
> 19. Why?
I'm allowing to set P4 to NULL pointer, if key_def is not needed by ephemeral table.

> > @@ -1571,7 +1571,10 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
> >   #endif
> >   	case P4_COLLSEQ:{
> >   			struct coll *pColl = pOp->p4.pColl;
> > -			sqlite3XPrintf(&x, "(%.20s)", pColl->name);
> > +			if (pColl != NULL)
> > +				sqlite3XPrintf(&x, "(%.20s)", pColl->name);
> > +			else
> > +				sqlite3XPrintf(&x, "(binary)");
> 20. Stray change?
Nope. Since I've allowed NULL pointers to reach P4, sometimes NILL pointer
of collation reaches this line. For Tarantool this is perfectly fine, since
NULL for struct coll* simply means binary collation.

--
Regards, Kirill Yukhin

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index c96d351..e3aadab 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -2664,10 +2664,10 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
 	VdbeCoverage(v);
 	regRecord = sqlite3GetTempReg(pParse);
 
-	sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord,
-				&iPartIdxLabel, 0, 0);
+	sql_generate_index_key(pParse, pIndex, iTab, regRecord,
+			       &iPartIdxLabel, 0, 0);
 	sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord);
-	sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
+	sql_resolve_part_idx_label(pParse, iPartIdxLabel);
 	sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1 + 1);
 	VdbeCoverage(v);
 	sqlite3VdbeJumpHere(v, addr1);
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 3455f52..551bcd8 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -29,593 +29,379 @@
  * SUCH DAMAGE.
  */
 
-/*
- * This file contains C code routines that are called by the parser
- * in order to generate code for DELETE FROM statements.
- */
-#include "sqliteInt.h"
 #include "box/session.h"
+#include "box/schema.h"
+#include "sqliteInt.h"
+#include "tarantoolInt.h"
 
-/*
- * While a SrcList can in general represent multiple tables and subqueries
- * (as in the FROM clause of a SELECT statement) in this case it contains
- * the name of a single table, as one might find in an INSERT, DELETE,
- * or UPDATE statement.  Look up that table in the symbol table and
- * return a pointer.  Set an error message and return NULL if the table
- * name is not found or if any other error occurs.
- *
- * The following fields are initialized appropriate in pSrc:
- *
- *    pSrc->a[0].pTab       Pointer to the Table object
- *    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if there is one
- *
- */
-Table *
-sqlite3SrcListLookup(Parse * pParse, SrcList * pSrc)
+struct Table *
+sql_list_lookup_table(struct Parse *parse, SrcList *src_list)
 {
-	struct SrcList_item *pItem = pSrc->a;
-	Table *pTab;
-	assert(pItem && pSrc->nSrc == 1);
-	pTab = sqlite3LocateTable(pParse, 0, pItem->zName);
-	sqlite3DeleteTable(pParse->db, pItem->pTab);
-	pItem->pTab = pTab;
-	if (pTab != NULL)
-		pTab->nTabRef++;
-	if (sqlite3IndexedByLookup(pParse, pItem))
-		pTab = NULL;
-	return pTab;
+	struct SrcList_item *item = src_list->a;
+	assert(item != NULL && src_list->nSrc == 1);
+	struct Table *table = sqlite3LocateTable(parse, 0, item->zName);
+	sqlite3DeleteTable(parse->db, item->pTab);
+	item->pTab = table;
+	if (table != NULL)
+		table->nTabRef++;
+	if (sqlite3IndexedByLookup(parse, item))
+		table = NULL;
+	return table;
 }
 
-/*
- * Check to make sure the given table is writable.  If it is not
- * writable, generate an error message and return 1.  If it is
- * writable return 0;
- */
-int
-sqlite3IsReadOnly(Parse * pParse, Table * pTab, int viewOk)
-{
-	/*
-	 * A table is not writable if it is a system table
-	 * (i.e. _sql_stat1), this call is not part of a
-	 * nested parse. In either case leave an error message in
-	 * pParse and return non-zero.
-	 */
-	if ((pTab->tabFlags & TF_Readonly) != 0 && pParse->nested == 0) {
-		sqlite3ErrorMsg(pParse, "table %s may not be modified",
-				pTab->zName);
-		return 1;
-	}
-#ifndef SQLITE_OMIT_VIEW
-	if (!viewOk && space_is_view(pTab)) {
-		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
-				pTab->zName);
-		return 1;
-	}
-#endif
-	return 0;
-}
-
-#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-/*
- * Evaluate a view and store its result in an ephemeral table.  The
- * pWhere argument is an optional WHERE clause that restricts the
- * set of rows in the view that are to be added to the ephemeral table.
- */
 void
-sqlite3MaterializeView(Parse * pParse,	/* Parsing context */
-		       Table * pView,	/* View definition */
-		       Expr * pWhere,	/* Optional WHERE clause to be added */
-		       int iCur)	/* Cursor number for ephemeral table */
+sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
+		     int cursor)
 {
-	SelectDest dest;
-	Select *pSel;
-	SrcList *pFrom;
-	sqlite3 *db = pParse->db;
-	pWhere = sqlite3ExprDup(db, pWhere, 0);
-	pFrom = sqlite3SrcListAppend(db, 0, 0);
-	if (pFrom) {
-		assert(pFrom->nSrc == 1);
-		pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName);
-		assert(pFrom->a[0].pOn == 0);
-		assert(pFrom->a[0].pUsing == 0);
-	}
-	pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0,
-				0, 0, 0);
-	sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur);
-	sqlite3Select(pParse, pSel, &dest);
-	sqlite3SelectDelete(db, pSel);
-}
-#endif				/* !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */
-
-#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
-/*
- * Generate an expression tree to implement the WHERE, ORDER BY,
- * and LIMIT/OFFSET portion of DELETE and UPDATE statements.
- *
- *     DELETE FROM table_wxyz WHERE a<5 ORDER BY a LIMIT 1;
- *                            \__________________________/
- *                               pLimitWhere (pInClause)
- */
-Expr *
-sqlite3LimitWhere(Parse * pParse,	/* The parser context */
-		  SrcList * pSrc,	/* the FROM clause -- which tables to scan */
-		  Expr * pWhere,	/* The WHERE clause.  May be null */
-		  ExprList * pOrderBy,	/* The ORDER BY clause.  May be null */
-		  Expr * pLimit,	/* The LIMIT clause.  May be null */
-		  Expr * pOffset,	/* The OFFSET clause.  May be null */
-		  char *zStmtType	/* Either DELETE or UPDATE.  For err msgs. */
-    )
-{
-	Expr *pWhereRowid = NULL;	/* WHERE rowid .. */
-	Expr *pInClause = NULL;	/* WHERE rowid IN ( select ) */
-	Expr *pSelectRowid = NULL;	/* SELECT rowid ... */
-	ExprList *pEList = NULL;	/* Expression list contaning only pSelectRowid */
-	SrcList *pSelectSrc = NULL;	/* SELECT rowid FROM x ... (dup of pSrc) */
-	Select *pSelect = NULL;	/* Complete SELECT tree */
-
-	/* Check that there isn't an ORDER BY without a LIMIT clause.
-	 */
-	if (pOrderBy && (pLimit == 0)) {
-		sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s",
-				zStmtType);
-		goto limit_where_cleanup;
-	}
-
-	/* We only need to generate a select expression if there
-	 * is a limit/offset term to enforce.
-	 */
-	if (pLimit == 0) {
-		/* if pLimit is null, pOffset will always be null as well. */
-		assert(pOffset == 0);
-		return pWhere;
+	struct sqlite3 *db = parse->db;
+	where = sqlite3ExprDup(db, where, 0);
+	struct SrcList *from = sqlite3SrcListAppend(db, NULL, NULL);
+	if (from != NULL) {
+		assert(from->nSrc == 1);
+		from->a[0].zName = sqlite3DbStrDup(db, name);
+		assert(from->a[0].pOn == NULL);
+		assert(from->a[0].pUsing == NULL);
 	}
-
-	/* Generate a select expression tree to enforce the limit/offset
-	 * term for the DELETE or UPDATE statement.  For example:
-	 *   DELETE FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
-	 * becomes:
-	 *   DELETE FROM table_a WHERE rowid IN (
-	 *     SELECT rowid FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
-	 *   );
-	 */
-
-	pSelectRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0);
-	if (pSelectRowid == 0)
-		goto limit_where_cleanup;
-	pEList = sqlite3ExprListAppend(pParse, 0, pSelectRowid);
-	if (pEList == 0)
-		goto limit_where_cleanup;
-
-	/* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree
-	 * and the SELECT subtree.
-	 */
-	pSelectSrc = sqlite3SrcListDup(pParse->db, pSrc, 0);
-	if (pSelectSrc == 0) {
-		sqlite3ExprListDelete(pParse->db, pEList);
-		goto limit_where_cleanup;
-	}
-
-	/* generate the SELECT expression tree. */
-	pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0, 0,
-				   pOrderBy, 0, pLimit, pOffset);
-	if (pSelect == 0)
-		return 0;
-
-	/* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */
-	pWhereRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0);
-	pInClause =
-	    pWhereRowid ? sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0) : 0;
-	sqlite3PExprAddSelect(pParse, pInClause, pSelect);
-	return pInClause;
-
- limit_where_cleanup:
-	sql_expr_free(pParse->db, pWhere);
-	sqlite3ExprListDelete(pParse->db, pOrderBy);
-	sql_expr_free(pParse->db, pLimit);
-	sql_expr_free(pParse->db, pOffset);
-	return 0;
+	struct Select *select = sqlite3SelectNew(parse, NULL, from, where, NULL,
+						 NULL, NULL, 0, NULL, NULL);
+	struct SelectDest dest;
+	sqlite3SelectDestInit(&dest, SRT_EphemTab, cursor);
+	sqlite3Select(parse, select, &dest);
+	sqlite3SelectDelete(db, select);
 }
-#endif				/* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) */
 
-/*
- * Generate code for a DELETE FROM statement.
- *
- *     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
- *                 \________/       \________________/
- *                  pTabList              pWhere
- */
 void
-sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
-		  SrcList * pTabList,	/* The table from which we should delete things */
-		  Expr * pWhere)	/* The WHERE clause.  May be null */
+sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
+		      struct Expr *where)
 {
-	Vdbe *v;		/* The virtual database engine */
-	Table *pTab;		/* The table from which records will be deleted */
-	int i;			/* Loop counter */
-	WhereInfo *pWInfo;	/* Information about the WHERE clause */
-	Index *pIdx;		/* For looping over indices of the table */
-	int iTabCur;		/* Cursor number for the table */
-	int iDataCur = 0;	/* VDBE cursor for the canonical data source */
-	int iIdxCur = 0;	/* Cursor number of the first index */
-	int nIdx;		/* Number of indices */
-	sqlite3 *db;		/* Main database structure */
-	NameContext sNC;	/* Name context to resolve expressions in */
-	int memCnt = -1;	/* Memory cell used for change counting */
-	int eOnePass;		/* ONEPASS_OFF or _SINGLE or _MULTI */
-	int aiCurOnePass[2];	/* The write cursors opened by WHERE_ONEPASS */
-	u8 *aToOpen = 0;	/* Open cursor iTabCur+j if aToOpen[j] is true */
-	Index *pPk = 0;		/* The PRIMARY KEY index on the table */
-	int iPk = 0;		/* First of nPk registers holding PRIMARY KEY value */
-	i16 nPk;		/* Number of columns in the PRIMARY KEY */
-	int iKey;		/* Memory cell holding key of row to be deleted */
-	i16 nKey;		/* Number of memory cells in the row key */
-	int iEphCur = 0;	/* Ephemeral table holding all primary key values */
-	int addrBypass = 0;	/* Address of jump over the delete logic */
-	int addrLoop = 0;	/* Top of the delete loop */
-	int addrEphOpen = 0;	/* Instruction to open the Ephemeral table */
-	int bComplex;		/* True if there are triggers or FKs or
-				 * subqueries in the WHERE clause
-				 */
-	struct session *user_session = current_session();
+	struct sqlite3 *db = parse->db;
+	if (parse->nErr || db->mallocFailed)
+		goto delete_from_cleanup;
 
-#ifndef SQLITE_OMIT_TRIGGER
-	int isView;		/* True if attempting to delete from a view */
-	Trigger *pTrigger;	/* List of table triggers, if required */
-#endif
+	assert(tab_list->nSrc == 1);
 
-	db = pParse->db;
-	if (pParse->nErr || db->mallocFailed) {
+	/* Locate the table which we want to delete.  This table
+	 * has to be put in an SrcList structure because some of
+	 * the subroutines we will be calling are designed to work
+	 * with multiple tables and expect an SrcList* parameter
+	 * instead of just a Table* parameter.
+	 */
+	struct Table *table = sql_list_lookup_table(parse, tab_list);
+	if (table == NULL)
 		goto delete_from_cleanup;
-	}
-	assert(pTabList->nSrc == 1);
 
-	/* Locate the table which we want to delete.  This table has to be
-	 * put in an SrcList structure because some of the subroutines we
-	 * will be calling are designed to work with multiple tables and expect
-	 * an SrcList* parameter instead of just a Table* parameter.
+	struct space *space = space_by_id(SQLITE_PAGENO_TO_SPACEID(table->tnum));
+	assert(space != NULL);
+	/* Figure out if we have any triggers and if the table
+	 * being deleted from is a view.
 	 */
-	pTab = sqlite3SrcListLookup(pParse, pTabList);
-	if (pTab == 0)
-		goto delete_from_cleanup;
+	struct Trigger *trigger_list = sqlite3TriggersExist(table, TK_DELETE,
+							    NULL, NULL);
 
-	/* Figure out if we have any triggers and if the table being
-	 * deleted from is a view
+	bool is_view = space->def->opts.is_view;
+	/* True if there are triggers or FKs or subqueries in the
+	 * WHERE clause.
 	 */
-#ifndef SQLITE_OMIT_TRIGGER
-	pTrigger = sqlite3TriggersExist(pTab, TK_DELETE, 0, 0);
-	isView = pTab->pSelect != 0;
-	bComplex = pTrigger || sqlite3FkRequired(pTab, 0);
-#else
-#define pTrigger 0
-#define isView 0
-#endif
-#ifdef SQLITE_OMIT_VIEW
-#undef isView
-#define isView 0
-#endif
-
-	/* If pTab is really a view, make sure it has been initialized.
+	bool is_complex = (trigger_list != NULL) || sqlite3FkRequired(table, 0);
+
+	/* If table is really a view, make sure it has been
+	 * initialized.
 	 */
-	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
+	if (sqlite3ViewGetColumnNames(parse, table))
 		goto delete_from_cleanup;
-	}
 
-	if (sqlite3IsReadOnly(pParse, pTab, (pTrigger ? 1 : 0))) {
+	if (is_view && trigger_list == NULL) {
+		sqlite3ErrorMsg(parse, "cannot modify %s because it is a view",
+				space->def->name);
 		goto delete_from_cleanup;
 	}
-	assert(!isView || pTrigger);
 
 	/* Assign cursor numbers to the table and all its indices.
 	 */
-	assert(pTabList->nSrc == 1);
-	iTabCur = pTabList->a[0].iCursor = pParse->nTab++;
-	for (nIdx = 0, pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext, nIdx++) {
-		pParse->nTab++;
-	}
+	int tab_cursor = tab_list->a[0].iCursor = parse->nTab++;
+	parse->nTab += space->index_count;
 
-	/* Begin generating code.
-	 */
-	v = sqlite3GetVdbe(pParse);
-	if (v == 0) {
+	/* Begin generating code.  */
+	struct Vdbe *v = sqlite3GetVdbe(parse);
+	if (v == NULL)
 		goto delete_from_cleanup;
-	}
-	if (pParse->nested == 0)
+
+	if (parse->nested == 0)
 		sqlite3VdbeCountChanges(v);
-	sql_set_multi_write(pParse, true);
+	sql_set_multi_write(parse, true);
 
-	/* If we are trying to delete from a view, realize that view into
-	 * an ephemeral table.
+	/* If we are trying to delete from a view, realize that
+	 * view into an ephemeral table.
 	 */
-#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-	if (isView) {
-		sqlite3MaterializeView(pParse, pTab, pWhere, iTabCur);
-		iDataCur = iIdxCur = iTabCur;
+	if (is_view) {
+		sql_materialize_view(parse, space->def->name, where,
+				     tab_cursor);
 	}
-#endif
 
-	/* Resolve the column names in the WHERE clause.
-	 */
-	memset(&sNC, 0, sizeof(sNC));
-	sNC.pParse = pParse;
-	sNC.pSrcList = pTabList;
-	if (sqlite3ResolveExprNames(&sNC, pWhere)) {
+	/* Resolve the column names in the WHERE clause. */
+	struct NameContext nc;
+	memset(&nc, 0, sizeof(nc));
+	nc.pParse = parse;
+	nc.pSrcList = tab_list;
+	if (sqlite3ResolveExprNames(&nc, where))
 		goto delete_from_cleanup;
-	}
 
-	/* Initialize the counter of the number of rows deleted, if
-	 * we are counting rows.
+	/* Initialize the counter of the number of rows deleted,
+	 * if we are counting rows.
 	 */
+	int reg_count = -1;
+	struct session *user_session = current_session();
 	if (user_session->sql_flags & SQLITE_CountRows) {
-		memCnt = ++pParse->nMem;
-		sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt);
+		reg_count = ++parse->nMem;
+		sqlite3VdbeAddOp2(v, OP_Integer, 0, reg_count);
 	}
-#ifndef SQLITE_OMIT_TRUNCATE_OPTIMIZATION
-	/* Special case: A DELETE without a WHERE clause deletes everything.
-	 * It is easier just to erase the whole table. Prior to version 3.6.5,
-	 * this optimization caused the row change count (the value returned by
-	 * API function sqlite3_count_changes) to be set incorrectly.
+	/* Special case: A DELETE without a WHERE clause deletes
+	 * everything. It is easier just to erase the whole table.
 	 */
-	if (pWhere == 0 && !bComplex
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-	    && db->xPreUpdateCallback == 0
-#endif
-	    ) {
-		assert(!isView);
-
-		sqlite3VdbeAddOp1(v, OP_Clear, pTab->tnum);
-
-		/* Do not start Tarantool's transaction in case of truncate optimization.
-		   This is workaround until system tables cannot be changes inside a
-		   transaction (_truncate).  */
-		pParse->initiateTTrans = false;
-	} else
-#endif				/* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */
-	{
-		u16 wcf =
-		    WHERE_ONEPASS_DESIRED | WHERE_DUPLICATES_OK |
-		    WHERE_SEEK_TABLE;
-		if (sNC.ncFlags & NC_VarSelect)
-			bComplex = 1;
-		wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW);
-		/* Create an ephemeral table used to hold all primary keys for
-		 * rows to be deleted. Since VIEW is held in ephemeral table,
-		 * there is no PK for it, so columns should be loaded manually.
+	if (where == NULL && !is_complex) {
+		assert(!is_view);
+
+		sqlite3VdbeAddOp1(v, OP_Clear, table->tnum);
+
+		/* Do not start Tarantool's transaction in case of
+		 * truncate optimization. This is workaround until
+		 * system tables cannot be changes inside a
+		 * transaction (_truncate).
 		 */
-		if (isView) {
-			nPk = pTab->nCol;
-			iPk = pParse->nMem + 1;
-			pParse->nMem += nPk;
-			iEphCur = pParse->nTab++;
-			struct key_def *def = key_def_new(nPk);
-			if (def == NULL) {
-				sqlite3OomFault(db);
-				goto delete_from_cleanup;
-			}
-			addrEphOpen =
-				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
-						  nPk, 0, (char*)def, P4_KEYDEF);
+		parse->initiateTTrans = false;
+	} else {
+		uint16_t wcf = WHERE_ONEPASS_DESIRED | WHERE_DUPLICATES_OK |
+			       WHERE_SEEK_TABLE;
+		if (nc.ncFlags & NC_VarSelect)
+			is_complex = true;
+		wcf |= (is_complex ? 0 : WHERE_ONEPASS_MULTIROW);
+		/* Create an ephemeral table used to hold all
+		 * primary keys for rows to be deleted. Since VIEW
+		 * is held in ephemeral table, there is no PK for
+		 * it, so columns should be loaded manually.
+		 */
+		struct Index *pk = NULL;
+		int reg_pk = parse->nMem + 1;
+		int pk_len;
+		int eph_cursor = parse->nTab++;
+		int addr_eph_open = sqlite3VdbeCurrentAddr(v);
+		if (is_view) {
+			pk_len = table->nCol;
+			parse->nMem += pk_len;
+			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral,
+					  eph_cursor, pk_len, 0,
+					  (char*)NULL, P4_KEYDEF);
 		} else {
-			pPk = sqlite3PrimaryKeyIndex(pTab);
-			assert(pPk != 0);
-			nPk = index_column_count(pPk);
-			iPk = pParse->nMem + 1;
-			pParse->nMem += nPk;
-			iEphCur = pParse->nTab++;
-			addrEphOpen =
-			    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEphCur,
-					      nPk);
-			sql_vdbe_set_p4_key_def(pParse, pPk);
+			pk = sqlite3PrimaryKeyIndex(table);
+			assert(pk != NULL);
+			pk_len = index_column_count(pk);
+			parse->nMem += pk_len;
+			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, eph_cursor,
+					  pk_len);
+			sql_vdbe_set_p4_key_def(parse, pk);
 		}
 
-		/* Construct a query to find the primary key for every row
-		 * to be deleted, based on the WHERE clause. Set variable eOnePass
-		 * to indicate the strategy used to implement this delete:
+		/* Construct a query to find the primary key for
+		 * every row to be deleted, based on the WHERE
+		 * clause. Set variable one_pass to indicate the
+		 * strategy used to implement this delete:
 		 *
-		 *  ONEPASS_OFF:    Two-pass approach - use a FIFO for PK values.
-		 *  ONEPASS_SINGLE: One-pass approach - at most one row deleted.
-		 *  ONEPASS_MULTI:  One-pass approach - any number of rows may be deleted.
+		 * ONEPASS_OFF:    Two-pass approach - use a FIFO
+		 * for PK values.
+		 * ONEPASS_SINGLE: One-pass approach - at most one
+		 * row deleted.
+		 * ONEPASS_MULTI:  One-pass approach - any number
+		 * of rows may be deleted.
 		 */
-		pWInfo =
-		    sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf,
-				      iTabCur + 1);
-		if (pWInfo == 0)
+		struct WhereInfo *winfo =
+		    sqlite3WhereBegin(parse, tab_list, where, NULL, NULL, wcf,
+				      tab_cursor + 1);
+		if (winfo == NULL)
 			goto delete_from_cleanup;
-		eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
-		assert(eOnePass != ONEPASS_MULTI);
-		/* Tarantool workaround: see comment in sqlite3WhereBegin.  */
-		/* assert( bComplex || eOnePass!=ONEPASS_OFF ); */
-
-		/* Keep track of the number of rows to be deleted */
-		if (user_session->sql_flags & SQLITE_CountRows) {
-			sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1);
-		}
+
+		/* The write cursors opened by WHERE_ONEPASS */
+		int one_pass_cur[2];
+		int one_pass = sqlite3WhereOkOnePass(winfo, one_pass_cur);
+		assert(one_pass != ONEPASS_MULTI);
+		/* Tarantool: see comment in
+		 * sqlite3WhereOkOnePass.
+		 */
+		/* assert(is_complex || one_pass != ONEPASS_OFF); */
+
+		/* Keep track of the number of rows to be
+		 * deleted.
+		 */
+		if (user_session->sql_flags & SQLITE_CountRows)
+			sqlite3VdbeAddOp2(v, OP_AddImm, reg_count, 1);
 
 		/* Extract the primary key for the current row */
-		if (!isView) {
-			for (i = 0; i < nPk; i++) {
-				assert(pPk->aiColumn[i] >= 0);
-				sqlite3ExprCodeGetColumnOfTable(v, pTab,
-								iTabCur,
-								pPk->
+		if (!is_view) {
+			for (int i = 0; i < pk_len; i++) {
+				assert(pk->aiColumn[i] >= 0);
+				sqlite3ExprCodeGetColumnOfTable(v, table,
+								tab_cursor,
+								pk->
 								aiColumn[i],
-								iPk + i);
+								reg_pk + i);
 			}
 		} else {
-			for (i = 0; i < nPk; i++) {
-				sqlite3VdbeAddOp3(v, OP_Column, iDataCur,
-						  i, iPk + i);
+			for (int i = 0; i < pk_len; i++) {
+				sqlite3VdbeAddOp3(v, OP_Column, tab_cursor,
+						  i, reg_pk + i);
 			}
 		}
-		iKey = iPk;
 
-		if (eOnePass != ONEPASS_OFF) {
-			/* For ONEPASS, no need to store the primary-key. There is only
-			 * one, so just keep it in its register(s) and fall through to the
-			 * delete code.
+		int reg_key;
+		int key_len;
+		if (one_pass != ONEPASS_OFF) {
+			/* For ONEPASS, no need to store the
+			 * primary-key. There is only one, so just
+			 * keep it in its register(s) and fall
+			 * through to the delete code.
 			 */
-			nKey = nPk;	/* OP_Found will use an unpacked key */
-			aToOpen = sqlite3DbMallocRawNN(db, nIdx + 2);
-			if (aToOpen == 0) {
-				sqlite3WhereEnd(pWInfo);
-				goto delete_from_cleanup;
-			}
-			memset(aToOpen, 1, nIdx + 1);
-			aToOpen[nIdx + 1] = 0;
-			if (aiCurOnePass[0] >= 0)
-				aToOpen[aiCurOnePass[0] - iTabCur] = 0;
-			if (aiCurOnePass[1] >= 0)
-				aToOpen[aiCurOnePass[1] - iTabCur] = 0;
-			if (addrEphOpen)
-				sqlite3VdbeChangeToNoop(v, addrEphOpen);
+			reg_key = reg_pk;
+			/* OP_Found will use an unpacked key */
+			key_len = pk_len;
+			sqlite3VdbeChangeToNoop(v, addr_eph_open);
 		} else {
-			/* Add the PK key for this row to the temporary table */
-			iKey = ++pParse->nMem;
-			nKey = 0;	/* Zero tells OP_Found to use a composite key */
-			const char *zAff = isView ? 0 :
-					  sqlite3IndexAffinityStr(pParse->db, pPk);
-			sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey, zAff, nPk);
-			/* Set flag to save memory allocating one by malloc. */
+			/* Add the PK key for this row to the
+			 * temporary table.
+			 */
+			reg_key = ++parse->nMem;
+			/* Zero tells OP_Found to use a composite
+			 * key.
+			 */
+			key_len = 0;
+			const char *zAff = is_view ? NULL :
+					  sqlite3IndexAffinityStr(parse->db, pk);
+			sqlite3VdbeAddOp4(v, OP_MakeRecord, reg_pk, pk_len,
+					  reg_key, zAff, pk_len);
+			/* Set flag to save memory allocating one
+			 * by malloc.
+			 */
 			sqlite3VdbeChangeP5(v, 1);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, eph_cursor, reg_key);
 		}
 
-		/* If this DELETE cannot use the ONEPASS strategy, this is the
-		 * end of the WHERE loop
+		/* If this DELETE cannot use the ONEPASS strategy,
+		 * this is the end of the WHERE loop.
 		 */
-		if (eOnePass != ONEPASS_OFF) {
-			addrBypass = sqlite3VdbeMakeLabel(v);
-		} else {
-			sqlite3WhereEnd(pWInfo);
-		}
-
-		/* Unless this is a view, open cursors for the table we are
-		 * deleting from and all its indices. If this is a view, then the
-		 * only effect this statement has is to fire the INSTEAD OF
+		int addr_bypass = 0;
+		if (one_pass != ONEPASS_OFF)
+			addr_bypass = sqlite3VdbeMakeLabel(v);
+		else
+			sqlite3WhereEnd(winfo);
+
+		/* Unless this is a view, open cursors for the
+		 * table we are deleting from and all its indices.
+		 * If this is a view, then the only effect this
+		 * statement has is to fire the INSTEAD OF
 		 * triggers.
 		 */
-		if (!isView) {
+		if (!is_view) {
 			int iAddrOnce = 0;
-			if (eOnePass == ONEPASS_MULTI) {
+			if (one_pass == ONEPASS_MULTI) {
 				iAddrOnce = sqlite3VdbeAddOp0(v, OP_Once);
 				VdbeCoverage(v);
 			}
-			sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite,
-						   OPFLAG_FORDELETE, iTabCur,
-						   aToOpen, &iDataCur,
-						   &iIdxCur,
-						   ON_CONFLICT_ACTION_NONE, 0);
-			assert(pPk || iDataCur == iTabCur);
-			assert(pPk || iIdxCur == iDataCur + 1);
-			if (eOnePass == ONEPASS_MULTI)
+
+			int space_ptr_reg = ++parse->nMem;
+			sqlite3VdbeAddOp4Ptr(v, OP_LoadPtr, 0, space_ptr_reg, 0,
+					     (void *) space);
+			sqlite3VdbeAddOp3(v, OP_OpenWrite, tab_cursor,
+					  table->tnum, space_ptr_reg);
+			sql_vdbe_set_p4_key_def(parse, pk);
+			VdbeComment((v, "%s", pk->zName));
+
+			if (one_pass == ONEPASS_MULTI)
 				sqlite3VdbeJumpHere(v, iAddrOnce);
 		}
 
-		/* Set up a loop over the primary-keys that were found in the
-		 * where-clause loop above.
+		/* Set up a loop over the primary-keys that were
+		 * found in the where-clause loop above.
 		 */
-		if (eOnePass != ONEPASS_OFF) {
-			assert(nKey == nPk);	/* OP_Found will use an unpacked key */
-			if (aToOpen[iDataCur - iTabCur]) {
-				assert(pPk != 0 || pTab->pSelect != 0);
-				sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur,
-						     addrBypass, iKey, nKey);
+		int addr_loop = 0;
+		if (one_pass != ONEPASS_OFF) {
+			/* OP_Found will use an unpacked key. */
+			assert(key_len == pk_len);
+			assert(pk != NULL || table->pSelect != NULL);
+			sqlite3VdbeAddOp4Int(v, OP_NotFound, tab_cursor,
+					     addr_bypass, reg_key, key_len);
 
-				VdbeCoverage(v);
-			}
+			VdbeCoverage(v);
 		} else {
-			addrLoop = sqlite3VdbeAddOp1(v, OP_Rewind, iEphCur);
+			addr_loop = sqlite3VdbeAddOp1(v, OP_Rewind, eph_cursor);
 			VdbeCoverage(v);
-			sqlite3VdbeAddOp2(v, OP_RowData, iEphCur, iKey);
+			sqlite3VdbeAddOp2(v, OP_RowData, eph_cursor, reg_key);
 		}
 
 		/* Delete the row */
-		{
-			int count = (pParse->nested == 0);	/* True to count changes */
-			int iIdxNoSeek = -1;
-			if (bComplex == 0 && aiCurOnePass[1] != iDataCur
-			    /* Tarantool: as far as ONEPASS is disabled, there's no index
-			       w/o need of seeking.  */
-			    && eOnePass != ONEPASS_OFF) {
-				iIdxNoSeek = aiCurOnePass[1];
-			}
-			sqlite3GenerateRowDelete(pParse, pTab, pTrigger,
-						 iDataCur, iIdxCur, iKey, nKey,
-						 count,
-						 ON_CONFLICT_ACTION_DEFAULT,
-						 eOnePass,
-						 iIdxNoSeek);
-		}
+		int idx_noseek = -1;
+		if (!is_complex && one_pass_cur[1] != tab_cursor
+		    /* Tarantool: as far as ONEPASS is disabled,
+		     * there's no index w/o need of seeking.
+		     */
+		    && one_pass != ONEPASS_OFF)
+			idx_noseek = one_pass_cur[1];
+
+		sql_generate_row_delete(parse, table, trigger_list, tab_cursor,
+					reg_key, key_len, parse->nested == 0,
+					ON_CONFLICT_ACTION_DEFAULT, one_pass,
+					idx_noseek);
 
 		/* End of the loop over all primary-keys. */
-		if (eOnePass != ONEPASS_OFF) {
-			sqlite3VdbeResolveLabel(v, addrBypass);
-			sqlite3WhereEnd(pWInfo);
+		if (one_pass != ONEPASS_OFF) {
+			sqlite3VdbeResolveLabel(v, addr_bypass);
+			sqlite3WhereEnd(winfo);
 		} else {
-			sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop + 1);
+			sqlite3VdbeAddOp2(v, OP_Next, eph_cursor,
+					  addr_loop + 1);
 			VdbeCoverage(v);
-			sqlite3VdbeJumpHere(v, addrLoop);
+			sqlite3VdbeJumpHere(v, addr_loop);
 		}
-	}			/* End non-truncate path */
+	}
 
-	/* Return the number of rows that were deleted. If this routine is
-	 * generating code because of a call to sqlite3NestedParse(), do not
-	 * invoke the callback function.
+	/* Return the number of rows that were deleted. If this
+	 * routine is generating code because of a call to
+	 * sqlite3NestedParse(), do not invoke the callback
+	 * function.
 	 */
 	if ((user_session->sql_flags & SQLITE_CountRows) &&
-	    !pParse->nested && !pParse->pTriggerTab) {
-		sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1);
+	    parse->nested == 0 && !parse->pTriggerTab) {
+		sqlite3VdbeAddOp2(v, OP_ResultRow, reg_count, 1);
 		sqlite3VdbeSetNumCols(v, 1);
 		sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted",
 				      SQLITE_STATIC);
 	}
 
  delete_from_cleanup:
-	sqlite3SrcListDelete(db, pTabList);
-	sql_expr_free(db, pWhere, false);
-	sqlite3DbFree(db, aToOpen);
-	return;
+	sqlite3SrcListDelete(db, tab_list);
+	sql_expr_free(db, where, false);
 }
 
-/* Generate VDBE code for
- * DELETE FROM <pTab.z> WHERE
- *        <columns[0]> = <values[0]>
- *    AND  ...
- *    AND <columns[nPairs - 1]> = <values[nPairs - 1]>;
- *
- * This function does not increment the nested counter and is
- * faster than nested parsing of the request above.
- * @param pParse Parser context.
- * @param pTab Table name.
- * @param columns Column names array.
- * @param values Column values array.
- * @param nPairs Length of @columns and @values.
- *
- * In case of error the @values elements are deleted.
- */
 void
-sqlite3DeleteByKey(Parse *pParse, char *zTab, const char **columns,
-		   Expr **values, int nPairs)
+sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
+		    struct Expr **values, int pairs_count)
 {
-	Expr *where = NULL;
-	SrcList *src;
-
-		assert(nPairs > 0);
-	if (pParse->nErr > 0 || pParse->db->mallocFailed)
+	struct Expr *where = NULL;
+	if (parse->nErr > 0 || parse->db->mallocFailed)
 		goto error;
-	src = sql_alloc_src_list(pParse->db);
-	src->a[0].zName = sqlite3DbStrDup(pParse->db, zTab);
+	struct SrcList *src = sql_alloc_src_list(parse->db);
 	if (src == NULL)
 		goto error;
+	src->a[0].zName = sqlite3DbStrDup(parse->db, t_name);
 	/* Dummy init of INDEXED BY clause. */
-	Token t = { NULL, 0, false };
-	sqlite3SrcListIndexedBy(pParse, src, &t);
+	struct Token t = {NULL, 0, false};
+	sqlite3SrcListIndexedBy(parse, src, &t);
 
-	for (int i = 0; i < nPairs; ++i) {
-		Expr *col_expr = sqlite3Expr(pParse->db, TK_ID, columns[i]);
+	assert(pairs_count > 0);
+	for (int i = 0; i < pairs_count; ++i) {
+		struct Expr *col_expr =
+			sqlite3Expr(parse->db, TK_ID, columns[i]);
 		if (col_expr == NULL || values[i] == NULL)
 			goto error;
-		Expr *eq_expr =
-		    sqlite3PExpr(pParse, TK_EQ, col_expr, values[i]);
+		struct Expr *eq_expr =
+		    sqlite3PExpr(parse, TK_EQ, col_expr, values[i]);
 		/* In case of error the values[i] had been deleted in
 		 * sqlite3PExpr already. Do not delete it second time in the
 		 * cycle below.
@@ -626,337 +412,204 @@ sqlite3DeleteByKey(Parse *pParse, char *zTab, const char **columns,
 		if (i == 0) {
 			where = eq_expr;
 		} else {
-			where = sqlite3ExprAnd(pParse->db, where, eq_expr);
+			where = sqlite3ExprAnd(parse->db, where, eq_expr);
 			if (where == NULL)
 				goto error;
 		}
 	}
 	/* DeleteFrom frees the src and exprs in case of error. */
-	sqlite3DeleteFrom(pParse, src, where);
+	sql_table_delete_from(parse, src, where);
 	return;
 
  error:
-	sql_expr_free(pParse->db, where, false);
-	for (int i = 0; i < nPairs; ++i)
-		sql_expr_free(pParse->db, values[i], false);
+	sql_expr_free(parse->db, where, false);
+	for (int i = 0; i < pairs_count; ++i)
+		sql_expr_free(parse->db, values[i], false);
 }
 
-/* Make sure "isView" and other macros defined above are undefined. Otherwise
- * they may interfere with compilation of other functions in this file
- * (or in another file, if this file becomes part of the amalgamation).
- */
-#ifdef isView
-#undef isView
-#endif
-#ifdef pTrigger
-#undef pTrigger
-#endif
-
-/*
- * This routine generates VDBE code that causes a single row of a
- * single table to be deleted.  Both the original table entry and
- * all indices are removed.
- *
- * Preconditions:
- *
- *   1.  iDataCur is an open cursor on the btree that is the canonical data
- *       store for the table.  (This will be the PRIMARY KEY index)
- *
- *   2.  Read/write cursors for all indices of pTab must be open as
- *       cursor number iIdxCur+i for the i-th index.
- *
- *   3.  The primary key for the row to be deleted must be stored in a
- *       sequence of nPk memory cells starting at iPk.  If nPk==0 that means
- *       that a search record formed from OP_MakeRecord is contained in the
- *       single memory location iPk.
- *
- * eMode:
- *   Parameter eMode may be passed either ONEPASS_OFF (0), ONEPASS_SINGLE, or
- *   ONEPASS_MULTI.  If eMode is not ONEPASS_OFF, then the cursor
- *   iDataCur already points to the row to delete. If eMode is ONEPASS_OFF
- *   then this function must seek iDataCur to the entry identified by iPk
- *   and nPk before reading from it.
- *
- *   If eMode is ONEPASS_MULTI, then this call is being made as part
- *   of a ONEPASS delete that affects multiple rows. In this case, if
- *   iIdxNoSeek is a valid cursor number (>=0), then its position should
- *   be preserved following the delete operation. Or, if iIdxNoSeek is not
- *   a valid cursor number, the position of iDataCur should be preserved
- *   instead.
- *
- * iIdxNoSeek:
- *   If iIdxNoSeek is a valid cursor number (>=0), then it identifies an
- *   index cursor (from within array of cursors starting at iIdxCur) that
- *   already points to the index entry to be deleted.
- */
 void
-sqlite3GenerateRowDelete(Parse * pParse,	/* Parsing context */
-			 Table * pTab,		/* Table containing the row to be deleted */
-			 Trigger * pTrigger,	/* List of triggers to (potentially) fire */
-			 int iDataCur,		/* Cursor from which column data is extracted */
-			 int iIdxCur,		/* First index cursor */
-			 int iPk,		/* First memory cell containing the PRIMARY KEY */
-			 i16 nPk,		/* Number of PRIMARY KEY memory cells */
-			 u8 count,		/* If non-zero, increment the row change counter */
-			 enum on_conflict_action onconf,		/* Default ON CONFLICT policy for triggers */
-			 u8 eMode,		/* ONEPASS_OFF, _SINGLE, or _MULTI.  See above */
-			 int iIdxNoSeek)	/* Cursor number of cursor that does not need seeking */
+sql_generate_row_delete(struct Parse *parse, struct Table *table,
+			struct Trigger *trigger_list, int cursor, int reg_pk,
+			short npk, bool need_update_count,
+			enum on_conflict_action onconf, u8 mode,
+			int idx_noseek)
 {
-	Vdbe *v = pParse->pVdbe;	/* Vdbe */
-	int iOld = 0;		/* First register in OLD.* array */
-	int iLabel;		/* Label resolved to end of generated code */
-
-	/* Vdbe is guaranteed to have been allocated by this stage. */
-	assert(v);
-	(void)iIdxCur;
+	struct Vdbe *v = parse->pVdbe;
+	/* Vdbe is guaranteed to have been allocated by this
+	 * stage.
+	 */
+	assert(v != NULL);
 	VdbeModuleComment((v, "BEGIN: GenRowDel(%d,%d,%d,%d)",
-			   iDataCur, iIdxCur, iPk, (int)nPk));
+			   cursor, iIdxCur, reg_pk, (int)nPk));
 
-	/* Seek cursor iCur to the row to delete. If this row no longer exists
-	 * (this can happen if a trigger program has already deleted it), do
-	 * not attempt to delete it or fire any DELETE triggers.
+	/* Seek cursor iCur to the row to delete. If this row no
+	 * longer exists (this can happen if a trigger program has
+	 * already deleted it), do not attempt to delete it or
+	 * fire any DELETE triggers.
 	 */
-	iLabel = sqlite3VdbeMakeLabel(v);
-	if (eMode == ONEPASS_OFF) {
-		sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, iLabel, iPk, nPk);
+	int label = sqlite3VdbeMakeLabel(v);
+	if (mode == ONEPASS_OFF) {
+		sqlite3VdbeAddOp4Int(v, OP_NotFound, cursor, label, reg_pk, npk);
 		VdbeCoverageIf(v, opSeek == OP_NotFound);
 	}
 
+	int first_old_reg = 0;
 	/* If there are any triggers to fire, allocate a range of registers to
 	 * use for the old.* references in the triggers.
 	 */
-	if (sqlite3FkRequired(pTab, 0) || pTrigger) {
-		u32 mask;	/* Mask of OLD.* columns in use */
-		int iCol;	/* Iterator used while populating OLD.* */
-		int addrStart;	/* Start of BEFORE trigger programs */
-
-		/* TODO: Could use temporary registers here.
-		 */
-		mask =
-		    sqlite3TriggerColmask(pParse, pTrigger, 0, 0,
-					  TRIGGER_BEFORE | TRIGGER_AFTER, pTab,
+	if (sqlite3FkRequired(table, 0) || trigger_list != NULL) {
+		/* Mask of OLD.* columns in use */
+		/* TODO: Could use temporary registers here. */
+		uint32_t mask =
+		    sqlite3TriggerColmask(parse, trigger_list, 0, 0,
+					  TRIGGER_BEFORE | TRIGGER_AFTER, table,
 					  onconf);
-		mask |= sqlite3FkOldmask(pParse, pTab);
-		iOld = pParse->nMem + 1;
-		pParse->nMem += (1 + pTab->nCol);
+		mask |= sqlite3FkOldmask(parse, table);
+		first_old_reg = parse->nMem + 1;
+		parse->nMem += (1 + table->nCol);
 
-		/* Populate the OLD.* pseudo-table register array. These values will be
-		 * used by any BEFORE and AFTER triggers that exist.
+		/* Populate the OLD.* pseudo-table register array.
+		 * These values will be used by any BEFORE and
+		 * AFTER triggers that exist.
 		 */
-		sqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld);
-		for (iCol = 0; iCol < pTab->nCol; iCol++) {
+		sqlite3VdbeAddOp2(v, OP_Copy, reg_pk, first_old_reg);
+		for (int i = 0; i < table->nCol; i++) {
 			testcase(mask != 0xffffffff && iCol == 31);
 			testcase(mask != 0xffffffff && iCol == 32);
 			if (mask == 0xffffffff
-			    || (iCol <= 31 && (mask & MASKBIT32(iCol)) != 0)) {
-				sqlite3ExprCodeGetColumnOfTable(v, pTab,
-								iDataCur, iCol,
-								iOld + iCol +
-								1);
+			    || (i <= 31 && (mask & MASKBIT32(i)) != 0)) {
+				sqlite3ExprCodeGetColumnOfTable(v, table,
+								cursor, i,
+								first_old_reg +
+								i + 1);
 			}
 		}
 
 		/* Invoke BEFORE DELETE trigger programs. */
-		addrStart = sqlite3VdbeCurrentAddr(v);
-		sqlite3CodeRowTrigger(pParse, pTrigger,
-				      TK_DELETE, 0, TRIGGER_BEFORE, pTab, iOld,
-				      onconf, iLabel);
-
-		/* If any BEFORE triggers were coded, then seek the cursor to the
-		 * row to be deleted again. It may be that the BEFORE triggers moved
-		 * the cursor or of already deleted the row that the cursor was
-		 * pointing to.
+		int addr_start = sqlite3VdbeCurrentAddr(v);
+		sqlite3CodeRowTrigger(parse, trigger_list,
+				      TK_DELETE, 0, TRIGGER_BEFORE, table,
+				      first_old_reg, onconf, label);
+
+		/* If any BEFORE triggers were coded, then seek
+		 * the cursor to the row to be deleted again. It
+		 * may be that the BEFORE triggers moved the
+		 * cursor or of already deleted the row that the
+		 * cursor was pointing to.
 		 */
-		if (addrStart < sqlite3VdbeCurrentAddr(v)) {
-			sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, iLabel, iPk,
-					     nPk);
+		if (addr_start < sqlite3VdbeCurrentAddr(v)) {
+			sqlite3VdbeAddOp4Int(v, OP_NotFound, cursor, label,
+					     reg_pk, npk);
 			VdbeCoverageIf(v, opSeek == OP_NotFound);
 		}
 
-		/* Do FK processing. This call checks that any FK constraints that
-		 * refer to this table (i.e. constraints attached to other tables)
-		 * are not violated by deleting this row.
+		/* Do FK processing. This call checks that any FK
+		 * constraints that refer to this table (i.e.
+		 * constraints attached to other tables) are not
+		 * violated by deleting this row.
 		 */
-		sqlite3FkCheck(pParse, pTab, iOld, 0, 0);
+		sqlite3FkCheck(parse, table, first_old_reg, 0, NULL);
 	}
 
-	/* Delete the index and table entries. Skip this step if pTab is really
-	 * a view (in which case the only effect of the DELETE statement is to
-	 * fire the INSTEAD OF triggers).
-	 *
-	 * If variable 'count' is non-zero, then this OP_Delete instruction should
-	 * invoke the update-hook. The pre-update-hook, on the other hand should
-	 * be invoked unless table pTab is a system table. The difference is that
-	 * the update-hook is not invoked for rows removed by REPLACE, but the
-	 * pre-update-hook is.
+	/* Delete the index and table entries. Skip this step if
+	 * table is really a view (in which case the only effect
+	 * of the DELETE statement is to fire the INSTEAD OF
+	 * triggers).
 	 */
-	if (pTab->pSelect == 0) {
-		u8 p5 = 0;
-		/* kyukhin: Tarantool handles indices uypdate automatically.  */
-		/* sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek);  */
-		sqlite3VdbeAddOp2(v, OP_Delete, iDataCur,
-				  (count ? OPFLAG_NCHANGE : 0));
-		if (eMode != ONEPASS_OFF) {
+	if (table->pSelect == NULL) {
+		uint8_t p5 = 0;
+		sqlite3VdbeAddOp2(v, OP_Delete, cursor,
+				  (need_update_count ? OPFLAG_NCHANGE : 0));
+		if (mode != ONEPASS_OFF)
 			sqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE);
-		}
-		if (iIdxNoSeek >= 0) {
-			sqlite3VdbeAddOp1(v, OP_Delete, iIdxNoSeek);
-		}
-		if (eMode == ONEPASS_MULTI)
+
+		if (idx_noseek >= 0)
+			sqlite3VdbeAddOp1(v, OP_Delete, idx_noseek);
+
+		if (mode == ONEPASS_MULTI)
 			p5 |= OPFLAG_SAVEPOSITION;
 		sqlite3VdbeChangeP5(v, p5);
 	}
 
-	/* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
-	 * handle rows (possibly in other tables) that refer via a foreign key
-	 * to the row just deleted.
+	/* Do any ON CASCADE, SET NULL or SET DEFAULT operations
+	 * required to handle rows (possibly in other tables) that
+	 * refer via a foreign key to the row just deleted.
 	 */
-	sqlite3FkActions(pParse, pTab, 0, iOld, 0);
+	sqlite3FkActions(parse, table, 0, first_old_reg, 0);
 
 	/* Invoke AFTER DELETE trigger programs. */
-	sqlite3CodeRowTrigger(pParse, pTrigger,
-			      TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf,
-			      iLabel);
+	sqlite3CodeRowTrigger(parse, trigger_list,
+			      TK_DELETE, 0, TRIGGER_AFTER, table, first_old_reg,
+			      onconf, label);
 
-	/* Jump here if the row had already been deleted before any BEFORE
-	 * trigger programs were invoked. Or if a trigger program throws a
-	 * RAISE(IGNORE) exception.
+	/* Jump here if the row had already been deleted before
+	 * any BEFORE trigger programs were invoked. Or if a trigger program
+	 * throws a RAISE(IGNORE) exception.
 	 */
-	sqlite3VdbeResolveLabel(v, iLabel);
+	sqlite3VdbeResolveLabel(v, label);
 	VdbeModuleComment((v, "END: GenRowDel()"));
 }
 
-/*
- * This routine generates VDBE code that causes the deletion of all
- * index entries associated with a single row of a single table, pTab
- *
- * Preconditions:
- *
- *   1.  A read/write cursor "iDataCur" must be open on the canonical storage
- *       btree for the table pTab.  (This will be primary key index)
- *
- *   2.  Read/write cursor for primary index of pTab must be open as
- *       cursor number iIdxCur.  (The pTab->pIndex index is the 0-th index.)
- *
- *   3.  The "iDataCur" cursor must be already positioned on the row
- *       that is to be deleted.
- */
-void
-sqlite3GenerateRowIndexDelete(Parse * pParse,	/* Parsing and code generating context */
-			      Table * pTab,	/* Table containing the row to be deleted */
-			      int iDataCur,	/* Cursor of table holding data. */
-			      int iIdxCur)	/* Primary index cursor */
-{
-	int r1 = -1;		/* Register holding an index key */
-	int iPartIdxLabel;	/* Jump destination for skipping partial index entries */
-	Vdbe *v;		/* The prepared statement under construction */
-	Index *pPk;		/* PRIMARY KEY index */
-
-	v = pParse->pVdbe;
-	pPk = sqlite3PrimaryKeyIndex(pTab);
-	/* In Tarantool it is enough to delete row just from pk */
-	VdbeModuleComment((v, "GenRowIdxDel for %s", pPk->zName));
-	r1 = sqlite3GenerateIndexKey(pParse, pPk, iDataCur, 0, &iPartIdxLabel,
-				     NULL, r1);
-	sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur, r1, index_column_count(pPk));
-	sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
-}
-
-/*
- * Generate code that will assemble an index key and stores it in register
- * regOut.  The key with be for index pIdx which is an index on pTab.
- * iCur is the index of a cursor open on the pTab table and pointing to
- * the entry that needs indexing.
- * iCur must be the cursor of the PRIMARY KEY index.
- *
- * Return a register number which is the first in a block of
- * registers that holds the elements of the index key.  The
- * block of registers has already been deallocated by the time
- * this routine returns.
- *
- * If *piPartIdxLabel is not NULL, fill it in with a label and jump
- * to that label if pIdx is a partial index that should be skipped.
- * The label should be resolved using sqlite3ResolvePartIdxLabel().
- * A partial index should be skipped if its WHERE clause evaluates
- * to false or null.  If pIdx is not a partial index, *piPartIdxLabel
- * will be set to zero which is an empty label that is ignored by
- * sqlite3ResolvePartIdxLabel().
- *
- * The pPrior and regPrior parameters are used to implement a cache to
- * avoid unnecessary register loads.  If pPrior is not NULL, then it is
- * a pointer to a different index for which an index key has just been
- * computed into register regPrior.  If the current pIdx index is generating
- * its key into the same sequence of registers and if pPrior and pIdx share
- * a column in common, then the register corresponding to that column already
- * holds the correct value and the loading of that register is skipped.
- * This optimization is helpful when doing a DELETE or an INTEGRITY_CHECK
- * on a table with multiple indices, and especially with
- * the PRIMARY KEY columns of the index.
- */
 int
-sqlite3GenerateIndexKey(Parse * pParse,	/* Parsing context */
-			Index * pIdx,	/* The index for which to generate a key */
-			int iDataCur,	/* Cursor number from which to take column data */
-			int regOut,	/* Put the new key into this register if not 0 */
-			int *piPartIdxLabel,	/* OUT: Jump to this label to skip partial index */
-			Index * pPrior,	/* Previously generated index key */
-			int regPrior)	/* Register holding previous generated key */
+sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
+		       int reg_out, int *part_idx_label, struct Index *prev,
+		       int reg_prev)
 {
-	Vdbe *v = pParse->pVdbe;
-	int j;
-	int regBase;
-	int nCol;
-
-	if (piPartIdxLabel) {
-		if (pIdx->pPartIdxWhere) {
-			*piPartIdxLabel = sqlite3VdbeMakeLabel(v);
-			pParse->iSelfTab = iDataCur;
-			sqlite3ExprCachePush(pParse);
-			sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere,
-					      *piPartIdxLabel,
+	struct Vdbe *v = parse->pVdbe;
+
+	if (part_idx_label != NULL) {
+		if (index->pPartIdxWhere != NULL) {
+			*part_idx_label = sqlite3VdbeMakeLabel(v);
+			parse->iSelfTab = cursor;
+			sqlite3ExprCachePush(parse);
+			sqlite3ExprIfFalseDup(parse, index->pPartIdxWhere,
+					      *part_idx_label,
 					      SQLITE_JUMPIFNULL);
 		} else {
-			*piPartIdxLabel = 0;
+			*part_idx_label = 0;
 		}
 	}
-	nCol = index_column_count(pIdx);
-	regBase = sqlite3GetTempRange(pParse, nCol);
-	if (pPrior && (regBase != regPrior || pPrior->pPartIdxWhere))
-		pPrior = 0;
-	for (j = 0; j < nCol; j++) {
-		if (pPrior && pPrior->aiColumn[j] == pIdx->aiColumn[j]
-		    && pPrior->aiColumn[j] != XN_EXPR) {
-			/* This column was already computed by the previous index */
+	int col_cnt = index_column_count(index);
+	int reg_base = sqlite3GetTempRange(parse, col_cnt);
+	if (prev != NULL && (reg_base != reg_prev ||
+			     prev->pPartIdxWhere != NULL))
+		prev = NULL;
+	for (int j = 0; j < col_cnt; j++) {
+		if (prev != NULL && prev->aiColumn[j] == index->aiColumn[j]
+		    && prev->aiColumn[j] != XN_EXPR) {
+			/*
+			 * This column was already computed by the
+			 * previous index.
+			 */
 			continue;
 		}
-		sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j,
-					       regBase + j);
-		/* If the column affinity is REAL but the number is an integer, then it
-		 * might be stored in the table as an integer (using a compact
-		 * representation) then converted to REAL by an OP_RealAffinity opcode.
-		 * But we are getting ready to store this value back into an index, where
-		 * it should be converted by to INTEGER again.  So omit the OP_RealAffinity
-		 * opcode if it is present
+		sqlite3ExprCodeLoadIndexColumn(parse, index, cursor, j,
+					       reg_base + j);
+		/*
+		 * If the column affinity is REAL but the number
+		 * is an integer, then it might be stored in the
+		 * table as an integer (using a compact
+		 * representation) then converted to REAL by an
+		 * OP_RealAffinity opcode. But we are getting
+		 * ready to store this value back into an index,
+		 * where it should be converted by to INTEGER
+		 * again.  So omit the OP_RealAffinity opcode if
+		 * it is present
 		 */
 		sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity);
 	}
-	if (regOut) {
-		sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut);
-	}
-	sqlite3ReleaseTempRange(pParse, regBase, nCol);
-	return regBase;
+	if (reg_out != 0)
+		sqlite3VdbeAddOp3(v, OP_MakeRecord, reg_base, col_cnt, reg_out);
+
+	sqlite3ReleaseTempRange(parse, reg_base, col_cnt);
+	return reg_base;
 }
 
-/*
- * If a prior call to sqlite3GenerateIndexKey() generated a jump-over label
- * because it was a partial index, then this routine should be called to
- * resolve that label.
- */
 void
-sqlite3ResolvePartIdxLabel(Parse * pParse, int iLabel)
+sql_resolve_part_idx_label(struct Parse *parse, int label)
 {
-	if (iLabel) {
-		sqlite3VdbeResolveLabel(pParse->pVdbe, iLabel);
-		sqlite3ExprCachePop(pParse);
+	if (label != 0) {
+		sqlite3VdbeResolveLabel(parse->pVdbe, label);
+		sqlite3ExprCachePop(parse);
 	}
 }
diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index 60b4786..a21dd2d 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -784,7 +784,8 @@ sqlite3FkDropTable(Parse * pParse, SrcList * pName, Table * pTab)
 		pParse->disableTriggers = 1;
 		/* Staring new transaction before DELETE FROM <tbl> */
 		sqlite3VdbeAddOp0(v, OP_TTransaction);
-		sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0);
+		sql_table_delete_from(pParse, sqlite3SrcListDup(db, pName, 0),
+				      NULL);
 		pParse->disableTriggers = 0;
 
 		/* If the DELETE has generated immediate foreign key constraint
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 1ad7469..2d49296 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -379,7 +379,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 	zTab = pTabList->a[0].zName;
 	if (NEVER(zTab == 0))
 		goto insert_cleanup;
-	pTab = sqlite3SrcListLookup(pParse, pTabList);
+	pTab = sql_list_lookup_table(pParse, pTabList);
 	if (pTab == 0) {
 		goto insert_cleanup;
 	}
@@ -406,13 +406,13 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 	/* If pTab is really a view, make sure it has been initialized.
 	 * ViewGetColumnNames() is a no-op if pTab is not a view.
 	 */
-	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
+	if (sqlite3ViewGetColumnNames(pParse, pTab))
 		goto insert_cleanup;
-	}
 
-	/* Cannot insert into a read-only table.
-	 */
-	if (sqlite3IsReadOnly(pParse, pTab, tmask)) {
+	/* Cannot insert into a read-only table. */
+	if (isView && tmask == 0) {
+		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
+				pTab->zName);
 		goto insert_cleanup;
 	}
 
@@ -1511,13 +1511,13 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 								 TK_DELETE, 0,
 								 0);
 				}
-				sqlite3GenerateRowDelete(pParse, pTab, pTrigger,
-							 iDataCur, iIdxCur,
-							 regR, nPkField, 0,
-							 ON_CONFLICT_ACTION_REPLACE,
-							 (pIdx ==
-							  pPk ? ONEPASS_SINGLE :
-							  ONEPASS_OFF), -1);
+				sql_generate_row_delete(pParse, pTab, pTrigger,
+							iDataCur,
+							regR, nPkField, 0,
+							ON_CONFLICT_ACTION_REPLACE,
+							(pIdx ==
+							 pPk ? ONEPASS_SINGLE :
+							 ONEPASS_OFF), -1);
 				seenReplace = 1;
 				break;
 			}
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 872647d..259243d 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -719,28 +719,14 @@ limit_opt(A) ::= LIMIT expr(X) COMMA expr(Y).
 
 /////////////////////////// The DELETE statement /////////////////////////////
 //
-%ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
-cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W) 
-        orderby_opt(O) limit_opt(L). {
-  sqlite3WithPush(pParse, C, 1);
-  sqlite3SrcListIndexedBy(pParse, X, &I);
-  W = sqlite3LimitWhere(pParse, X, W, O, L.pLimit, L.pOffset, "DELETE");
-  sqlSubProgramsRemaining = SQL_MAX_COMPILING_TRIGGERS;
-  /* Instruct SQL to initate Tarantool's transaction.  */
-  pParse->initiateTTrans = true;
-  sqlite3DeleteFrom(pParse,X,W);
-}
-%endif
-%ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
 cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W). {
   sqlite3WithPush(pParse, C, 1);
   sqlite3SrcListIndexedBy(pParse, X, &I);
   sqlSubProgramsRemaining = SQL_MAX_COMPILING_TRIGGERS;
   /* Instruct SQL to initate Tarantool's transaction.  */
   pParse->initiateTTrans = true;
-  sqlite3DeleteFrom(pParse,X,W);
+  sql_table_delete_from(pParse,X,W);
 }
-%endif
 
 %type where_opt {Expr*}
 %destructor where_opt {sql_expr_free(pParse->db, $$, false);}
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index e056d63..1a6722b 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1994,7 +1994,6 @@ sql_space_tuple_log_count(struct Table *tab);
 /*
  * Allowed values for Table.tabFlags.
  */
-#define TF_Readonly        0x01	/* Read-only system table */
 #define TF_Ephemeral       0x02	/* An ephemeral table */
 #define TF_HasPrimaryKey   0x04	/* Table has a primary key */
 #define TF_Autoincrement   0x08	/* Integer primary key is autoincrement */
@@ -3671,15 +3670,68 @@ int sqlite3Select(Parse *, Select *, SelectDest *);
 Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
 			 Expr *, ExprList *, u32, Expr *, Expr *);
 void sqlite3SelectDelete(sqlite3 *, Select *);
-Table *sqlite3SrcListLookup(Parse *, SrcList *);
-int sqlite3IsReadOnly(Parse *, Table *, int);
+
+/**
+ * While a SrcList can in general represent multiple tables and
+ * subqueries (as in the FROM clause of a SELECT statement) in
+ * this case it contains the name of a single table, as one might
+ * find in an INSERT, DELETE, or UPDATE statement.  Look up that
+ * table in the symbol table and return a pointer.  Set an error
+ * message and return NULL if the table name is not found or if
+ * any other error occurs.
+ *
+ * The following fields are initialized appropriate in src_list:
+ *
+ *    src_list->a[0].pTab       Pointer to the Table object.
+ *    src_list->a[0].pIndex     Pointer to the INDEXED BY index, if
+ *                              there is one.
+ *
+ * @param parse Parsing context.
+ * @param src_list List containing single table element.
+ * @retval Table object if found, NULL oterwise.
+ */
+struct Table *
+sql_list_lookup_table(struct Parse *parse, struct SrcList *src_list);
+
 void sqlite3OpenTable(Parse *, int iCur, Table *, int);
-#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
-Expr *sqlite3LimitWhere(Parse *, SrcList *, Expr *, ExprList *, Expr *, Expr *,
-			char *);
-#endif
-void sqlite3DeleteFrom(Parse *, SrcList *, Expr *);
-void sqlite3DeleteByKey(Parse *, char *, const char **, Expr **, int);
+/**
+ * Generate code for a DELETE FROM statement.
+ *
+ *     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
+ *                 \________/       \________________/
+ *                  tab_list              where
+ *
+ * @param parse Parsing context.
+ * @param tab_list List of single element which table from which
+ * deletetion if performed.
+ * @param where The WHERE clause.  May be NULL.
+ */
+void
+sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
+		      struct Expr *where);
+
+/**
+ * Generate VDBE code for
+ * DELETE FROM <t_name> WHERE
+ *        <columns[0]> = <values[0]>
+ *    AND  ...
+ *    AND <columns[nPairs - 1]> = <values[pairs_count - 1]>;
+ *
+ * This function does not increment the nested counter and is
+ * faster than nested parsing of the request above.
+ *
+ * @param parse Parser context.
+ * @param t_name Table name.
+ * @param columns Column names array.
+ * @param values Column values array.
+ * @param pairs_count Length of @columns and @values.
+ *
+ * In case of error the @values elements are deleted.
+ */
+void
+sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
+		    struct Expr **values, int pairs_count);
+
 void sqlite3Update(Parse *, SrcList *, ExprList *, Expr *,
 		   enum on_conflict_action);
 WhereInfo *sqlite3WhereBegin(Parse *, SrcList *, Expr *, ExprList *, ExprList *,
@@ -3782,11 +3834,118 @@ int sqlite3ExprContainsSubquery(Expr *);
 int sqlite3ExprIsInteger(Expr *, int *);
 int sqlite3ExprCanBeNull(const Expr *);
 int sqlite3ExprNeedsNoAffinityChange(const Expr *, char);
-void sqlite3GenerateRowDelete(Parse *, Table *, Trigger *, int, int, int, i16,
-			      u8, enum on_conflict_action, u8, int);
-void sqlite3GenerateRowIndexDelete(Parse *, Table *, int, int);
-int sqlite3GenerateIndexKey(Parse *, Index *, int, int, int *, Index *, int);
-void sqlite3ResolvePartIdxLabel(Parse *, int);
+
+/**
+ * This routine generates VDBE code that causes a single row of a
+ * single table to be deleted.  Both the original table entry and
+ * all indices are removed.
+ *
+ * Preconditions:
+ *
+ *   1.  cursor is an open cursor on the btree that is the
+ *       canonical data store for the table.  (This will be the
+ *       PRIMARY KEY index)
+ *
+ *   2.  The primary key for the row to be deleted must be stored
+ *       in a sequence of npk memory cells starting at ipk. If
+ *       npk==0 that means that a search record formed from
+ *       OP_MakeRecord is contained in the single memory location
+ *       iPk.
+ *
+ * mode:
+ *   Parameter mode may be passed either ONEPASS_OFF (0),
+ *   ONEPASS_SINGLE, or ONEPASS_MULTI.  If mode is not
+ *   ONEPASS_OFF, then the cursor already points to the row to
+ *   delete. If eMode is ONEPASS_OFF then this function must seek
+ *   cursor to the entry identified by ipk and npk before reading
+ *   from it.
+ *
+ *   If mode is ONEPASS_MULTI, then this call is being made as
+ *   part of a ONEPASS delete that affects multiple rows. In this
+ *   case, if idx_noseek is a valid cursor number (>=0), then its
+ *   position should be preserved following the delete operation.
+ *   Or, if idx_noseek is not a valid cursor number, the position
+ *   of cursor should be preserved instead.
+ *
+ * @param parse Parsing context.
+ * @param table Table containing the row to be deleted.
+ * @param trigger_list List of triggers to (potentially) fire.
+ * @param cursor Cursor from which column data is extracted/
+ * @param ipk First memory cell containing the PRIMARY KEY.
+ * @param npk umber of PRIMARY KEY memory cells.
+ * @param need_update_count. If non-zero, increment the row change
+ *        counter.
+ * @param onconf Default ON CONFLICT policy for triggers.
+ * @param mode ONEPASS_OFF, _SINGLE, or _MULTI.  See above.
+ * @param idx_noseek If it is a valid cursor number (>=0),
+ *        then it identifies an index cursor that already points
+ *        to the index entry to be deleted.
+ */
+void
+sql_generate_row_delete(struct Parse *parse, struct Table *table,
+			struct Trigger *trigger_list, int cursor, int ipk,
+			short npk, bool need_update_count,
+			enum on_conflict_action onconf, u8 eMode,
+			int idx_noseek);
+
+/**
+ * Generate code that will assemble an index key and stores it in
+ * register reg_out.  The key with be for index pIdx which is an
+ * index on table. cursor is the index of a cursor open on the
+ * table table and pointing to the entry that needs indexing.
+ * cursor must be the cursor of the PRIMARY KEY index.
+ *
+ * Return a register number which is the first in a block of
+ * registers that holds the elements of the index key.  The
+ * block of registers has already been deallocated by the time
+ * this routine returns.
+ *
+ * If *part_idx_label is not NULL, fill it in with a label and
+ * jump to that label if pIdx is a partial index that should be
+ * skipped. The label should be resolved using
+ * sql_resolve_part_idx_label(). A partial index should be skipped
+ * if its WHERE clause evaluates to false or null.  If index is
+ * not a partial index, *piPartIdxLabel will be set to zero which
+ * is an empty label that is ignored by sql_resolve_part_idx_label().
+ *
+ * The pPrior and regPrior parameters are used to implement a
+ * cache to avoid unnecessary register loads.  If prev is not
+ * NULL, then it is a pointer to a different index for which an
+ * index key has just been computed into register reg_prev. If the
+ * current pIdx index is generating its key into the same
+ * sequence of registers and if prev and index share a column in
+ * common, then the register corresponding to that column already
+ * holds the correct value and the loading of that register is
+ * skipped. This optimization is helpful when doing a DELETE or
+ * an INTEGRITY_CHECK on a table with multiple indices, and
+ * especially with the PRIMARY KEY columns of the index.
+ *
+ * @param parse Parsing context.
+ * @param index The index for which to generate a key.
+ * @param cursor Cursor number from which to take column data.
+ * @param reg_out Put the new key into this register if not NULL.
+ * @param[out] part_idx_label Jump to this label to skip partial
+ *        index.
+ * @param prev Previously generated index key
+ * @param reg_prev Register holding previous generated key.
+ * @retval Register containing new record
+ */
+int
+sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
+		       int reg_out, int *part_idx_label, struct Index *prev,
+		       int reg_prev);
+
+/**
+ * If a prior call to sql_generate_index_key() generated a
+ * jump-over label because it was a partial index, then this
+ * routine should be called to resolve that label.
+ *
+ * @param parse Parsing context.
+ * @param label Label to resolve.
+ */
+void
+sql_resolve_part_idx_label(struct Parse *parse, int label);
+
 void sqlite3GenerateConstraintChecks(Parse *, Table *, int *, int, int, int,
 				     int, u8, struct on_conflict *, int, int *,
 				     int *);
@@ -3831,9 +3990,20 @@ int sqlite3SafetyCheckOk(sqlite3 *);
 int sqlite3SafetyCheckSickOrOk(sqlite3 *);
 void sqlite3ChangeCookie(Parse *);
 
-#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-void sqlite3MaterializeView(Parse *, Table *, Expr *, int);
-#endif
+/**
+ * Evaluate a view and store its result in an ephemeral table.
+ * The where argument is an optional WHERE clause that restricts
+ * the set of rows in the view that are to be added to the
+ * ephemeral table.
+ *
+ * @param parse Parsing context.
+ * @param name View name.
+ * @param where Option WHERE clause to be added.
+ * @param cursor Cursor number for ephemeral table.
+ */
+void
+sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
+		     int cursor);
 
 #ifndef SQLITE_OMIT_TRIGGER
 void sqlite3BeginTrigger(Parse *, Token *, int, int, IdList *, SrcList *,
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index 28c56db..e6d9925 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -111,7 +111,7 @@ sqlite3BeginTrigger(Parse * pParse,	/* The parse context of the CREATE TRIGGER s
 	if (sqlite3FixSrcList(&sFix, pTableName)) {
 		goto trigger_cleanup;
 	}
-	pTab = sqlite3SrcListLookup(pParse, pTableName);
+	pTab = sql_list_lookup_table(pParse, pTableName);
 	if (!pTab) {
 		goto trigger_cleanup;
 	}
@@ -744,11 +744,11 @@ codeTriggerProgram(Parse * pParse,	/* The parser context */
 				break;
 			}
 		case TK_DELETE:{
-				sqlite3DeleteFrom(pParse,
-						  targetSrcList(pParse, pStep),
-						  sqlite3ExprDup(db,
-								 pStep->pWhere,
-								 0)
+				sql_table_delete_from(pParse,
+						      targetSrcList(pParse, pStep),
+						      sqlite3ExprDup(db,
+								     pStep->pWhere,
+								     0)
 				    );
 				break;
 			}
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index de1349b..60cb229 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -163,7 +163,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 
 	/* Locate the table which we want to update.
 	 */
-	pTab = sqlite3SrcListLookup(pParse, pTabList);
+	pTab = sql_list_lookup_table(pParse, pTabList);
 	if (pTab == 0)
 		goto update_cleanup;
 
@@ -187,7 +187,9 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
 		goto update_cleanup;
 	}
-	if (sqlite3IsReadOnly(pParse, pTab, tmask)) {
+	if (isView && tmask == 0) {
+		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
+				pTab->zName);
 		goto update_cleanup;
 	}
 
@@ -324,7 +326,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	 */
 #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
 	if (isView) {
-		sqlite3MaterializeView(pParse, pTab, pWhere, iDataCur);
+		sql_materialize_view(pParse, pTab->zName, pWhere, iDataCur);
 		/* Number of columns from SELECT plus ID.*/
 		nKey = pTab->nCol + 1;
 	}
@@ -603,7 +605,6 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 						     nKey);
 			VdbeCoverageNeverTaken(v);
 		}
-		sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur);
 
 		/* If changing the PK value, or if there are foreign key constraints
 		 * to process, delete the old record. Otherwise, add a noop OP_Delete
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index c6710d2..2ad6f3e 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -3236,7 +3236,7 @@ case OP_OpenTEphemeral: {
 	BtCursor *pBtCur;
 	assert(pOp->p1 >= 0);
 	assert(pOp->p2 > 0);
-	assert(pOp->p4type != P4_KEYDEF || pOp->p4.key_def != NULL);
+	assert(pOp->p4type == P4_KEYDEF);
 
 	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
 	if (pCx == 0) goto no_mem;
@@ -3249,7 +3249,7 @@ case OP_OpenTEphemeral: {
 
 	rc = tarantoolSqlite3EphemeralCreate(pCx->uc.pCursor, pOp->p2,
 					     pOp->p4.key_def);
-	pCx->key_def  = pCx->uc.pCursor->index->def->key_def;
+	pCx->key_def = pCx->uc.pCursor->index->def->key_def;
 	if (rc) goto abort_due_to_error;
 	break;
 }
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index e67fcae..4a8a9d2 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1134,7 +1134,7 @@ sqlite3VdbeChangeP4(Vdbe * p, int addr, const char *zP4, int n)
 	} if (n == P4_BOOL) {
 		pOp->p4.b = *(bool*)zP4;
 		pOp->p4type = P4_BOOL;
-	} else if (zP4 != 0) {
+	} else {
 		assert(n < 0);
 		pOp->p4.p = (void *)zP4;
 		pOp->p4type = (signed char)n;
@@ -1571,7 +1571,10 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
 #endif
 	case P4_COLLSEQ:{
 			struct coll *pColl = pOp->p4.pColl;
-			sqlite3XPrintf(&x, "(%.20s)", pColl->name);
+			if (pColl != NULL)
+				sqlite3XPrintf(&x, "(%.20s)", pColl->name);
+			else
+				sqlite3XPrintf(&x, "(binary)");
 			break;
 		}
 	case P4_FUNCDEF:{
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index b497a5b..3519f34 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -840,11 +840,11 @@ constructAutomaticIndex(Parse * pParse,			/* The parsing context */
 	}
 	regRecord = sqlite3GetTempReg(pParse);
 	regBase =
-	    sqlite3GenerateIndexKey(pParse, pIdx, pLevel->iTabCur, regRecord, 0,
-				    0, 0);
+	    sql_generate_index_key(pParse, pIdx, pLevel->iTabCur, regRecord, 0,
+				   0, 0);
 	sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord);
 	if (pPartial)
-		sqlite3VdbeResolveLabel(v, iContinue);
+		sql_resolve_part_idx_label(v, iContinue);
 	if (pTabItem->fg.viaCoroutine) {
 		sqlite3VdbeChangeP2(v, addrCounter, regBase + n);
 		translateColumnToCopy(v, addrTop, pLevel->iTabCur,

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

* [tarantool-patches] Re: [PATCH 1/2] sql: allow key_def to be NULL for ephemeral create
  2018-05-16 15:24 ` [tarantool-patches] [PATCH 1/2] sql: allow key_def to be NULL for ephemeral create Kirill Yukhin
@ 2018-05-17 15:49   ` Kirill Yukhin
  2018-05-17 16:47     ` Vladislav Shpilevoy
  0 siblings, 1 reply; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-17 15:49 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

Hi Vlad,

On 16 мая 18:24, Kirill Yukhin wrote:
> There're many cases in SQL FE, where exact key def passed to
> doesn't ephemeral table matter. It is used to store data only.
> Only field count makes sense in such a case. Hoewever it is
> passed separately (in P2). So, allow P4 field to be NULL for
> OP_OpenTEphemeral. Update delte from routine from sql/delete.c
> accordingly.
> 
> Part of #3235
Pasting updated patch from 1st round of review of 1/2 patch.

diff --git a/src/box/sql.c b/src/box/sql.c
index 6104a6d..e502f22 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -370,8 +370,9 @@ int tarantoolSqlite3Count(BtCursor *pCur, i64 *pnEntry)
  *
  * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
  */
-int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
-				    struct key_def *def)
+int
+tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
+				struct key_def *def)
 {
 	assert(pCur);
 	assert(pCur->curFlags & BTCF_TEphemCursor);
@@ -381,7 +382,7 @@ int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
 		return SQL_TARANTOOL_ERROR;
 	for (uint32_t part = 0; part < field_count; ++part) {
 		struct coll *coll;
-		if (part < def->part_count)
+		if (def != NULL && part < def->part_count)
 			coll = def->parts[part].coll;
 		else
 			coll = NULL;
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 22130ef..1ad7469 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -566,13 +566,8 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 			regRec = sqlite3GetTempReg(pParse);
 			regCopy = sqlite3GetTempRange(pParse, nColumn);
 			regTempId = sqlite3GetTempReg(pParse);
-			struct key_def *def = key_def_new(nColumn + 1);
-			if (def == NULL) {
-				sqlite3OomFault(db);
-				goto insert_cleanup;
-			}
 			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, srcTab, nColumn+1,
-					  0, (char*)def, P4_KEYDEF);
+					  0, (char*)NULL, P4_KEYDEF);
 			/* Create counter for rowid */
 			sqlite3VdbeAddOp4Dup8(v, OP_Int64,
 					      0 /* unused */,
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index bd895bc..da4d816 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -2215,13 +2215,8 @@ generateWithRecursiveQuery(Parse * pParse,	/* Parsing context */
 		VdbeComment((v, "Orderby table"));
 		destQueue.pOrderBy = pOrderBy;
 	} else {
-		struct key_def *def = key_def_new(nCol + 1);
-		if (def == NULL) {
-			sqlite3OomFault(pParse->db);
-			goto end_of_recursive_query;
-		}
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iQueue, nCol + 1, 0,
-				  (char*)def, P4_KEYDEF);
+				  (char*)NULL, P4_KEYDEF);
 		VdbeComment((v, "Queue table"));
 	}
 	if (iDistinct) {
@@ -2422,13 +2417,8 @@ multiSelect(Parse * pParse,	/* Parsing context */
 	if (dest.eDest == SRT_EphemTab) {
 		assert(p->pEList);
 		int nCols = p->pEList->nExpr;
-		struct key_def *def = key_def_new(nCols + 1);
-		if (def == NULL) {
-			sqlite3OomFault(db);
-			goto multi_select_end;
-		}
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, dest.iSDParm, nCols + 1,
-				  0, (char*)def, P4_KEYDEF);
+				  0, (char*)NULL, P4_KEYDEF);
 		VdbeComment((v, "Destination temp"));
 		dest.eDest = SRT_Table;
 	}
@@ -5608,11 +5598,8 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	/* If the output is destined for a temporary table, open that table.
 	 */
 	if (pDest->eDest == SRT_EphemTab) {
-		struct key_def *def = key_def_new(pEList->nExpr + 1);
-		if (def == NULL)
-			sqlite3OomFault(db);
 		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, pDest->iSDParm,
-				  pEList->nExpr + 1, 0, (char*)def, P4_KEYDEF);
+				  pEList->nExpr + 1, 0, (char*)NULL, P4_KEYDEF);
 
 		VdbeComment((v, "Output table"));
 	}
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index f3db92c..de1349b 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -358,13 +358,8 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
 
 	if (isView) {
-		struct key_def *def = key_def_new(nKey);
-		if (def == NULL) {
-			sqlite3OomFault(db);
-			goto update_cleanup;
-		}
 		addrOpen = sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEph,
-					     nKey, 0, (char*)def, P4_KEYDEF);
+					     nKey, 0, (char*)NULL, P4_KEYDEF);
 	} else {
 		addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph, nPk);
 		sql_vdbe_set_p4_key_def(pParse, pPk);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index b4b59da..c6710d2 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -3225,9 +3225,9 @@ open_cursor_set_hints:
 /**
  * Opcode: OpenTEphemeral P1 P2 * P4 *
  * Synopsis:
- * @param P1 index of new cursor to be created
- * @param P2 number of columns in a new table
- * @param P4 key def for new table
+ * @param P1 index of new cursor to be created.
+ * @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.
  */
@@ -3236,21 +3236,20 @@ case OP_OpenTEphemeral: {
 	BtCursor *pBtCur;
 	assert(pOp->p1 >= 0);
 	assert(pOp->p2 > 0);
-	assert(pOp->p4.key_def != NULL);
-	assert(pOp->p4type == P4_KEYDEF);
+	assert(pOp->p4type != P4_KEYDEF || pOp->p4.key_def != NULL);
 
 	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
 	if (pCx == 0) goto no_mem;
 	pCx->nullRow = 1;
 
-	pCx->key_def  = pOp->p4.key_def;
 	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,
-					     pCx->key_def);
+					     pOp->p4.key_def);
+	pCx->key_def  = pCx->uc.pCursor->index->def->key_def;
 	if (rc) goto abort_due_to_error;
 	break;
 }

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

* [tarantool-patches] Re: [PATCH 2/2] sql: refactor delete routines
  2018-05-17 15:48       ` Kirill Yukhin
@ 2018-05-17 16:47         ` Vladislav Shpilevoy
  2018-05-18  6:56           ` Kirill Yukhin
  0 siblings, 1 reply; 17+ messages in thread
From: Vladislav Shpilevoy @ 2018-05-17 16:47 UTC (permalink / raw)
  To: Kirill Yukhin; +Cc: tarantool-patches

Thanks for review fixes! Almost done! See 13 comments below.

>> On 16/05/2018 19:29, Kirill Yukhin wrote:
>>> Hi Vlad,
>>> On 16 мая 18:24, Kirill Yukhin wrote:
>>> --- a/src/box/sql/delete.c
>>> +++ b/src/box/sql/delete.c
>>> -Table *
>>> -sqlite3SrcListLookup(Parse * pParse, SrcList * pSrc)
>>> +struct Table *
>>> +sql_list_lookup_table(struct Parse *parse, SrcList *src_list)
>>>    {
>>
>>> +	struct SrcList_item *item = src_list->a;
>>> +	struct Table *table;
>>> +	assert(item != NULL && src_list->nSrc == 1);
>>> +	table = sqlite3LocateTable(parse, 0, item->zName);
>>> +	sqlite3DeleteTable(parse->db, item->pTab);
>>> +	item->pTab = table;
>>> +	if (table != NULL)
>>> +		table->nTabRef++;
>>> +	if (sqlite3IndexedByLookup(parse, item))
>>> +		table = NULL;
>>
>> 1. What about --nTabRef; ? sqlite3IndexedByLookup does not unref
>> table. Looks like original SQLite bug.
> I'll submit a question to SQLite author.

1. Obviously, it is a bug. The code is very simple, and it is easy to see,
that table is never unreferenced on the error. Lets fix this in Tarantool.
Will Hipp fix this or not in vanila SQLite after you write him - it will be
his problem.

> 
>>> +sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
>>> +		    struct Expr **values, int pairs_count)
>>>    {
>>> -	Expr *where = NULL;
>>> -	SrcList *src;
>>> -
>>> -		assert(nPairs > 0);
>>> -	if (pParse->nErr > 0 || pParse->db->mallocFailed)
>>> +	if (parse->nErr > 0 || parse->db->mallocFailed)
>>>    		goto error;
>>> -	src = sql_alloc_src_list(pParse->db);
>>> -	src->a[0].zName = sqlite3DbStrDup(pParse->db, zTab);
>>> +	struct SrcList *src = sql_alloc_src_list(parse->db);
>>> +	src->a[0].zName = sqlite3DbStrDup(parse->db, t_name);
>>>    	if (src == NULL)
>> 2. Maybe first check src on NULL and then use it? Looks like the second SQLite bug.
> Fixed.

2. Now I see, that sql_delete_by_where is never called.

Searching 4937 files for "sql_delete_by_where"

/Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:
   381
   382  void
   383: sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
   384  		    struct Expr **values, int pairs_count)
   385  {

/Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/sqliteInt.h:
  3730   */
  3731  void
  3732: sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
  3733  		    struct Expr **values, int pairs_count);
  3734

> 
>>> +sql_generate_row_delete(struct Parse *parse, struct Table *table,
>>> +			struct Trigger *trigger_list, int cursor, int reg_pk,
>>
>> 3. I think, it is time to invent a standard name for cursors: I see, that both 'cursor' and 'reg_pk'
>> are cursors, but their names differs very. My proposal: each cursor variable must end with '_cursor'.
>> So data_cursor and pk_cursor instead of cursor and reg_pk.
> Well, actualy no. Cursor is a cursor, this is immediate value in VDBE. Register (reg_pk) is a memory
> cell. They're not the same thing for sure.
> 
>>> +			short npk, bool is_count,

3. Oh, now I see. I was confused by names mismatch in the function declaration and implementation.
In the implementation you use 'reg_pk' name, but in the declaration you use 'ipk'. Please, fix
this too.
> 
>>> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
>>> --- a/src/box/sql/sqliteInt.h
>>> +++ b/src/box/sql/sqliteInt.h
>>> @@ -3671,15 +3670,69 @@ int sqlite3Select(Parse *, Select *, SelectDest *);
>>>    Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
>>>    			 Expr *, ExprList *, u32, Expr *, Expr *);
>>>    void sqlite3SelectDelete(sqlite3 *, Select *);
>>> -Table *sqlite3SrcListLookup(Parse *, SrcList *);
>>> +
>>> +/**
>>> + * While a SrcList can in general represent multiple tables and
>>> + * subqueries (as in the FROM clause of a SELECT statement) in
>>> + * this case it contains the name of a single table, as one might
>>> + * find in an INSERT, DELETE, or UPDATE statement.  Look up that
>>> + * table in the symbol table and return a pointer.  Set an error
>>> + * message and return NULL if the table name is not found or if
>>> + * any other error occurs.
>>> + *
>>> + * The following fields are initialized appropriate in src_list:
>>> + *
>>> + *    pSrc->a[0].pTab       Pointer to the Table object.
>> 5. No pSrc anymore.
> Fixed.
> 
>>> + *    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if

4. Now it is out of 66 symbols. Sorry, it is not my rules(

> @@ -3782,11 +3834,118 @@ int sqlite3ExprContainsSubquery(Expr *);
> +/**
> + * This routine generates VDBE code that causes a single row of a
> + * single table to be deleted.  Both the original table entry and
> + * all indices are removed.
> + *
> + * Preconditions:
> + *
> + *   1.  cursor is an open cursor on the btree that is the
> + *       canonical data store for the table.  (This will be the
> + *       PRIMARY KEY index)
> + *
> + *   2.  The primary key for the row to be deleted must be stored
> + *       in a sequence of npk memory cells starting at ipk. If
> + *       npk==0 that means that a search record formed from
> + *       OP_MakeRecord is contained in the single memory location
> + *       iPk.
> + *
> + * mode:
> + *   Parameter mode may be passed either ONEPASS_OFF (0),
> + *   ONEPASS_SINGLE, or ONEPASS_MULTI.  If mode is not
> + *   ONEPASS_OFF, then the cursor already points to the row to
> + *   delete. If eMode is ONEPASS_OFF then this function must seek
> + *   cursor to the entry identified by ipk and npk before reading
> + *   from it.
> + *
> + *   If mode is ONEPASS_MULTI, then this call is being made as
> + *   part of a ONEPASS delete that affects multiple rows. In this
> + *   case, if idx_noseek is a valid cursor number (>=0), then its
> + *   position should be preserved following the delete operation.
> + *   Or, if idx_noseek is not a valid cursor number, the position
> + *   of cursor should be preserved instead.
> + *
> + * @param parse Parsing context.
> + * @param table Table containing the row to be deleted.
> + * @param trigger_list List of triggers to (potentially) fire.
> + * @param cursor Cursor from which column data is extracted/
> + * @param ipk First memory cell containing the PRIMARY KEY.
> + * @param npk umber of PRIMARY KEY memory cells.
> + * @param need_update_count. If non-zero, increment the row change

5. Non-zero -> true. Now it is boolean variable.

> + *        counter.
> + * @param onconf Default ON CONFLICT policy for triggers.
> + * @param mode ONEPASS_OFF, _SINGLE, or _MULTI.  See above.

6. 'mode' in @param, but 'eMode' in arguments.

> + * @param idx_noseek If it is a valid cursor number (>=0),
> + *        then it identifies an index cursor that already points
> + *        to the index entry to be deleted.
> + */
> +void
> +sql_generate_row_delete(struct Parse *parse, struct Table *table,
> +			struct Trigger *trigger_list, int cursor, int ipk,
> +			short npk, bool need_update_count,
> +			enum on_conflict_action onconf, u8 eMode,
> +			int idx_noseek);
> +
> +/**
> + * Generate code that will assemble an index key and stores it in
> + * register reg_out.  The key with be for index pIdx which is an

7. No pIdx. 'The key will be' maybe?

> + * index on table. cursor is the index of a cursor open on the
> + * table table and pointing to the entry that needs indexing.
> + * cursor must be the cursor of the PRIMARY KEY index.
> + *
> + * Return a register number which is the first in a block of
> + * registers that holds the elements of the index key.  The
> + * block of registers has already been deallocated by the time
> + * this routine returns.
> + *
> + * If *part_idx_label is not NULL, fill it in with a label and

8. *part_idx_label can not be NULL - it is integer. Maybe part_idx_label
with no '*'?

> + * jump to that label if pIdx is a partial index that should be

9. No pIdx.

> + * skipped. The label should be resolved using
> + * sql_resolve_part_idx_label(). A partial index should be skipped
> + * if its WHERE clause evaluates to false or null.  If index is
> + * not a partial index, *piPartIdxLabel will be set to zero which

10. No piPartIdxLabel.

> + * is an empty label that is ignored by sql_resolve_part_idx_label().
> + *
> + * The pPrior and regPrior parameters are used to implement a

11. No pPrior and regPrior.

> + * cache to avoid unnecessary register loads.  If prev is not
> + * NULL, then it is a pointer to a different index for which an
> + * index key has just been computed into register reg_prev. If the
> + * current pIdx index is generating its key into the same

12. No pIdx.

> + * sequence of registers and if prev and index share a column in
> + * common, then the register corresponding to that column already
> + * holds the correct value and the loading of that register is
> + * skipped. This optimization is helpful when doing a DELETE or
> + * an INTEGRITY_CHECK on a table with multiple indices, and
> + * especially with the PRIMARY KEY columns of the index.
> + *
> + * @param parse Parsing context.
> + * @param index The index for which to generate a key.

13. Index is a key?

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

* [tarantool-patches] Re: [PATCH 1/2] sql: allow key_def to be NULL for ephemeral create
  2018-05-17 15:49   ` [tarantool-patches] " Kirill Yukhin
@ 2018-05-17 16:47     ` Vladislav Shpilevoy
  2018-05-18  6:57       ` Kirill Yukhin
  0 siblings, 1 reply; 17+ messages in thread
From: Vladislav Shpilevoy @ 2018-05-17 16:47 UTC (permalink / raw)
  To: Kirill Yukhin; +Cc: tarantool-patches

Thanks for review fixes! See 4 comments below.

On 17/05/2018 18:49, Kirill Yukhin wrote:
> Hi Vlad,
> 
> On 16 мая 18:24, Kirill Yukhin wrote:
>> There're many cases in SQL FE, where exact key def passed to
>> doesn't ephemeral table matter. It is used to store data only.

1. Can't read. Maybe 'where exact key def passed to ephemeral table doesn't matter' ?

>> Only field count makes sense in such a case. Hoewever it is

2. However.

>> passed separately (in P2). So, allow P4 field to be NULL for
>> OP_OpenTEphemeral. Update delte from routine from sql/delete.c

3. delete.

>> accordingly.
>>
>> Part of #3235
> Pasting updated patch from 1st round of review of 1/2 patch.
> 
> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index 22130ef..1ad7469 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -566,13 +566,8 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
>   			regRec = sqlite3GetTempReg(pParse);
>   			regCopy = sqlite3GetTempRange(pParse, nColumn);
>   			regTempId = sqlite3GetTempReg(pParse);
> -			struct key_def *def = key_def_new(nColumn + 1);
> -			if (def == NULL) {
> -				sqlite3OomFault(db);
> -				goto insert_cleanup;
> -			}
>   			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, srcTab, nColumn+1,
> -					  0, (char*)def, P4_KEYDEF);
> +					  0, (char*)NULL, P4_KEYDEF);

4. In the OP_OpenTEphemeral code you check for P4_KEYDEF absence, so you can use
AddOp2 here. And in other places.

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

* [tarantool-patches] Re: [PATCH 2/2] sql: refactor delete routines
  2018-05-17 16:47         ` Vladislav Shpilevoy
@ 2018-05-18  6:56           ` Kirill Yukhin
  2018-05-18 10:33             ` Vladislav Shpilevoy
  0 siblings, 1 reply; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-18  6:56 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi Vlad,
Thanks a lot for your inputs.
My answers inlined, updated patch in the bottom.

On 17 мая 19:47, Vladislav Shpilevoy wrote:
> Thanks for review fixes! Almost done! See 13 comments below.
> 
> > > On 16/05/2018 19:29, Kirill Yukhin wrote:
> > > > Hi Vlad,
> > > > On 16 мая 18:24, Kirill Yukhin wrote:
> > > > --- a/src/box/sql/delete.c
> > > > +++ b/src/box/sql/delete.c
> > > > -Table *
> > > > -sqlite3SrcListLookup(Parse * pParse, SrcList * pSrc)
> > > > +struct Table *
> > > > +sql_list_lookup_table(struct Parse *parse, SrcList *src_list)
> > > >    {
> > > 
> > > > +	struct SrcList_item *item = src_list->a;
> > > > +	struct Table *table;
> > > > +	assert(item != NULL && src_list->nSrc == 1);
> > > > +	table = sqlite3LocateTable(parse, 0, item->zName);
> > > > +	sqlite3DeleteTable(parse->db, item->pTab);
> > > > +	item->pTab = table;
> > > > +	if (table != NULL)
> > > > +		table->nTabRef++;
> > > > +	if (sqlite3IndexedByLookup(parse, item))
> > > > +		table = NULL;
> > > 
> > > 1. What about --nTabRef; ? sqlite3IndexedByLookup does not unref
> > > table. Looks like original SQLite bug.
> > I'll submit a question to SQLite author.
> 
> 1. Obviously, it is a bug. The code is very simple, and it is easy to see,
> that table is never unreferenced on the error. Lets fix this in Tarantool.
> Will Hipp fix this or not in vanila SQLite after you write him - it will be
> his problem.
Looks like this is not. nTabRef++ correspond not to table, which ultimately unused.
It rather correspond to item->pTab, which is not nullified and used around. I've
updated ref count increment to reflect the fact.

> > > > +sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
> > > > +		    struct Expr **values, int pairs_count)
> > > >    {
> > > > -	Expr *where = NULL;
> > > > -	SrcList *src;
> > > > -
> > > > -		assert(nPairs > 0);
> > > > -	if (pParse->nErr > 0 || pParse->db->mallocFailed)
> > > > +	if (parse->nErr > 0 || parse->db->mallocFailed)
> > > >    		goto error;
> > > > -	src = sql_alloc_src_list(pParse->db);
> > > > -	src->a[0].zName = sqlite3DbStrDup(pParse->db, zTab);
> > > > +	struct SrcList *src = sql_alloc_src_list(parse->db);
> > > > +	src->a[0].zName = sqlite3DbStrDup(parse->db, t_name);
> > > >    	if (src == NULL)
> > > 2. Maybe first check src on NULL and then use it? Looks like the second SQLite bug.
> > Fixed.
> 
> 2. Now I see, that sql_delete_by_where is never called.
Removed.

> > > > +sql_generate_row_delete(struct Parse *parse, struct Table *table,
> > > > +			struct Trigger *trigger_list, int cursor, int reg_pk,
> > > 
> > > 3. I think, it is time to invent a standard name for cursors: I see, that both 'cursor' and 'reg_pk'
> > > are cursors, but their names differs very. My proposal: each cursor variable must end with '_cursor'.
> > > So data_cursor and pk_cursor instead of cursor and reg_pk.
> > Well, actualy no. Cursor is a cursor, this is immediate value in VDBE. Register (reg_pk) is a memory
> > cell. They're not the same thing for sure.
> > 
> > > > +			short npk, bool is_count,
> 
> 3. Oh, now I see. I was confused by names mismatch in the function declaration and implementation.
> In the implementation you use 'reg_pk' name, but in the declaration you use 'ipk'. Please, fix
> this too.
Fixed.

> > > > diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> > > > --- a/src/box/sql/sqliteInt.h
> > > > +++ b/src/box/sql/sqliteInt.h
> > > > @@ -3671,15 +3670,69 @@ int sqlite3Select(Parse *, Select *, SelectDest *);
> > > >    Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
> > > >    			 Expr *, ExprList *, u32, Expr *, Expr *);
> > > >    void sqlite3SelectDelete(sqlite3 *, Select *);
> > > > -Table *sqlite3SrcListLookup(Parse *, SrcList *);
> > > > +
> > > > +/**
> > > > + * While a SrcList can in general represent multiple tables and
> > > > + * subqueries (as in the FROM clause of a SELECT statement) in
> > > > + * this case it contains the name of a single table, as one might
> > > > + * find in an INSERT, DELETE, or UPDATE statement.  Look up that
> > > > + * table in the symbol table and return a pointer.  Set an error
> > > > + * message and return NULL if the table name is not found or if
> > > > + * any other error occurs.
> > > > + *
> > > > + * The following fields are initialized appropriate in src_list:
> > > > + *
> > > > + *    pSrc->a[0].pTab       Pointer to the Table object.
> > > 5. No pSrc anymore.
> > Fixed.
> > > > + *    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if
> 4. Now it is out of 66 symbols. Sorry, it is not my rules(
Fixed.

> > @@ -3782,11 +3834,118 @@ int sqlite3ExprContainsSubquery(Expr *);
> > + * @param parse Parsing context.
> > + * @param table Table containing the row to be deleted.
> > + * @param trigger_list List of triggers to (potentially) fire.
> > + * @param cursor Cursor from which column data is extracted/
> > + * @param ipk First memory cell containing the PRIMARY KEY.
> > + * @param npk umber of PRIMARY KEY memory cells.
> > + * @param need_update_count. If non-zero, increment the row change
> 
> 5. Non-zero -> true. Now it is boolean variable.
Fixed.

> > + *        counter.
> > + * @param onconf Default ON CONFLICT policy for triggers.
> > + * @param mode ONEPASS_OFF, _SINGLE, or _MULTI.  See above.
> 
> 6. 'mode' in @param, but 'eMode' in arguments.
Fixed.

> > + * @param idx_noseek If it is a valid cursor number (>=0),
> > + *        then it identifies an index cursor that already points
> > + *        to the index entry to be deleted.
> > + */
> > +void
> > +sql_generate_row_delete(struct Parse *parse, struct Table *table,
> > +			struct Trigger *trigger_list, int cursor, int ipk,
> > +			short npk, bool need_update_count,
> > +			enum on_conflict_action onconf, u8 eMode,
> > +			int idx_noseek);
> > +
> > +/**
> > + * Generate code that will assemble an index key and stores it in
> > + * register reg_out.  The key with be for index pIdx which is an
> 7. No pIdx. 'The key will be' maybe?
Fixed.

> > + * index on table. cursor is the index of a cursor open on the
> > + * table table and pointing to the entry that needs indexing.
> > + * cursor must be the cursor of the PRIMARY KEY index.
> > + *
> > + * Return a register number which is the first in a block of
> > + * registers that holds the elements of the index key.  The
> > + * block of registers has already been deallocated by the time
> > + * this routine returns.
> > + *
> > + * If *part_idx_label is not NULL, fill it in with a label and
> 8. *part_idx_label can not be NULL - it is integer. Maybe part_idx_label
> with no '*'?
Fixed.

> > + * jump to that label if pIdx is a partial index that should be
> 9. No pIdx.
Fixed.

> > + * skipped. The label should be resolved using
> > + * sql_resolve_part_idx_label(). A partial index should be skipped
> > + * if its WHERE clause evaluates to false or null.  If index is
> > + * not a partial index, *piPartIdxLabel will be set to zero which
> 10. No piPartIdxLabel.
Fixed.

> > + * is an empty label that is ignored by sql_resolve_part_idx_label().
> > + *
> > + * The pPrior and regPrior parameters are used to implement a
> 11. No pPrior and regPrior.
Fixed.

> > + * cache to avoid unnecessary register loads.  If prev is not
> > + * NULL, then it is a pointer to a different index for which an
> > + * index key has just been computed into register reg_prev. If the
> > + * current pIdx index is generating its key into the same
> 12. No pIdx.
Fixed.

> > + * sequence of registers and if prev and index share a column in
> > + * common, then the register corresponding to that column already
> > + * holds the correct value and the loading of that register is
> > + * skipped. This optimization is helpful when doing a DELETE or
> > + * an INTEGRITY_CHECK on a table with multiple indices, and
> > + * especially with the PRIMARY KEY columns of the index.
> > + *
> > + * @param parse Parsing context.
> > + * @param index The index for which to generate a key.
> 13. Index is a key?
This is the index for which key is being generated.

--
Regards, Kirill Yukhin

commit f4b0303612c87e2202293d0b1118b35e2f0d2ad6
Author: Kirill Yukhin <kyukhin@tarantool.org>
Date:   Tue May 15 22:09:36 2018 +0300

    sql: refactor SQL delete routines
    
    Refactor DELETE FROM statements translation, update
    all live routines according to Tarantool's coding style.
    Use key_def instead of Table* where possible.
    Remove useless index delete routine as well.
    
    Part of #3235

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index d39f110..9a96553 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -2664,10 +2664,10 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
 	VdbeCoverage(v);
 	regRecord = sqlite3GetTempReg(pParse);
 
-	sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord,
-				&iPartIdxLabel, 0, 0);
+	sql_generate_index_key(pParse, pIndex, iTab, regRecord,
+			       &iPartIdxLabel, 0, 0);
 	sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord);
-	sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
+	sql_resolve_part_idx_label(pParse, iPartIdxLabel);
 	sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1 + 1);
 	VdbeCoverage(v);
 	sqlite3VdbeJumpHere(v, addr1);
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 2c1ce44..3056a2c 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -29,929 +29,538 @@
  * SUCH DAMAGE.
  */
 
-/*
- * This file contains C code routines that are called by the parser
- * in order to generate code for DELETE FROM statements.
- */
-#include "sqliteInt.h"
 #include "box/session.h"
+#include "box/schema.h"
+#include "sqliteInt.h"
+#include "tarantoolInt.h"
 
-/*
- * While a SrcList can in general represent multiple tables and subqueries
- * (as in the FROM clause of a SELECT statement) in this case it contains
- * the name of a single table, as one might find in an INSERT, DELETE,
- * or UPDATE statement.  Look up that table in the symbol table and
- * return a pointer.  Set an error message and return NULL if the table
- * name is not found or if any other error occurs.
- *
- * The following fields are initialized appropriate in pSrc:
- *
- *    pSrc->a[0].pTab       Pointer to the Table object
- *    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if there is one
- *
- */
-Table *
-sqlite3SrcListLookup(Parse * pParse, SrcList * pSrc)
-{
-	struct SrcList_item *pItem = pSrc->a;
-	Table *pTab;
-	assert(pItem && pSrc->nSrc == 1);
-	pTab = sqlite3LocateTable(pParse, 0, pItem->zName);
-	sqlite3DeleteTable(pParse->db, pItem->pTab);
-	pItem->pTab = pTab;
-	if (pTab != NULL)
-		pTab->nTabRef++;
-	if (sqlite3IndexedByLookup(pParse, pItem))
-		pTab = NULL;
-	return pTab;
-}
-
-/*
- * Check to make sure the given table is writable.  If it is not
- * writable, generate an error message and return 1.  If it is
- * writable return 0;
- */
-int
-sqlite3IsReadOnly(Parse * pParse, Table * pTab, int viewOk)
+struct Table *
+sql_list_lookup_table(struct Parse *parse, SrcList *src_list)
 {
-	/*
-	 * A table is not writable if it is a system table
-	 * (i.e. _sql_stat1), this call is not part of a
-	 * nested parse. In either case leave an error message in
-	 * pParse and return non-zero.
-	 */
-	if ((pTab->tabFlags & TF_Readonly) != 0 && pParse->nested == 0) {
-		sqlite3ErrorMsg(pParse, "table %s may not be modified",
-				pTab->zName);
-		return 1;
-	}
-#ifndef SQLITE_OMIT_VIEW
-	if (!viewOk && space_is_view(pTab)) {
-		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
-				pTab->zName);
-		return 1;
-	}
-#endif
-	return 0;
+	struct SrcList_item *item = src_list->a;
+	assert(item != NULL && src_list->nSrc == 1);
+	struct Table *table = sqlite3LocateTable(parse, 0, item->zName);
+	sqlite3DeleteTable(parse->db, item->pTab);
+	item->pTab = table;
+	if (table != NULL)
+		item->pTab->nTabRef++;
+	if (sqlite3IndexedByLookup(parse, item))
+		table = NULL;
+	return table;
 }
 
-#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-/*
- * Evaluate a view and store its result in an ephemeral table.  The
- * pWhere argument is an optional WHERE clause that restricts the
- * set of rows in the view that are to be added to the ephemeral table.
- */
 void
-sqlite3MaterializeView(Parse * pParse,	/* Parsing context */
-		       Table * pView,	/* View definition */
-		       Expr * pWhere,	/* Optional WHERE clause to be added */
-		       int iCur)	/* Cursor number for ephemeral table */
-{
-	SelectDest dest;
-	Select *pSel;
-	SrcList *pFrom;
-	sqlite3 *db = pParse->db;
-	pWhere = sqlite3ExprDup(db, pWhere, 0);
-	pFrom = sqlite3SrcListAppend(db, 0, 0);
-	if (pFrom) {
-		assert(pFrom->nSrc == 1);
-		pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName);
-		assert(pFrom->a[0].pOn == 0);
-		assert(pFrom->a[0].pUsing == 0);
-	}
-	pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0,
-				0, 0, 0);
-	sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur);
-	sqlite3Select(pParse, pSel, &dest);
-	sqlite3SelectDelete(db, pSel);
-}
-#endif				/* !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */
-
-#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
-/*
- * Generate an expression tree to implement the WHERE, ORDER BY,
- * and LIMIT/OFFSET portion of DELETE and UPDATE statements.
- *
- *     DELETE FROM table_wxyz WHERE a<5 ORDER BY a LIMIT 1;
- *                            \__________________________/
- *                               pLimitWhere (pInClause)
- */
-Expr *
-sqlite3LimitWhere(Parse * pParse,	/* The parser context */
-		  SrcList * pSrc,	/* the FROM clause -- which tables to scan */
-		  Expr * pWhere,	/* The WHERE clause.  May be null */
-		  ExprList * pOrderBy,	/* The ORDER BY clause.  May be null */
-		  Expr * pLimit,	/* The LIMIT clause.  May be null */
-		  Expr * pOffset,	/* The OFFSET clause.  May be null */
-		  char *zStmtType	/* Either DELETE or UPDATE.  For err msgs. */
-    )
+sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
+		     int cursor)
 {
-	Expr *pWhereRowid = NULL;	/* WHERE rowid .. */
-	Expr *pInClause = NULL;	/* WHERE rowid IN ( select ) */
-	Expr *pSelectRowid = NULL;	/* SELECT rowid ... */
-	ExprList *pEList = NULL;	/* Expression list contaning only pSelectRowid */
-	SrcList *pSelectSrc = NULL;	/* SELECT rowid FROM x ... (dup of pSrc) */
-	Select *pSelect = NULL;	/* Complete SELECT tree */
-
-	/* Check that there isn't an ORDER BY without a LIMIT clause.
-	 */
-	if (pOrderBy && (pLimit == 0)) {
-		sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s",
-				zStmtType);
-		goto limit_where_cleanup;
-	}
-
-	/* We only need to generate a select expression if there
-	 * is a limit/offset term to enforce.
-	 */
-	if (pLimit == 0) {
-		/* if pLimit is null, pOffset will always be null as well. */
-		assert(pOffset == 0);
-		return pWhere;
-	}
-
-	/* Generate a select expression tree to enforce the limit/offset
-	 * term for the DELETE or UPDATE statement.  For example:
-	 *   DELETE FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
-	 * becomes:
-	 *   DELETE FROM table_a WHERE rowid IN (
-	 *     SELECT rowid FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
-	 *   );
-	 */
-
-	pSelectRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0);
-	if (pSelectRowid == 0)
-		goto limit_where_cleanup;
-	pEList = sqlite3ExprListAppend(pParse, 0, pSelectRowid);
-	if (pEList == 0)
-		goto limit_where_cleanup;
-
-	/* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree
-	 * and the SELECT subtree.
-	 */
-	pSelectSrc = sqlite3SrcListDup(pParse->db, pSrc, 0);
-	if (pSelectSrc == 0) {
-		sqlite3ExprListDelete(pParse->db, pEList);
-		goto limit_where_cleanup;
+	struct sqlite3 *db = parse->db;
+	where = sqlite3ExprDup(db, where, 0);
+	struct SrcList *from = sqlite3SrcListAppend(db, NULL, NULL);
+	if (from != NULL) {
+		assert(from->nSrc == 1);
+		from->a[0].zName = sqlite3DbStrDup(db, name);
+		assert(from->a[0].pOn == NULL);
+		assert(from->a[0].pUsing == NULL);
 	}
-
-	/* generate the SELECT expression tree. */
-	pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0, 0,
-				   pOrderBy, 0, pLimit, pOffset);
-	if (pSelect == 0)
-		return 0;
-
-	/* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */
-	pWhereRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0);
-	pInClause =
-	    pWhereRowid ? sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0) : 0;
-	sqlite3PExprAddSelect(pParse, pInClause, pSelect);
-	return pInClause;
-
- limit_where_cleanup:
-	sql_expr_free(pParse->db, pWhere);
-	sqlite3ExprListDelete(pParse->db, pOrderBy);
-	sql_expr_free(pParse->db, pLimit);
-	sql_expr_free(pParse->db, pOffset);
-	return 0;
+	struct Select *select = sqlite3SelectNew(parse, NULL, from, where, NULL,
+						 NULL, NULL, 0, NULL, NULL);
+	struct SelectDest dest;
+	sqlite3SelectDestInit(&dest, SRT_EphemTab, cursor);
+	sqlite3Select(parse, select, &dest);
+	sqlite3SelectDelete(db, select);
 }
-#endif				/* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) */
 
-/*
- * Generate code for a DELETE FROM statement.
- *
- *     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
- *                 \________/       \________________/
- *                  pTabList              pWhere
- */
 void
-sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
-		  SrcList * pTabList,	/* The table from which we should delete things */
-		  Expr * pWhere)	/* The WHERE clause.  May be null */
+sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
+		      struct Expr *where)
 {
-	Vdbe *v;		/* The virtual database engine */
-	Table *pTab;		/* The table from which records will be deleted */
-	int i;			/* Loop counter */
-	WhereInfo *pWInfo;	/* Information about the WHERE clause */
-	Index *pIdx;		/* For looping over indices of the table */
-	int iTabCur;		/* Cursor number for the table */
-	int iDataCur = 0;	/* VDBE cursor for the canonical data source */
-	int iIdxCur = 0;	/* Cursor number of the first index */
-	int nIdx;		/* Number of indices */
-	sqlite3 *db;		/* Main database structure */
-	NameContext sNC;	/* Name context to resolve expressions in */
-	int memCnt = -1;	/* Memory cell used for change counting */
-	int eOnePass;		/* ONEPASS_OFF or _SINGLE or _MULTI */
-	int aiCurOnePass[2];	/* The write cursors opened by WHERE_ONEPASS */
-	u8 *aToOpen = 0;	/* Open cursor iTabCur+j if aToOpen[j] is true */
-	Index *pPk = 0;		/* The PRIMARY KEY index on the table */
-	int iPk = 0;		/* First of nPk registers holding PRIMARY KEY value */
-	i16 nPk;		/* Number of columns in the PRIMARY KEY */
-	int iKey;		/* Memory cell holding key of row to be deleted */
-	i16 nKey;		/* Number of memory cells in the row key */
-	int iEphCur = 0;	/* Ephemeral table holding all primary key values */
-	int addrBypass = 0;	/* Address of jump over the delete logic */
-	int addrLoop = 0;	/* Top of the delete loop */
-	int addrEphOpen = 0;	/* Instruction to open the Ephemeral table */
-	int bComplex;		/* True if there are triggers or FKs or
-				 * subqueries in the WHERE clause
-				 */
-	struct session *user_session = current_session();
+	struct sqlite3 *db = parse->db;
+	if (parse->nErr || db->mallocFailed)
+		goto delete_from_cleanup;
 
-#ifndef SQLITE_OMIT_TRIGGER
-	int isView;		/* True if attempting to delete from a view */
-	Trigger *pTrigger;	/* List of table triggers, if required */
-#endif
+	assert(tab_list->nSrc == 1);
 
-	db = pParse->db;
-	if (pParse->nErr || db->mallocFailed) {
+	/* Locate the table which we want to delete.  This table
+	 * has to be put in an SrcList structure because some of
+	 * the subroutines we will be calling are designed to work
+	 * with multiple tables and expect an SrcList* parameter
+	 * instead of just a Table* parameter.
+	 */
+	struct Table *table = sql_list_lookup_table(parse, tab_list);
+	if (table == NULL)
 		goto delete_from_cleanup;
-	}
-	assert(pTabList->nSrc == 1);
 
-	/* Locate the table which we want to delete.  This table has to be
-	 * put in an SrcList structure because some of the subroutines we
-	 * will be calling are designed to work with multiple tables and expect
-	 * an SrcList* parameter instead of just a Table* parameter.
+	struct space *space = space_by_id(SQLITE_PAGENO_TO_SPACEID(table->tnum));
+	assert(space != NULL);
+	/* Figure out if we have any triggers and if the table
+	 * being deleted from is a view.
 	 */
-	pTab = sqlite3SrcListLookup(pParse, pTabList);
-	if (pTab == 0)
-		goto delete_from_cleanup;
+	struct Trigger *trigger_list = sqlite3TriggersExist(table, TK_DELETE,
+							    NULL, NULL);
 
-	/* Figure out if we have any triggers and if the table being
-	 * deleted from is a view
+	bool is_view = space->def->opts.is_view;
+	/* True if there are triggers or FKs or subqueries in the
+	 * WHERE clause.
 	 */
-#ifndef SQLITE_OMIT_TRIGGER
-	pTrigger = sqlite3TriggersExist(pTab, TK_DELETE, 0, 0);
-	isView = pTab->pSelect != 0;
-	bComplex = pTrigger || sqlite3FkRequired(pTab, 0);
-#else
-#define pTrigger 0
-#define isView 0
-#endif
-#ifdef SQLITE_OMIT_VIEW
-#undef isView
-#define isView 0
-#endif
-
-	/* If pTab is really a view, make sure it has been initialized.
+	bool is_complex = (trigger_list != NULL) || sqlite3FkRequired(table, 0);
+
+	/* If table is really a view, make sure it has been
+	 * initialized.
 	 */
-	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
+	if (sqlite3ViewGetColumnNames(parse, table))
 		goto delete_from_cleanup;
-	}
 
-	if (sqlite3IsReadOnly(pParse, pTab, (pTrigger ? 1 : 0))) {
+	if (is_view && trigger_list == NULL) {
+		sqlite3ErrorMsg(parse, "cannot modify %s because it is a view",
+				space->def->name);
 		goto delete_from_cleanup;
 	}
-	assert(!isView || pTrigger);
 
 	/* Assign cursor numbers to the table and all its indices.
 	 */
-	assert(pTabList->nSrc == 1);
-	iTabCur = pTabList->a[0].iCursor = pParse->nTab++;
-	for (nIdx = 0, pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext, nIdx++) {
-		pParse->nTab++;
-	}
+	int tab_cursor = tab_list->a[0].iCursor = parse->nTab++;
+	parse->nTab += space->index_count;
 
-	/* Begin generating code.
-	 */
-	v = sqlite3GetVdbe(pParse);
-	if (v == 0) {
+	/* Begin generating code.  */
+	struct Vdbe *v = sqlite3GetVdbe(parse);
+	if (v == NULL)
 		goto delete_from_cleanup;
-	}
-	if (pParse->nested == 0)
+
+	if (parse->nested == 0)
 		sqlite3VdbeCountChanges(v);
-	sql_set_multi_write(pParse, true);
+	sql_set_multi_write(parse, true);
 
-	/* If we are trying to delete from a view, realize that view into
-	 * an ephemeral table.
+	/* If we are trying to delete from a view, realize that
+	 * view into an ephemeral table.
 	 */
-#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-	if (isView) {
-		sqlite3MaterializeView(pParse, pTab, pWhere, iTabCur);
-		iDataCur = iIdxCur = iTabCur;
+	if (is_view) {
+		sql_materialize_view(parse, space->def->name, where,
+				     tab_cursor);
 	}
-#endif
 
-	/* Resolve the column names in the WHERE clause.
-	 */
-	memset(&sNC, 0, sizeof(sNC));
-	sNC.pParse = pParse;
-	sNC.pSrcList = pTabList;
-	if (sqlite3ResolveExprNames(&sNC, pWhere)) {
+	/* Resolve the column names in the WHERE clause. */
+	struct NameContext nc;
+	memset(&nc, 0, sizeof(nc));
+	nc.pParse = parse;
+	nc.pSrcList = tab_list;
+	if (sqlite3ResolveExprNames(&nc, where))
 		goto delete_from_cleanup;
-	}
 
-	/* Initialize the counter of the number of rows deleted, if
-	 * we are counting rows.
+	/* Initialize the counter of the number of rows deleted,
+	 * if we are counting rows.
 	 */
+	int reg_count = -1;
+	struct session *user_session = current_session();
 	if (user_session->sql_flags & SQLITE_CountRows) {
-		memCnt = ++pParse->nMem;
-		sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt);
+		reg_count = ++parse->nMem;
+		sqlite3VdbeAddOp2(v, OP_Integer, 0, reg_count);
 	}
-#ifndef SQLITE_OMIT_TRUNCATE_OPTIMIZATION
-	/* Special case: A DELETE without a WHERE clause deletes everything.
-	 * It is easier just to erase the whole table. Prior to version 3.6.5,
-	 * this optimization caused the row change count (the value returned by
-	 * API function sqlite3_count_changes) to be set incorrectly.
+	/* Special case: A DELETE without a WHERE clause deletes
+	 * everything. It is easier just to erase the whole table.
 	 */
-	if (pWhere == 0 && !bComplex
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-	    && db->xPreUpdateCallback == 0
-#endif
-	    ) {
-		assert(!isView);
-
-		sqlite3VdbeAddOp1(v, OP_Clear, pTab->tnum);
-
-		/* Do not start Tarantool's transaction in case of truncate optimization.
-		   This is workaround until system tables cannot be changes inside a
-		   transaction (_truncate).  */
-		pParse->initiateTTrans = false;
-	} else
-#endif				/* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */
-	{
-		u16 wcf =
-		    WHERE_ONEPASS_DESIRED | WHERE_DUPLICATES_OK |
-		    WHERE_SEEK_TABLE;
-		if (sNC.ncFlags & NC_VarSelect)
-			bComplex = 1;
-		wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW);
-		/* Create an ephemeral table used to hold all primary keys for
-		 * rows to be deleted. Since VIEW is held in ephemeral table,
-		 * there is no PK for it, so columns should be loaded manually.
+	if (where == NULL && !is_complex) {
+		assert(!is_view);
+
+		sqlite3VdbeAddOp1(v, OP_Clear, table->tnum);
+
+		/* Do not start Tarantool's transaction in case of
+		 * truncate optimization. This is workaround until
+		 * system tables cannot be changes inside a
+		 * transaction (_truncate).
+		 */
+		parse->initiateTTrans = false;
+	} else {
+		uint16_t wcf = WHERE_ONEPASS_DESIRED | WHERE_DUPLICATES_OK |
+			WHERE_SEEK_TABLE;
+		if (nc.ncFlags & NC_VarSelect)
+			is_complex = true;
+		wcf |= (is_complex ? 0 : WHERE_ONEPASS_MULTIROW);
+		/* Create an ephemeral table used to hold all
+		 * primary keys for rows to be deleted. Since VIEW
+		 * is held in ephemeral table, there is no PK for
+		 * it, so columns should be loaded manually.
 		 */
-		if (isView) {
-			nPk = pTab->nCol;
-			iPk = pParse->nMem + 1;
-			pParse->nMem += nPk;
-			iEphCur = pParse->nTab++;
-			addrEphOpen =
-				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
-						  nPk);
+		struct Index *pk = NULL;
+		int reg_pk = parse->nMem + 1;
+		int pk_len;
+		int eph_cursor = parse->nTab++;
+		int addr_eph_open = sqlite3VdbeCurrentAddr(v);
+		if (is_view) {
+			pk_len = table->nCol;
+			parse->nMem += pk_len;
+			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
+					  eph_cursor, pk_len);
 		} else {
-			pPk = sqlite3PrimaryKeyIndex(pTab);
-			assert(pPk != 0);
-			nPk = index_column_count(pPk);
-			iPk = pParse->nMem + 1;
-			pParse->nMem += nPk;
-			iEphCur = pParse->nTab++;
-			addrEphOpen =
-			    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEphCur,
-					      nPk);
-			sql_vdbe_set_p4_key_def(pParse, pPk);
+			pk = sqlite3PrimaryKeyIndex(table);
+			assert(pk != NULL);
+			pk_len = index_column_count(pk);
+			parse->nMem += pk_len;
+			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, eph_cursor,
+					  pk_len);
+			sql_vdbe_set_p4_key_def(parse, pk);
 		}
 
-		/* Construct a query to find the primary key for every row
-		 * to be deleted, based on the WHERE clause. Set variable eOnePass
-		 * to indicate the strategy used to implement this delete:
+		/* Construct a query to find the primary key for
+		 * every row to be deleted, based on the WHERE
+		 * clause. Set variable one_pass to indicate the
+		 * strategy used to implement this delete:
 		 *
-		 *  ONEPASS_OFF:    Two-pass approach - use a FIFO for PK values.
-		 *  ONEPASS_SINGLE: One-pass approach - at most one row deleted.
-		 *  ONEPASS_MULTI:  One-pass approach - any number of rows may be deleted.
+		 * ONEPASS_OFF:    Two-pass approach - use a FIFO
+		 * for PK values.
+		 * ONEPASS_SINGLE: One-pass approach - at most one
+		 * row deleted.
+		 * ONEPASS_MULTI:  One-pass approach - any number
+		 * of rows may be deleted.
 		 */
-		pWInfo =
-		    sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf,
-				      iTabCur + 1);
-		if (pWInfo == 0)
+		struct WhereInfo *winfo =
+		    sqlite3WhereBegin(parse, tab_list, where, NULL, NULL, wcf,
+				      tab_cursor + 1);
+		if (winfo == NULL)
 			goto delete_from_cleanup;
-		eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
-		assert(eOnePass != ONEPASS_MULTI);
-		/* Tarantool workaround: see comment in sqlite3WhereBegin.  */
-		/* assert( bComplex || eOnePass!=ONEPASS_OFF ); */
-
-		/* Keep track of the number of rows to be deleted */
-		if (user_session->sql_flags & SQLITE_CountRows) {
-			sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1);
-		}
+
+		/* The write cursors opened by WHERE_ONEPASS */
+		int one_pass_cur[2];
+		int one_pass = sqlite3WhereOkOnePass(winfo, one_pass_cur);
+		assert(one_pass != ONEPASS_MULTI);
+		/* Tarantool: see comment in
+		 * sqlite3WhereOkOnePass.
+		 */
+		/* assert(is_complex || one_pass != ONEPASS_OFF); */
+
+		/* Keep track of the number of rows to be
+		 * deleted.
+		 */
+		if (user_session->sql_flags & SQLITE_CountRows)
+			sqlite3VdbeAddOp2(v, OP_AddImm, reg_count, 1);
 
 		/* Extract the primary key for the current row */
-		if (!isView) {
-			for (i = 0; i < nPk; i++) {
-				assert(pPk->aiColumn[i] >= 0);
-				sqlite3ExprCodeGetColumnOfTable(v, pTab,
-								iTabCur,
-								pPk->
+		if (!is_view) {
+			for (int i = 0; i < pk_len; i++) {
+				assert(pk->aiColumn[i] >= 0);
+				sqlite3ExprCodeGetColumnOfTable(v, table,
+								tab_cursor,
+								pk->
 								aiColumn[i],
-								iPk + i);
+								reg_pk + i);
 			}
 		} else {
-			for (i = 0; i < nPk; i++) {
-				sqlite3VdbeAddOp3(v, OP_Column, iDataCur,
-						  i, iPk + i);
+			for (int i = 0; i < pk_len; i++) {
+				sqlite3VdbeAddOp3(v, OP_Column, tab_cursor,
+						  i, reg_pk + i);
 			}
 		}
-		iKey = iPk;
 
-		if (eOnePass != ONEPASS_OFF) {
-			/* For ONEPASS, no need to store the primary-key. There is only
-			 * one, so just keep it in its register(s) and fall through to the
-			 * delete code.
+		int reg_key;
+		int key_len;
+		if (one_pass != ONEPASS_OFF) {
+			/* For ONEPASS, no need to store the
+			 * primary-key. There is only one, so just
+			 * keep it in its register(s) and fall
+			 * through to the delete code.
 			 */
-			nKey = nPk;	/* OP_Found will use an unpacked key */
-			aToOpen = sqlite3DbMallocRawNN(db, nIdx + 2);
-			if (aToOpen == 0) {
-				sqlite3WhereEnd(pWInfo);
-				goto delete_from_cleanup;
-			}
-			memset(aToOpen, 1, nIdx + 1);
-			aToOpen[nIdx + 1] = 0;
-			if (aiCurOnePass[0] >= 0)
-				aToOpen[aiCurOnePass[0] - iTabCur] = 0;
-			if (aiCurOnePass[1] >= 0)
-				aToOpen[aiCurOnePass[1] - iTabCur] = 0;
-			if (addrEphOpen)
-				sqlite3VdbeChangeToNoop(v, addrEphOpen);
+			reg_key = reg_pk;
+			/* OP_Found will use an unpacked key */
+			key_len = pk_len;
+			sqlite3VdbeChangeToNoop(v, addr_eph_open);
 		} else {
-			/* Add the PK key for this row to the temporary table */
-			iKey = ++pParse->nMem;
-			nKey = 0;	/* Zero tells OP_Found to use a composite key */
-			const char *zAff = isView ? 0 :
-					  sqlite3IndexAffinityStr(pParse->db, pPk);
-			sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey, zAff, nPk);
-			/* Set flag to save memory allocating one by malloc. */
+			/* Add the PK key for this row to the
+			 * temporary table.
+			 */
+			reg_key = ++parse->nMem;
+			/* Zero tells OP_Found to use a composite
+			 * key.
+			 */
+			key_len = 0;
+			const char *zAff = is_view ? NULL :
+					  sqlite3IndexAffinityStr(parse->db, pk);
+			sqlite3VdbeAddOp4(v, OP_MakeRecord, reg_pk, pk_len,
+					  reg_key, zAff, pk_len);
+			/* Set flag to save memory allocating one
+			 * by malloc.
+			 */
 			sqlite3VdbeChangeP5(v, 1);
-			sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey);
+			sqlite3VdbeAddOp2(v, OP_IdxInsert, eph_cursor, reg_key);
 		}
 
-		/* If this DELETE cannot use the ONEPASS strategy, this is the
-		 * end of the WHERE loop
+		/* If this DELETE cannot use the ONEPASS strategy,
+		 * this is the end of the WHERE loop.
 		 */
-		if (eOnePass != ONEPASS_OFF) {
-			addrBypass = sqlite3VdbeMakeLabel(v);
-		} else {
-			sqlite3WhereEnd(pWInfo);
-		}
-
-		/* Unless this is a view, open cursors for the table we are
-		 * deleting from and all its indices. If this is a view, then the
-		 * only effect this statement has is to fire the INSTEAD OF
+		int addr_bypass = 0;
+		if (one_pass != ONEPASS_OFF)
+			addr_bypass = sqlite3VdbeMakeLabel(v);
+		else
+			sqlite3WhereEnd(winfo);
+
+		/* Unless this is a view, open cursors for the
+		 * table we are deleting from and all its indices.
+		 * If this is a view, then the only effect this
+		 * statement has is to fire the INSTEAD OF
 		 * triggers.
 		 */
-		if (!isView) {
+		if (!is_view) {
 			int iAddrOnce = 0;
-			if (eOnePass == ONEPASS_MULTI) {
+			if (one_pass == ONEPASS_MULTI) {
 				iAddrOnce = sqlite3VdbeAddOp0(v, OP_Once);
 				VdbeCoverage(v);
 			}
-			sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite,
-						   OPFLAG_FORDELETE, iTabCur,
-						   aToOpen, &iDataCur,
-						   &iIdxCur,
-						   ON_CONFLICT_ACTION_NONE, 0);
-			assert(pPk || iDataCur == iTabCur);
-			assert(pPk || iIdxCur == iDataCur + 1);
-			if (eOnePass == ONEPASS_MULTI)
+
+			int space_ptr_reg = ++parse->nMem;
+			sqlite3VdbeAddOp4(v, OP_LoadPtr, 0, space_ptr_reg, 0,
+					  (void *)space, P4_SPACEPTR);
+			sqlite3VdbeAddOp3(v, OP_OpenWrite, tab_cursor,
+					  table->tnum, space_ptr_reg);
+			sql_vdbe_set_p4_key_def(parse, pk);
+			VdbeComment((v, "%s", pk->zName));
+
+			if (one_pass == ONEPASS_MULTI)
 				sqlite3VdbeJumpHere(v, iAddrOnce);
 		}
 
-		/* Set up a loop over the primary-keys that were found in the
-		 * where-clause loop above.
+		/* Set up a loop over the primary-keys that were
+		 * found in the where-clause loop above.
 		 */
-		if (eOnePass != ONEPASS_OFF) {
-			assert(nKey == nPk);	/* OP_Found will use an unpacked key */
-			if (aToOpen[iDataCur - iTabCur]) {
-				assert(pPk != 0 || pTab->pSelect != 0);
-				sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur,
-						     addrBypass, iKey, nKey);
+		int addr_loop = 0;
+		if (one_pass != ONEPASS_OFF) {
+			/* OP_Found will use an unpacked key. */
+			assert(key_len == pk_len);
+			assert(pk != NULL || table->pSelect != NULL);
+			sqlite3VdbeAddOp4Int(v, OP_NotFound, tab_cursor,
+					     addr_bypass, reg_key, key_len);
 
-				VdbeCoverage(v);
-			}
+			VdbeCoverage(v);
 		} else {
-			addrLoop = sqlite3VdbeAddOp1(v, OP_Rewind, iEphCur);
+			addr_loop = sqlite3VdbeAddOp1(v, OP_Rewind, eph_cursor);
 			VdbeCoverage(v);
-			sqlite3VdbeAddOp2(v, OP_RowData, iEphCur, iKey);
+			sqlite3VdbeAddOp2(v, OP_RowData, eph_cursor, reg_key);
 		}
 
 		/* Delete the row */
-		{
-			int count = (pParse->nested == 0);	/* True to count changes */
-			int iIdxNoSeek = -1;
-			if (bComplex == 0 && aiCurOnePass[1] != iDataCur
-			    /* Tarantool: as far as ONEPASS is disabled, there's no index
-			       w/o need of seeking.  */
-			    && eOnePass != ONEPASS_OFF) {
-				iIdxNoSeek = aiCurOnePass[1];
-			}
-			sqlite3GenerateRowDelete(pParse, pTab, pTrigger,
-						 iDataCur, iIdxCur, iKey, nKey,
-						 count,
-						 ON_CONFLICT_ACTION_DEFAULT,
-						 eOnePass,
-						 iIdxNoSeek);
-		}
+		int idx_noseek = -1;
+		if (!is_complex && one_pass_cur[1] != tab_cursor
+		    /* Tarantool: as far as ONEPASS is disabled,
+		     * there's no index w/o need of seeking.
+		     */
+		    && one_pass != ONEPASS_OFF)
+			idx_noseek = one_pass_cur[1];
+
+		sql_generate_row_delete(parse, table, trigger_list, tab_cursor,
+					reg_key, key_len, parse->nested == 0,
+					ON_CONFLICT_ACTION_DEFAULT, one_pass,
+					idx_noseek);
 
 		/* End of the loop over all primary-keys. */
-		if (eOnePass != ONEPASS_OFF) {
-			sqlite3VdbeResolveLabel(v, addrBypass);
-			sqlite3WhereEnd(pWInfo);
+		if (one_pass != ONEPASS_OFF) {
+			sqlite3VdbeResolveLabel(v, addr_bypass);
+			sqlite3WhereEnd(winfo);
 		} else {
-			sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop + 1);
+			sqlite3VdbeAddOp2(v, OP_Next, eph_cursor,
+					  addr_loop + 1);
 			VdbeCoverage(v);
-			sqlite3VdbeJumpHere(v, addrLoop);
+			sqlite3VdbeJumpHere(v, addr_loop);
 		}
-	}			/* End non-truncate path */
+	}
 
-	/* Return the number of rows that were deleted. If this routine is
-	 * generating code because of a call to sqlite3NestedParse(), do not
-	 * invoke the callback function.
+	/* Return the number of rows that were deleted. If this
+	 * routine is generating code because of a call to
+	 * sqlite3NestedParse(), do not invoke the callback
+	 * function.
 	 */
 	if ((user_session->sql_flags & SQLITE_CountRows) &&
-	    !pParse->nested && !pParse->pTriggerTab) {
-		sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1);
+	    parse->nested == 0 && !parse->pTriggerTab) {
+		sqlite3VdbeAddOp2(v, OP_ResultRow, reg_count, 1);
 		sqlite3VdbeSetNumCols(v, 1);
 		sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted",
 				      SQLITE_STATIC);
 	}
 
  delete_from_cleanup:
-	sqlite3SrcListDelete(db, pTabList);
-	sql_expr_free(db, pWhere, false);
-	sqlite3DbFree(db, aToOpen);
-	return;
-}
-
-/* Generate VDBE code for
- * DELETE FROM <pTab.z> WHERE
- *        <columns[0]> = <values[0]>
- *    AND  ...
- *    AND <columns[nPairs - 1]> = <values[nPairs - 1]>;
- *
- * This function does not increment the nested counter and is
- * faster than nested parsing of the request above.
- * @param pParse Parser context.
- * @param pTab Table name.
- * @param columns Column names array.
- * @param values Column values array.
- * @param nPairs Length of @columns and @values.
- *
- * In case of error the @values elements are deleted.
- */
-void
-sqlite3DeleteByKey(Parse *pParse, char *zTab, const char **columns,
-		   Expr **values, int nPairs)
-{
-	Expr *where = NULL;
-	SrcList *src;
-
-		assert(nPairs > 0);
-	if (pParse->nErr > 0 || pParse->db->mallocFailed)
-		goto error;
-	src = sql_alloc_src_list(pParse->db);
-	src->a[0].zName = sqlite3DbStrDup(pParse->db, zTab);
-	if (src == NULL)
-		goto error;
-	/* Dummy init of INDEXED BY clause. */
-	Token t = { NULL, 0, false };
-	sqlite3SrcListIndexedBy(pParse, src, &t);
-
-	for (int i = 0; i < nPairs; ++i) {
-		Expr *col_expr = sqlite3Expr(pParse->db, TK_ID, columns[i]);
-		if (col_expr == NULL || values[i] == NULL)
-			goto error;
-		Expr *eq_expr =
-		    sqlite3PExpr(pParse, TK_EQ, col_expr, values[i]);
-		/* In case of error the values[i] had been deleted in
-		 * sqlite3PExpr already. Do not delete it second time in the
-		 * cycle below.
-		 */
-		values[i] = NULL;
-		if (eq_expr == NULL)
-			goto error;
-		if (i == 0) {
-			where = eq_expr;
-		} else {
-			where = sqlite3ExprAnd(pParse->db, where, eq_expr);
-			if (where == NULL)
-				goto error;
-		}
-	}
-	/* DeleteFrom frees the src and exprs in case of error. */
-	sqlite3DeleteFrom(pParse, src, where);
-	return;
-
- error:
-	sql_expr_free(pParse->db, where, false);
-	for (int i = 0; i < nPairs; ++i)
-		sql_expr_free(pParse->db, values[i], false);
+	sqlite3SrcListDelete(db, tab_list);
+	sql_expr_free(db, where, false);
 }
 
-/* Make sure "isView" and other macros defined above are undefined. Otherwise
- * they may interfere with compilation of other functions in this file
- * (or in another file, if this file becomes part of the amalgamation).
- */
-#ifdef isView
-#undef isView
-#endif
-#ifdef pTrigger
-#undef pTrigger
-#endif
-
-/*
- * This routine generates VDBE code that causes a single row of a
- * single table to be deleted.  Both the original table entry and
- * all indices are removed.
- *
- * Preconditions:
- *
- *   1.  iDataCur is an open cursor on the btree that is the canonical data
- *       store for the table.  (This will be the PRIMARY KEY index)
- *
- *   2.  Read/write cursors for all indices of pTab must be open as
- *       cursor number iIdxCur+i for the i-th index.
- *
- *   3.  The primary key for the row to be deleted must be stored in a
- *       sequence of nPk memory cells starting at iPk.  If nPk==0 that means
- *       that a search record formed from OP_MakeRecord is contained in the
- *       single memory location iPk.
- *
- * eMode:
- *   Parameter eMode may be passed either ONEPASS_OFF (0), ONEPASS_SINGLE, or
- *   ONEPASS_MULTI.  If eMode is not ONEPASS_OFF, then the cursor
- *   iDataCur already points to the row to delete. If eMode is ONEPASS_OFF
- *   then this function must seek iDataCur to the entry identified by iPk
- *   and nPk before reading from it.
- *
- *   If eMode is ONEPASS_MULTI, then this call is being made as part
- *   of a ONEPASS delete that affects multiple rows. In this case, if
- *   iIdxNoSeek is a valid cursor number (>=0), then its position should
- *   be preserved following the delete operation. Or, if iIdxNoSeek is not
- *   a valid cursor number, the position of iDataCur should be preserved
- *   instead.
- *
- * iIdxNoSeek:
- *   If iIdxNoSeek is a valid cursor number (>=0), then it identifies an
- *   index cursor (from within array of cursors starting at iIdxCur) that
- *   already points to the index entry to be deleted.
- */
 void
-sqlite3GenerateRowDelete(Parse * pParse,	/* Parsing context */
-			 Table * pTab,		/* Table containing the row to be deleted */
-			 Trigger * pTrigger,	/* List of triggers to (potentially) fire */
-			 int iDataCur,		/* Cursor from which column data is extracted */
-			 int iIdxCur,		/* First index cursor */
-			 int iPk,		/* First memory cell containing the PRIMARY KEY */
-			 i16 nPk,		/* Number of PRIMARY KEY memory cells */
-			 u8 count,		/* If non-zero, increment the row change counter */
-			 enum on_conflict_action onconf,		/* Default ON CONFLICT policy for triggers */
-			 u8 eMode,		/* ONEPASS_OFF, _SINGLE, or _MULTI.  See above */
-			 int iIdxNoSeek)	/* Cursor number of cursor that does not need seeking */
+sql_generate_row_delete(struct Parse *parse, struct Table *table,
+			struct Trigger *trigger_list, int cursor, int reg_pk,
+			short npk, bool need_update_count,
+			enum on_conflict_action onconf, u8 mode,
+			int idx_noseek)
 {
-	Vdbe *v = pParse->pVdbe;	/* Vdbe */
-	int iOld = 0;		/* First register in OLD.* array */
-	int iLabel;		/* Label resolved to end of generated code */
-
-	/* Vdbe is guaranteed to have been allocated by this stage. */
-	assert(v);
-	(void)iIdxCur;
+	struct Vdbe *v = parse->pVdbe;
+	/* Vdbe is guaranteed to have been allocated by this
+	 * stage.
+	 */
+	assert(v != NULL);
 	VdbeModuleComment((v, "BEGIN: GenRowDel(%d,%d,%d,%d)",
-			   iDataCur, iIdxCur, iPk, (int)nPk));
+			   cursor, iIdxCur, reg_pk, (int)nPk));
 
-	/* Seek cursor iCur to the row to delete. If this row no longer exists
-	 * (this can happen if a trigger program has already deleted it), do
-	 * not attempt to delete it or fire any DELETE triggers.
+	/* Seek cursor iCur to the row to delete. If this row no
+	 * longer exists (this can happen if a trigger program has
+	 * already deleted it), do not attempt to delete it or
+	 * fire any DELETE triggers.
 	 */
-	iLabel = sqlite3VdbeMakeLabel(v);
-	if (eMode == ONEPASS_OFF) {
-		sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, iLabel, iPk, nPk);
+	int label = sqlite3VdbeMakeLabel(v);
+	if (mode == ONEPASS_OFF) {
+		sqlite3VdbeAddOp4Int(v, OP_NotFound, cursor, label, reg_pk, npk);
 		VdbeCoverageIf(v, opSeek == OP_NotFound);
 	}
 
+	int first_old_reg = 0;
 	/* If there are any triggers to fire, allocate a range of registers to
 	 * use for the old.* references in the triggers.
 	 */
-	if (sqlite3FkRequired(pTab, 0) || pTrigger) {
-		u32 mask;	/* Mask of OLD.* columns in use */
-		int iCol;	/* Iterator used while populating OLD.* */
-		int addrStart;	/* Start of BEFORE trigger programs */
-
-		/* TODO: Could use temporary registers here.
-		 */
-		mask =
-		    sqlite3TriggerColmask(pParse, pTrigger, 0, 0,
-					  TRIGGER_BEFORE | TRIGGER_AFTER, pTab,
+	if (sqlite3FkRequired(table, 0) || trigger_list != NULL) {
+		/* Mask of OLD.* columns in use */
+		/* TODO: Could use temporary registers here. */
+		uint32_t mask =
+		    sqlite3TriggerColmask(parse, trigger_list, 0, 0,
+					  TRIGGER_BEFORE | TRIGGER_AFTER, table,
 					  onconf);
-		mask |= sqlite3FkOldmask(pParse, pTab);
-		iOld = pParse->nMem + 1;
-		pParse->nMem += (1 + pTab->nCol);
+		mask |= sqlite3FkOldmask(parse, table);
+		first_old_reg = parse->nMem + 1;
+		parse->nMem += (1 + table->nCol);
 
-		/* Populate the OLD.* pseudo-table register array. These values will be
-		 * used by any BEFORE and AFTER triggers that exist.
+		/* Populate the OLD.* pseudo-table register array.
+		 * These values will be used by any BEFORE and
+		 * AFTER triggers that exist.
 		 */
-		sqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld);
-		for (iCol = 0; iCol < pTab->nCol; iCol++) {
+		sqlite3VdbeAddOp2(v, OP_Copy, reg_pk, first_old_reg);
+		for (int i = 0; i < table->nCol; i++) {
 			testcase(mask != 0xffffffff && iCol == 31);
 			testcase(mask != 0xffffffff && iCol == 32);
 			if (mask == 0xffffffff
-			    || (iCol <= 31 && (mask & MASKBIT32(iCol)) != 0)) {
-				sqlite3ExprCodeGetColumnOfTable(v, pTab,
-								iDataCur, iCol,
-								iOld + iCol +
-								1);
+			    || (i <= 31 && (mask & MASKBIT32(i)) != 0)) {
+				sqlite3ExprCodeGetColumnOfTable(v, table,
+								cursor, i,
+								first_old_reg +
+								i + 1);
 			}
 		}
 
 		/* Invoke BEFORE DELETE trigger programs. */
-		addrStart = sqlite3VdbeCurrentAddr(v);
-		sqlite3CodeRowTrigger(pParse, pTrigger,
-				      TK_DELETE, 0, TRIGGER_BEFORE, pTab, iOld,
-				      onconf, iLabel);
-
-		/* If any BEFORE triggers were coded, then seek the cursor to the
-		 * row to be deleted again. It may be that the BEFORE triggers moved
-		 * the cursor or of already deleted the row that the cursor was
-		 * pointing to.
+		int addr_start = sqlite3VdbeCurrentAddr(v);
+		sqlite3CodeRowTrigger(parse, trigger_list,
+				      TK_DELETE, 0, TRIGGER_BEFORE, table,
+				      first_old_reg, onconf, label);
+
+		/* If any BEFORE triggers were coded, then seek
+		 * the cursor to the row to be deleted again. It
+		 * may be that the BEFORE triggers moved the
+		 * cursor or of already deleted the row that the
+		 * cursor was pointing to.
 		 */
-		if (addrStart < sqlite3VdbeCurrentAddr(v)) {
-			sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, iLabel, iPk,
-					     nPk);
+		if (addr_start < sqlite3VdbeCurrentAddr(v)) {
+			sqlite3VdbeAddOp4Int(v, OP_NotFound, cursor, label,
+					     reg_pk, npk);
 			VdbeCoverageIf(v, opSeek == OP_NotFound);
 		}
 
-		/* Do FK processing. This call checks that any FK constraints that
-		 * refer to this table (i.e. constraints attached to other tables)
-		 * are not violated by deleting this row.
+		/* Do FK processing. This call checks that any FK
+		 * constraints that refer to this table (i.e.
+		 * constraints attached to other tables) are not
+		 * violated by deleting this row.
 		 */
-		sqlite3FkCheck(pParse, pTab, iOld, 0, 0);
+		sqlite3FkCheck(parse, table, first_old_reg, 0, NULL);
 	}
 
-	/* Delete the index and table entries. Skip this step if pTab is really
-	 * a view (in which case the only effect of the DELETE statement is to
-	 * fire the INSTEAD OF triggers).
-	 *
-	 * If variable 'count' is non-zero, then this OP_Delete instruction should
-	 * invoke the update-hook. The pre-update-hook, on the other hand should
-	 * be invoked unless table pTab is a system table. The difference is that
-	 * the update-hook is not invoked for rows removed by REPLACE, but the
-	 * pre-update-hook is.
+	/* Delete the index and table entries. Skip this step if
+	 * table is really a view (in which case the only effect
+	 * of the DELETE statement is to fire the INSTEAD OF
+	 * triggers).
 	 */
-	if (pTab->pSelect == 0) {
-		u8 p5 = 0;
-		/* kyukhin: Tarantool handles indices uypdate automatically.  */
-		/* sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek);  */
-		sqlite3VdbeAddOp2(v, OP_Delete, iDataCur,
-				  (count ? OPFLAG_NCHANGE : 0));
-		if (eMode != ONEPASS_OFF) {
+	if (table->pSelect == NULL) {
+		uint8_t p5 = 0;
+		sqlite3VdbeAddOp2(v, OP_Delete, cursor,
+				  (need_update_count ? OPFLAG_NCHANGE : 0));
+		if (mode != ONEPASS_OFF)
 			sqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE);
-		}
-		if (iIdxNoSeek >= 0) {
-			sqlite3VdbeAddOp1(v, OP_Delete, iIdxNoSeek);
-		}
-		if (eMode == ONEPASS_MULTI)
+
+		if (idx_noseek >= 0)
+			sqlite3VdbeAddOp1(v, OP_Delete, idx_noseek);
+
+		if (mode == ONEPASS_MULTI)
 			p5 |= OPFLAG_SAVEPOSITION;
 		sqlite3VdbeChangeP5(v, p5);
 	}
 
-	/* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
-	 * handle rows (possibly in other tables) that refer via a foreign key
-	 * to the row just deleted.
+	/* Do any ON CASCADE, SET NULL or SET DEFAULT operations
+	 * required to handle rows (possibly in other tables) that
+	 * refer via a foreign key to the row just deleted.
 	 */
-	sqlite3FkActions(pParse, pTab, 0, iOld, 0);
+	sqlite3FkActions(parse, table, 0, first_old_reg, 0);
 
 	/* Invoke AFTER DELETE trigger programs. */
-	sqlite3CodeRowTrigger(pParse, pTrigger,
-			      TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf,
-			      iLabel);
+	sqlite3CodeRowTrigger(parse, trigger_list,
+			      TK_DELETE, 0, TRIGGER_AFTER, table, first_old_reg,
+			      onconf, label);
 
-	/* Jump here if the row had already been deleted before any BEFORE
-	 * trigger programs were invoked. Or if a trigger program throws a
-	 * RAISE(IGNORE) exception.
+	/* Jump here if the row had already been deleted before
+	 * any BEFORE trigger programs were invoked. Or if a trigger program
+	 * throws a RAISE(IGNORE) exception.
 	 */
-	sqlite3VdbeResolveLabel(v, iLabel);
+	sqlite3VdbeResolveLabel(v, label);
 	VdbeModuleComment((v, "END: GenRowDel()"));
 }
 
-/*
- * This routine generates VDBE code that causes the deletion of all
- * index entries associated with a single row of a single table, pTab
- *
- * Preconditions:
- *
- *   1.  A read/write cursor "iDataCur" must be open on the canonical storage
- *       btree for the table pTab.  (This will be primary key index)
- *
- *   2.  Read/write cursor for primary index of pTab must be open as
- *       cursor number iIdxCur.  (The pTab->pIndex index is the 0-th index.)
- *
- *   3.  The "iDataCur" cursor must be already positioned on the row
- *       that is to be deleted.
- */
-void
-sqlite3GenerateRowIndexDelete(Parse * pParse,	/* Parsing and code generating context */
-			      Table * pTab,	/* Table containing the row to be deleted */
-			      int iDataCur,	/* Cursor of table holding data. */
-			      int iIdxCur)	/* Primary index cursor */
-{
-	int r1 = -1;		/* Register holding an index key */
-	int iPartIdxLabel;	/* Jump destination for skipping partial index entries */
-	Vdbe *v;		/* The prepared statement under construction */
-	Index *pPk;		/* PRIMARY KEY index */
-
-	v = pParse->pVdbe;
-	pPk = sqlite3PrimaryKeyIndex(pTab);
-	/* In Tarantool it is enough to delete row just from pk */
-	VdbeModuleComment((v, "GenRowIdxDel for %s", pPk->zName));
-	r1 = sqlite3GenerateIndexKey(pParse, pPk, iDataCur, 0, &iPartIdxLabel,
-				     NULL, r1);
-	sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur, r1, index_column_count(pPk));
-	sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
-}
-
-/*
- * Generate code that will assemble an index key and stores it in register
- * regOut.  The key with be for index pIdx which is an index on pTab.
- * iCur is the index of a cursor open on the pTab table and pointing to
- * the entry that needs indexing.
- * iCur must be the cursor of the PRIMARY KEY index.
- *
- * Return a register number which is the first in a block of
- * registers that holds the elements of the index key.  The
- * block of registers has already been deallocated by the time
- * this routine returns.
- *
- * If *piPartIdxLabel is not NULL, fill it in with a label and jump
- * to that label if pIdx is a partial index that should be skipped.
- * The label should be resolved using sqlite3ResolvePartIdxLabel().
- * A partial index should be skipped if its WHERE clause evaluates
- * to false or null.  If pIdx is not a partial index, *piPartIdxLabel
- * will be set to zero which is an empty label that is ignored by
- * sqlite3ResolvePartIdxLabel().
- *
- * The pPrior and regPrior parameters are used to implement a cache to
- * avoid unnecessary register loads.  If pPrior is not NULL, then it is
- * a pointer to a different index for which an index key has just been
- * computed into register regPrior.  If the current pIdx index is generating
- * its key into the same sequence of registers and if pPrior and pIdx share
- * a column in common, then the register corresponding to that column already
- * holds the correct value and the loading of that register is skipped.
- * This optimization is helpful when doing a DELETE or an INTEGRITY_CHECK
- * on a table with multiple indices, and especially with
- * the PRIMARY KEY columns of the index.
- */
 int
-sqlite3GenerateIndexKey(Parse * pParse,	/* Parsing context */
-			Index * pIdx,	/* The index for which to generate a key */
-			int iDataCur,	/* Cursor number from which to take column data */
-			int regOut,	/* Put the new key into this register if not 0 */
-			int *piPartIdxLabel,	/* OUT: Jump to this label to skip partial index */
-			Index * pPrior,	/* Previously generated index key */
-			int regPrior)	/* Register holding previous generated key */
+sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
+		       int reg_out, int *part_idx_label, struct Index *prev,
+		       int reg_prev)
 {
-	Vdbe *v = pParse->pVdbe;
-	int j;
-	int regBase;
-	int nCol;
-
-	if (piPartIdxLabel) {
-		if (pIdx->pPartIdxWhere) {
-			*piPartIdxLabel = sqlite3VdbeMakeLabel(v);
-			pParse->iSelfTab = iDataCur;
-			sqlite3ExprCachePush(pParse);
-			sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere,
-					      *piPartIdxLabel,
+	struct Vdbe *v = parse->pVdbe;
+
+	if (part_idx_label != NULL) {
+		if (index->pPartIdxWhere != NULL) {
+			*part_idx_label = sqlite3VdbeMakeLabel(v);
+			parse->iSelfTab = cursor;
+			sqlite3ExprCachePush(parse);
+			sqlite3ExprIfFalseDup(parse, index->pPartIdxWhere,
+					      *part_idx_label,
 					      SQLITE_JUMPIFNULL);
 		} else {
-			*piPartIdxLabel = 0;
+			*part_idx_label = 0;
 		}
 	}
-	nCol = index_column_count(pIdx);
-	regBase = sqlite3GetTempRange(pParse, nCol);
-	if (pPrior && (regBase != regPrior || pPrior->pPartIdxWhere))
-		pPrior = 0;
-	for (j = 0; j < nCol; j++) {
-		if (pPrior && pPrior->aiColumn[j] == pIdx->aiColumn[j]
-		    && pPrior->aiColumn[j] != XN_EXPR) {
-			/* This column was already computed by the previous index */
+	int col_cnt = index_column_count(index);
+	int reg_base = sqlite3GetTempRange(parse, col_cnt);
+	if (prev != NULL && (reg_base != reg_prev ||
+			     prev->pPartIdxWhere != NULL))
+		prev = NULL;
+	for (int j = 0; j < col_cnt; j++) {
+		if (prev != NULL && prev->aiColumn[j] == index->aiColumn[j]
+		    && prev->aiColumn[j] != XN_EXPR) {
+			/*
+			 * This column was already computed by the
+			 * previous index.
+			 */
 			continue;
 		}
-		sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j,
-					       regBase + j);
-		/* If the column affinity is REAL but the number is an integer, then it
-		 * might be stored in the table as an integer (using a compact
-		 * representation) then converted to REAL by an OP_RealAffinity opcode.
-		 * But we are getting ready to store this value back into an index, where
-		 * it should be converted by to INTEGER again.  So omit the OP_RealAffinity
-		 * opcode if it is present
+		sqlite3ExprCodeLoadIndexColumn(parse, index, cursor, j,
+					       reg_base + j);
+		/*
+		 * If the column affinity is REAL but the number
+		 * is an integer, then it might be stored in the
+		 * table as an integer (using a compact
+		 * representation) then converted to REAL by an
+		 * OP_RealAffinity opcode. But we are getting
+		 * ready to store this value back into an index,
+		 * where it should be converted by to INTEGER
+		 * again.  So omit the OP_RealAffinity opcode if
+		 * it is present
 		 */
 		sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity);
 	}
-	if (regOut) {
-		sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut);
-	}
-	sqlite3ReleaseTempRange(pParse, regBase, nCol);
-	return regBase;
+	if (reg_out != 0)
+		sqlite3VdbeAddOp3(v, OP_MakeRecord, reg_base, col_cnt, reg_out);
+
+	sqlite3ReleaseTempRange(parse, reg_base, col_cnt);
+	return reg_base;
 }
 
-/*
- * If a prior call to sqlite3GenerateIndexKey() generated a jump-over label
- * because it was a partial index, then this routine should be called to
- * resolve that label.
- */
 void
-sqlite3ResolvePartIdxLabel(Parse * pParse, int iLabel)
+sql_resolve_part_idx_label(struct Parse *parse, int label)
 {
-	if (iLabel) {
-		sqlite3VdbeResolveLabel(pParse->pVdbe, iLabel);
-		sqlite3ExprCachePop(pParse);
+	if (label != 0) {
+		sqlite3VdbeResolveLabel(parse->pVdbe, label);
+		sqlite3ExprCachePop(parse);
 	}
 }
diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
index 60b4786..a21dd2d 100644
--- a/src/box/sql/fkey.c
+++ b/src/box/sql/fkey.c
@@ -784,7 +784,8 @@ sqlite3FkDropTable(Parse * pParse, SrcList * pName, Table * pTab)
 		pParse->disableTriggers = 1;
 		/* Staring new transaction before DELETE FROM <tbl> */
 		sqlite3VdbeAddOp0(v, OP_TTransaction);
-		sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0);
+		sql_table_delete_from(pParse, sqlite3SrcListDup(db, pName, 0),
+				      NULL);
 		pParse->disableTriggers = 0;
 
 		/* If the DELETE has generated immediate foreign key constraint
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 54a7e4a..97d18ae 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -379,7 +379,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 	zTab = pTabList->a[0].zName;
 	if (NEVER(zTab == 0))
 		goto insert_cleanup;
-	pTab = sqlite3SrcListLookup(pParse, pTabList);
+	pTab = sql_list_lookup_table(pParse, pTabList);
 	if (pTab == 0) {
 		goto insert_cleanup;
 	}
@@ -406,13 +406,13 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 	/* If pTab is really a view, make sure it has been initialized.
 	 * ViewGetColumnNames() is a no-op if pTab is not a view.
 	 */
-	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
+	if (sqlite3ViewGetColumnNames(pParse, pTab))
 		goto insert_cleanup;
-	}
 
-	/* Cannot insert into a read-only table.
-	 */
-	if (sqlite3IsReadOnly(pParse, pTab, tmask)) {
+	/* Cannot insert into a read-only table. */
+	if (isView && tmask == 0) {
+		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
+				pTab->zName);
 		goto insert_cleanup;
 	}
 
@@ -1510,13 +1510,13 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
 								 TK_DELETE, 0,
 								 0);
 				}
-				sqlite3GenerateRowDelete(pParse, pTab, pTrigger,
-							 iDataCur, iIdxCur,
-							 regR, nPkField, 0,
-							 ON_CONFLICT_ACTION_REPLACE,
-							 (pIdx ==
-							  pPk ? ONEPASS_SINGLE :
-							  ONEPASS_OFF), -1);
+				sql_generate_row_delete(pParse, pTab, pTrigger,
+							iDataCur,
+							regR, nPkField, 0,
+							ON_CONFLICT_ACTION_REPLACE,
+							(pIdx ==
+							 pPk ? ONEPASS_SINGLE :
+							 ONEPASS_OFF), -1);
 				seenReplace = 1;
 				break;
 			}
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 872647d..259243d 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -719,28 +719,14 @@ limit_opt(A) ::= LIMIT expr(X) COMMA expr(Y).
 
 /////////////////////////// The DELETE statement /////////////////////////////
 //
-%ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
-cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W) 
-        orderby_opt(O) limit_opt(L). {
-  sqlite3WithPush(pParse, C, 1);
-  sqlite3SrcListIndexedBy(pParse, X, &I);
-  W = sqlite3LimitWhere(pParse, X, W, O, L.pLimit, L.pOffset, "DELETE");
-  sqlSubProgramsRemaining = SQL_MAX_COMPILING_TRIGGERS;
-  /* Instruct SQL to initate Tarantool's transaction.  */
-  pParse->initiateTTrans = true;
-  sqlite3DeleteFrom(pParse,X,W);
-}
-%endif
-%ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
 cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W). {
   sqlite3WithPush(pParse, C, 1);
   sqlite3SrcListIndexedBy(pParse, X, &I);
   sqlSubProgramsRemaining = SQL_MAX_COMPILING_TRIGGERS;
   /* Instruct SQL to initate Tarantool's transaction.  */
   pParse->initiateTTrans = true;
-  sqlite3DeleteFrom(pParse,X,W);
+  sql_table_delete_from(pParse,X,W);
 }
-%endif
 
 %type where_opt {Expr*}
 %destructor where_opt {sql_expr_free(pParse->db, $$, false);}
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index e056d63..8c6df2e 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -1994,7 +1994,6 @@ sql_space_tuple_log_count(struct Table *tab);
 /*
  * Allowed values for Table.tabFlags.
  */
-#define TF_Readonly        0x01	/* Read-only system table */
 #define TF_Ephemeral       0x02	/* An ephemeral table */
 #define TF_HasPrimaryKey   0x04	/* Table has a primary key */
 #define TF_Autoincrement   0x08	/* Integer primary key is autoincrement */
@@ -3671,15 +3670,46 @@ int sqlite3Select(Parse *, Select *, SelectDest *);
 Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
 			 Expr *, ExprList *, u32, Expr *, Expr *);
 void sqlite3SelectDelete(sqlite3 *, Select *);
-Table *sqlite3SrcListLookup(Parse *, SrcList *);
-int sqlite3IsReadOnly(Parse *, Table *, int);
+
+/**
+ * While a SrcList can in general represent multiple tables and
+ * subqueries (as in the FROM clause of a SELECT statement) in
+ * this case it contains the name of a single table, as one might
+ * find in an INSERT, DELETE, or UPDATE statement.  Look up that
+ * table in the symbol table and return a pointer.  Set an error
+ * message and return NULL if the table name is not found or if
+ * any other error occurs.
+ *
+ * The following fields are initialized appropriate in src_list:
+ *
+ *    src_list->a[0].pTab       Pointer to the Table object.
+ *    src_list->a[0].pIndex     Pointer to the INDEXED BY index,
+ *                              if there is one.
+ *
+ * @param parse Parsing context.
+ * @param src_list List containing single table element.
+ * @retval Table object if found, NULL oterwise.
+ */
+struct Table *
+sql_list_lookup_table(struct Parse *parse, struct SrcList *src_list);
+
 void sqlite3OpenTable(Parse *, int iCur, Table *, int);
-#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
-Expr *sqlite3LimitWhere(Parse *, SrcList *, Expr *, ExprList *, Expr *, Expr *,
-			char *);
-#endif
-void sqlite3DeleteFrom(Parse *, SrcList *, Expr *);
-void sqlite3DeleteByKey(Parse *, char *, const char **, Expr **, int);
+/**
+ * Generate code for a DELETE FROM statement.
+ *
+ *     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
+ *                 \________/       \________________/
+ *                  tab_list              where
+ *
+ * @param parse Parsing context.
+ * @param tab_list List of single element which table from which
+ * deletetion if performed.
+ * @param where The WHERE clause.  May be NULL.
+ */
+void
+sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
+		      struct Expr *where);
+
 void sqlite3Update(Parse *, SrcList *, ExprList *, Expr *,
 		   enum on_conflict_action);
 WhereInfo *sqlite3WhereBegin(Parse *, SrcList *, Expr *, ExprList *, ExprList *,
@@ -3782,11 +3812,118 @@ int sqlite3ExprContainsSubquery(Expr *);
 int sqlite3ExprIsInteger(Expr *, int *);
 int sqlite3ExprCanBeNull(const Expr *);
 int sqlite3ExprNeedsNoAffinityChange(const Expr *, char);
-void sqlite3GenerateRowDelete(Parse *, Table *, Trigger *, int, int, int, i16,
-			      u8, enum on_conflict_action, u8, int);
-void sqlite3GenerateRowIndexDelete(Parse *, Table *, int, int);
-int sqlite3GenerateIndexKey(Parse *, Index *, int, int, int *, Index *, int);
-void sqlite3ResolvePartIdxLabel(Parse *, int);
+
+/**
+ * This routine generates VDBE code that causes a single row of a
+ * single table to be deleted.  Both the original table entry and
+ * all indices are removed.
+ *
+ * Preconditions:
+ *
+ *   1.  cursor is an open cursor on the btree that is the
+ *       canonical data store for the table.  (This will be the
+ *       PRIMARY KEY index)
+ *
+ *   2.  The primary key for the row to be deleted must be stored
+ *       in a sequence of npk memory cells starting at reg_pk. If
+ *       npk==0 that means that a search record formed from
+ *       OP_MakeRecord is contained in the single memory location
+ *       reg_pk.
+ *
+ * mode:
+ *   Parameter mode may be passed either ONEPASS_OFF (0),
+ *   ONEPASS_SINGLE, or ONEPASS_MULTI.  If mode is not
+ *   ONEPASS_OFF, then the cursor already points to the row to
+ *   delete. If mode is ONEPASS_OFF then this function must seek
+ *   cursor to the entry identified by reg_pk and npk before
+ *   reading from it.
+ *
+ *   If mode is ONEPASS_MULTI, then this call is being made as
+ *   part of a ONEPASS delete that affects multiple rows. In this
+ *   case, if idx_noseek is a valid cursor number (>=0), then its
+ *   position should be preserved following the delete operation.
+ *   Or, if idx_noseek is not a valid cursor number, the position
+ *   of cursor should be preserved instead.
+ *
+ * @param parse Parsing context.
+ * @param table Table containing the row to be deleted.
+ * @param trigger_list List of triggers to (potentially) fire.
+ * @param cursor Cursor from which column data is extracted/
+ * @param reg_pk First memory cell containing the PRIMARY KEY.
+ * @param npk umber of PRIMARY KEY memory cells.
+ * @param need_update_count. If non-zero, increment the row change
+ *        counter.
+ * @param onconf Default ON CONFLICT policy for triggers.
+ * @param mode ONEPASS_OFF, _SINGLE, or _MULTI.  See above.
+ * @param idx_noseek If it is a valid cursor number (>=0),
+ *        then it identifies an index cursor that already points
+ *        to the index entry to be deleted.
+ */
+void
+sql_generate_row_delete(struct Parse *parse, struct Table *table,
+			struct Trigger *trigger_list, int cursor, int reg_pk,
+			short npk, bool need_update_count,
+			enum on_conflict_action onconf, u8 mode,
+			int idx_noseek);
+
+/**
+ * Generate code that will assemble an index key and stores it in
+ * register reg_out.  The key will be for index which is an
+ * index on table. cursor is the index of a cursor open on the
+ * table table and pointing to the entry that needs indexing.
+ * cursor must be the cursor of the PRIMARY KEY index.
+ *
+ * Return a register number which is the first in a block of
+ * registers that holds the elements of the index key.  The
+ * block of registers has already been deallocated by the time
+ * this routine returns.
+ *
+ * If part_idx_label is not NULL, fill it in with a label and
+ * jump to that label if index is a partial index that should be
+ * skipped. The label should be resolved using
+ * sql_resolve_part_idx_label(). A partial index should be skipped
+ * if its WHERE clause evaluates to false or null.  If index is
+ * not a partial index, *part_idx_label will be set to zero which
+ * is an empty label that is ignored by sql_resolve_part_idx_label().
+ *
+ * The prev and reg_prev parameters are used to implement a
+ * cache to avoid unnecessary register loads.  If prev is not
+ * NULL, then it is a pointer to a different index for which an
+ * index key has just been computed into register reg_prev. If the
+ * current index is generating its key into the same
+ * sequence of registers and if prev and index share a column in
+ * common, then the register corresponding to that column already
+ * holds the correct value and the loading of that register is
+ * skipped. This optimization is helpful when doing a DELETE or
+ * an INTEGRITY_CHECK on a table with multiple indices, and
+ * especially with the PRIMARY KEY columns of the index.
+ *
+ * @param parse Parsing context.
+ * @param index The index for which to generate a key.
+ * @param cursor Cursor number from which to take column data.
+ * @param reg_out Put the new key into this register if not NULL.
+ * @param[out] part_idx_label Jump to this label to skip partial
+ *        index.
+ * @param prev Previously generated index key
+ * @param reg_prev Register holding previous generated key.
+ * @retval Register containing new record
+ */
+int
+sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
+		       int reg_out, int *part_idx_label, struct Index *prev,
+		       int reg_prev);
+
+/**
+ * If a prior call to sql_generate_index_key() generated a
+ * jump-over label because it was a partial index, then this
+ * routine should be called to resolve that label.
+ *
+ * @param parse Parsing context.
+ * @param label Label to resolve.
+ */
+void
+sql_resolve_part_idx_label(struct Parse *parse, int label);
+
 void sqlite3GenerateConstraintChecks(Parse *, Table *, int *, int, int, int,
 				     int, u8, struct on_conflict *, int, int *,
 				     int *);
@@ -3831,9 +3968,20 @@ int sqlite3SafetyCheckOk(sqlite3 *);
 int sqlite3SafetyCheckSickOrOk(sqlite3 *);
 void sqlite3ChangeCookie(Parse *);
 
-#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-void sqlite3MaterializeView(Parse *, Table *, Expr *, int);
-#endif
+/**
+ * Evaluate a view and store its result in an ephemeral table.
+ * The where argument is an optional WHERE clause that restricts
+ * the set of rows in the view that are to be added to the
+ * ephemeral table.
+ *
+ * @param parse Parsing context.
+ * @param name View name.
+ * @param where Option WHERE clause to be added.
+ * @param cursor Cursor number for ephemeral table.
+ */
+void
+sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
+		     int cursor);
 
 #ifndef SQLITE_OMIT_TRIGGER
 void sqlite3BeginTrigger(Parse *, Token *, int, int, IdList *, SrcList *,
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index 28c56db..e6d9925 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -111,7 +111,7 @@ sqlite3BeginTrigger(Parse * pParse,	/* The parse context of the CREATE TRIGGER s
 	if (sqlite3FixSrcList(&sFix, pTableName)) {
 		goto trigger_cleanup;
 	}
-	pTab = sqlite3SrcListLookup(pParse, pTableName);
+	pTab = sql_list_lookup_table(pParse, pTableName);
 	if (!pTab) {
 		goto trigger_cleanup;
 	}
@@ -744,11 +744,11 @@ codeTriggerProgram(Parse * pParse,	/* The parser context */
 				break;
 			}
 		case TK_DELETE:{
-				sqlite3DeleteFrom(pParse,
-						  targetSrcList(pParse, pStep),
-						  sqlite3ExprDup(db,
-								 pStep->pWhere,
-								 0)
+				sql_table_delete_from(pParse,
+						      targetSrcList(pParse, pStep),
+						      sqlite3ExprDup(db,
+								     pStep->pWhere,
+								     0)
 				    );
 				break;
 			}
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 204adc2..c521a3b 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -163,7 +163,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 
 	/* Locate the table which we want to update.
 	 */
-	pTab = sqlite3SrcListLookup(pParse, pTabList);
+	pTab = sql_list_lookup_table(pParse, pTabList);
 	if (pTab == 0)
 		goto update_cleanup;
 
@@ -187,7 +187,9 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
 		goto update_cleanup;
 	}
-	if (sqlite3IsReadOnly(pParse, pTab, tmask)) {
+	if (isView && tmask == 0) {
+		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
+				pTab->zName);
 		goto update_cleanup;
 	}
 
@@ -324,7 +326,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	 */
 #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
 	if (isView) {
-		sqlite3MaterializeView(pParse, pTab, pWhere, iDataCur);
+		sql_materialize_view(pParse, pTab->zName, pWhere, iDataCur);
 		/* Number of columns from SELECT plus ID.*/
 		nKey = pTab->nCol + 1;
 	}
@@ -603,7 +605,6 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 						     nKey);
 			VdbeCoverageNeverTaken(v);
 		}
-		sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur);
 
 		/* If changing the PK value, or if there are foreign key constraints
 		 * to process, delete the old record. Otherwise, add a noop OP_Delete
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 1304149..7ac14e2 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1124,7 +1124,7 @@ sqlite3VdbeChangeP4(Vdbe * p, int addr, const char *zP4, int n)
 	} if (n == P4_BOOL) {
 		pOp->p4.b = *(bool*)zP4;
 		pOp->p4type = P4_BOOL;
-	} else if (zP4 != 0) {
+	} else {
 		assert(n < 0);
 		pOp->p4.p = (void *)zP4;
 		pOp->p4type = (signed char)n;
@@ -1561,7 +1561,10 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
 #endif
 	case P4_COLLSEQ:{
 			struct coll *pColl = pOp->p4.pColl;
-			sqlite3XPrintf(&x, "(%.20s)", pColl->name);
+			if (pColl != NULL)
+				sqlite3XPrintf(&x, "(%.20s)", pColl->name);
+			else
+				sqlite3XPrintf(&x, "(binary)");
 			break;
 		}
 	case P4_FUNCDEF:{
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index b497a5b..3519f34 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -840,11 +840,11 @@ constructAutomaticIndex(Parse * pParse,			/* The parsing context */
 	}
 	regRecord = sqlite3GetTempReg(pParse);
 	regBase =
-	    sqlite3GenerateIndexKey(pParse, pIdx, pLevel->iTabCur, regRecord, 0,
-				    0, 0);
+	    sql_generate_index_key(pParse, pIdx, pLevel->iTabCur, regRecord, 0,
+				   0, 0);
 	sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord);
 	if (pPartial)
-		sqlite3VdbeResolveLabel(v, iContinue);
+		sql_resolve_part_idx_label(v, iContinue);
 	if (pTabItem->fg.viaCoroutine) {
 		sqlite3VdbeChangeP2(v, addrCounter, regBase + n);
 		translateColumnToCopy(v, addrTop, pLevel->iTabCur,

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

* [tarantool-patches] Re: [PATCH 1/2] sql: allow key_def to be NULL for ephemeral create
  2018-05-17 16:47     ` Vladislav Shpilevoy
@ 2018-05-18  6:57       ` Kirill Yukhin
  2018-05-18 10:33         ` Vladislav Shpilevoy
  0 siblings, 1 reply; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-18  6:57 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi Vlad!
Thanks for uyour inputs.

My answers inlined, patch in the bottom.

On 17 мая 19:47, Vladislav Shpilevoy wrote:
> Thanks for review fixes! See 4 comments below.
> 
> On 17/05/2018 18:49, Kirill Yukhin wrote:
> > Hi Vlad,
> > 
> > On 16 мая 18:24, Kirill Yukhin wrote:
> > > There're many cases in SQL FE, where exact key def passed to
> > > doesn't ephemeral table matter. It is used to store data only.
> 
> 1. Can't read. Maybe 'where exact key def passed to ephemeral table doesn't matter' ?
> 
> > > Only field count makes sense in such a case. Hoewever it is
> 
> 2. However.
> 
> > > passed separately (in P2). So, allow P4 field to be NULL for
> > > OP_OpenTEphemeral. Update delte from routine from sql/delete.c
> 
> 3. delete.
> 
> > > accordingly.
> > > 
> > > Part of #3235
> > Pasting updated patch from 1st round of review of 1/2 patch.
> > 
> > diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> > index 22130ef..1ad7469 100644
> > --- a/src/box/sql/insert.c
> > +++ b/src/box/sql/insert.c
> > @@ -566,13 +566,8 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
> >   			regRec = sqlite3GetTempReg(pParse);
> >   			regCopy = sqlite3GetTempRange(pParse, nColumn);
> >   			regTempId = sqlite3GetTempReg(pParse);
> > -			struct key_def *def = key_def_new(nColumn + 1);
> > -			if (def == NULL) {
> > -				sqlite3OomFault(db);
> > -				goto insert_cleanup;
> > -			}
> >   			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, srcTab, nColumn+1,
> > -					  0, (char*)def, P4_KEYDEF);
> > +					  0, (char*)NULL, P4_KEYDEF);
> 
> 4. In the OP_OpenTEphemeral code you check for P4_KEYDEF absence, so you can use
> AddOp2 here. And in other places.
Fixed around the code.

--
Regards, Kirill Yukhin

commit 9e5ed7bb5e0d01ff5b8f9d5c6af82e915135fcb0
Author: Kirill Yukhin <kyukhin@tarantool.org>
Date:   Wed May 16 09:47:02 2018 +0300

    sql: allow key_def to be NULL for ephemeral create
    
    There're many cases in SQL FE, where exact key def passed to
    ephemeral table doesn't matter. It is used to store data only.
    Only field count makes sense in such a case. However it is
    passed separately (in P2). So, allow P4 field to be NULL for
    OP_OpenTEphemeral. Update delete from routine from sql/delete.c
    accordingly.
    
    Part of #3235

diff --git a/src/box/sql.c b/src/box/sql.c
index 195fdfb..6d2e0b9 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -370,8 +370,9 @@ int tarantoolSqlite3Count(BtCursor *pCur, i64 *pnEntry)
  *
  * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
  */
-int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
-				    struct key_def *def)
+int
+tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
+				struct key_def *def)
 {
 	assert(pCur);
 	assert(pCur->curFlags & BTCF_TEphemCursor);
@@ -381,7 +382,7 @@ int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
 		return SQL_TARANTOOL_ERROR;
 	for (uint32_t part = 0; part < field_count; ++part) {
 		struct coll *coll;
-		if (part < def->part_count)
+		if (def != NULL && part < def->part_count)
 			coll = def->parts[part].coll;
 		else
 			coll = NULL;
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 3455f52..2c1ce44 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -386,14 +386,9 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
 			iPk = pParse->nMem + 1;
 			pParse->nMem += nPk;
 			iEphCur = pParse->nTab++;
-			struct key_def *def = key_def_new(nPk);
-			if (def == NULL) {
-				sqlite3OomFault(db);
-				goto delete_from_cleanup;
-			}
 			addrEphOpen =
 				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
-						  nPk, 0, (char*)def, P4_KEYDEF);
+						  nPk);
 		} else {
 			pPk = sqlite3PrimaryKeyIndex(pTab);
 			assert(pPk != 0);
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 5ada87e..54a7e4a 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -566,13 +566,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 			regRec = sqlite3GetTempReg(pParse);
 			regCopy = sqlite3GetTempRange(pParse, nColumn);
 			regTempId = sqlite3GetTempReg(pParse);
-			struct key_def *def = key_def_new(nColumn + 1);
-			if (def == NULL) {
-				sqlite3OomFault(db);
-				goto insert_cleanup;
-			}
-			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, srcTab, nColumn+1,
-					  0, (char*)def, P4_KEYDEF);
+			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, srcTab, nColumn+1);
 			/* Create counter for rowid */
 			sqlite3VdbeAddOp4Dup8(v, OP_Int64,
 					      0 /* unused */,
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index bd895bc..de111d3 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -2215,13 +2215,7 @@ generateWithRecursiveQuery(Parse * pParse,	/* Parsing context */
 		VdbeComment((v, "Orderby table"));
 		destQueue.pOrderBy = pOrderBy;
 	} else {
-		struct key_def *def = key_def_new(nCol + 1);
-		if (def == NULL) {
-			sqlite3OomFault(pParse->db);
-			goto end_of_recursive_query;
-		}
-		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iQueue, nCol + 1, 0,
-				  (char*)def, P4_KEYDEF);
+		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iQueue, nCol + 1);
 		VdbeComment((v, "Queue table"));
 	}
 	if (iDistinct) {
@@ -2422,13 +2416,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
 	if (dest.eDest == SRT_EphemTab) {
 		assert(p->pEList);
 		int nCols = p->pEList->nExpr;
-		struct key_def *def = key_def_new(nCols + 1);
-		if (def == NULL) {
-			sqlite3OomFault(db);
-			goto multi_select_end;
-		}
-		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, dest.iSDParm, nCols + 1,
-				  0, (char*)def, P4_KEYDEF);
+		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, dest.iSDParm, nCols + 1);
 		VdbeComment((v, "Destination temp"));
 		dest.eDest = SRT_Table;
 	}
@@ -5608,11 +5596,8 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	/* If the output is destined for a temporary table, open that table.
 	 */
 	if (pDest->eDest == SRT_EphemTab) {
-		struct key_def *def = key_def_new(pEList->nExpr + 1);
-		if (def == NULL)
-			sqlite3OomFault(db);
-		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, pDest->iSDParm,
-				  pEList->nExpr + 1, 0, (char*)def, P4_KEYDEF);
+		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, pDest->iSDParm,
+				  pEList->nExpr + 1);
 
 		VdbeComment((v, "Output table"));
 	}
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index f3db92c..204adc2 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -358,13 +358,8 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
 
 	if (isView) {
-		struct key_def *def = key_def_new(nKey);
-		if (def == NULL) {
-			sqlite3OomFault(db);
-			goto update_cleanup;
-		}
-		addrOpen = sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEph,
-					     nKey, 0, (char*)def, P4_KEYDEF);
+		addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph,
+					     nKey);
 	} else {
 		addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph, nPk);
 		sql_vdbe_set_p4_key_def(pParse, pPk);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index e27fe2f..455198d 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -3225,9 +3225,9 @@ open_cursor_set_hints:
 /**
  * Opcode: OpenTEphemeral P1 P2 * P4 *
  * Synopsis:
- * @param P1 index of new cursor to be created
- * @param P2 number of columns in a new table
- * @param P4 key def for new table
+ * @param P1 index of new cursor to be created.
+ * @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.
  */
@@ -3236,21 +3236,20 @@ case OP_OpenTEphemeral: {
 	BtCursor *pBtCur;
 	assert(pOp->p1 >= 0);
 	assert(pOp->p2 > 0);
-	assert(pOp->p4.key_def != NULL);
-	assert(pOp->p4type == P4_KEYDEF);
+	assert(pOp->p4type != P4_KEYDEF || pOp->p4.key_def != NULL);
 
 	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
 	if (pCx == 0) goto no_mem;
 	pCx->nullRow = 1;
 
-	pCx->key_def  = pOp->p4.key_def;
 	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,
-					     pCx->key_def);
+					     pOp->p4.key_def);
+	pCx->key_def = pCx->uc.pCursor->index->def->key_def;
 	if (rc) goto abort_due_to_error;
 	break;
 }

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

* [tarantool-patches] Re: [PATCH 2/2] sql: refactor delete routines
  2018-05-18  6:56           ` Kirill Yukhin
@ 2018-05-18 10:33             ` Vladislav Shpilevoy
  0 siblings, 0 replies; 17+ messages in thread
From: Vladislav Shpilevoy @ 2018-05-18 10:33 UTC (permalink / raw)
  To: Kirill Yukhin; +Cc: tarantool-patches

Hello. Thanks for fixes! I pushed commit with more minor fixes on the branch.
You can squash it, if you want. The patch is LGTM, even if you decide
to do not squash.

On 18/05/2018 09:56, Kirill Yukhin wrote:
> Hi Vlad,
> Thanks a lot for your inputs.
> My answers inlined, updated patch in the bottom.
> 
> On 17 мая 19:47, Vladislav Shpilevoy wrote:
>> Thanks for review fixes! Almost done! See 13 comments below.
>>
>>>> On 16/05/2018 19:29, Kirill Yukhin wrote:
>>>>> Hi Vlad,
>>>>> On 16 мая 18:24, Kirill Yukhin wrote:
>>>>> --- a/src/box/sql/delete.c
>>>>> +++ b/src/box/sql/delete.c
>>>>> -Table *
>>>>> -sqlite3SrcListLookup(Parse * pParse, SrcList * pSrc)
>>>>> +struct Table *
>>>>> +sql_list_lookup_table(struct Parse *parse, SrcList *src_list)
>>>>>     {
>>>>
>>>>> +	struct SrcList_item *item = src_list->a;
>>>>> +	struct Table *table;
>>>>> +	assert(item != NULL && src_list->nSrc == 1);
>>>>> +	table = sqlite3LocateTable(parse, 0, item->zName);
>>>>> +	sqlite3DeleteTable(parse->db, item->pTab);
>>>>> +	item->pTab = table;
>>>>> +	if (table != NULL)
>>>>> +		table->nTabRef++;
>>>>> +	if (sqlite3IndexedByLookup(parse, item))
>>>>> +		table = NULL;
>>>>
>>>> 1. What about --nTabRef; ? sqlite3IndexedByLookup does not unref
>>>> table. Looks like original SQLite bug.
>>> I'll submit a question to SQLite author.
>>
>> 1. Obviously, it is a bug. The code is very simple, and it is easy to see,
>> that table is never unreferenced on the error. Lets fix this in Tarantool.
>> Will Hipp fix this or not in vanila SQLite after you write him - it will be
>> his problem.
> Looks like this is not. nTabRef++ correspond not to table, which ultimately unused.
> It rather correspond to item->pTab, which is not nullified and used around. I've
> updated ref count increment to reflect the fact.
> 
>>>>> +sql_delete_by_where(struct Parse *parse, char *t_name, const char **columns,
>>>>> +		    struct Expr **values, int pairs_count)
>>>>>     {
>>>>> -	Expr *where = NULL;
>>>>> -	SrcList *src;
>>>>> -
>>>>> -		assert(nPairs > 0);
>>>>> -	if (pParse->nErr > 0 || pParse->db->mallocFailed)
>>>>> +	if (parse->nErr > 0 || parse->db->mallocFailed)
>>>>>     		goto error;
>>>>> -	src = sql_alloc_src_list(pParse->db);
>>>>> -	src->a[0].zName = sqlite3DbStrDup(pParse->db, zTab);
>>>>> +	struct SrcList *src = sql_alloc_src_list(parse->db);
>>>>> +	src->a[0].zName = sqlite3DbStrDup(parse->db, t_name);
>>>>>     	if (src == NULL)
>>>> 2. Maybe first check src on NULL and then use it? Looks like the second SQLite bug.
>>> Fixed.
>>
>> 2. Now I see, that sql_delete_by_where is never called.
> Removed.
> 
>>>>> +sql_generate_row_delete(struct Parse *parse, struct Table *table,
>>>>> +			struct Trigger *trigger_list, int cursor, int reg_pk,
>>>>
>>>> 3. I think, it is time to invent a standard name for cursors: I see, that both 'cursor' and 'reg_pk'
>>>> are cursors, but their names differs very. My proposal: each cursor variable must end with '_cursor'.
>>>> So data_cursor and pk_cursor instead of cursor and reg_pk.
>>> Well, actualy no. Cursor is a cursor, this is immediate value in VDBE. Register (reg_pk) is a memory
>>> cell. They're not the same thing for sure.
>>>
>>>>> +			short npk, bool is_count,
>>
>> 3. Oh, now I see. I was confused by names mismatch in the function declaration and implementation.
>> In the implementation you use 'reg_pk' name, but in the declaration you use 'ipk'. Please, fix
>> this too.
> Fixed.
> 
>>>>> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
>>>>> --- a/src/box/sql/sqliteInt.h
>>>>> +++ b/src/box/sql/sqliteInt.h
>>>>> @@ -3671,15 +3670,69 @@ int sqlite3Select(Parse *, Select *, SelectDest *);
>>>>>     Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
>>>>>     			 Expr *, ExprList *, u32, Expr *, Expr *);
>>>>>     void sqlite3SelectDelete(sqlite3 *, Select *);
>>>>> -Table *sqlite3SrcListLookup(Parse *, SrcList *);
>>>>> +
>>>>> +/**
>>>>> + * While a SrcList can in general represent multiple tables and
>>>>> + * subqueries (as in the FROM clause of a SELECT statement) in
>>>>> + * this case it contains the name of a single table, as one might
>>>>> + * find in an INSERT, DELETE, or UPDATE statement.  Look up that
>>>>> + * table in the symbol table and return a pointer.  Set an error
>>>>> + * message and return NULL if the table name is not found or if
>>>>> + * any other error occurs.
>>>>> + *
>>>>> + * The following fields are initialized appropriate in src_list:
>>>>> + *
>>>>> + *    pSrc->a[0].pTab       Pointer to the Table object.
>>>> 5. No pSrc anymore.
>>> Fixed.
>>>>> + *    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if
>> 4. Now it is out of 66 symbols. Sorry, it is not my rules(
> Fixed.
> 
>>> @@ -3782,11 +3834,118 @@ int sqlite3ExprContainsSubquery(Expr *);
>>> + * @param parse Parsing context.
>>> + * @param table Table containing the row to be deleted.
>>> + * @param trigger_list List of triggers to (potentially) fire.
>>> + * @param cursor Cursor from which column data is extracted/
>>> + * @param ipk First memory cell containing the PRIMARY KEY.
>>> + * @param npk umber of PRIMARY KEY memory cells.
>>> + * @param need_update_count. If non-zero, increment the row change
>>
>> 5. Non-zero -> true. Now it is boolean variable.
> Fixed.
> 
>>> + *        counter.
>>> + * @param onconf Default ON CONFLICT policy for triggers.
>>> + * @param mode ONEPASS_OFF, _SINGLE, or _MULTI.  See above.
>>
>> 6. 'mode' in @param, but 'eMode' in arguments.
> Fixed.
> 
>>> + * @param idx_noseek If it is a valid cursor number (>=0),
>>> + *        then it identifies an index cursor that already points
>>> + *        to the index entry to be deleted.
>>> + */
>>> +void
>>> +sql_generate_row_delete(struct Parse *parse, struct Table *table,
>>> +			struct Trigger *trigger_list, int cursor, int ipk,
>>> +			short npk, bool need_update_count,
>>> +			enum on_conflict_action onconf, u8 eMode,
>>> +			int idx_noseek);
>>> +
>>> +/**
>>> + * Generate code that will assemble an index key and stores it in
>>> + * register reg_out.  The key with be for index pIdx which is an
>> 7. No pIdx. 'The key will be' maybe?
> Fixed.
> 
>>> + * index on table. cursor is the index of a cursor open on the
>>> + * table table and pointing to the entry that needs indexing.
>>> + * cursor must be the cursor of the PRIMARY KEY index.
>>> + *
>>> + * Return a register number which is the first in a block of
>>> + * registers that holds the elements of the index key.  The
>>> + * block of registers has already been deallocated by the time
>>> + * this routine returns.
>>> + *
>>> + * If *part_idx_label is not NULL, fill it in with a label and
>> 8. *part_idx_label can not be NULL - it is integer. Maybe part_idx_label
>> with no '*'?
> Fixed.
> 
>>> + * jump to that label if pIdx is a partial index that should be
>> 9. No pIdx.
> Fixed.
> 
>>> + * skipped. The label should be resolved using
>>> + * sql_resolve_part_idx_label(). A partial index should be skipped
>>> + * if its WHERE clause evaluates to false or null.  If index is
>>> + * not a partial index, *piPartIdxLabel will be set to zero which
>> 10. No piPartIdxLabel.
> Fixed.
> 
>>> + * is an empty label that is ignored by sql_resolve_part_idx_label().
>>> + *
>>> + * The pPrior and regPrior parameters are used to implement a
>> 11. No pPrior and regPrior.
> Fixed.
> 
>>> + * cache to avoid unnecessary register loads.  If prev is not
>>> + * NULL, then it is a pointer to a different index for which an
>>> + * index key has just been computed into register reg_prev. If the
>>> + * current pIdx index is generating its key into the same
>> 12. No pIdx.
> Fixed.
> 
>>> + * sequence of registers and if prev and index share a column in
>>> + * common, then the register corresponding to that column already
>>> + * holds the correct value and the loading of that register is
>>> + * skipped. This optimization is helpful when doing a DELETE or
>>> + * an INTEGRITY_CHECK on a table with multiple indices, and
>>> + * especially with the PRIMARY KEY columns of the index.
>>> + *
>>> + * @param parse Parsing context.
>>> + * @param index The index for which to generate a key.
>> 13. Index is a key?
> This is the index for which key is being generated.
> 
> --
> Regards, Kirill Yukhin
> 
> commit f4b0303612c87e2202293d0b1118b35e2f0d2ad6
> Author: Kirill Yukhin <kyukhin@tarantool.org>
> Date:   Tue May 15 22:09:36 2018 +0300
> 
>      sql: refactor SQL delete routines
>      
>      Refactor DELETE FROM statements translation, update
>      all live routines according to Tarantool's coding style.
>      Use key_def instead of Table* where possible.
>      Remove useless index delete routine as well.
>      
>      Part of #3235
> 
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index d39f110..9a96553 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -2664,10 +2664,10 @@ sqlite3RefillIndex(Parse * pParse, Index * pIndex, int memRootPage)
>   	VdbeCoverage(v);
>   	regRecord = sqlite3GetTempReg(pParse);
>   
> -	sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord,
> -				&iPartIdxLabel, 0, 0);
> +	sql_generate_index_key(pParse, pIndex, iTab, regRecord,
> +			       &iPartIdxLabel, 0, 0);
>   	sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord);
> -	sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
> +	sql_resolve_part_idx_label(pParse, iPartIdxLabel);
>   	sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1 + 1);
>   	VdbeCoverage(v);
>   	sqlite3VdbeJumpHere(v, addr1);
> diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
> index 2c1ce44..3056a2c 100644
> --- a/src/box/sql/delete.c
> +++ b/src/box/sql/delete.c
> @@ -29,929 +29,538 @@
>    * SUCH DAMAGE.
>    */
>   
> -/*
> - * This file contains C code routines that are called by the parser
> - * in order to generate code for DELETE FROM statements.
> - */
> -#include "sqliteInt.h"
>   #include "box/session.h"
> +#include "box/schema.h"
> +#include "sqliteInt.h"
> +#include "tarantoolInt.h"
>   
> -/*
> - * While a SrcList can in general represent multiple tables and subqueries
> - * (as in the FROM clause of a SELECT statement) in this case it contains
> - * the name of a single table, as one might find in an INSERT, DELETE,
> - * or UPDATE statement.  Look up that table in the symbol table and
> - * return a pointer.  Set an error message and return NULL if the table
> - * name is not found or if any other error occurs.
> - *
> - * The following fields are initialized appropriate in pSrc:
> - *
> - *    pSrc->a[0].pTab       Pointer to the Table object
> - *    pSrc->a[0].pIndex     Pointer to the INDEXED BY index, if there is one
> - *
> - */
> -Table *
> -sqlite3SrcListLookup(Parse * pParse, SrcList * pSrc)
> -{
> -	struct SrcList_item *pItem = pSrc->a;
> -	Table *pTab;
> -	assert(pItem && pSrc->nSrc == 1);
> -	pTab = sqlite3LocateTable(pParse, 0, pItem->zName);
> -	sqlite3DeleteTable(pParse->db, pItem->pTab);
> -	pItem->pTab = pTab;
> -	if (pTab != NULL)
> -		pTab->nTabRef++;
> -	if (sqlite3IndexedByLookup(pParse, pItem))
> -		pTab = NULL;
> -	return pTab;
> -}
> -
> -/*
> - * Check to make sure the given table is writable.  If it is not
> - * writable, generate an error message and return 1.  If it is
> - * writable return 0;
> - */
> -int
> -sqlite3IsReadOnly(Parse * pParse, Table * pTab, int viewOk)
> +struct Table *
> +sql_list_lookup_table(struct Parse *parse, SrcList *src_list)
>   {
> -	/*
> -	 * A table is not writable if it is a system table
> -	 * (i.e. _sql_stat1), this call is not part of a
> -	 * nested parse. In either case leave an error message in
> -	 * pParse and return non-zero.
> -	 */
> -	if ((pTab->tabFlags & TF_Readonly) != 0 && pParse->nested == 0) {
> -		sqlite3ErrorMsg(pParse, "table %s may not be modified",
> -				pTab->zName);
> -		return 1;
> -	}
> -#ifndef SQLITE_OMIT_VIEW
> -	if (!viewOk && space_is_view(pTab)) {
> -		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
> -				pTab->zName);
> -		return 1;
> -	}
> -#endif
> -	return 0;
> +	struct SrcList_item *item = src_list->a;
> +	assert(item != NULL && src_list->nSrc == 1);
> +	struct Table *table = sqlite3LocateTable(parse, 0, item->zName);
> +	sqlite3DeleteTable(parse->db, item->pTab);
> +	item->pTab = table;
> +	if (table != NULL)
> +		item->pTab->nTabRef++;
> +	if (sqlite3IndexedByLookup(parse, item))
> +		table = NULL;
> +	return table;
>   }
>   
> -#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
> -/*
> - * Evaluate a view and store its result in an ephemeral table.  The
> - * pWhere argument is an optional WHERE clause that restricts the
> - * set of rows in the view that are to be added to the ephemeral table.
> - */
>   void
> -sqlite3MaterializeView(Parse * pParse,	/* Parsing context */
> -		       Table * pView,	/* View definition */
> -		       Expr * pWhere,	/* Optional WHERE clause to be added */
> -		       int iCur)	/* Cursor number for ephemeral table */
> -{
> -	SelectDest dest;
> -	Select *pSel;
> -	SrcList *pFrom;
> -	sqlite3 *db = pParse->db;
> -	pWhere = sqlite3ExprDup(db, pWhere, 0);
> -	pFrom = sqlite3SrcListAppend(db, 0, 0);
> -	if (pFrom) {
> -		assert(pFrom->nSrc == 1);
> -		pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName);
> -		assert(pFrom->a[0].pOn == 0);
> -		assert(pFrom->a[0].pUsing == 0);
> -	}
> -	pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0,
> -				0, 0, 0);
> -	sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur);
> -	sqlite3Select(pParse, pSel, &dest);
> -	sqlite3SelectDelete(db, pSel);
> -}
> -#endif				/* !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */
> -
> -#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
> -/*
> - * Generate an expression tree to implement the WHERE, ORDER BY,
> - * and LIMIT/OFFSET portion of DELETE and UPDATE statements.
> - *
> - *     DELETE FROM table_wxyz WHERE a<5 ORDER BY a LIMIT 1;
> - *                            \__________________________/
> - *                               pLimitWhere (pInClause)
> - */
> -Expr *
> -sqlite3LimitWhere(Parse * pParse,	/* The parser context */
> -		  SrcList * pSrc,	/* the FROM clause -- which tables to scan */
> -		  Expr * pWhere,	/* The WHERE clause.  May be null */
> -		  ExprList * pOrderBy,	/* The ORDER BY clause.  May be null */
> -		  Expr * pLimit,	/* The LIMIT clause.  May be null */
> -		  Expr * pOffset,	/* The OFFSET clause.  May be null */
> -		  char *zStmtType	/* Either DELETE or UPDATE.  For err msgs. */
> -    )
> +sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
> +		     int cursor)
>   {
> -	Expr *pWhereRowid = NULL;	/* WHERE rowid .. */
> -	Expr *pInClause = NULL;	/* WHERE rowid IN ( select ) */
> -	Expr *pSelectRowid = NULL;	/* SELECT rowid ... */
> -	ExprList *pEList = NULL;	/* Expression list contaning only pSelectRowid */
> -	SrcList *pSelectSrc = NULL;	/* SELECT rowid FROM x ... (dup of pSrc) */
> -	Select *pSelect = NULL;	/* Complete SELECT tree */
> -
> -	/* Check that there isn't an ORDER BY without a LIMIT clause.
> -	 */
> -	if (pOrderBy && (pLimit == 0)) {
> -		sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s",
> -				zStmtType);
> -		goto limit_where_cleanup;
> -	}
> -
> -	/* We only need to generate a select expression if there
> -	 * is a limit/offset term to enforce.
> -	 */
> -	if (pLimit == 0) {
> -		/* if pLimit is null, pOffset will always be null as well. */
> -		assert(pOffset == 0);
> -		return pWhere;
> -	}
> -
> -	/* Generate a select expression tree to enforce the limit/offset
> -	 * term for the DELETE or UPDATE statement.  For example:
> -	 *   DELETE FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
> -	 * becomes:
> -	 *   DELETE FROM table_a WHERE rowid IN (
> -	 *     SELECT rowid FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
> -	 *   );
> -	 */
> -
> -	pSelectRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0);
> -	if (pSelectRowid == 0)
> -		goto limit_where_cleanup;
> -	pEList = sqlite3ExprListAppend(pParse, 0, pSelectRowid);
> -	if (pEList == 0)
> -		goto limit_where_cleanup;
> -
> -	/* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree
> -	 * and the SELECT subtree.
> -	 */
> -	pSelectSrc = sqlite3SrcListDup(pParse->db, pSrc, 0);
> -	if (pSelectSrc == 0) {
> -		sqlite3ExprListDelete(pParse->db, pEList);
> -		goto limit_where_cleanup;
> +	struct sqlite3 *db = parse->db;
> +	where = sqlite3ExprDup(db, where, 0);
> +	struct SrcList *from = sqlite3SrcListAppend(db, NULL, NULL);
> +	if (from != NULL) {
> +		assert(from->nSrc == 1);
> +		from->a[0].zName = sqlite3DbStrDup(db, name);
> +		assert(from->a[0].pOn == NULL);
> +		assert(from->a[0].pUsing == NULL);
>   	}
> -
> -	/* generate the SELECT expression tree. */
> -	pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0, 0,
> -				   pOrderBy, 0, pLimit, pOffset);
> -	if (pSelect == 0)
> -		return 0;
> -
> -	/* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */
> -	pWhereRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0);
> -	pInClause =
> -	    pWhereRowid ? sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0) : 0;
> -	sqlite3PExprAddSelect(pParse, pInClause, pSelect);
> -	return pInClause;
> -
> - limit_where_cleanup:
> -	sql_expr_free(pParse->db, pWhere);
> -	sqlite3ExprListDelete(pParse->db, pOrderBy);
> -	sql_expr_free(pParse->db, pLimit);
> -	sql_expr_free(pParse->db, pOffset);
> -	return 0;
> +	struct Select *select = sqlite3SelectNew(parse, NULL, from, where, NULL,
> +						 NULL, NULL, 0, NULL, NULL);
> +	struct SelectDest dest;
> +	sqlite3SelectDestInit(&dest, SRT_EphemTab, cursor);
> +	sqlite3Select(parse, select, &dest);
> +	sqlite3SelectDelete(db, select);
>   }
> -#endif				/* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) */
>   
> -/*
> - * Generate code for a DELETE FROM statement.
> - *
> - *     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
> - *                 \________/       \________________/
> - *                  pTabList              pWhere
> - */
>   void
> -sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
> -		  SrcList * pTabList,	/* The table from which we should delete things */
> -		  Expr * pWhere)	/* The WHERE clause.  May be null */
> +sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
> +		      struct Expr *where)
>   {
> -	Vdbe *v;		/* The virtual database engine */
> -	Table *pTab;		/* The table from which records will be deleted */
> -	int i;			/* Loop counter */
> -	WhereInfo *pWInfo;	/* Information about the WHERE clause */
> -	Index *pIdx;		/* For looping over indices of the table */
> -	int iTabCur;		/* Cursor number for the table */
> -	int iDataCur = 0;	/* VDBE cursor for the canonical data source */
> -	int iIdxCur = 0;	/* Cursor number of the first index */
> -	int nIdx;		/* Number of indices */
> -	sqlite3 *db;		/* Main database structure */
> -	NameContext sNC;	/* Name context to resolve expressions in */
> -	int memCnt = -1;	/* Memory cell used for change counting */
> -	int eOnePass;		/* ONEPASS_OFF or _SINGLE or _MULTI */
> -	int aiCurOnePass[2];	/* The write cursors opened by WHERE_ONEPASS */
> -	u8 *aToOpen = 0;	/* Open cursor iTabCur+j if aToOpen[j] is true */
> -	Index *pPk = 0;		/* The PRIMARY KEY index on the table */
> -	int iPk = 0;		/* First of nPk registers holding PRIMARY KEY value */
> -	i16 nPk;		/* Number of columns in the PRIMARY KEY */
> -	int iKey;		/* Memory cell holding key of row to be deleted */
> -	i16 nKey;		/* Number of memory cells in the row key */
> -	int iEphCur = 0;	/* Ephemeral table holding all primary key values */
> -	int addrBypass = 0;	/* Address of jump over the delete logic */
> -	int addrLoop = 0;	/* Top of the delete loop */
> -	int addrEphOpen = 0;	/* Instruction to open the Ephemeral table */
> -	int bComplex;		/* True if there are triggers or FKs or
> -				 * subqueries in the WHERE clause
> -				 */
> -	struct session *user_session = current_session();
> +	struct sqlite3 *db = parse->db;
> +	if (parse->nErr || db->mallocFailed)
> +		goto delete_from_cleanup;
>   
> -#ifndef SQLITE_OMIT_TRIGGER
> -	int isView;		/* True if attempting to delete from a view */
> -	Trigger *pTrigger;	/* List of table triggers, if required */
> -#endif
> +	assert(tab_list->nSrc == 1);
>   
> -	db = pParse->db;
> -	if (pParse->nErr || db->mallocFailed) {
> +	/* Locate the table which we want to delete.  This table
> +	 * has to be put in an SrcList structure because some of
> +	 * the subroutines we will be calling are designed to work
> +	 * with multiple tables and expect an SrcList* parameter
> +	 * instead of just a Table* parameter.
> +	 */
> +	struct Table *table = sql_list_lookup_table(parse, tab_list);
> +	if (table == NULL)
>   		goto delete_from_cleanup;
> -	}
> -	assert(pTabList->nSrc == 1);
>   
> -	/* Locate the table which we want to delete.  This table has to be
> -	 * put in an SrcList structure because some of the subroutines we
> -	 * will be calling are designed to work with multiple tables and expect
> -	 * an SrcList* parameter instead of just a Table* parameter.
> +	struct space *space = space_by_id(SQLITE_PAGENO_TO_SPACEID(table->tnum));
> +	assert(space != NULL);
> +	/* Figure out if we have any triggers and if the table
> +	 * being deleted from is a view.
>   	 */
> -	pTab = sqlite3SrcListLookup(pParse, pTabList);
> -	if (pTab == 0)
> -		goto delete_from_cleanup;
> +	struct Trigger *trigger_list = sqlite3TriggersExist(table, TK_DELETE,
> +							    NULL, NULL);
>   
> -	/* Figure out if we have any triggers and if the table being
> -	 * deleted from is a view
> +	bool is_view = space->def->opts.is_view;
> +	/* True if there are triggers or FKs or subqueries in the
> +	 * WHERE clause.
>   	 */
> -#ifndef SQLITE_OMIT_TRIGGER
> -	pTrigger = sqlite3TriggersExist(pTab, TK_DELETE, 0, 0);
> -	isView = pTab->pSelect != 0;
> -	bComplex = pTrigger || sqlite3FkRequired(pTab, 0);
> -#else
> -#define pTrigger 0
> -#define isView 0
> -#endif
> -#ifdef SQLITE_OMIT_VIEW
> -#undef isView
> -#define isView 0
> -#endif
> -
> -	/* If pTab is really a view, make sure it has been initialized.
> +	bool is_complex = (trigger_list != NULL) || sqlite3FkRequired(table, 0);
> +
> +	/* If table is really a view, make sure it has been
> +	 * initialized.
>   	 */
> -	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
> +	if (sqlite3ViewGetColumnNames(parse, table))
>   		goto delete_from_cleanup;
> -	}
>   
> -	if (sqlite3IsReadOnly(pParse, pTab, (pTrigger ? 1 : 0))) {
> +	if (is_view && trigger_list == NULL) {
> +		sqlite3ErrorMsg(parse, "cannot modify %s because it is a view",
> +				space->def->name);
>   		goto delete_from_cleanup;
>   	}
> -	assert(!isView || pTrigger);
>   
>   	/* Assign cursor numbers to the table and all its indices.
>   	 */
> -	assert(pTabList->nSrc == 1);
> -	iTabCur = pTabList->a[0].iCursor = pParse->nTab++;
> -	for (nIdx = 0, pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext, nIdx++) {
> -		pParse->nTab++;
> -	}
> +	int tab_cursor = tab_list->a[0].iCursor = parse->nTab++;
> +	parse->nTab += space->index_count;
>   
> -	/* Begin generating code.
> -	 */
> -	v = sqlite3GetVdbe(pParse);
> -	if (v == 0) {
> +	/* Begin generating code.  */
> +	struct Vdbe *v = sqlite3GetVdbe(parse);
> +	if (v == NULL)
>   		goto delete_from_cleanup;
> -	}
> -	if (pParse->nested == 0)
> +
> +	if (parse->nested == 0)
>   		sqlite3VdbeCountChanges(v);
> -	sql_set_multi_write(pParse, true);
> +	sql_set_multi_write(parse, true);
>   
> -	/* If we are trying to delete from a view, realize that view into
> -	 * an ephemeral table.
> +	/* If we are trying to delete from a view, realize that
> +	 * view into an ephemeral table.
>   	 */
> -#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
> -	if (isView) {
> -		sqlite3MaterializeView(pParse, pTab, pWhere, iTabCur);
> -		iDataCur = iIdxCur = iTabCur;
> +	if (is_view) {
> +		sql_materialize_view(parse, space->def->name, where,
> +				     tab_cursor);
>   	}
> -#endif
>   
> -	/* Resolve the column names in the WHERE clause.
> -	 */
> -	memset(&sNC, 0, sizeof(sNC));
> -	sNC.pParse = pParse;
> -	sNC.pSrcList = pTabList;
> -	if (sqlite3ResolveExprNames(&sNC, pWhere)) {
> +	/* Resolve the column names in the WHERE clause. */
> +	struct NameContext nc;
> +	memset(&nc, 0, sizeof(nc));
> +	nc.pParse = parse;
> +	nc.pSrcList = tab_list;
> +	if (sqlite3ResolveExprNames(&nc, where))
>   		goto delete_from_cleanup;
> -	}
>   
> -	/* Initialize the counter of the number of rows deleted, if
> -	 * we are counting rows.
> +	/* Initialize the counter of the number of rows deleted,
> +	 * if we are counting rows.
>   	 */
> +	int reg_count = -1;
> +	struct session *user_session = current_session();
>   	if (user_session->sql_flags & SQLITE_CountRows) {
> -		memCnt = ++pParse->nMem;
> -		sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt);
> +		reg_count = ++parse->nMem;
> +		sqlite3VdbeAddOp2(v, OP_Integer, 0, reg_count);
>   	}
> -#ifndef SQLITE_OMIT_TRUNCATE_OPTIMIZATION
> -	/* Special case: A DELETE without a WHERE clause deletes everything.
> -	 * It is easier just to erase the whole table. Prior to version 3.6.5,
> -	 * this optimization caused the row change count (the value returned by
> -	 * API function sqlite3_count_changes) to be set incorrectly.
> +	/* Special case: A DELETE without a WHERE clause deletes
> +	 * everything. It is easier just to erase the whole table.
>   	 */
> -	if (pWhere == 0 && !bComplex
> -#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
> -	    && db->xPreUpdateCallback == 0
> -#endif
> -	    ) {
> -		assert(!isView);
> -
> -		sqlite3VdbeAddOp1(v, OP_Clear, pTab->tnum);
> -
> -		/* Do not start Tarantool's transaction in case of truncate optimization.
> -		   This is workaround until system tables cannot be changes inside a
> -		   transaction (_truncate).  */
> -		pParse->initiateTTrans = false;
> -	} else
> -#endif				/* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */
> -	{
> -		u16 wcf =
> -		    WHERE_ONEPASS_DESIRED | WHERE_DUPLICATES_OK |
> -		    WHERE_SEEK_TABLE;
> -		if (sNC.ncFlags & NC_VarSelect)
> -			bComplex = 1;
> -		wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW);
> -		/* Create an ephemeral table used to hold all primary keys for
> -		 * rows to be deleted. Since VIEW is held in ephemeral table,
> -		 * there is no PK for it, so columns should be loaded manually.
> +	if (where == NULL && !is_complex) {
> +		assert(!is_view);
> +
> +		sqlite3VdbeAddOp1(v, OP_Clear, table->tnum);
> +
> +		/* Do not start Tarantool's transaction in case of
> +		 * truncate optimization. This is workaround until
> +		 * system tables cannot be changes inside a
> +		 * transaction (_truncate).
> +		 */
> +		parse->initiateTTrans = false;
> +	} else {
> +		uint16_t wcf = WHERE_ONEPASS_DESIRED | WHERE_DUPLICATES_OK |
> +			WHERE_SEEK_TABLE;
> +		if (nc.ncFlags & NC_VarSelect)
> +			is_complex = true;
> +		wcf |= (is_complex ? 0 : WHERE_ONEPASS_MULTIROW);
> +		/* Create an ephemeral table used to hold all
> +		 * primary keys for rows to be deleted. Since VIEW
> +		 * is held in ephemeral table, there is no PK for
> +		 * it, so columns should be loaded manually.
>   		 */
> -		if (isView) {
> -			nPk = pTab->nCol;
> -			iPk = pParse->nMem + 1;
> -			pParse->nMem += nPk;
> -			iEphCur = pParse->nTab++;
> -			addrEphOpen =
> -				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
> -						  nPk);
> +		struct Index *pk = NULL;
> +		int reg_pk = parse->nMem + 1;
> +		int pk_len;
> +		int eph_cursor = parse->nTab++;
> +		int addr_eph_open = sqlite3VdbeCurrentAddr(v);
> +		if (is_view) {
> +			pk_len = table->nCol;
> +			parse->nMem += pk_len;
> +			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral,
> +					  eph_cursor, pk_len);
>   		} else {
> -			pPk = sqlite3PrimaryKeyIndex(pTab);
> -			assert(pPk != 0);
> -			nPk = index_column_count(pPk);
> -			iPk = pParse->nMem + 1;
> -			pParse->nMem += nPk;
> -			iEphCur = pParse->nTab++;
> -			addrEphOpen =
> -			    sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEphCur,
> -					      nPk);
> -			sql_vdbe_set_p4_key_def(pParse, pPk);
> +			pk = sqlite3PrimaryKeyIndex(table);
> +			assert(pk != NULL);
> +			pk_len = index_column_count(pk);
> +			parse->nMem += pk_len;
> +			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, eph_cursor,
> +					  pk_len);
> +			sql_vdbe_set_p4_key_def(parse, pk);
>   		}
>   
> -		/* Construct a query to find the primary key for every row
> -		 * to be deleted, based on the WHERE clause. Set variable eOnePass
> -		 * to indicate the strategy used to implement this delete:
> +		/* Construct a query to find the primary key for
> +		 * every row to be deleted, based on the WHERE
> +		 * clause. Set variable one_pass to indicate the
> +		 * strategy used to implement this delete:
>   		 *
> -		 *  ONEPASS_OFF:    Two-pass approach - use a FIFO for PK values.
> -		 *  ONEPASS_SINGLE: One-pass approach - at most one row deleted.
> -		 *  ONEPASS_MULTI:  One-pass approach - any number of rows may be deleted.
> +		 * ONEPASS_OFF:    Two-pass approach - use a FIFO
> +		 * for PK values.
> +		 * ONEPASS_SINGLE: One-pass approach - at most one
> +		 * row deleted.
> +		 * ONEPASS_MULTI:  One-pass approach - any number
> +		 * of rows may be deleted.
>   		 */
> -		pWInfo =
> -		    sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf,
> -				      iTabCur + 1);
> -		if (pWInfo == 0)
> +		struct WhereInfo *winfo =
> +		    sqlite3WhereBegin(parse, tab_list, where, NULL, NULL, wcf,
> +				      tab_cursor + 1);
> +		if (winfo == NULL)
>   			goto delete_from_cleanup;
> -		eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
> -		assert(eOnePass != ONEPASS_MULTI);
> -		/* Tarantool workaround: see comment in sqlite3WhereBegin.  */
> -		/* assert( bComplex || eOnePass!=ONEPASS_OFF ); */
> -
> -		/* Keep track of the number of rows to be deleted */
> -		if (user_session->sql_flags & SQLITE_CountRows) {
> -			sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1);
> -		}
> +
> +		/* The write cursors opened by WHERE_ONEPASS */
> +		int one_pass_cur[2];
> +		int one_pass = sqlite3WhereOkOnePass(winfo, one_pass_cur);
> +		assert(one_pass != ONEPASS_MULTI);
> +		/* Tarantool: see comment in
> +		 * sqlite3WhereOkOnePass.
> +		 */
> +		/* assert(is_complex || one_pass != ONEPASS_OFF); */
> +
> +		/* Keep track of the number of rows to be
> +		 * deleted.
> +		 */
> +		if (user_session->sql_flags & SQLITE_CountRows)
> +			sqlite3VdbeAddOp2(v, OP_AddImm, reg_count, 1);
>   
>   		/* Extract the primary key for the current row */
> -		if (!isView) {
> -			for (i = 0; i < nPk; i++) {
> -				assert(pPk->aiColumn[i] >= 0);
> -				sqlite3ExprCodeGetColumnOfTable(v, pTab,
> -								iTabCur,
> -								pPk->
> +		if (!is_view) {
> +			for (int i = 0; i < pk_len; i++) {
> +				assert(pk->aiColumn[i] >= 0);
> +				sqlite3ExprCodeGetColumnOfTable(v, table,
> +								tab_cursor,
> +								pk->
>   								aiColumn[i],
> -								iPk + i);
> +								reg_pk + i);
>   			}
>   		} else {
> -			for (i = 0; i < nPk; i++) {
> -				sqlite3VdbeAddOp3(v, OP_Column, iDataCur,
> -						  i, iPk + i);
> +			for (int i = 0; i < pk_len; i++) {
> +				sqlite3VdbeAddOp3(v, OP_Column, tab_cursor,
> +						  i, reg_pk + i);
>   			}
>   		}
> -		iKey = iPk;
>   
> -		if (eOnePass != ONEPASS_OFF) {
> -			/* For ONEPASS, no need to store the primary-key. There is only
> -			 * one, so just keep it in its register(s) and fall through to the
> -			 * delete code.
> +		int reg_key;
> +		int key_len;
> +		if (one_pass != ONEPASS_OFF) {
> +			/* For ONEPASS, no need to store the
> +			 * primary-key. There is only one, so just
> +			 * keep it in its register(s) and fall
> +			 * through to the delete code.
>   			 */
> -			nKey = nPk;	/* OP_Found will use an unpacked key */
> -			aToOpen = sqlite3DbMallocRawNN(db, nIdx + 2);
> -			if (aToOpen == 0) {
> -				sqlite3WhereEnd(pWInfo);
> -				goto delete_from_cleanup;
> -			}
> -			memset(aToOpen, 1, nIdx + 1);
> -			aToOpen[nIdx + 1] = 0;
> -			if (aiCurOnePass[0] >= 0)
> -				aToOpen[aiCurOnePass[0] - iTabCur] = 0;
> -			if (aiCurOnePass[1] >= 0)
> -				aToOpen[aiCurOnePass[1] - iTabCur] = 0;
> -			if (addrEphOpen)
> -				sqlite3VdbeChangeToNoop(v, addrEphOpen);
> +			reg_key = reg_pk;
> +			/* OP_Found will use an unpacked key */
> +			key_len = pk_len;
> +			sqlite3VdbeChangeToNoop(v, addr_eph_open);
>   		} else {
> -			/* Add the PK key for this row to the temporary table */
> -			iKey = ++pParse->nMem;
> -			nKey = 0;	/* Zero tells OP_Found to use a composite key */
> -			const char *zAff = isView ? 0 :
> -					  sqlite3IndexAffinityStr(pParse->db, pPk);
> -			sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey, zAff, nPk);
> -			/* Set flag to save memory allocating one by malloc. */
> +			/* Add the PK key for this row to the
> +			 * temporary table.
> +			 */
> +			reg_key = ++parse->nMem;
> +			/* Zero tells OP_Found to use a composite
> +			 * key.
> +			 */
> +			key_len = 0;
> +			const char *zAff = is_view ? NULL :
> +					  sqlite3IndexAffinityStr(parse->db, pk);
> +			sqlite3VdbeAddOp4(v, OP_MakeRecord, reg_pk, pk_len,
> +					  reg_key, zAff, pk_len);
> +			/* Set flag to save memory allocating one
> +			 * by malloc.
> +			 */
>   			sqlite3VdbeChangeP5(v, 1);
> -			sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey);
> +			sqlite3VdbeAddOp2(v, OP_IdxInsert, eph_cursor, reg_key);
>   		}
>   
> -		/* If this DELETE cannot use the ONEPASS strategy, this is the
> -		 * end of the WHERE loop
> +		/* If this DELETE cannot use the ONEPASS strategy,
> +		 * this is the end of the WHERE loop.
>   		 */
> -		if (eOnePass != ONEPASS_OFF) {
> -			addrBypass = sqlite3VdbeMakeLabel(v);
> -		} else {
> -			sqlite3WhereEnd(pWInfo);
> -		}
> -
> -		/* Unless this is a view, open cursors for the table we are
> -		 * deleting from and all its indices. If this is a view, then the
> -		 * only effect this statement has is to fire the INSTEAD OF
> +		int addr_bypass = 0;
> +		if (one_pass != ONEPASS_OFF)
> +			addr_bypass = sqlite3VdbeMakeLabel(v);
> +		else
> +			sqlite3WhereEnd(winfo);
> +
> +		/* Unless this is a view, open cursors for the
> +		 * table we are deleting from and all its indices.
> +		 * If this is a view, then the only effect this
> +		 * statement has is to fire the INSTEAD OF
>   		 * triggers.
>   		 */
> -		if (!isView) {
> +		if (!is_view) {
>   			int iAddrOnce = 0;
> -			if (eOnePass == ONEPASS_MULTI) {
> +			if (one_pass == ONEPASS_MULTI) {
>   				iAddrOnce = sqlite3VdbeAddOp0(v, OP_Once);
>   				VdbeCoverage(v);
>   			}
> -			sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite,
> -						   OPFLAG_FORDELETE, iTabCur,
> -						   aToOpen, &iDataCur,
> -						   &iIdxCur,
> -						   ON_CONFLICT_ACTION_NONE, 0);
> -			assert(pPk || iDataCur == iTabCur);
> -			assert(pPk || iIdxCur == iDataCur + 1);
> -			if (eOnePass == ONEPASS_MULTI)
> +
> +			int space_ptr_reg = ++parse->nMem;
> +			sqlite3VdbeAddOp4(v, OP_LoadPtr, 0, space_ptr_reg, 0,
> +					  (void *)space, P4_SPACEPTR);
> +			sqlite3VdbeAddOp3(v, OP_OpenWrite, tab_cursor,
> +					  table->tnum, space_ptr_reg);
> +			sql_vdbe_set_p4_key_def(parse, pk);
> +			VdbeComment((v, "%s", pk->zName));
> +
> +			if (one_pass == ONEPASS_MULTI)
>   				sqlite3VdbeJumpHere(v, iAddrOnce);
>   		}
>   
> -		/* Set up a loop over the primary-keys that were found in the
> -		 * where-clause loop above.
> +		/* Set up a loop over the primary-keys that were
> +		 * found in the where-clause loop above.
>   		 */
> -		if (eOnePass != ONEPASS_OFF) {
> -			assert(nKey == nPk);	/* OP_Found will use an unpacked key */
> -			if (aToOpen[iDataCur - iTabCur]) {
> -				assert(pPk != 0 || pTab->pSelect != 0);
> -				sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur,
> -						     addrBypass, iKey, nKey);
> +		int addr_loop = 0;
> +		if (one_pass != ONEPASS_OFF) {
> +			/* OP_Found will use an unpacked key. */
> +			assert(key_len == pk_len);
> +			assert(pk != NULL || table->pSelect != NULL);
> +			sqlite3VdbeAddOp4Int(v, OP_NotFound, tab_cursor,
> +					     addr_bypass, reg_key, key_len);
>   
> -				VdbeCoverage(v);
> -			}
> +			VdbeCoverage(v);
>   		} else {
> -			addrLoop = sqlite3VdbeAddOp1(v, OP_Rewind, iEphCur);
> +			addr_loop = sqlite3VdbeAddOp1(v, OP_Rewind, eph_cursor);
>   			VdbeCoverage(v);
> -			sqlite3VdbeAddOp2(v, OP_RowData, iEphCur, iKey);
> +			sqlite3VdbeAddOp2(v, OP_RowData, eph_cursor, reg_key);
>   		}
>   
>   		/* Delete the row */
> -		{
> -			int count = (pParse->nested == 0);	/* True to count changes */
> -			int iIdxNoSeek = -1;
> -			if (bComplex == 0 && aiCurOnePass[1] != iDataCur
> -			    /* Tarantool: as far as ONEPASS is disabled, there's no index
> -			       w/o need of seeking.  */
> -			    && eOnePass != ONEPASS_OFF) {
> -				iIdxNoSeek = aiCurOnePass[1];
> -			}
> -			sqlite3GenerateRowDelete(pParse, pTab, pTrigger,
> -						 iDataCur, iIdxCur, iKey, nKey,
> -						 count,
> -						 ON_CONFLICT_ACTION_DEFAULT,
> -						 eOnePass,
> -						 iIdxNoSeek);
> -		}
> +		int idx_noseek = -1;
> +		if (!is_complex && one_pass_cur[1] != tab_cursor
> +		    /* Tarantool: as far as ONEPASS is disabled,
> +		     * there's no index w/o need of seeking.
> +		     */
> +		    && one_pass != ONEPASS_OFF)
> +			idx_noseek = one_pass_cur[1];
> +
> +		sql_generate_row_delete(parse, table, trigger_list, tab_cursor,
> +					reg_key, key_len, parse->nested == 0,
> +					ON_CONFLICT_ACTION_DEFAULT, one_pass,
> +					idx_noseek);
>   
>   		/* End of the loop over all primary-keys. */
> -		if (eOnePass != ONEPASS_OFF) {
> -			sqlite3VdbeResolveLabel(v, addrBypass);
> -			sqlite3WhereEnd(pWInfo);
> +		if (one_pass != ONEPASS_OFF) {
> +			sqlite3VdbeResolveLabel(v, addr_bypass);
> +			sqlite3WhereEnd(winfo);
>   		} else {
> -			sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop + 1);
> +			sqlite3VdbeAddOp2(v, OP_Next, eph_cursor,
> +					  addr_loop + 1);
>   			VdbeCoverage(v);
> -			sqlite3VdbeJumpHere(v, addrLoop);
> +			sqlite3VdbeJumpHere(v, addr_loop);
>   		}
> -	}			/* End non-truncate path */
> +	}
>   
> -	/* Return the number of rows that were deleted. If this routine is
> -	 * generating code because of a call to sqlite3NestedParse(), do not
> -	 * invoke the callback function.
> +	/* Return the number of rows that were deleted. If this
> +	 * routine is generating code because of a call to
> +	 * sqlite3NestedParse(), do not invoke the callback
> +	 * function.
>   	 */
>   	if ((user_session->sql_flags & SQLITE_CountRows) &&
> -	    !pParse->nested && !pParse->pTriggerTab) {
> -		sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1);
> +	    parse->nested == 0 && !parse->pTriggerTab) {
> +		sqlite3VdbeAddOp2(v, OP_ResultRow, reg_count, 1);
>   		sqlite3VdbeSetNumCols(v, 1);
>   		sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted",
>   				      SQLITE_STATIC);
>   	}
>   
>    delete_from_cleanup:
> -	sqlite3SrcListDelete(db, pTabList);
> -	sql_expr_free(db, pWhere, false);
> -	sqlite3DbFree(db, aToOpen);
> -	return;
> -}
> -
> -/* Generate VDBE code for
> - * DELETE FROM <pTab.z> WHERE
> - *        <columns[0]> = <values[0]>
> - *    AND  ...
> - *    AND <columns[nPairs - 1]> = <values[nPairs - 1]>;
> - *
> - * This function does not increment the nested counter and is
> - * faster than nested parsing of the request above.
> - * @param pParse Parser context.
> - * @param pTab Table name.
> - * @param columns Column names array.
> - * @param values Column values array.
> - * @param nPairs Length of @columns and @values.
> - *
> - * In case of error the @values elements are deleted.
> - */
> -void
> -sqlite3DeleteByKey(Parse *pParse, char *zTab, const char **columns,
> -		   Expr **values, int nPairs)
> -{
> -	Expr *where = NULL;
> -	SrcList *src;
> -
> -		assert(nPairs > 0);
> -	if (pParse->nErr > 0 || pParse->db->mallocFailed)
> -		goto error;
> -	src = sql_alloc_src_list(pParse->db);
> -	src->a[0].zName = sqlite3DbStrDup(pParse->db, zTab);
> -	if (src == NULL)
> -		goto error;
> -	/* Dummy init of INDEXED BY clause. */
> -	Token t = { NULL, 0, false };
> -	sqlite3SrcListIndexedBy(pParse, src, &t);
> -
> -	for (int i = 0; i < nPairs; ++i) {
> -		Expr *col_expr = sqlite3Expr(pParse->db, TK_ID, columns[i]);
> -		if (col_expr == NULL || values[i] == NULL)
> -			goto error;
> -		Expr *eq_expr =
> -		    sqlite3PExpr(pParse, TK_EQ, col_expr, values[i]);
> -		/* In case of error the values[i] had been deleted in
> -		 * sqlite3PExpr already. Do not delete it second time in the
> -		 * cycle below.
> -		 */
> -		values[i] = NULL;
> -		if (eq_expr == NULL)
> -			goto error;
> -		if (i == 0) {
> -			where = eq_expr;
> -		} else {
> -			where = sqlite3ExprAnd(pParse->db, where, eq_expr);
> -			if (where == NULL)
> -				goto error;
> -		}
> -	}
> -	/* DeleteFrom frees the src and exprs in case of error. */
> -	sqlite3DeleteFrom(pParse, src, where);
> -	return;
> -
> - error:
> -	sql_expr_free(pParse->db, where, false);
> -	for (int i = 0; i < nPairs; ++i)
> -		sql_expr_free(pParse->db, values[i], false);
> +	sqlite3SrcListDelete(db, tab_list);
> +	sql_expr_free(db, where, false);
>   }
>   
> -/* Make sure "isView" and other macros defined above are undefined. Otherwise
> - * they may interfere with compilation of other functions in this file
> - * (or in another file, if this file becomes part of the amalgamation).
> - */
> -#ifdef isView
> -#undef isView
> -#endif
> -#ifdef pTrigger
> -#undef pTrigger
> -#endif
> -
> -/*
> - * This routine generates VDBE code that causes a single row of a
> - * single table to be deleted.  Both the original table entry and
> - * all indices are removed.
> - *
> - * Preconditions:
> - *
> - *   1.  iDataCur is an open cursor on the btree that is the canonical data
> - *       store for the table.  (This will be the PRIMARY KEY index)
> - *
> - *   2.  Read/write cursors for all indices of pTab must be open as
> - *       cursor number iIdxCur+i for the i-th index.
> - *
> - *   3.  The primary key for the row to be deleted must be stored in a
> - *       sequence of nPk memory cells starting at iPk.  If nPk==0 that means
> - *       that a search record formed from OP_MakeRecord is contained in the
> - *       single memory location iPk.
> - *
> - * eMode:
> - *   Parameter eMode may be passed either ONEPASS_OFF (0), ONEPASS_SINGLE, or
> - *   ONEPASS_MULTI.  If eMode is not ONEPASS_OFF, then the cursor
> - *   iDataCur already points to the row to delete. If eMode is ONEPASS_OFF
> - *   then this function must seek iDataCur to the entry identified by iPk
> - *   and nPk before reading from it.
> - *
> - *   If eMode is ONEPASS_MULTI, then this call is being made as part
> - *   of a ONEPASS delete that affects multiple rows. In this case, if
> - *   iIdxNoSeek is a valid cursor number (>=0), then its position should
> - *   be preserved following the delete operation. Or, if iIdxNoSeek is not
> - *   a valid cursor number, the position of iDataCur should be preserved
> - *   instead.
> - *
> - * iIdxNoSeek:
> - *   If iIdxNoSeek is a valid cursor number (>=0), then it identifies an
> - *   index cursor (from within array of cursors starting at iIdxCur) that
> - *   already points to the index entry to be deleted.
> - */
>   void
> -sqlite3GenerateRowDelete(Parse * pParse,	/* Parsing context */
> -			 Table * pTab,		/* Table containing the row to be deleted */
> -			 Trigger * pTrigger,	/* List of triggers to (potentially) fire */
> -			 int iDataCur,		/* Cursor from which column data is extracted */
> -			 int iIdxCur,		/* First index cursor */
> -			 int iPk,		/* First memory cell containing the PRIMARY KEY */
> -			 i16 nPk,		/* Number of PRIMARY KEY memory cells */
> -			 u8 count,		/* If non-zero, increment the row change counter */
> -			 enum on_conflict_action onconf,		/* Default ON CONFLICT policy for triggers */
> -			 u8 eMode,		/* ONEPASS_OFF, _SINGLE, or _MULTI.  See above */
> -			 int iIdxNoSeek)	/* Cursor number of cursor that does not need seeking */
> +sql_generate_row_delete(struct Parse *parse, struct Table *table,
> +			struct Trigger *trigger_list, int cursor, int reg_pk,
> +			short npk, bool need_update_count,
> +			enum on_conflict_action onconf, u8 mode,
> +			int idx_noseek)
>   {
> -	Vdbe *v = pParse->pVdbe;	/* Vdbe */
> -	int iOld = 0;		/* First register in OLD.* array */
> -	int iLabel;		/* Label resolved to end of generated code */
> -
> -	/* Vdbe is guaranteed to have been allocated by this stage. */
> -	assert(v);
> -	(void)iIdxCur;
> +	struct Vdbe *v = parse->pVdbe;
> +	/* Vdbe is guaranteed to have been allocated by this
> +	 * stage.
> +	 */
> +	assert(v != NULL);
>   	VdbeModuleComment((v, "BEGIN: GenRowDel(%d,%d,%d,%d)",
> -			   iDataCur, iIdxCur, iPk, (int)nPk));
> +			   cursor, iIdxCur, reg_pk, (int)nPk));
>   
> -	/* Seek cursor iCur to the row to delete. If this row no longer exists
> -	 * (this can happen if a trigger program has already deleted it), do
> -	 * not attempt to delete it or fire any DELETE triggers.
> +	/* Seek cursor iCur to the row to delete. If this row no
> +	 * longer exists (this can happen if a trigger program has
> +	 * already deleted it), do not attempt to delete it or
> +	 * fire any DELETE triggers.
>   	 */
> -	iLabel = sqlite3VdbeMakeLabel(v);
> -	if (eMode == ONEPASS_OFF) {
> -		sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, iLabel, iPk, nPk);
> +	int label = sqlite3VdbeMakeLabel(v);
> +	if (mode == ONEPASS_OFF) {
> +		sqlite3VdbeAddOp4Int(v, OP_NotFound, cursor, label, reg_pk, npk);
>   		VdbeCoverageIf(v, opSeek == OP_NotFound);
>   	}
>   
> +	int first_old_reg = 0;
>   	/* If there are any triggers to fire, allocate a range of registers to
>   	 * use for the old.* references in the triggers.
>   	 */
> -	if (sqlite3FkRequired(pTab, 0) || pTrigger) {
> -		u32 mask;	/* Mask of OLD.* columns in use */
> -		int iCol;	/* Iterator used while populating OLD.* */
> -		int addrStart;	/* Start of BEFORE trigger programs */
> -
> -		/* TODO: Could use temporary registers here.
> -		 */
> -		mask =
> -		    sqlite3TriggerColmask(pParse, pTrigger, 0, 0,
> -					  TRIGGER_BEFORE | TRIGGER_AFTER, pTab,
> +	if (sqlite3FkRequired(table, 0) || trigger_list != NULL) {
> +		/* Mask of OLD.* columns in use */
> +		/* TODO: Could use temporary registers here. */
> +		uint32_t mask =
> +		    sqlite3TriggerColmask(parse, trigger_list, 0, 0,
> +					  TRIGGER_BEFORE | TRIGGER_AFTER, table,
>   					  onconf);
> -		mask |= sqlite3FkOldmask(pParse, pTab);
> -		iOld = pParse->nMem + 1;
> -		pParse->nMem += (1 + pTab->nCol);
> +		mask |= sqlite3FkOldmask(parse, table);
> +		first_old_reg = parse->nMem + 1;
> +		parse->nMem += (1 + table->nCol);
>   
> -		/* Populate the OLD.* pseudo-table register array. These values will be
> -		 * used by any BEFORE and AFTER triggers that exist.
> +		/* Populate the OLD.* pseudo-table register array.
> +		 * These values will be used by any BEFORE and
> +		 * AFTER triggers that exist.
>   		 */
> -		sqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld);
> -		for (iCol = 0; iCol < pTab->nCol; iCol++) {
> +		sqlite3VdbeAddOp2(v, OP_Copy, reg_pk, first_old_reg);
> +		for (int i = 0; i < table->nCol; i++) {
>   			testcase(mask != 0xffffffff && iCol == 31);
>   			testcase(mask != 0xffffffff && iCol == 32);
>   			if (mask == 0xffffffff
> -			    || (iCol <= 31 && (mask & MASKBIT32(iCol)) != 0)) {
> -				sqlite3ExprCodeGetColumnOfTable(v, pTab,
> -								iDataCur, iCol,
> -								iOld + iCol +
> -								1);
> +			    || (i <= 31 && (mask & MASKBIT32(i)) != 0)) {
> +				sqlite3ExprCodeGetColumnOfTable(v, table,
> +								cursor, i,
> +								first_old_reg +
> +								i + 1);
>   			}
>   		}
>   
>   		/* Invoke BEFORE DELETE trigger programs. */
> -		addrStart = sqlite3VdbeCurrentAddr(v);
> -		sqlite3CodeRowTrigger(pParse, pTrigger,
> -				      TK_DELETE, 0, TRIGGER_BEFORE, pTab, iOld,
> -				      onconf, iLabel);
> -
> -		/* If any BEFORE triggers were coded, then seek the cursor to the
> -		 * row to be deleted again. It may be that the BEFORE triggers moved
> -		 * the cursor or of already deleted the row that the cursor was
> -		 * pointing to.
> +		int addr_start = sqlite3VdbeCurrentAddr(v);
> +		sqlite3CodeRowTrigger(parse, trigger_list,
> +				      TK_DELETE, 0, TRIGGER_BEFORE, table,
> +				      first_old_reg, onconf, label);
> +
> +		/* If any BEFORE triggers were coded, then seek
> +		 * the cursor to the row to be deleted again. It
> +		 * may be that the BEFORE triggers moved the
> +		 * cursor or of already deleted the row that the
> +		 * cursor was pointing to.
>   		 */
> -		if (addrStart < sqlite3VdbeCurrentAddr(v)) {
> -			sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, iLabel, iPk,
> -					     nPk);
> +		if (addr_start < sqlite3VdbeCurrentAddr(v)) {
> +			sqlite3VdbeAddOp4Int(v, OP_NotFound, cursor, label,
> +					     reg_pk, npk);
>   			VdbeCoverageIf(v, opSeek == OP_NotFound);
>   		}
>   
> -		/* Do FK processing. This call checks that any FK constraints that
> -		 * refer to this table (i.e. constraints attached to other tables)
> -		 * are not violated by deleting this row.
> +		/* Do FK processing. This call checks that any FK
> +		 * constraints that refer to this table (i.e.
> +		 * constraints attached to other tables) are not
> +		 * violated by deleting this row.
>   		 */
> -		sqlite3FkCheck(pParse, pTab, iOld, 0, 0);
> +		sqlite3FkCheck(parse, table, first_old_reg, 0, NULL);
>   	}
>   
> -	/* Delete the index and table entries. Skip this step if pTab is really
> -	 * a view (in which case the only effect of the DELETE statement is to
> -	 * fire the INSTEAD OF triggers).
> -	 *
> -	 * If variable 'count' is non-zero, then this OP_Delete instruction should
> -	 * invoke the update-hook. The pre-update-hook, on the other hand should
> -	 * be invoked unless table pTab is a system table. The difference is that
> -	 * the update-hook is not invoked for rows removed by REPLACE, but the
> -	 * pre-update-hook is.
> +	/* Delete the index and table entries. Skip this step if
> +	 * table is really a view (in which case the only effect
> +	 * of the DELETE statement is to fire the INSTEAD OF
> +	 * triggers).
>   	 */
> -	if (pTab->pSelect == 0) {
> -		u8 p5 = 0;
> -		/* kyukhin: Tarantool handles indices uypdate automatically.  */
> -		/* sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek);  */
> -		sqlite3VdbeAddOp2(v, OP_Delete, iDataCur,
> -				  (count ? OPFLAG_NCHANGE : 0));
> -		if (eMode != ONEPASS_OFF) {
> +	if (table->pSelect == NULL) {
> +		uint8_t p5 = 0;
> +		sqlite3VdbeAddOp2(v, OP_Delete, cursor,
> +				  (need_update_count ? OPFLAG_NCHANGE : 0));
> +		if (mode != ONEPASS_OFF)
>   			sqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE);
> -		}
> -		if (iIdxNoSeek >= 0) {
> -			sqlite3VdbeAddOp1(v, OP_Delete, iIdxNoSeek);
> -		}
> -		if (eMode == ONEPASS_MULTI)
> +
> +		if (idx_noseek >= 0)
> +			sqlite3VdbeAddOp1(v, OP_Delete, idx_noseek);
> +
> +		if (mode == ONEPASS_MULTI)
>   			p5 |= OPFLAG_SAVEPOSITION;
>   		sqlite3VdbeChangeP5(v, p5);
>   	}
>   
> -	/* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
> -	 * handle rows (possibly in other tables) that refer via a foreign key
> -	 * to the row just deleted.
> +	/* Do any ON CASCADE, SET NULL or SET DEFAULT operations
> +	 * required to handle rows (possibly in other tables) that
> +	 * refer via a foreign key to the row just deleted.
>   	 */
> -	sqlite3FkActions(pParse, pTab, 0, iOld, 0);
> +	sqlite3FkActions(parse, table, 0, first_old_reg, 0);
>   
>   	/* Invoke AFTER DELETE trigger programs. */
> -	sqlite3CodeRowTrigger(pParse, pTrigger,
> -			      TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf,
> -			      iLabel);
> +	sqlite3CodeRowTrigger(parse, trigger_list,
> +			      TK_DELETE, 0, TRIGGER_AFTER, table, first_old_reg,
> +			      onconf, label);
>   
> -	/* Jump here if the row had already been deleted before any BEFORE
> -	 * trigger programs were invoked. Or if a trigger program throws a
> -	 * RAISE(IGNORE) exception.
> +	/* Jump here if the row had already been deleted before
> +	 * any BEFORE trigger programs were invoked. Or if a trigger program
> +	 * throws a RAISE(IGNORE) exception.
>   	 */
> -	sqlite3VdbeResolveLabel(v, iLabel);
> +	sqlite3VdbeResolveLabel(v, label);
>   	VdbeModuleComment((v, "END: GenRowDel()"));
>   }
>   
> -/*
> - * This routine generates VDBE code that causes the deletion of all
> - * index entries associated with a single row of a single table, pTab
> - *
> - * Preconditions:
> - *
> - *   1.  A read/write cursor "iDataCur" must be open on the canonical storage
> - *       btree for the table pTab.  (This will be primary key index)
> - *
> - *   2.  Read/write cursor for primary index of pTab must be open as
> - *       cursor number iIdxCur.  (The pTab->pIndex index is the 0-th index.)
> - *
> - *   3.  The "iDataCur" cursor must be already positioned on the row
> - *       that is to be deleted.
> - */
> -void
> -sqlite3GenerateRowIndexDelete(Parse * pParse,	/* Parsing and code generating context */
> -			      Table * pTab,	/* Table containing the row to be deleted */
> -			      int iDataCur,	/* Cursor of table holding data. */
> -			      int iIdxCur)	/* Primary index cursor */
> -{
> -	int r1 = -1;		/* Register holding an index key */
> -	int iPartIdxLabel;	/* Jump destination for skipping partial index entries */
> -	Vdbe *v;		/* The prepared statement under construction */
> -	Index *pPk;		/* PRIMARY KEY index */
> -
> -	v = pParse->pVdbe;
> -	pPk = sqlite3PrimaryKeyIndex(pTab);
> -	/* In Tarantool it is enough to delete row just from pk */
> -	VdbeModuleComment((v, "GenRowIdxDel for %s", pPk->zName));
> -	r1 = sqlite3GenerateIndexKey(pParse, pPk, iDataCur, 0, &iPartIdxLabel,
> -				     NULL, r1);
> -	sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur, r1, index_column_count(pPk));
> -	sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
> -}
> -
> -/*
> - * Generate code that will assemble an index key and stores it in register
> - * regOut.  The key with be for index pIdx which is an index on pTab.
> - * iCur is the index of a cursor open on the pTab table and pointing to
> - * the entry that needs indexing.
> - * iCur must be the cursor of the PRIMARY KEY index.
> - *
> - * Return a register number which is the first in a block of
> - * registers that holds the elements of the index key.  The
> - * block of registers has already been deallocated by the time
> - * this routine returns.
> - *
> - * If *piPartIdxLabel is not NULL, fill it in with a label and jump
> - * to that label if pIdx is a partial index that should be skipped.
> - * The label should be resolved using sqlite3ResolvePartIdxLabel().
> - * A partial index should be skipped if its WHERE clause evaluates
> - * to false or null.  If pIdx is not a partial index, *piPartIdxLabel
> - * will be set to zero which is an empty label that is ignored by
> - * sqlite3ResolvePartIdxLabel().
> - *
> - * The pPrior and regPrior parameters are used to implement a cache to
> - * avoid unnecessary register loads.  If pPrior is not NULL, then it is
> - * a pointer to a different index for which an index key has just been
> - * computed into register regPrior.  If the current pIdx index is generating
> - * its key into the same sequence of registers and if pPrior and pIdx share
> - * a column in common, then the register corresponding to that column already
> - * holds the correct value and the loading of that register is skipped.
> - * This optimization is helpful when doing a DELETE or an INTEGRITY_CHECK
> - * on a table with multiple indices, and especially with
> - * the PRIMARY KEY columns of the index.
> - */
>   int
> -sqlite3GenerateIndexKey(Parse * pParse,	/* Parsing context */
> -			Index * pIdx,	/* The index for which to generate a key */
> -			int iDataCur,	/* Cursor number from which to take column data */
> -			int regOut,	/* Put the new key into this register if not 0 */
> -			int *piPartIdxLabel,	/* OUT: Jump to this label to skip partial index */
> -			Index * pPrior,	/* Previously generated index key */
> -			int regPrior)	/* Register holding previous generated key */
> +sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
> +		       int reg_out, int *part_idx_label, struct Index *prev,
> +		       int reg_prev)
>   {
> -	Vdbe *v = pParse->pVdbe;
> -	int j;
> -	int regBase;
> -	int nCol;
> -
> -	if (piPartIdxLabel) {
> -		if (pIdx->pPartIdxWhere) {
> -			*piPartIdxLabel = sqlite3VdbeMakeLabel(v);
> -			pParse->iSelfTab = iDataCur;
> -			sqlite3ExprCachePush(pParse);
> -			sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere,
> -					      *piPartIdxLabel,
> +	struct Vdbe *v = parse->pVdbe;
> +
> +	if (part_idx_label != NULL) {
> +		if (index->pPartIdxWhere != NULL) {
> +			*part_idx_label = sqlite3VdbeMakeLabel(v);
> +			parse->iSelfTab = cursor;
> +			sqlite3ExprCachePush(parse);
> +			sqlite3ExprIfFalseDup(parse, index->pPartIdxWhere,
> +					      *part_idx_label,
>   					      SQLITE_JUMPIFNULL);
>   		} else {
> -			*piPartIdxLabel = 0;
> +			*part_idx_label = 0;
>   		}
>   	}
> -	nCol = index_column_count(pIdx);
> -	regBase = sqlite3GetTempRange(pParse, nCol);
> -	if (pPrior && (regBase != regPrior || pPrior->pPartIdxWhere))
> -		pPrior = 0;
> -	for (j = 0; j < nCol; j++) {
> -		if (pPrior && pPrior->aiColumn[j] == pIdx->aiColumn[j]
> -		    && pPrior->aiColumn[j] != XN_EXPR) {
> -			/* This column was already computed by the previous index */
> +	int col_cnt = index_column_count(index);
> +	int reg_base = sqlite3GetTempRange(parse, col_cnt);
> +	if (prev != NULL && (reg_base != reg_prev ||
> +			     prev->pPartIdxWhere != NULL))
> +		prev = NULL;
> +	for (int j = 0; j < col_cnt; j++) {
> +		if (prev != NULL && prev->aiColumn[j] == index->aiColumn[j]
> +		    && prev->aiColumn[j] != XN_EXPR) {
> +			/*
> +			 * This column was already computed by the
> +			 * previous index.
> +			 */
>   			continue;
>   		}
> -		sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j,
> -					       regBase + j);
> -		/* If the column affinity is REAL but the number is an integer, then it
> -		 * might be stored in the table as an integer (using a compact
> -		 * representation) then converted to REAL by an OP_RealAffinity opcode.
> -		 * But we are getting ready to store this value back into an index, where
> -		 * it should be converted by to INTEGER again.  So omit the OP_RealAffinity
> -		 * opcode if it is present
> +		sqlite3ExprCodeLoadIndexColumn(parse, index, cursor, j,
> +					       reg_base + j);
> +		/*
> +		 * If the column affinity is REAL but the number
> +		 * is an integer, then it might be stored in the
> +		 * table as an integer (using a compact
> +		 * representation) then converted to REAL by an
> +		 * OP_RealAffinity opcode. But we are getting
> +		 * ready to store this value back into an index,
> +		 * where it should be converted by to INTEGER
> +		 * again.  So omit the OP_RealAffinity opcode if
> +		 * it is present
>   		 */
>   		sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity);
>   	}
> -	if (regOut) {
> -		sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut);
> -	}
> -	sqlite3ReleaseTempRange(pParse, regBase, nCol);
> -	return regBase;
> +	if (reg_out != 0)
> +		sqlite3VdbeAddOp3(v, OP_MakeRecord, reg_base, col_cnt, reg_out);
> +
> +	sqlite3ReleaseTempRange(parse, reg_base, col_cnt);
> +	return reg_base;
>   }
>   
> -/*
> - * If a prior call to sqlite3GenerateIndexKey() generated a jump-over label
> - * because it was a partial index, then this routine should be called to
> - * resolve that label.
> - */
>   void
> -sqlite3ResolvePartIdxLabel(Parse * pParse, int iLabel)
> +sql_resolve_part_idx_label(struct Parse *parse, int label)
>   {
> -	if (iLabel) {
> -		sqlite3VdbeResolveLabel(pParse->pVdbe, iLabel);
> -		sqlite3ExprCachePop(pParse);
> +	if (label != 0) {
> +		sqlite3VdbeResolveLabel(parse->pVdbe, label);
> +		sqlite3ExprCachePop(parse);
>   	}
>   }
> diff --git a/src/box/sql/fkey.c b/src/box/sql/fkey.c
> index 60b4786..a21dd2d 100644
> --- a/src/box/sql/fkey.c
> +++ b/src/box/sql/fkey.c
> @@ -784,7 +784,8 @@ sqlite3FkDropTable(Parse * pParse, SrcList * pName, Table * pTab)
>   		pParse->disableTriggers = 1;
>   		/* Staring new transaction before DELETE FROM <tbl> */
>   		sqlite3VdbeAddOp0(v, OP_TTransaction);
> -		sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0);
> +		sql_table_delete_from(pParse, sqlite3SrcListDup(db, pName, 0),
> +				      NULL);
>   		pParse->disableTriggers = 0;
>   
>   		/* If the DELETE has generated immediate foreign key constraint
> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index 54a7e4a..97d18ae 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -379,7 +379,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
>   	zTab = pTabList->a[0].zName;
>   	if (NEVER(zTab == 0))
>   		goto insert_cleanup;
> -	pTab = sqlite3SrcListLookup(pParse, pTabList);
> +	pTab = sql_list_lookup_table(pParse, pTabList);
>   	if (pTab == 0) {
>   		goto insert_cleanup;
>   	}
> @@ -406,13 +406,13 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
>   	/* If pTab is really a view, make sure it has been initialized.
>   	 * ViewGetColumnNames() is a no-op if pTab is not a view.
>   	 */
> -	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
> +	if (sqlite3ViewGetColumnNames(pParse, pTab))
>   		goto insert_cleanup;
> -	}
>   
> -	/* Cannot insert into a read-only table.
> -	 */
> -	if (sqlite3IsReadOnly(pParse, pTab, tmask)) {
> +	/* Cannot insert into a read-only table. */
> +	if (isView && tmask == 0) {
> +		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
> +				pTab->zName);
>   		goto insert_cleanup;
>   	}
>   
> @@ -1510,13 +1510,13 @@ sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
>   								 TK_DELETE, 0,
>   								 0);
>   				}
> -				sqlite3GenerateRowDelete(pParse, pTab, pTrigger,
> -							 iDataCur, iIdxCur,
> -							 regR, nPkField, 0,
> -							 ON_CONFLICT_ACTION_REPLACE,
> -							 (pIdx ==
> -							  pPk ? ONEPASS_SINGLE :
> -							  ONEPASS_OFF), -1);
> +				sql_generate_row_delete(pParse, pTab, pTrigger,
> +							iDataCur,
> +							regR, nPkField, 0,
> +							ON_CONFLICT_ACTION_REPLACE,
> +							(pIdx ==
> +							 pPk ? ONEPASS_SINGLE :
> +							 ONEPASS_OFF), -1);
>   				seenReplace = 1;
>   				break;
>   			}
> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
> index 872647d..259243d 100644
> --- a/src/box/sql/parse.y
> +++ b/src/box/sql/parse.y
> @@ -719,28 +719,14 @@ limit_opt(A) ::= LIMIT expr(X) COMMA expr(Y).
>   
>   /////////////////////////// The DELETE statement /////////////////////////////
>   //
> -%ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
> -cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W)
> -        orderby_opt(O) limit_opt(L). {
> -  sqlite3WithPush(pParse, C, 1);
> -  sqlite3SrcListIndexedBy(pParse, X, &I);
> -  W = sqlite3LimitWhere(pParse, X, W, O, L.pLimit, L.pOffset, "DELETE");
> -  sqlSubProgramsRemaining = SQL_MAX_COMPILING_TRIGGERS;
> -  /* Instruct SQL to initate Tarantool's transaction.  */
> -  pParse->initiateTTrans = true;
> -  sqlite3DeleteFrom(pParse,X,W);
> -}
> -%endif
> -%ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
>   cmd ::= with(C) DELETE FROM fullname(X) indexed_opt(I) where_opt(W). {
>     sqlite3WithPush(pParse, C, 1);
>     sqlite3SrcListIndexedBy(pParse, X, &I);
>     sqlSubProgramsRemaining = SQL_MAX_COMPILING_TRIGGERS;
>     /* Instruct SQL to initate Tarantool's transaction.  */
>     pParse->initiateTTrans = true;
> -  sqlite3DeleteFrom(pParse,X,W);
> +  sql_table_delete_from(pParse,X,W);
>   }
> -%endif
>   
>   %type where_opt {Expr*}
>   %destructor where_opt {sql_expr_free(pParse->db, $$, false);}
> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> index e056d63..8c6df2e 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -1994,7 +1994,6 @@ sql_space_tuple_log_count(struct Table *tab);
>   /*
>    * Allowed values for Table.tabFlags.
>    */
> -#define TF_Readonly        0x01	/* Read-only system table */
>   #define TF_Ephemeral       0x02	/* An ephemeral table */
>   #define TF_HasPrimaryKey   0x04	/* Table has a primary key */
>   #define TF_Autoincrement   0x08	/* Integer primary key is autoincrement */
> @@ -3671,15 +3670,46 @@ int sqlite3Select(Parse *, Select *, SelectDest *);
>   Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
>   			 Expr *, ExprList *, u32, Expr *, Expr *);
>   void sqlite3SelectDelete(sqlite3 *, Select *);
> -Table *sqlite3SrcListLookup(Parse *, SrcList *);
> -int sqlite3IsReadOnly(Parse *, Table *, int);
> +
> +/**
> + * While a SrcList can in general represent multiple tables and
> + * subqueries (as in the FROM clause of a SELECT statement) in
> + * this case it contains the name of a single table, as one might
> + * find in an INSERT, DELETE, or UPDATE statement.  Look up that
> + * table in the symbol table and return a pointer.  Set an error
> + * message and return NULL if the table name is not found or if
> + * any other error occurs.
> + *
> + * The following fields are initialized appropriate in src_list:
> + *
> + *    src_list->a[0].pTab       Pointer to the Table object.
> + *    src_list->a[0].pIndex     Pointer to the INDEXED BY index,
> + *                              if there is one.
> + *
> + * @param parse Parsing context.
> + * @param src_list List containing single table element.
> + * @retval Table object if found, NULL oterwise.
> + */
> +struct Table *
> +sql_list_lookup_table(struct Parse *parse, struct SrcList *src_list);
> +
>   void sqlite3OpenTable(Parse *, int iCur, Table *, int);
> -#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
> -Expr *sqlite3LimitWhere(Parse *, SrcList *, Expr *, ExprList *, Expr *, Expr *,
> -			char *);
> -#endif
> -void sqlite3DeleteFrom(Parse *, SrcList *, Expr *);
> -void sqlite3DeleteByKey(Parse *, char *, const char **, Expr **, int);
> +/**
> + * Generate code for a DELETE FROM statement.
> + *
> + *     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
> + *                 \________/       \________________/
> + *                  tab_list              where
> + *
> + * @param parse Parsing context.
> + * @param tab_list List of single element which table from which
> + * deletetion if performed.
> + * @param where The WHERE clause.  May be NULL.
> + */
> +void
> +sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
> +		      struct Expr *where);
> +
>   void sqlite3Update(Parse *, SrcList *, ExprList *, Expr *,
>   		   enum on_conflict_action);
>   WhereInfo *sqlite3WhereBegin(Parse *, SrcList *, Expr *, ExprList *, ExprList *,
> @@ -3782,11 +3812,118 @@ int sqlite3ExprContainsSubquery(Expr *);
>   int sqlite3ExprIsInteger(Expr *, int *);
>   int sqlite3ExprCanBeNull(const Expr *);
>   int sqlite3ExprNeedsNoAffinityChange(const Expr *, char);
> -void sqlite3GenerateRowDelete(Parse *, Table *, Trigger *, int, int, int, i16,
> -			      u8, enum on_conflict_action, u8, int);
> -void sqlite3GenerateRowIndexDelete(Parse *, Table *, int, int);
> -int sqlite3GenerateIndexKey(Parse *, Index *, int, int, int *, Index *, int);
> -void sqlite3ResolvePartIdxLabel(Parse *, int);
> +
> +/**
> + * This routine generates VDBE code that causes a single row of a
> + * single table to be deleted.  Both the original table entry and
> + * all indices are removed.
> + *
> + * Preconditions:
> + *
> + *   1.  cursor is an open cursor on the btree that is the
> + *       canonical data store for the table.  (This will be the
> + *       PRIMARY KEY index)
> + *
> + *   2.  The primary key for the row to be deleted must be stored
> + *       in a sequence of npk memory cells starting at reg_pk. If
> + *       npk==0 that means that a search record formed from
> + *       OP_MakeRecord is contained in the single memory location
> + *       reg_pk.
> + *
> + * mode:
> + *   Parameter mode may be passed either ONEPASS_OFF (0),
> + *   ONEPASS_SINGLE, or ONEPASS_MULTI.  If mode is not
> + *   ONEPASS_OFF, then the cursor already points to the row to
> + *   delete. If mode is ONEPASS_OFF then this function must seek
> + *   cursor to the entry identified by reg_pk and npk before
> + *   reading from it.
> + *
> + *   If mode is ONEPASS_MULTI, then this call is being made as
> + *   part of a ONEPASS delete that affects multiple rows. In this
> + *   case, if idx_noseek is a valid cursor number (>=0), then its
> + *   position should be preserved following the delete operation.
> + *   Or, if idx_noseek is not a valid cursor number, the position
> + *   of cursor should be preserved instead.
> + *
> + * @param parse Parsing context.
> + * @param table Table containing the row to be deleted.
> + * @param trigger_list List of triggers to (potentially) fire.
> + * @param cursor Cursor from which column data is extracted/
> + * @param reg_pk First memory cell containing the PRIMARY KEY.
> + * @param npk umber of PRIMARY KEY memory cells.
> + * @param need_update_count. If non-zero, increment the row change
> + *        counter.
> + * @param onconf Default ON CONFLICT policy for triggers.
> + * @param mode ONEPASS_OFF, _SINGLE, or _MULTI.  See above.
> + * @param idx_noseek If it is a valid cursor number (>=0),
> + *        then it identifies an index cursor that already points
> + *        to the index entry to be deleted.
> + */
> +void
> +sql_generate_row_delete(struct Parse *parse, struct Table *table,
> +			struct Trigger *trigger_list, int cursor, int reg_pk,
> +			short npk, bool need_update_count,
> +			enum on_conflict_action onconf, u8 mode,
> +			int idx_noseek);
> +
> +/**
> + * Generate code that will assemble an index key and stores it in
> + * register reg_out.  The key will be for index which is an
> + * index on table. cursor is the index of a cursor open on the
> + * table table and pointing to the entry that needs indexing.
> + * cursor must be the cursor of the PRIMARY KEY index.
> + *
> + * Return a register number which is the first in a block of
> + * registers that holds the elements of the index key.  The
> + * block of registers has already been deallocated by the time
> + * this routine returns.
> + *
> + * If part_idx_label is not NULL, fill it in with a label and
> + * jump to that label if index is a partial index that should be
> + * skipped. The label should be resolved using
> + * sql_resolve_part_idx_label(). A partial index should be skipped
> + * if its WHERE clause evaluates to false or null.  If index is
> + * not a partial index, *part_idx_label will be set to zero which
> + * is an empty label that is ignored by sql_resolve_part_idx_label().
> + *
> + * The prev and reg_prev parameters are used to implement a
> + * cache to avoid unnecessary register loads.  If prev is not
> + * NULL, then it is a pointer to a different index for which an
> + * index key has just been computed into register reg_prev. If the
> + * current index is generating its key into the same
> + * sequence of registers and if prev and index share a column in
> + * common, then the register corresponding to that column already
> + * holds the correct value and the loading of that register is
> + * skipped. This optimization is helpful when doing a DELETE or
> + * an INTEGRITY_CHECK on a table with multiple indices, and
> + * especially with the PRIMARY KEY columns of the index.
> + *
> + * @param parse Parsing context.
> + * @param index The index for which to generate a key.
> + * @param cursor Cursor number from which to take column data.
> + * @param reg_out Put the new key into this register if not NULL.
> + * @param[out] part_idx_label Jump to this label to skip partial
> + *        index.
> + * @param prev Previously generated index key
> + * @param reg_prev Register holding previous generated key.
> + * @retval Register containing new record
> + */
> +int
> +sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
> +		       int reg_out, int *part_idx_label, struct Index *prev,
> +		       int reg_prev);
> +
> +/**
> + * If a prior call to sql_generate_index_key() generated a
> + * jump-over label because it was a partial index, then this
> + * routine should be called to resolve that label.
> + *
> + * @param parse Parsing context.
> + * @param label Label to resolve.
> + */
> +void
> +sql_resolve_part_idx_label(struct Parse *parse, int label);
> +
>   void sqlite3GenerateConstraintChecks(Parse *, Table *, int *, int, int, int,
>   				     int, u8, struct on_conflict *, int, int *,
>   				     int *);
> @@ -3831,9 +3968,20 @@ int sqlite3SafetyCheckOk(sqlite3 *);
>   int sqlite3SafetyCheckSickOrOk(sqlite3 *);
>   void sqlite3ChangeCookie(Parse *);
>   
> -#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
> -void sqlite3MaterializeView(Parse *, Table *, Expr *, int);
> -#endif
> +/**
> + * Evaluate a view and store its result in an ephemeral table.
> + * The where argument is an optional WHERE clause that restricts
> + * the set of rows in the view that are to be added to the
> + * ephemeral table.
> + *
> + * @param parse Parsing context.
> + * @param name View name.
> + * @param where Option WHERE clause to be added.
> + * @param cursor Cursor number for ephemeral table.
> + */
> +void
> +sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
> +		     int cursor);
>   
>   #ifndef SQLITE_OMIT_TRIGGER
>   void sqlite3BeginTrigger(Parse *, Token *, int, int, IdList *, SrcList *,
> diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
> index 28c56db..e6d9925 100644
> --- a/src/box/sql/trigger.c
> +++ b/src/box/sql/trigger.c
> @@ -111,7 +111,7 @@ sqlite3BeginTrigger(Parse * pParse,	/* The parse context of the CREATE TRIGGER s
>   	if (sqlite3FixSrcList(&sFix, pTableName)) {
>   		goto trigger_cleanup;
>   	}
> -	pTab = sqlite3SrcListLookup(pParse, pTableName);
> +	pTab = sql_list_lookup_table(pParse, pTableName);
>   	if (!pTab) {
>   		goto trigger_cleanup;
>   	}
> @@ -744,11 +744,11 @@ codeTriggerProgram(Parse * pParse,	/* The parser context */
>   				break;
>   			}
>   		case TK_DELETE:{
> -				sqlite3DeleteFrom(pParse,
> -						  targetSrcList(pParse, pStep),
> -						  sqlite3ExprDup(db,
> -								 pStep->pWhere,
> -								 0)
> +				sql_table_delete_from(pParse,
> +						      targetSrcList(pParse, pStep),
> +						      sqlite3ExprDup(db,
> +								     pStep->pWhere,
> +								     0)
>   				    );
>   				break;
>   			}
> diff --git a/src/box/sql/update.c b/src/box/sql/update.c
> index 204adc2..c521a3b 100644
> --- a/src/box/sql/update.c
> +++ b/src/box/sql/update.c
> @@ -163,7 +163,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>   
>   	/* Locate the table which we want to update.
>   	 */
> -	pTab = sqlite3SrcListLookup(pParse, pTabList);
> +	pTab = sql_list_lookup_table(pParse, pTabList);
>   	if (pTab == 0)
>   		goto update_cleanup;
>   
> @@ -187,7 +187,9 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>   	if (sqlite3ViewGetColumnNames(pParse, pTab)) {
>   		goto update_cleanup;
>   	}
> -	if (sqlite3IsReadOnly(pParse, pTab, tmask)) {
> +	if (isView && tmask == 0) {
> +		sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view",
> +				pTab->zName);
>   		goto update_cleanup;
>   	}
>   
> @@ -324,7 +326,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>   	 */
>   #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
>   	if (isView) {
> -		sqlite3MaterializeView(pParse, pTab, pWhere, iDataCur);
> +		sql_materialize_view(pParse, pTab->zName, pWhere, iDataCur);
>   		/* Number of columns from SELECT plus ID.*/
>   		nKey = pTab->nCol + 1;
>   	}
> @@ -603,7 +605,6 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>   						     nKey);
>   			VdbeCoverageNeverTaken(v);
>   		}
> -		sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur);
>   
>   		/* If changing the PK value, or if there are foreign key constraints
>   		 * to process, delete the old record. Otherwise, add a noop OP_Delete
> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> index 1304149..7ac14e2 100644
> --- a/src/box/sql/vdbeaux.c
> +++ b/src/box/sql/vdbeaux.c
> @@ -1124,7 +1124,7 @@ sqlite3VdbeChangeP4(Vdbe * p, int addr, const char *zP4, int n)
>   	} if (n == P4_BOOL) {
>   		pOp->p4.b = *(bool*)zP4;
>   		pOp->p4type = P4_BOOL;
> -	} else if (zP4 != 0) {
> +	} else {
>   		assert(n < 0);
>   		pOp->p4.p = (void *)zP4;
>   		pOp->p4type = (signed char)n;
> @@ -1561,7 +1561,10 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
>   #endif
>   	case P4_COLLSEQ:{
>   			struct coll *pColl = pOp->p4.pColl;
> -			sqlite3XPrintf(&x, "(%.20s)", pColl->name);
> +			if (pColl != NULL)
> +				sqlite3XPrintf(&x, "(%.20s)", pColl->name);
> +			else
> +				sqlite3XPrintf(&x, "(binary)");
>   			break;
>   		}
>   	case P4_FUNCDEF:{
> diff --git a/src/box/sql/where.c b/src/box/sql/where.c
> index b497a5b..3519f34 100644
> --- a/src/box/sql/where.c
> +++ b/src/box/sql/where.c
> @@ -840,11 +840,11 @@ constructAutomaticIndex(Parse * pParse,			/* The parsing context */
>   	}
>   	regRecord = sqlite3GetTempReg(pParse);
>   	regBase =
> -	    sqlite3GenerateIndexKey(pParse, pIdx, pLevel->iTabCur, regRecord, 0,
> -				    0, 0);
> +	    sql_generate_index_key(pParse, pIdx, pLevel->iTabCur, regRecord, 0,
> +				   0, 0);
>   	sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord);
>   	if (pPartial)
> -		sqlite3VdbeResolveLabel(v, iContinue);
> +		sql_resolve_part_idx_label(v, iContinue);
>   	if (pTabItem->fg.viaCoroutine) {
>   		sqlite3VdbeChangeP2(v, addrCounter, regBase + n);
>   		translateColumnToCopy(v, addrTop, pLevel->iTabCur,
> 

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

* [tarantool-patches] Re: [PATCH 1/2] sql: allow key_def to be NULL for ephemeral create
  2018-05-18  6:57       ` Kirill Yukhin
@ 2018-05-18 10:33         ` Vladislav Shpilevoy
  2018-05-18 10:48           ` Kirill Yukhin
  0 siblings, 1 reply; 17+ messages in thread
From: Vladislav Shpilevoy @ 2018-05-18 10:33 UTC (permalink / raw)
  To: Kirill Yukhin; +Cc: tarantool-patches

Hello. Thanks for fixes!

> diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
> index 3455f52..2c1ce44 100644
> --- a/src/box/sql/delete.c
> +++ b/src/box/sql/delete.c
> @@ -386,14 +386,9 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
>   			iPk = pParse->nMem + 1;
>   			pParse->nMem += nPk;
>   			iEphCur = pParse->nTab++;
> -			struct key_def *def = key_def_new(nPk);
> -			if (def == NULL) {
> -				sqlite3OomFault(db);
> -				goto delete_from_cleanup;
> -			}
>   			addrEphOpen =
>   				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
> -						  nPk, 0, (char*)def, P4_KEYDEF);
> +						  nPk);

Compilation error.

/Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/delete.c:391:12: error: too few arguments to function call, expected 7, have 4
                                                   nPk);
                                                      ^
/Users/v.shpilevoy/Work/Repositories/tarantool/src/box/sql/vdbe.h:227:1: note: 'sqlite3VdbeAddOp4' declared here
int sqlite3VdbeAddOp4(Vdbe *, int, int, int, int, const char *zP4, int);
^
1 error generated.

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

* [tarantool-patches] Re: [PATCH 1/2] sql: allow key_def to be NULL for ephemeral create
  2018-05-18 10:33         ` Vladislav Shpilevoy
@ 2018-05-18 10:48           ` Kirill Yukhin
  2018-05-18 10:50             ` Vladislav Shpilevoy
  0 siblings, 1 reply; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-18 10:48 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

On 18 мая 13:33, Vladislav Shpilevoy wrote:
> Hello. Thanks for fixes!
> 
> > diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
> > index 3455f52..2c1ce44 100644
> > --- a/src/box/sql/delete.c
> > +++ b/src/box/sql/delete.c
> > @@ -386,14 +386,9 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
> >   			iPk = pParse->nMem + 1;
> >   			pParse->nMem += nPk;
> >   			iEphCur = pParse->nTab++;
> > -			struct key_def *def = key_def_new(nPk);
> > -			if (def == NULL) {
> > -				sqlite3OomFault(db);
> > -				goto delete_from_cleanup;
> > -			}
> >   			addrEphOpen =
> >   				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
> > -						  nPk, 0, (char*)def, P4_KEYDEF);
> > +						  nPk);
> 
> Compilation error.
Whoops, fixed.

--
Regards, Kirill Yukhin

commit 8640d51f1fb60022b4b61b84fd5c6111c5dbe916
Author: Kirill Yukhin <kyukhin@tarantool.org>
Date:   Wed May 16 09:47:02 2018 +0300

    sql: allow key_def to be NULL for ephemeral create
    
    There're many cases in SQL FE, where exact key def passed to
    ephemeral table doesn't matter. It is used to store data only.
    Only field count makes sense in such a case. However it is
    passed separately (in P2). So, allow P4 field to be NULL for
    OP_OpenTEphemeral. Update delete from routine from sql/delete.c
    accordingly.
    
    Part of #3235

diff --git a/src/box/sql.c b/src/box/sql.c
index 195fdfb..6d2e0b9 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -370,8 +370,9 @@ int tarantoolSqlite3Count(BtCursor *pCur, i64 *pnEntry)
  *
  * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
  */
-int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
-				    struct key_def *def)
+int
+tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
+				struct key_def *def)
 {
 	assert(pCur);
 	assert(pCur->curFlags & BTCF_TEphemCursor);
@@ -381,7 +382,7 @@ int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
 		return SQL_TARANTOOL_ERROR;
 	for (uint32_t part = 0; part < field_count; ++part) {
 		struct coll *coll;
-		if (part < def->part_count)
+		if (def != NULL && part < def->part_count)
 			coll = def->parts[part].coll;
 		else
 			coll = NULL;
diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 3455f52..1a2f3d0 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -386,14 +386,9 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
 			iPk = pParse->nMem + 1;
 			pParse->nMem += nPk;
 			iEphCur = pParse->nTab++;
-			struct key_def *def = key_def_new(nPk);
-			if (def == NULL) {
-				sqlite3OomFault(db);
-				goto delete_from_cleanup;
-			}
 			addrEphOpen =
-				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
-						  nPk, 0, (char*)def, P4_KEYDEF);
+				sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEphCur,
+						  nPk);
 		} else {
 			pPk = sqlite3PrimaryKeyIndex(pTab);
 			assert(pPk != 0);
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 5ada87e..54a7e4a 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -566,13 +566,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 			regRec = sqlite3GetTempReg(pParse);
 			regCopy = sqlite3GetTempRange(pParse, nColumn);
 			regTempId = sqlite3GetTempReg(pParse);
-			struct key_def *def = key_def_new(nColumn + 1);
-			if (def == NULL) {
-				sqlite3OomFault(db);
-				goto insert_cleanup;
-			}
-			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, srcTab, nColumn+1,
-					  0, (char*)def, P4_KEYDEF);
+			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, srcTab, nColumn+1);
 			/* Create counter for rowid */
 			sqlite3VdbeAddOp4Dup8(v, OP_Int64,
 					      0 /* unused */,
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index bd895bc..de111d3 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -2215,13 +2215,7 @@ generateWithRecursiveQuery(Parse * pParse,	/* Parsing context */
 		VdbeComment((v, "Orderby table"));
 		destQueue.pOrderBy = pOrderBy;
 	} else {
-		struct key_def *def = key_def_new(nCol + 1);
-		if (def == NULL) {
-			sqlite3OomFault(pParse->db);
-			goto end_of_recursive_query;
-		}
-		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iQueue, nCol + 1, 0,
-				  (char*)def, P4_KEYDEF);
+		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iQueue, nCol + 1);
 		VdbeComment((v, "Queue table"));
 	}
 	if (iDistinct) {
@@ -2422,13 +2416,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
 	if (dest.eDest == SRT_EphemTab) {
 		assert(p->pEList);
 		int nCols = p->pEList->nExpr;
-		struct key_def *def = key_def_new(nCols + 1);
-		if (def == NULL) {
-			sqlite3OomFault(db);
-			goto multi_select_end;
-		}
-		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, dest.iSDParm, nCols + 1,
-				  0, (char*)def, P4_KEYDEF);
+		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, dest.iSDParm, nCols + 1);
 		VdbeComment((v, "Destination temp"));
 		dest.eDest = SRT_Table;
 	}
@@ -5608,11 +5596,8 @@ sqlite3Select(Parse * pParse,		/* The parser context */
 	/* If the output is destined for a temporary table, open that table.
 	 */
 	if (pDest->eDest == SRT_EphemTab) {
-		struct key_def *def = key_def_new(pEList->nExpr + 1);
-		if (def == NULL)
-			sqlite3OomFault(db);
-		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, pDest->iSDParm,
-				  pEList->nExpr + 1, 0, (char*)def, P4_KEYDEF);
+		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, pDest->iSDParm,
+				  pEList->nExpr + 1);
 
 		VdbeComment((v, "Output table"));
 	}
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index f3db92c..204adc2 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -358,13 +358,8 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
 
 	if (isView) {
-		struct key_def *def = key_def_new(nKey);
-		if (def == NULL) {
-			sqlite3OomFault(db);
-			goto update_cleanup;
-		}
-		addrOpen = sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEph,
-					     nKey, 0, (char*)def, P4_KEYDEF);
+		addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph,
+					     nKey);
 	} else {
 		addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph, nPk);
 		sql_vdbe_set_p4_key_def(pParse, pPk);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index e27fe2f..455198d 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -3225,9 +3225,9 @@ open_cursor_set_hints:
 /**
  * Opcode: OpenTEphemeral P1 P2 * P4 *
  * Synopsis:
- * @param P1 index of new cursor to be created
- * @param P2 number of columns in a new table
- * @param P4 key def for new table
+ * @param P1 index of new cursor to be created.
+ * @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.
  */
@@ -3236,21 +3236,20 @@ case OP_OpenTEphemeral: {
 	BtCursor *pBtCur;
 	assert(pOp->p1 >= 0);
 	assert(pOp->p2 > 0);
-	assert(pOp->p4.key_def != NULL);
-	assert(pOp->p4type == P4_KEYDEF);
+	assert(pOp->p4type != P4_KEYDEF || pOp->p4.key_def != NULL);
 
 	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
 	if (pCx == 0) goto no_mem;
 	pCx->nullRow = 1;
 
-	pCx->key_def  = pOp->p4.key_def;
 	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,
-					     pCx->key_def);
+					     pOp->p4.key_def);
+	pCx->key_def = pCx->uc.pCursor->index->def->key_def;
 	if (rc) goto abort_due_to_error;
 	break;
 }

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

* [tarantool-patches] Re: [PATCH 1/2] sql: allow key_def to be NULL for ephemeral create
  2018-05-18 10:48           ` Kirill Yukhin
@ 2018-05-18 10:50             ` Vladislav Shpilevoy
  0 siblings, 0 replies; 17+ messages in thread
From: Vladislav Shpilevoy @ 2018-05-18 10:50 UTC (permalink / raw)
  To: tarantool-patches, Kirill Yukhin

Now LGTM.

On 18/05/2018 13:48, Kirill Yukhin wrote:
> On 18 мая 13:33, Vladislav Shpilevoy wrote:
>> Hello. Thanks for fixes!
>>
>>> diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
>>> index 3455f52..2c1ce44 100644
>>> --- a/src/box/sql/delete.c
>>> +++ b/src/box/sql/delete.c
>>> @@ -386,14 +386,9 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
>>>    			iPk = pParse->nMem + 1;
>>>    			pParse->nMem += nPk;
>>>    			iEphCur = pParse->nTab++;
>>> -			struct key_def *def = key_def_new(nPk);
>>> -			if (def == NULL) {
>>> -				sqlite3OomFault(db);
>>> -				goto delete_from_cleanup;
>>> -			}
>>>    			addrEphOpen =
>>>    				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
>>> -						  nPk, 0, (char*)def, P4_KEYDEF);
>>> +						  nPk);
>>
>> Compilation error.
> Whoops, fixed.
> 
> --
> Regards, Kirill Yukhin
> 
> commit 8640d51f1fb60022b4b61b84fd5c6111c5dbe916
> Author: Kirill Yukhin <kyukhin@tarantool.org>
> Date:   Wed May 16 09:47:02 2018 +0300
> 
>      sql: allow key_def to be NULL for ephemeral create
>      
>      There're many cases in SQL FE, where exact key def passed to
>      ephemeral table doesn't matter. It is used to store data only.
>      Only field count makes sense in such a case. However it is
>      passed separately (in P2). So, allow P4 field to be NULL for
>      OP_OpenTEphemeral. Update delete from routine from sql/delete.c
>      accordingly.
>      
>      Part of #3235
> 
> diff --git a/src/box/sql.c b/src/box/sql.c
> index 195fdfb..6d2e0b9 100644
> --- a/src/box/sql.c
> +++ b/src/box/sql.c
> @@ -370,8 +370,9 @@ int tarantoolSqlite3Count(BtCursor *pCur, i64 *pnEntry)
>    *
>    * @retval SQLITE_OK on success, SQLITE_TARANTOOL_ERROR otherwise.
>    */
> -int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
> -				    struct key_def *def)
> +int
> +tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
> +				struct key_def *def)
>   {
>   	assert(pCur);
>   	assert(pCur->curFlags & BTCF_TEphemCursor);
> @@ -381,7 +382,7 @@ int tarantoolSqlite3EphemeralCreate(BtCursor *pCur, uint32_t field_count,
>   		return SQL_TARANTOOL_ERROR;
>   	for (uint32_t part = 0; part < field_count; ++part) {
>   		struct coll *coll;
> -		if (part < def->part_count)
> +		if (def != NULL && part < def->part_count)
>   			coll = def->parts[part].coll;
>   		else
>   			coll = NULL;
> diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
> index 3455f52..1a2f3d0 100644
> --- a/src/box/sql/delete.c
> +++ b/src/box/sql/delete.c
> @@ -386,14 +386,9 @@ sqlite3DeleteFrom(Parse * pParse,	/* The parser context */
>   			iPk = pParse->nMem + 1;
>   			pParse->nMem += nPk;
>   			iEphCur = pParse->nTab++;
> -			struct key_def *def = key_def_new(nPk);
> -			if (def == NULL) {
> -				sqlite3OomFault(db);
> -				goto delete_from_cleanup;
> -			}
>   			addrEphOpen =
> -				sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEphCur,
> -						  nPk, 0, (char*)def, P4_KEYDEF);
> +				sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEphCur,
> +						  nPk);
>   		} else {
>   			pPk = sqlite3PrimaryKeyIndex(pTab);
>   			assert(pPk != 0);
> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index 5ada87e..54a7e4a 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -566,13 +566,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
>   			regRec = sqlite3GetTempReg(pParse);
>   			regCopy = sqlite3GetTempRange(pParse, nColumn);
>   			regTempId = sqlite3GetTempReg(pParse);
> -			struct key_def *def = key_def_new(nColumn + 1);
> -			if (def == NULL) {
> -				sqlite3OomFault(db);
> -				goto insert_cleanup;
> -			}
> -			sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, srcTab, nColumn+1,
> -					  0, (char*)def, P4_KEYDEF);
> +			sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, srcTab, nColumn+1);
>   			/* Create counter for rowid */
>   			sqlite3VdbeAddOp4Dup8(v, OP_Int64,
>   					      0 /* unused */,
> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index bd895bc..de111d3 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -2215,13 +2215,7 @@ generateWithRecursiveQuery(Parse * pParse,	/* Parsing context */
>   		VdbeComment((v, "Orderby table"));
>   		destQueue.pOrderBy = pOrderBy;
>   	} else {
> -		struct key_def *def = key_def_new(nCol + 1);
> -		if (def == NULL) {
> -			sqlite3OomFault(pParse->db);
> -			goto end_of_recursive_query;
> -		}
> -		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iQueue, nCol + 1, 0,
> -				  (char*)def, P4_KEYDEF);
> +		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iQueue, nCol + 1);
>   		VdbeComment((v, "Queue table"));
>   	}
>   	if (iDistinct) {
> @@ -2422,13 +2416,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
>   	if (dest.eDest == SRT_EphemTab) {
>   		assert(p->pEList);
>   		int nCols = p->pEList->nExpr;
> -		struct key_def *def = key_def_new(nCols + 1);
> -		if (def == NULL) {
> -			sqlite3OomFault(db);
> -			goto multi_select_end;
> -		}
> -		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, dest.iSDParm, nCols + 1,
> -				  0, (char*)def, P4_KEYDEF);
> +		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, dest.iSDParm, nCols + 1);
>   		VdbeComment((v, "Destination temp"));
>   		dest.eDest = SRT_Table;
>   	}
> @@ -5608,11 +5596,8 @@ sqlite3Select(Parse * pParse,		/* The parser context */
>   	/* If the output is destined for a temporary table, open that table.
>   	 */
>   	if (pDest->eDest == SRT_EphemTab) {
> -		struct key_def *def = key_def_new(pEList->nExpr + 1);
> -		if (def == NULL)
> -			sqlite3OomFault(db);
> -		sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, pDest->iSDParm,
> -				  pEList->nExpr + 1, 0, (char*)def, P4_KEYDEF);
> +		sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, pDest->iSDParm,
> +				  pEList->nExpr + 1);
>   
>   		VdbeComment((v, "Output table"));
>   	}
> diff --git a/src/box/sql/update.c b/src/box/sql/update.c
> index f3db92c..204adc2 100644
> --- a/src/box/sql/update.c
> +++ b/src/box/sql/update.c
> @@ -358,13 +358,8 @@ sqlite3Update(Parse * pParse,		/* The parser context */
>   	sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
>   
>   	if (isView) {
> -		struct key_def *def = key_def_new(nKey);
> -		if (def == NULL) {
> -			sqlite3OomFault(db);
> -			goto update_cleanup;
> -		}
> -		addrOpen = sqlite3VdbeAddOp4(v, OP_OpenTEphemeral, iEph,
> -					     nKey, 0, (char*)def, P4_KEYDEF);
> +		addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph,
> +					     nKey);
>   	} else {
>   		addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph, nPk);
>   		sql_vdbe_set_p4_key_def(pParse, pPk);
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index e27fe2f..455198d 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -3225,9 +3225,9 @@ open_cursor_set_hints:
>   /**
>    * Opcode: OpenTEphemeral P1 P2 * P4 *
>    * Synopsis:
> - * @param P1 index of new cursor to be created
> - * @param P2 number of columns in a new table
> - * @param P4 key def for new table
> + * @param P1 index of new cursor to be created.
> + * @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.
>    */
> @@ -3236,21 +3236,20 @@ case OP_OpenTEphemeral: {
>   	BtCursor *pBtCur;
>   	assert(pOp->p1 >= 0);
>   	assert(pOp->p2 > 0);
> -	assert(pOp->p4.key_def != NULL);
> -	assert(pOp->p4type == P4_KEYDEF);
> +	assert(pOp->p4type != P4_KEYDEF || pOp->p4.key_def != NULL);
>   
>   	pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_TARANTOOL);
>   	if (pCx == 0) goto no_mem;
>   	pCx->nullRow = 1;
>   
> -	pCx->key_def  = pOp->p4.key_def;
>   	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,
> -					     pCx->key_def);
> +					     pOp->p4.key_def);
> +	pCx->key_def = pCx->uc.pCursor->index->def->key_def;
>   	if (rc) goto abort_due_to_error;
>   	break;
>   }
> 

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

* [tarantool-patches] Re: [PATCH 0/2] sql: refactor DELETE STMT translation
  2018-05-16 15:24 [tarantool-patches] [PATCH 0/2] sql: refactor DELETE STMT translation Kirill Yukhin
  2018-05-16 15:24 ` [tarantool-patches] [PATCH 1/2] sql: allow key_def to be NULL for ephemeral create Kirill Yukhin
  2018-05-16 15:24 ` [tarantool-patches] [PATCH 2/2] sql: refactor delete routines Kirill Yukhin
@ 2018-05-18 11:01 ` Kirill Yukhin
  2 siblings, 0 replies; 17+ messages in thread
From: Kirill Yukhin @ 2018-05-18 11:01 UTC (permalink / raw)
  To: v.shpilevoy; +Cc: tarantool-patches

Hello,
On 16 мая 18:24, Kirill Yukhin wrote:
> Issue: https://github.com/tarantool/tarantool/issues/3235
> Branch: https://github.com/tarantool/tarantool/tree/kyukhin/gh-3235-refactor-delete-3
> 
> This two-patch set refactors SQL DELETE statements translation. It allows
> not to create dummy key_defs and passing NULL instead to OP_OpenTEphemeral.
> It also make box/sql/delete.c obey Tarantool coding style.
> 
> Part of #3235
> 
> Kirill Yukhin (2):
>   sql: allow key_def to be NULL for ephemeral create
>   sql: refactor delete routines
Both patches received OK, so I've checked them into 2.0 branch.

--
Regards, Kirill Yukhin

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

end of thread, other threads:[~2018-05-18 11:01 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-05-16 15:24 [tarantool-patches] [PATCH 0/2] sql: refactor DELETE STMT translation Kirill Yukhin
2018-05-16 15:24 ` [tarantool-patches] [PATCH 1/2] sql: allow key_def to be NULL for ephemeral create Kirill Yukhin
2018-05-17 15:49   ` [tarantool-patches] " Kirill Yukhin
2018-05-17 16:47     ` Vladislav Shpilevoy
2018-05-18  6:57       ` Kirill Yukhin
2018-05-18 10:33         ` Vladislav Shpilevoy
2018-05-18 10:48           ` Kirill Yukhin
2018-05-18 10:50             ` Vladislav Shpilevoy
2018-05-16 15:24 ` [tarantool-patches] [PATCH 2/2] sql: refactor delete routines Kirill Yukhin
2018-05-16 16:29   ` [tarantool-patches] " Kirill Yukhin
2018-05-17 14:23     ` Vladislav Shpilevoy
2018-05-17 15:48       ` Kirill Yukhin
2018-05-17 16:47         ` Vladislav Shpilevoy
2018-05-18  6:56           ` Kirill Yukhin
2018-05-18 10:33             ` Vladislav Shpilevoy
2018-05-17 15:18     ` Vladislav Shpilevoy
2018-05-18 11:01 ` [tarantool-patches] Re: [PATCH 0/2] sql: refactor DELETE STMT translation Kirill Yukhin

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