[tarantool-patches] Re: [PATCH 2/2] sql: refactor delete routines

Kirill Yukhin kyukhin at tarantool.org
Thu May 17 18:48:45 MSK 2018


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,




More information about the Tarantool-patches mailing list