[tarantool-patches] [PATCH 09/10] sql: disable ON CONFLICT actions for indexes

Nikita Pettik korablev at tarantool.org
Sun Aug 12 17:13:05 MSK 2018


After struggling with SQLite legacy ON CONFLICT actions, finally we
decided to remove them. Still, it is allowed to specify action on NULL
conflict and on whole query action (e.g. INSERT OR IGNORE).

This patch also fixes behaviour of INSERT/UPDATE OR REPLACE statement.
Now we are looking at each secondary index to find an entry with given
key in order to delete it (since original space:replace() provides real
replace only in PK). Almost the same happens for UPDATE OR IGNORE: as
far as UPDATE causes implicit deletion of old entry and insertion of
new one, in case of duplicates in secondary indexes we should omit
removal of old entry.

Finally, removed extra cursors allocations from UPDATE and INSERT
processing and moved the rest closer to their real usage.

Closes #3566
Closes #3565
Part of #3561
---
 src/box/sql.c                            |   7 -
 src/box/sql/build.c                      |  90 +---
 src/box/sql/insert.c                     | 827 ++++++++-----------------------
 src/box/sql/main.c                       |  45 +-
 src/box/sql/parse.y                      |  30 +-
 src/box/sql/sqliteInt.h                  | 131 +++--
 src/box/sql/update.c                     | 248 +++------
 src/box/sql/vdbe.c                       |   9 +-
 src/box/sql/vdbeaux.c                    |  11 +-
 src/box/sql/where.c                      |   5 -
 test/sql-tap/conflict3.test.lua          | 402 ---------------
 test/sql-tap/gh-2931-savepoints.test.lua |   2 +-
 test/sql-tap/gh2140-trans.test.lua       |  54 +-
 test/sql-tap/gh2964-abort.test.lua       |  11 +-
 test/sql-tap/index1.test.lua             | 111 +----
 test/sql-tap/null.test.lua               |   6 +-
 test/sql-tap/tkt-4a03edc4c8.test.lua     |   6 +-
 test/sql-tap/triggerC.test.lua           |   2 +-
 test/sql/on-conflict.result              | 134 +++--
 test/sql/on-conflict.test.lua            |  79 +--
 20 files changed, 550 insertions(+), 1660 deletions(-)
 delete mode 100755 test/sql-tap/conflict3.test.lua

diff --git a/src/box/sql.c b/src/box/sql.c
index de617c8b0..1f59946f0 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1497,13 +1497,6 @@ int tarantoolSqlite3MakeIdxOpts(SqliteIndex *index, const char *zSql, void *buf)
 	p = enc->encode_map(base, 2);
 	/* Mark as unique pk and unique indexes */
 	p = enc->encode_str(p, "unique", 6);
-	/* If user didn't defined ON CONFLICT OPTIONS, all uniqueness checks
-	 * will be made by Tarantool. However, Tarantool doesn't have ON
-	 * CONFLIT option, so in that case (except ON CONFLICT ABORT, which is
-	 * default behavior) uniqueness will be checked by SQL.
-	 * INSERT OR REPLACE/IGNORE uniqueness checks will be also done by
-	 * Tarantool.
-	 */
 	p = enc->encode_bool(p, sql_index_is_unique(index));
 	p = enc->encode_str(p, "sql", 3);
 	p = enc->encode_str(p, zSql, zSql ? strlen(zSql) : 0);
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 3ef9ea96e..9d7f0a4e0 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -115,52 +115,6 @@ sql_finish_coding(struct Parse *parse_context)
 	}
 }
 
-/**
- * This is a function which should be called during execution
- * of sqlite3EndTable. It set defaults for columns having no
- * separate NULL/NOT NULL specifiers and ensures that only
- * PRIMARY KEY constraint may have ON CONFLICT REPLACE clause.
- *
- * @param parser SQL Parser object.
- * @param table Space which should be checked.
- * @retval -1 on error. Parser SQL_TARANTOOL_ERROR is set.
- * @retval 0 on success.
- */
-static int
-actualize_on_conflict_actions(struct Parse *parser, struct Table *table)
-{
-	const char *err_msg = NULL;
-	struct field_def *field = table->def->fields;
-	struct Index *pk = sqlite3PrimaryKeyIndex(table);
-	for (uint32_t i = 0; i < table->def->field_count; ++i, ++field) {
-		if (field->nullable_action == ON_CONFLICT_ACTION_DEFAULT) {
-			/* Set default nullability NONE. */
-			field->nullable_action = ON_CONFLICT_ACTION_NONE;
-			field->is_nullable = true;
-		}
-		if (field->nullable_action == ON_CONFLICT_ACTION_REPLACE &&
-		    (pk == NULL || key_def_find(pk->def->key_def, i) == NULL))
-			goto non_pk_on_conflict_error;
-	}
-
-	for (struct Index *idx = table->pIndex; idx; idx = idx->pNext) {
-		if (idx->onError == ON_CONFLICT_ACTION_REPLACE &&
-		    !sql_index_is_primary(idx))
-			goto non_pk_on_conflict_error;
-	}
-
-	return 0;
-
-non_pk_on_conflict_error:
-	err_msg = tt_sprintf("only PRIMARY KEY constraint can have "
-			     "ON CONFLICT REPLACE clause - %s",
-			     table->def->name);
-	diag_set(ClientError, ER_SQL, err_msg);
-	parser->rc = SQL_TARANTOOL_ERROR;
-	parser->nErr++;
-	return -1;
-}
-
 /*
  * Locate the in-memory structure that describes a particular database
  * table given the name of that table. Return NULL if not found.
@@ -871,7 +825,6 @@ field_def_create_for_pk(struct Parse *parser, struct field_def *field,
 void
 sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 		     ExprList * pList,	/* List of field names to be indexed */
-		     int onError,	/* What to do with a uniqueness conflict */
 		     int autoInc,	/* True if the AUTOINCREMENT keyword is present */
 		     enum sort_order sortOrder
     )
@@ -926,7 +879,7 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 							     &token, 0));
 		if (list == NULL)
 			goto primary_key_exit;
-		sql_create_index(pParse, 0, 0, list, onError, 0, SORT_ORDER_ASC,
+		sql_create_index(pParse, 0, 0, list, 0, SORT_ORDER_ASC,
 				 false, SQL_INDEX_TYPE_CONSTRAINT_PK);
 		if (db->mallocFailed)
 			goto primary_key_exit;
@@ -935,8 +888,8 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 				"INTEGER PRIMARY KEY or INT PRIMARY KEY");
 		goto primary_key_exit;
 	} else {
-		sql_create_index(pParse, 0, 0, pList, onError, 0, sortOrder,
-				 false, SQL_INDEX_TYPE_CONSTRAINT_PK);
+		sql_create_index(pParse, 0, 0, pList, 0, sortOrder, false,
+				 SQL_INDEX_TYPE_CONSTRAINT_PK);
 		pList = 0;
 		if (pParse->nErr > 0)
 			goto primary_key_exit;
@@ -1669,8 +1622,19 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 		}
 	}
 
-	if (actualize_on_conflict_actions(pParse, p))
-		goto cleanup;
+	/*
+	 * Actualize conflict action for NOT NULL constraint.
+	 * Set defaults for columns having no separate
+	 * NULL/NOT NULL specifiers.
+	 */
+	struct field_def *field = p->def->fields;
+	for (uint32_t i = 0; i < p->def->field_count; ++i, ++field) {
+		if (field->nullable_action == ON_CONFLICT_ACTION_DEFAULT) {
+			/* Set default nullability NONE. */
+			field->nullable_action = ON_CONFLICT_ACTION_NONE;
+			field->is_nullable = true;
+		}
+	}
 
 	if (db->init.busy) {
 		/*
@@ -2738,9 +2702,8 @@ sql_index_is_unique(const struct Index *idx)
 void
 sql_create_index(struct Parse *parse, struct Token *token,
 		 struct SrcList *tbl_name, struct ExprList *col_list,
-		 enum on_conflict_action on_error, struct Token *start,
-		 enum sort_order sort_order, bool if_not_exist,
-		 enum sql_index_type idx_type) {
+		 struct Token *start, enum sort_order sort_order,
+		 bool if_not_exist, enum sql_index_type idx_type) {
 	/* The index to be created. */
 	struct Index *index = NULL;
 	/* Name of the index. */
@@ -2898,8 +2861,6 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		goto exit_create_index;
 
 	index->pTable = table;
-	index->onError = (u8) on_error;
-
 	/*
 	 * TODO: Issue a warning if two or more columns of the
 	 * index are identical.
@@ -3003,21 +2964,6 @@ sql_create_index(struct Parse *parse, struct Token *token,
 			if (k != key_def->part_count)
 				continue;
 
-			if (index->onError != existing_idx->onError) {
-				if (index->onError !=
-				    ON_CONFLICT_ACTION_DEFAULT &&
-				    existing_idx->onError !=
-				    ON_CONFLICT_ACTION_DEFAULT)
-					sqlite3ErrorMsg(parse,
-							"conflicting "\
-							"ON CONFLICT "\
-							"clauses specified");
-
-				if (existing_idx->onError ==
-				    ON_CONFLICT_ACTION_DEFAULT)
-					existing_idx->onError = index->onError;
-			}
-
 			bool is_named =
 				constraint_is_named(existing_idx->def->name);
 			/* CREATE TABLE t(a, UNIQUE(a), PRIMARY KEY(a)). */
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index cb120384a..271886ec6 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -272,10 +272,7 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 	char *zTab;		/* Name of the table into which we are inserting */
 	int i, j;		/* Loop counters */
 	Vdbe *v;		/* Generate code into this virtual machine */
-	Index *pIdx;		/* For looping over indices of the table */
 	int nColumn;		/* Number of columns in the data */
-	int iDataCur = 0;	/* VDBE cursor that is the main data repository */
-	int iIdxCur = 0;	/* First index cursor */
 	int endOfLoop;		/* Label for the end of the insertion loop */
 	int srcTab = 0;		/* Data comes from this temporary cursor if >=0 */
 	int addrInsTop = 0;	/* Jump to label "D" */
@@ -375,11 +372,14 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 	}
 #endif				/* SQLITE_OMIT_XFER_OPT */
 
-	/* Allocate registers for holding the tupleid of the new row,
-	 * the content of the new row, and the assembled row record.
+	/*
+	 * Allocate registers for holding the tupleid of the new
+	 * row (if it isn't required first register will contain
+	 * NULL), the content of the new row, and the assembled
+	 * row record.
 	 */
 	regTupleid = regIns = pParse->nMem + 1;
-	pParse->nMem += def->field_count + 1;
+	pParse->nMem += def->field_count + 2;
 	regData = regTupleid + 1;
 
 	/* If the INSERT statement included an IDLIST term, then make sure
@@ -550,27 +550,6 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 		regRowCount = ++pParse->nMem;
 		sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
 	}
-
-	/* If this is not a view, open the table and and all indices */
-	if (!is_view) {
-		int nIdx;
-		nIdx =
-		    sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0,
-					       -1, 0, &iDataCur, &iIdxCur,
-					       on_error, 0);
-
-		aRegIdx = sqlite3DbMallocRawNN(db, sizeof(int) * (nIdx + 1));
-		if (aRegIdx == 0) {
-			goto insert_cleanup;
-		}
-		for (i = 0, pIdx = pTab->pIndex; i < nIdx;
-		     pIdx = pIdx->pNext, i++) {
-			assert(pIdx);
-			aRegIdx[i] = ++pParse->nMem;
-			pParse->nMem += pIdx->def->key_def->part_count;
-		}
-	}
-
 	/* This is the top of the main insertion loop */
 	if (useTempTable) {
 		/* This block codes the top of loop only.  The complete loop is the
@@ -764,20 +743,20 @@ sqlite3Insert(Parse * pParse,	/* Parser context */
 			}
 		}
 
-		/* Generate code to check constraints and generate index keys
-		   and do the insertion.
+		/*
+		 * Generate code to check constraints and process
+		 * final insertion.
 		 */
-		int isReplace;	/* Set to true if constraints may cause a replace */
-		struct on_conflict on_conflict;
-		on_conflict.override_error = on_error;
-		on_conflict.optimized_action = ON_CONFLICT_ACTION_NONE;
-		sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur,
-						iIdxCur, regIns, 0,
-						true, &on_conflict,
-						endOfLoop, &isReplace, 0);
+		vdbe_emit_constraint_checks(pParse, pTab, regIns + 1,
+					    on_error, endOfLoop, 0);
 		fkey_emit_check(pParse, pTab, 0, regIns, 0);
-		vdbe_emit_insertion_completion(v, iIdxCur, aRegIdx[0],
-					       &on_conflict);
+		int pk_cursor = pParse->nTab++;
+		struct space *space = space_by_id(pTab->def->id);
+		assert(space != NULL);
+		vdbe_emit_open_cursor(pParse, pk_cursor, 0, space);
+		vdbe_emit_insertion_completion(v, pk_cursor, regIns + 1,
+					       pTab->def->field_count,
+					       on_error);
 	}
 
 	/* Update the count of rows that are inserted
@@ -874,646 +853,239 @@ checkConstraintUnchanged(Expr * pExpr, int *aiChng)
 	return !w.eCode;
 }
 
-/*
- * Generate code to do constraint checks prior to an INSERT or an UPDATE
- * on table pTab.
- *
- * The regNewData parameter is the first register in a range that contains
- * the data to be inserted or the data after the update.  There will be
- * pTab->def->field_count+1 registers in this range.  The first register (the
- * one that regNewData points to) will contain NULL.  The second register
- * in the range will contain the content of the first table column.
- * The third register will contain the content of the second table column.
- * And so forth.
- *
- * The regOldData parameter is similar to regNewData except that it contains
- * the data prior to an UPDATE rather than afterwards.  regOldData is zero
- * for an INSERT.  This routine can distinguish between UPDATE and INSERT by
- * checking regOldData for zero.
- *
- * For an UPDATE, the pkChng boolean is true if the primary key
- * might be modified by the UPDATE.  If pkChng is false, then the key of
- * the iDataCur content table is guaranteed to be unchanged by the UPDATE.
- *
- * On an INSERT, pkChng will only be true if the INSERT statement provides
- * an integer value for INTEGER PRIMARY KEY alias.
- *
- * The code generated by this routine will store new index entries into
- * registers identified by aRegIdx[].  No index entry is created for
- * indices where aRegIdx[i]==0.  The order of indices in aRegIdx[] is
- * the same as the order of indices on the linked list of indices
- * at pTab->pIndex.
- *
- * The caller must have already opened writeable cursors on the main
- * table and all applicable indices (that is to say, all indices for which
- * aRegIdx[] is not zero).  iDataCur is the cursor for the PRIMARY KEY index.
- * iIdxCur is the cursor for the first index in the pTab->pIndex list.
- * Cursors for other indices are at iIdxCur+N for the N-th element
- * of the pTab->pIndex list.
- *
- * This routine also generates code to check constraints.  NOT NULL,
- * CHECK, and UNIQUE constraints are all checked.  If a constraint fails,
- * then the appropriate action is performed.  There are five possible
- * actions: ROLLBACK, ABORT, FAIL, REPLACE, and IGNORE.
- *
- *  Constraint type  Action       What Happens
- *  ---------------  ----------   ----------------------------------------
- *  any              ROLLBACK     The current transaction is rolled back and
- *                                sqlite3_step() returns immediately with a
- *                                return code of SQLITE_CONSTRAINT.
- *
- *  any              ABORT        Back out changes from the current command
- *                                only (do not do a complete rollback) then
- *                                cause sqlite3_step() to return immediately
- *                                with SQLITE_CONSTRAINT.
- *
- *  any              FAIL         Sqlite3_step() returns immediately with a
- *                                return code of SQLITE_CONSTRAINT.  The
- *                                transaction is not rolled back and any
- *                                changes to prior rows are retained.
- *
- *  any              IGNORE       The attempt in insert or update the current
- *                                row is skipped, without throwing an error.
- *                                Processing continues with the next row.
- *                                (There is an immediate jump to ignoreDest.)
- *
- *  NOT NULL         REPLACE      The NULL value is replace by the default
- *                                value for that column.  If the default value
- *                                is NULL, the action is the same as ABORT.
- *
- *  UNIQUE           REPLACE      The other row that conflicts with the row
- *                                being inserted is removed.
- *
- *  CHECK            REPLACE      Illegal.  The results in an exception.
- *
- * Which action to take is determined by the override_error
- * parameter in struct on_conflict.
- * Or if override_error==ON_CONFLICT_ACTION_DEFAULT, then the
- * pParse->onError parameter is used.  Or if
- * pParse->onError==ON_CONFLICT_ACTION_DEFAULT then the on_error
- * value for the constraint is used.
- */
 void
-sqlite3GenerateConstraintChecks(Parse * pParse,		/* The parser context */
-				Table * pTab,		/* The table being inserted or updated */
-				int *aRegIdx,		/* Use register aRegIdx[i] for index i.  0 for unused */
-				int iDataCur,		/* Canonical data cursor (main table or PK index) */
-				int iIdxCur,		/* First index cursor */
-				int regNewData,		/* First register in a range holding values to insert */
-				int regOldData,		/* Previous content.  0 for INSERTs */
-				u8 pkChng,		/* Non-zero if the PRIMARY KEY changed */
-				struct on_conflict *on_conflict,
-				int ignoreDest,		/* Jump to this label on an ON_CONFLICT_ACTION_IGNORE resolution */
-				int *pbMayReplace,	/* OUT: Set to true if constraint may cause a replace */
-				int *aiChng)		/* column i is unchanged if aiChng[i]<0 */
+vdbe_emit_constraint_checks(struct Parse *parse_context, struct Table *tab,
+			    int new_tuple_reg,
+			    enum on_conflict_action on_conflict,
+			    int ignore_label, int *upd_cols)
 {
-	Vdbe *v;		/* VDBE under constrution */
-	Index *pIdx;		/* Pointer to one of the indices */
-	Index *pPk = 0;		/* The PRIMARY KEY index */
-	sqlite3 *db;		/* Database connection */
-	int addr1;		/* Address of jump instruction */
-	int seenReplace = 0;	/* True if REPLACE is used to resolve INT PK conflict */
-	u8 isUpdate;		/* True if this is an UPDATE operation */
-	u8 bAffinityDone = 0;	/* True if the OP_Affinity operation has been run */
 	struct session *user_session = current_session();
-
-	isUpdate = regOldData != 0;
-	db = pParse->db;
-	v = sqlite3GetVdbe(pParse);
-	assert(v != 0);
-	struct space_def *def = pTab->def;
-	/* This table is not a VIEW */
+	struct sqlite3 *db = parse_context->db;
+	struct Vdbe *v = sqlite3GetVdbe(parse_context);
+	assert(v != NULL);
+	struct space_def *def = tab->def;
+	/* Insertion into VIEW is prohibited. */
 	assert(!def->opts.is_view);
-
-	pPk = sqlite3PrimaryKeyIndex(pTab);
-
-	/* Record that this module has started */
-	VdbeModuleComment((v, "BEGIN: GenCnstCks(%d,%d,%d,%d,%d)",
-			   iDataCur, iIdxCur, regNewData, regOldData, pkChng));
-
-	enum on_conflict_action override_error = on_conflict->override_error;
-	enum on_conflict_action on_error;
-	/* Test all NOT NULL constraints.
-	 */
+	bool is_update = upd_cols != NULL;
+	/* Test all NOT NULL constraints. */
 	for (uint32_t i = 0; i < def->field_count; i++) {
-		if (aiChng && aiChng[i] < 0) {
-			/* Don't bother checking for NOT NULL on columns that do not change */
+		/*
+		 * Don't bother checking for NOT NULL on columns
+		 * that do not change.
+		 */
+		if (is_update && upd_cols[i] < 0)
 			continue;
-		}
-		if (def->fields[i].is_nullable || pTab->iAutoIncPKey == (int) i)
-			continue;	/* This column is allowed to be NULL */
-
-		on_error = table_column_nullable_action(pTab, i);
-		if (override_error != ON_CONFLICT_ACTION_DEFAULT)
-			on_error = override_error;
-		/* ABORT is a default error action */
-		if (on_error == ON_CONFLICT_ACTION_DEFAULT)
-			on_error = ON_CONFLICT_ACTION_ABORT;
-
-		struct Expr *dflt = NULL;
-		dflt = space_column_default_expr(pTab->def->id, i);
-		if (on_error == ON_CONFLICT_ACTION_REPLACE && dflt == 0)
-			on_error = ON_CONFLICT_ACTION_ABORT;
-
-		assert(on_error != ON_CONFLICT_ACTION_NONE);
-		switch (on_error) {
+		/* This column is allowed to be NULL. */
+		if (def->fields[i].is_nullable || tab->iAutoIncPKey == (int) i)
+			continue;
+		enum on_conflict_action on_conflict_nullable =
+			on_conflict != ON_CONFLICT_ACTION_DEFAULT ?
+			on_conflict : table_column_nullable_action(tab, i);
+		/* ABORT is a default error action. */
+		if (on_conflict_nullable == ON_CONFLICT_ACTION_DEFAULT)
+			on_conflict_nullable = ON_CONFLICT_ACTION_ABORT;
+		struct Expr *dflt = space_column_default_expr(tab->def->id, i);
+		if (on_conflict_nullable == ON_CONFLICT_ACTION_REPLACE &&
+		    dflt == NULL)
+			on_conflict_nullable = ON_CONFLICT_ACTION_ABORT;
+		switch (on_conflict_nullable) {
 		case ON_CONFLICT_ACTION_ABORT:
-			sqlite3MayAbort(pParse);
-			/* Fall through */
+			sqlite3MayAbort(parse_context);
 		case ON_CONFLICT_ACTION_ROLLBACK:
 		case ON_CONFLICT_ACTION_FAIL: {
-				char *zMsg =
-				    sqlite3MPrintf(db, "%s.%s", def->name,
-						   def->fields[i].name);
-				sqlite3VdbeAddOp3(v, OP_HaltIfNull,
-						  SQLITE_CONSTRAINT_NOTNULL,
-						  on_error, regNewData + 1 + i);
-				sqlite3VdbeAppendP4(v, zMsg, P4_DYNAMIC);
-				sqlite3VdbeChangeP5(v, P5_ConstraintNotNull);
-				VdbeCoverage(v);
-				break;
-			}
+			char *err_msg = sqlite3MPrintf(db, "%s.%s", def->name,
+						       def->fields[i].name);
+			sqlite3VdbeAddOp3(v, OP_HaltIfNull,
+					  SQLITE_CONSTRAINT_NOTNULL,
+					  on_conflict_nullable,
+					  new_tuple_reg + i);
+			sqlite3VdbeAppendP4(v, err_msg, P4_DYNAMIC);
+			sqlite3VdbeChangeP5(v, P5_ConstraintNotNull);
+			break;
+		}
 		case ON_CONFLICT_ACTION_IGNORE: {
-				sqlite3VdbeAddOp2(v, OP_IsNull,
-						  regNewData + 1 + i,
-						  ignoreDest);
-				VdbeCoverage(v);
-				break;
-			}
-		default:{
-				assert(on_error == ON_CONFLICT_ACTION_REPLACE);
-				addr1 =
-				    sqlite3VdbeAddOp1(v, OP_NotNull,
-						      regNewData + 1 + i);
-				VdbeCoverage(v);
-				sqlite3ExprCode(pParse, dflt,
-						regNewData + 1 + i);
-				sqlite3VdbeJumpHere(v, addr1);
-				break;
-			}
+			sqlite3VdbeAddOp2(v, OP_IsNull, new_tuple_reg + i,
+					  ignore_label);
+			break;
+		}
+		case ON_CONFLICT_ACTION_REPLACE: {
+			int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull,
+						  new_tuple_reg + i);
+			sqlite3ExprCode(parse_context, dflt,
+					new_tuple_reg + i);
+			sqlite3VdbeJumpHere(v, addr1);
+			break;
+		}
+		default:
+			unreachable();
 		}
 	}
-
 	/*
-	 * Get server checks.
-	 * Test all CHECK constraints.
+	 * For CHECK constraint and for INSERT/UPDATE conflict
+	 * action DEFAULT and ABORT in fact has the same meaning.
 	 */
-	ExprList *checks = space_checks_expr_list(pTab->def->id);
+	if (on_conflict == ON_CONFLICT_ACTION_DEFAULT)
+		on_conflict = ON_CONFLICT_ACTION_ABORT;
+	/* Test all CHECK constraints. */
+	struct ExprList *checks = space_checks_expr_list(tab->def->id);
 	if (checks != NULL &&
 	    (user_session->sql_flags & SQLITE_IgnoreChecks) == 0) {
-		pParse->ckBase = regNewData + 1;
-		if (override_error != ON_CONFLICT_ACTION_DEFAULT)
-			on_error = override_error;
-		else
-			on_error = ON_CONFLICT_ACTION_ABORT;
-
+		parse_context->ckBase = new_tuple_reg;
 		for (int i = 0; i < checks->nExpr; i++) {
-			int allOk;
-			Expr *pExpr = checks->a[i].pExpr;
-			if (aiChng
-			    && checkConstraintUnchanged(pExpr, aiChng))
+			struct Expr *expr = checks->a[i].pExpr;
+			if (is_update &&
+			    checkConstraintUnchanged(expr, upd_cols))
 				continue;
-			allOk = sqlite3VdbeMakeLabel(v);
-			sqlite3ExprIfTrue(pParse, pExpr, allOk,
+			int all_ok = sqlite3VdbeMakeLabel(v);
+			sqlite3ExprIfTrue(parse_context, expr, all_ok,
 					  SQLITE_JUMPIFNULL);
-			if (on_error == ON_CONFLICT_ACTION_IGNORE) {
-				sqlite3VdbeGoto(v, ignoreDest);
+			if (on_conflict == ON_CONFLICT_ACTION_IGNORE) {
+				sqlite3VdbeGoto(v, ignore_label);
 			} else {
-				char *zName = checks->a[i].zName;
-				if (zName == NULL)
-					zName = def->name;
-				if (on_error == ON_CONFLICT_ACTION_REPLACE)
-					on_error = ON_CONFLICT_ACTION_ABORT;
-				sqlite3HaltConstraint(pParse,
+				char *name = checks->a[i].zName;
+				if (name == NULL)
+					name = def->name;
+				enum on_conflict_action on_conflict_check =
+					on_conflict;
+				if (on_conflict == ON_CONFLICT_ACTION_REPLACE)
+					on_conflict_check =
+						ON_CONFLICT_ACTION_ABORT;
+				sqlite3HaltConstraint(parse_context,
 						      SQLITE_CONSTRAINT_CHECK,
-						      on_error, zName,
+						      on_conflict_check, name,
 						      P4_TRANSIENT,
 						      P5_ConstraintCheck);
 			}
-			sqlite3VdbeResolveLabel(v, allOk);
+			sqlite3VdbeResolveLabel(v, all_ok);
 		}
 	}
-
+	sql_emit_table_affinity(v, tab->def, new_tuple_reg);
 	/*
-	 * Test all UNIQUE constraints by creating entries for
-	 * each UNIQUE index and making sure that duplicate entries
-	 * do not already exist. Compute the revised record entries
-	 * for indices as we go.
-	 *
-	 * This loop also handles the case of the PRIMARY KEY index.
+	 * If PK is marked as INTEGER, use it as strict type,
+	 * not as affinity. Emit code for type checking.
+	 * FIXME: should be removed after introducing
+	 * strict typing.
 	 */
-	pIdx = pTab->pIndex;
-	for (int ix = 0; pIdx != NULL; pIdx = pIdx->pNext, ix++) {
-		int regIdx;	/* Range of registers hold conent for pIdx */
-		int regR;	/* Range of registers holding conflicting PK */
-		int iThisCur;	/* Cursor for this UNIQUE index */
-		int addrUniqueOk;	/* Jump here if the UNIQUE constraint is satisfied */
-		bool uniqueByteCodeNeeded = false;
-
-		/*
-		 * ABORT and DEFAULT error actions can be handled
-		 * by Tarantool facilitites without emitting VDBE
-		 * bytecode.
-		 */
-		if ((pIdx->onError != ON_CONFLICT_ACTION_ABORT &&
-		     pIdx->onError != ON_CONFLICT_ACTION_DEFAULT) ||
-		    (override_error != ON_CONFLICT_ACTION_ABORT &&
-		     override_error != ON_CONFLICT_ACTION_DEFAULT)) {
-			uniqueByteCodeNeeded = true;
-		}
-
-		if (aRegIdx[ix] == 0)
-			continue;	/* Skip indices that do not change */
-		if (bAffinityDone == 0) {
-			sql_emit_table_affinity(v, pTab->def, regNewData + 1);
-			bAffinityDone = 1;
-		}
-		iThisCur = iIdxCur + ix;
-		addrUniqueOk = sqlite3VdbeMakeLabel(v);
-
-		/* Create a record for this index entry as it should appear after
-		 * the insert or update.  Store that record in the aRegIdx[ix] register
-		 */
-		regIdx = aRegIdx[ix] + 1;
-		uint32_t part_count = pIdx->def->key_def->part_count;
-		if (uniqueByteCodeNeeded) {
-			for (uint32_t i = 0; i < part_count; ++i) {
-				uint32_t fieldno =
-					pIdx->def->key_def->parts[i].fieldno;
-				int reg;
-				/*
-				 * OP_SCopy copies value in
-				 * separate register, which later
-				 * will be used by OP_NoConflict.
-				 * But OP_NoConflict is necessary
-				 * only in cases when bytecode is
-				 * needed for proper UNIQUE
-				 * constraint handling.
-				 */
-				reg = fieldno + regNewData + 1;
-				sqlite3VdbeAddOp2(v, OP_SCopy, reg, regIdx + i);
-				VdbeComment((v, "%s",
-					    def->fields[fieldno].name));
+	struct Index *pk = sqlite3PrimaryKeyIndex(tab);
+	uint32_t part_count = pk->def->key_def->part_count;
+	if (part_count == 1) {
+		uint32_t fieldno = pk->def->key_def->parts[0].fieldno;
+		int reg_pk = new_tuple_reg + fieldno;
+		if (tab->def->fields[fieldno].affinity == AFFINITY_INTEGER) {
+			int skip_if_null = sqlite3VdbeMakeLabel(v);
+			if (tab->iAutoIncPKey >= 0) {
+				sqlite3VdbeAddOp2(v, OP_IsNull, reg_pk,
+						  skip_if_null);
 			}
+			sqlite3VdbeAddOp2(v, OP_MustBeInt, reg_pk, 0);
+			sqlite3VdbeResolveLabel(v, skip_if_null);
 		}
-
-		bool table_ipk_autoinc = false;
-		int reg_pk = -1;
-		if (sql_index_is_primary(pIdx)) {
-			/* If PK is marked as INTEGER, use it as strict type,
-			 * not as affinity. Emit code for type checking */
-			if (part_count == 1) {
-				uint32_t fieldno =
-					pIdx->def->key_def->parts[0].fieldno;
-				reg_pk = regNewData + 1 + fieldno;
-
-				if (pTab->def->fields[fieldno].affinity ==
-				    AFFINITY_INTEGER) {
-					int skip_if_null = sqlite3VdbeMakeLabel(v);
-					if (pTab->iAutoIncPKey >= 0) {
-						sqlite3VdbeAddOp2(v, OP_IsNull,
-								  reg_pk,
-								  skip_if_null);
-						table_ipk_autoinc = true;
-					}
-					sqlite3VdbeAddOp2(v, OP_MustBeInt,
-							  reg_pk,
-							  0);
-					sqlite3VdbeResolveLabel(v, skip_if_null);
-				}
-			}
-
-			sqlite3VdbeAddOp3(v, OP_MakeRecord, regNewData + 1,
-					  def->field_count, aRegIdx[ix]);
-			VdbeComment((v, "for %s", pIdx->def->name));
-		}
-
-		/* In an UPDATE operation, if this index is the PRIMARY KEY
-		 * index and there has been no change the primary key, then no
-		 * collision is possible.  The collision detection
-		 * logic below can all be skipped.
-		 */
-		if (isUpdate && pPk == pIdx && pkChng == 0) {
-			sqlite3VdbeResolveLabel(v, addrUniqueOk);
+	}
+	/*
+	 * Other actions except for REPLACE and UPDATE OR IGNORE
+	 * can be handled by setting appropriate flag in OP_Halt.
+	 */
+	if (!(on_conflict == ON_CONFLICT_ACTION_IGNORE && is_update) &&
+	    on_conflict != ON_CONFLICT_ACTION_REPLACE)
+		return;
+	/* Calculate MAX range of register we may occupy. */
+	uint32_t reg_count = 0;
+	for (struct Index *idx = tab->pIndex; idx != NULL; idx = idx->pNext) {
+		if (idx->def->key_def->part_count > reg_count)
+			reg_count = idx->def->key_def->part_count;
+	}
+	int idx_key_reg = ++parse_context->nMem;
+	parse_context->nMem += reg_count;
+	/*
+	 * To handle INSERT OR REPLACE statement we should check
+	 * all unique secondary indexes on containing entry with
+	 * the same key. If index contains it, we must invoke
+	 * ON DELETE trigger and remove entry.
+	 * For UPDATE OR IGNORE we must check that no entries
+	 * exist in indexes which contain updated columns.
+	 * Otherwise, we should skip removal of old entry and
+	 * insertion of new one.
+	 */
+	for (struct Index *idx = tab->pIndex; idx != NULL; idx = idx->pNext) {
+		/* Conflicts may occur only in UNIQUE indexes. */
+		if (! sql_index_is_unique(idx))
 			continue;
-		}
-		if (!sql_index_is_unique(pIdx)) {
-			sqlite3VdbeResolveLabel(v, addrUniqueOk);
+		if (on_conflict == ON_CONFLICT_ACTION_IGNORE) {
+			/*
+			 * We are interested only in indexes which
+			 * contain updated columns.
+			 */
+			struct key_def *kd = idx->def->key_def;
+			for (uint32_t i = 0; i < kd->part_count; ++i) {
+				if (upd_cols[kd->parts[i].fieldno] >= 0)
+					goto process_index;
+				}
 			continue;
 		}
-
-		on_error = pIdx->onError;
+process_index: ;
+		int cursor = parse_context->nTab++;
+		vdbe_emit_open_cursor(parse_context, cursor, idx->def->iid,
+				      space_by_id(tab->def->id));
 		/*
-		 * If we are doing INSERT OR IGNORE,
-		 * INSERT OR FAIL, then error action will
-		 * be the same for all space indexes and
-		 * uniqueness can be ensured by Tarantool.
+		 * If there is no conflict in current index, just
+		 * jump to the start of next iteration. Label is
+		 * used for REPLACE action only.
 		 */
-		if (override_error == ON_CONFLICT_ACTION_FAIL ||
-		    override_error == ON_CONFLICT_ACTION_IGNORE ||
-		    override_error == ON_CONFLICT_ACTION_ABORT) {
-			sqlite3VdbeResolveLabel(v, addrUniqueOk);
-			continue;
-		}
-
-		if (override_error != ON_CONFLICT_ACTION_DEFAULT)
-			on_error = override_error;
-		/* ABORT is a default error action */
-		if (on_error == ON_CONFLICT_ACTION_DEFAULT)
-			on_error = ON_CONFLICT_ACTION_ABORT;
-
+		int skip_index = sqlite3VdbeMakeLabel(v);
 		/*
-		 * Collision detection may be omitted if all of
-		 * the following are true:
-		 *   (1) The conflict resolution algorithm is
-		 *       REPLACE or IGNORE.
-		 *   (2) There are no secondary indexes on the
-		 *       table.
-		 *   (3) No delete triggers need to be fired if
-		 *       there is a conflict.
-		 *   (4) No FK constraint counters need to be
-		 *       updated if a conflict occurs.
+		 * Copy index key to continuous range of
+		 * registers. Initially whole tuple is located at
+		 * [new_tuple_reg ... new_tuple_reg + field_count]
+		 * We are copying key to [reg ... reg + part_count]
 		 */
-		bool no_secondary_indexes = (ix == 0 &&
-					     pIdx->pNext == 0);
-		bool proper_error_action =
-			(on_error == ON_CONFLICT_ACTION_REPLACE ||
-			 on_error == ON_CONFLICT_ACTION_IGNORE);
-		bool no_delete_triggers =
-			(user_session->sql_flags & SQLITE_RecTriggers) == 0 ||
-			sql_triggers_exist(pTab, TK_DELETE, NULL, NULL) == NULL;
-		struct space *space = space_by_id(pTab->def->id);
-		assert(space != NULL);
-		bool no_foreign_keys =
-			(user_session->sql_flags & SQLITE_ForeignKeys) == 0 ||
-			(rlist_empty(&space->child_fkey) &&
-			 ! rlist_empty(&space->parent_fkey));
-
-		if (no_secondary_indexes && no_foreign_keys &&
-		    proper_error_action && no_delete_triggers) {
-			/*
-			 * Save that possible optimized error
-			 * action, which can be used later
-			 * during execution of
-			 * vdbe_emit_insertion_completion().
-			 */
-			on_conflict->optimized_action = on_error;
-			sqlite3VdbeResolveLabel(v, addrUniqueOk);
-			continue;
-		}
-
-		/* Check to see if the new index entry will be unique */
-		if (table_ipk_autoinc)
-			sqlite3VdbeAddOp2(v, OP_IsNull,
-					  reg_pk,
-					  addrUniqueOk);
-
-		if (uniqueByteCodeNeeded) {
-			sqlite3VdbeAddOp4Int(v, OP_NoConflict, iThisCur,
-					     addrUniqueOk, regIdx,
-					     pIdx->def->key_def->part_count);
+		uint32_t part_count = idx->def->key_def->part_count;
+		for (uint32_t i = 0; i < part_count; ++i) {
+			uint32_t fieldno = idx->def->key_def->parts[i].fieldno;
+			int reg = fieldno + new_tuple_reg;
+			sqlite3VdbeAddOp2(v, OP_SCopy, reg, idx_key_reg + i);
 		}
-		VdbeCoverage(v);
-
-		uint32_t pk_part_count = pPk->def->key_def->part_count;
-		/* Generate code to handle collisions */
-		regR = pIdx == pPk ? regIdx :
-		       sqlite3GetTempRange(pParse, pk_part_count);
-		if (isUpdate || on_error == ON_CONFLICT_ACTION_REPLACE) {
-			int x;
-			/* Extract the PRIMARY KEY from the end of the index entry and
-			 * store it in registers regR..regR+nPk-1
-			 */
-			if (pIdx != pPk) {
-				for (uint32_t i = 0; i < pk_part_count; i++) {
-					x = pPk->def->key_def->parts[i].fieldno;
-					sqlite3VdbeAddOp3(v, OP_Column,
-							  iThisCur, x, regR + i);
-					VdbeComment((v, "%s.%s", def->name,
-						     def->fields[x].name));
-				}
-			}
-			if (isUpdate && uniqueByteCodeNeeded) {
-				/* Only conflict if the new PRIMARY KEY
-				 * values are actually different from the old.
-				 *
-				 * For a UNIQUE index, only conflict if the PRIMARY KEY values
-				 * of the matched index row are different from the original PRIMARY
-				 * KEY values of this row before the update.
-				 */
-				int addrJump = sqlite3VdbeCurrentAddr(v) +
-					       pk_part_count;
-				int op = OP_Ne;
-				int regCmp = sql_index_is_primary(pIdx) ?
-					     regIdx : regR;
-				struct key_part *part =
-					pPk->def->key_def->parts;
-				for (uint32_t i = 0; i < pk_part_count;
-				     ++i, ++part) {
-					char *p4 = (char *) part->coll;
-					x = part->fieldno;
-					if (pTab->def->id == 0)
-						x = -1;
-					if (i == (pk_part_count - 1)) {
-						addrJump = addrUniqueOk;
-						op = OP_Eq;
-					}
-					sqlite3VdbeAddOp4(v, op,
-							  regOldData + 1 + x,
-							  addrJump, regCmp + i,
-							  p4, P4_COLLSEQ);
-					sqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
-					VdbeCoverageIf(v, op == OP_Eq);
-					VdbeCoverageIf(v, op == OP_Ne);
-				}
-			}
-		}
-
-		/*
-		 * Generate bytecode which executes when entry is
-		 * not unique for constraints with following
-		 * error actions:
-		 * 1) ROLLBACK.
-		 * 2) FAIL.
-		 * 3) IGNORE.
-		 * 4) REPLACE.
-		 *
-		 * ON CONFLICT ABORT is a default error action
-		 * for constraints and therefore can be handled
-		 * by Tarantool facilities.
-		 */
-		assert(on_error != ON_CONFLICT_ACTION_NONE);
-		switch (on_error) {
-		case ON_CONFLICT_ACTION_FAIL:
-		case ON_CONFLICT_ACTION_ROLLBACK:
-			parser_emit_unique_constraint(pParse, on_error, pIdx);
-			break;
-		case ON_CONFLICT_ACTION_ABORT:
-			break;
-		case ON_CONFLICT_ACTION_IGNORE:
-			sqlite3VdbeGoto(v, ignoreDest);
-			break;
-		default: {
-			struct sql_trigger *trigger = NULL;
-			assert(on_error == ON_CONFLICT_ACTION_REPLACE);
-			sql_set_multi_write(pParse, true);
-			if (user_session->sql_flags & SQLITE_RecTriggers) {
-				trigger = sql_triggers_exist(pTab, TK_DELETE,
-							      NULL, NULL);
-			}
-			sql_generate_row_delete(pParse, pTab, trigger,
-						iDataCur, regR, pk_part_count,
+		if (on_conflict == ON_CONFLICT_ACTION_IGNORE) {
+			sqlite3VdbeAddOp4Int(v, OP_Found, cursor,
+					     ignore_label, idx_key_reg,
+					     part_count);
+		} else {
+			assert(on_conflict == ON_CONFLICT_ACTION_REPLACE);
+			sqlite3VdbeAddOp4Int(v, OP_NoConflict, cursor,
+					     skip_index, idx_key_reg,
+					     part_count);
+			sql_set_multi_write(parse_context, true);
+			struct sql_trigger *trigger =
+				sql_triggers_exist(tab, TK_DELETE, NULL, NULL);
+			sql_generate_row_delete(parse_context, tab, trigger,
+						cursor, idx_key_reg, part_count,
 						false,
 						ON_CONFLICT_ACTION_REPLACE,
-						pIdx == pPk ? ONEPASS_SINGLE :
-						ONEPASS_OFF, -1);
-			seenReplace = 1;
-			break;
+						ONEPASS_SINGLE, -1);
+			sqlite3VdbeResolveLabel(v, skip_index);
 		}
-		}
-		sqlite3VdbeResolveLabel(v, addrUniqueOk);
-		if (regR != regIdx)
-			sqlite3ReleaseTempRange(pParse, regR, pk_part_count);
 	}
-
-	*pbMayReplace = seenReplace;
-	VdbeModuleComment((v, "END: GenCnstCks(%d)", seenReplace));
 }
 
 void
-vdbe_emit_insertion_completion(Vdbe *v, int cursor_id, int tuple_id,
-			       struct on_conflict *on_conflict)
+vdbe_emit_insertion_completion(struct Vdbe *v, int cursor_id, int raw_data_reg,
+			       uint32_t tuple_len,
+			       enum on_conflict_action on_conflict)
 {
 	assert(v != NULL);
-	int opcode;
-	enum on_conflict_action override_error = on_conflict->override_error;
-	enum on_conflict_action optimized_action =
-						on_conflict->optimized_action;
-
-	if (override_error == ON_CONFLICT_ACTION_REPLACE ||
-	    (optimized_action == ON_CONFLICT_ACTION_REPLACE &&
-	     override_error == ON_CONFLICT_ACTION_DEFAULT))
-		opcode = OP_IdxReplace;
-	else
-		opcode = OP_IdxInsert;
-
+	int opcode = OP_IdxInsert;
 	u16 pik_flags = OPFLAG_NCHANGE;
-	if (override_error == ON_CONFLICT_ACTION_IGNORE)
-		pik_flags |= OPFLAG_OE_IGNORE;
-	else if (override_error == ON_CONFLICT_ACTION_IGNORE ||
-		 (optimized_action == ON_CONFLICT_ACTION_IGNORE &&
-		  override_error == ON_CONFLICT_ACTION_DEFAULT))
+	if (on_conflict == ON_CONFLICT_ACTION_IGNORE)
 		pik_flags |= OPFLAG_OE_IGNORE;
-	else if (override_error == ON_CONFLICT_ACTION_FAIL)
+	else if (on_conflict == ON_CONFLICT_ACTION_FAIL)
 		pik_flags |= OPFLAG_OE_FAIL;
-
-	sqlite3VdbeAddOp2(v, opcode, cursor_id, tuple_id);
+	else if (on_conflict == ON_CONFLICT_ACTION_ROLLBACK)
+		pik_flags |= OPFLAG_OE_ROLLBACK;
+	sqlite3VdbeAddOp3(v, OP_MakeRecord, raw_data_reg, tuple_len,
+			  raw_data_reg + tuple_len);
+	sqlite3VdbeAddOp2(v, opcode, cursor_id, raw_data_reg + tuple_len);
 	sqlite3VdbeChangeP5(v, pik_flags);
 }
 
-/*
- * Allocate cursors for the pTab table and all its indices and generate
- * code to open and initialized those cursors.
- *
- * The cursor for the object that contains the complete data (index)
- * is returned in *piDataCur.  The first index cursor is
- * returned in *piIdxCur.  The number of indices is returned.
- *
- * Use iBase as the first cursor (the first index) if it is non-negative.
- * If iBase is negative, then allocate the next available cursor.
- *
- * *piDataCur will be somewhere in the range
- * of *piIdxCurs, depending on where the PRIMARY KEY index appears on the
- * pTab->pIndex list.
- */
-int
-sqlite3OpenTableAndIndices(Parse * pParse,	/* Parsing context */
-			   Table * pTab,	/* Table to be opened */
-			   int op,		/* OP_OpenRead or OP_OpenWrite */
-			   u8 p5,		/* P5 value for OP_Open* opcodes */
-			   int iBase,		/* Use this for the table cursor, if there is one */
-			   u8 * aToOpen,	/* If not NULL: boolean for each table and index */
-			   int *piDataCur,	/* Write the database source cursor number here */
-			   int *piIdxCur, 	/* Write the first index cursor number here */
-			   u8 overrideError,	/* Override error action for indexes */
-			   u8 isUpdate)		/* Opened for udpate or not */
-{
-	int i;
-	int iDataCur;
-	Index *pIdx;
-	Vdbe *v;
-
-	assert(op == OP_OpenRead || op == OP_OpenWrite);
-	assert(op == OP_OpenWrite || p5 == 0);
-	v = sqlite3GetVdbe(pParse);
-	assert(v != 0);
-	if (iBase < 0)
-		iBase = pParse->nTab;
-	iDataCur = iBase++;
-	if (piDataCur)
-		*piDataCur = iDataCur;
-	if (piIdxCur)
-		*piIdxCur = iBase;
-	struct space *space = space_by_id(pTab->def->id);
-	assert(space != NULL);
-	/* One iteration of this cycle adds OpenRead/OpenWrite which
-	 * opens cursor for current index.
-	 *
-	 * For update cursor on index is required, however if insertion
-	 * is done by Tarantool only, cursor is not needed so don't
-	 * open it.
-	 */
-	for (i = 0, pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext, i++) {
-		/* Cursor is needed:
-		 * 1) For indexes in UPDATE statement
-		 * 2) For PRIMARY KEY index
-		 * 3) For table mentioned in FOREIGN KEY constraint
-		 * 4) For an index which has ON CONFLICT action which require
-		 *    VDBE bytecode - ROLLBACK, IGNORE, FAIL, REPLACE:
-		 *    1. If user specified non-default ON CONFLICT (not
-		 *       ON_CONFLICT_ACTION_NONE or _Abort) clause
-		 *       for an non-primary unique index, then bytecode is needed
-		 *    	 for proper error action.
-		 *    2. INSERT/UPDATE OR IGNORE/ABORT/FAIL/REPLACE -
-		 *       Tarantool is able handle by itself.
-		 *       INSERT/UPDATE OR ROLLBACK - sql
-		 *       bytecode is needed in this case.
-		 *
-		 * If all conditions from list above are false then skip
-		 * iteration and don't open new index cursor
-		 */
-
-		if (isUpdate ||
-		    ! rlist_empty(&space->parent_fkey) ||
-		    sql_index_is_primary(pIdx) ||
-		    /* Condition 4 */
-		    (pIdx->def->opts.is_unique &&
-		     pIdx->onError != ON_CONFLICT_ACTION_DEFAULT &&
-		     /* Condition 4.1 */
-		     pIdx->onError != ON_CONFLICT_ACTION_ABORT) ||
-		     /* Condition 4.2 */
-		     overrideError == ON_CONFLICT_ACTION_ROLLBACK) {
-
-			int iIdxCur = iBase++;
-			if (sql_index_is_primary(pIdx)) {
-				if (piDataCur)
-					*piDataCur = iIdxCur;
-				p5 = 0;
-			}
-			if (aToOpen == 0 || aToOpen[i + 1]) {
-				sqlite3VdbeAddOp4(v, op, iIdxCur, pIdx->def->iid,
-						  0, (void *) space,
-						  P4_SPACEPTR);
-				sqlite3VdbeChangeP5(v, p5);
-				VdbeComment((v, "%s", pIdx->def->name));
-			}
-		}
-	}
-	if (iBase > pParse->nTab)
-		pParse->nTab = iBase;
-	return i;
-}
-
 #ifdef SQLITE_TEST
 /*
  * The following global variable is incremented whenever the
@@ -1545,9 +1117,6 @@ xferCompatibleIndex(Index * pDest, Index * pSrc)
 	uint32_t src_idx_part_count = pSrc->def->key_def->part_count;
 	if (dest_idx_part_count != src_idx_part_count)
 		return 0;
-	if (pDest->onError != pSrc->onError) {
-		return 0;	/* Different conflict resolution strategies */
-	}
 	struct key_part *src_part = pSrc->def->key_def->parts;
 	struct key_part *dest_part = pDest->def->key_def->parts;
 	for (uint32_t i = 0; i < src_idx_part_count;
diff --git a/src/box/sql/main.c b/src/box/sql/main.c
index a9a0385d6..69b2fec80 100644
--- a/src/box/sql/main.c
+++ b/src/box/sql/main.c
@@ -726,10 +726,9 @@ sqlite3_close_v2(sqlite3 * db)
  * but are "saved" in case the table pages are moved around.
  */
 void
-sqlite3RollbackAll(Vdbe * pVdbe, int tripCode)
+sqlite3RollbackAll(Vdbe * pVdbe)
 {
 	sqlite3 *db = pVdbe->db;
-	(void)tripCode;
 
 	/* If one has been configured, invoke the rollback-hook callback */
 	if (db->xRollbackCallback && (!pVdbe->auto_commit)) {
@@ -761,9 +760,6 @@ sqlite3ErrName(int rc)
 		case SQLITE_ABORT:
 			zName = "SQLITE_ABORT";
 			break;
-		case SQLITE_ABORT_ROLLBACK:
-			zName = "SQLITE_ABORT_ROLLBACK";
-			break;
 		case SQLITE_BUSY:
 			zName = "SQLITE_BUSY";
 			break;
@@ -963,20 +959,9 @@ sqlite3ErrStr(int rc)
 		/* SQL_TARANTOOL_ERROR */ "SQL-/Tarantool error",
 	};
 	const char *zErr = "unknown error";
-	switch (rc) {
-	case SQLITE_ABORT_ROLLBACK:{
-			zErr = "abort due to ROLLBACK";
-			break;
-		}
-	default:{
-			rc &= 0xff;
-			if (ALWAYS(rc >= 0) && rc < ArraySize(aMsg)
-			    && aMsg[rc] != 0) {
-				zErr = aMsg[rc];
-			}
-			break;
-		}
-	}
+	rc &= 0xff;
+	if (ALWAYS(rc >= 0) && rc < ArraySize(aMsg) && aMsg[rc] != 0)
+		zErr = aMsg[rc];
 	return zErr;
 }
 
@@ -999,28 +984,6 @@ sqliteDefaultBusyCallback(void *ptr,	/* Database connection */
 	return 1;
 }
 
-/*
- * Invoke the given busy handler.
- *
- * This routine is called when an operation failed with a lock.
- * If this routine returns non-zero, the lock is retried.  If it
- * returns 0, the operation aborts with an SQLITE_BUSY error.
- */
-int
-sqlite3InvokeBusyHandler(BusyHandler * p)
-{
-	int rc;
-	if (NEVER(p == 0) || p->xFunc == 0 || p->nBusy < 0)
-		return 0;
-	rc = p->xFunc(p->pArg, p->nBusy);
-	if (rc == 0) {
-		p->nBusy = -1;
-	} else {
-		p->nBusy++;
-	}
-	return rc;
-}
-
 /*
  * This routine sets the busy callback for an Sqlite database to the
  * given callback function with the given argument.
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 66ad84ce2..e0e2a4ea1 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -280,11 +280,11 @@ ccons ::= NULL onconf(R).        {
         sql_column_add_nullable_action(pParse, R);
 }
 ccons ::= NOT NULL onconf(R).    {sql_column_add_nullable_action(pParse, R);}
-ccons ::= PRIMARY KEY sortorder(Z) onconf(R) autoinc(I).
-                                 {sqlite3AddPrimaryKey(pParse,0,R,I,Z);}
-ccons ::= UNIQUE index_onconf(R). {sql_create_index(pParse,0,0,0,R,0,
-						   SORT_ORDER_ASC, false,
-						   SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
+ccons ::= PRIMARY KEY sortorder(Z) autoinc(I).
+                                 {sqlite3AddPrimaryKey(pParse,0,I,Z);}
+ccons ::= UNIQUE.                {sql_create_index(pParse,0,0,0,0,
+                                                   SORT_ORDER_ASC, false,
+                                                   SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
 ccons ::= CHECK LP expr(X) RP.   {sql_add_check_constraint(pParse,&X);}
 ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R).
                                  {sql_create_foreign_key(pParse, NULL, NULL, NULL, &T, TA, false, R);}
@@ -334,12 +334,12 @@ conslist ::= tcons.
 tconscomma ::= COMMA.            {pParse->constraintName.n = 0;}
 tconscomma ::= .
 tcons ::= CONSTRAINT nm(X).      {pParse->constraintName = X;}
-tcons ::= PRIMARY KEY LP sortlist(X) autoinc(I) RP onconf(R).
-                                 {sqlite3AddPrimaryKey(pParse,X,R,I,0);}
-tcons ::= UNIQUE LP sortlist(X) RP index_onconf(R).
-                                 {sql_create_index(pParse,0,0,X,R,0,
-						   SORT_ORDER_ASC,false,
-						   SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
+tcons ::= PRIMARY KEY LP sortlist(X) autoinc(I) RP.
+                                 {sqlite3AddPrimaryKey(pParse,X,I,0);}
+tcons ::= UNIQUE LP sortlist(X) RP.
+                                 {sql_create_index(pParse,0,0,X,0,
+                                                   SORT_ORDER_ASC,false,
+                                                   SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
 tcons ::= CHECK LP expr(E) RP onconf.
                                  {sql_add_check_constraint(pParse,&E);}
 tcons ::= FOREIGN KEY LP eidlist(FA) RP
@@ -357,8 +357,6 @@ defer_subclause_opt(A) ::= defer_subclause(A).
 %type index_onconf {int}
 %type orconf {int}
 %type resolvetype {int}
-index_onconf(A) ::= .                           {A = ON_CONFLICT_ACTION_DEFAULT;}
-index_onconf(A) ::= ON CONFLICT resolvetype(X). {A = X;}
 onconf(A) ::= .                              {A = ON_CONFLICT_ACTION_ABORT;}
 onconf(A) ::= ON CONFLICT resolvetype(X).    {A = X;}
 orconf(A) ::= .                              {A = ON_CONFLICT_ACTION_DEFAULT;}
@@ -1200,10 +1198,8 @@ paren_exprlist(A) ::= LP exprlist(X) RP.  {A = X;}
 //
 cmd ::= createkw(S) uniqueflag(U) INDEX ifnotexists(NE) nm(X)
         ON nm(Y) LP sortlist(Z) RP. {
-  enum on_conflict_action on_error =
-          U ? ON_CONFLICT_ACTION_ABORT : ON_CONFLICT_ACTION_NONE;
-  sql_create_index(pParse, &X, sqlite3SrcListAppend(pParse->db,0,&Y), Z,
-                   on_error, &S, SORT_ORDER_ASC, NE, U);
+  sql_create_index(pParse, &X, sqlite3SrcListAppend(pParse->db,0,&Y), Z, &S,
+                   SORT_ORDER_ASC, NE, U);
 }
 
 %type uniqueflag {int}
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 0ffc8d548..45dab0be4 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -649,7 +649,6 @@ sqlite3_exec(sqlite3 *,	/* An open database */
 #define SQLITE_IOERR_GETTEMPPATH       (SQLITE_IOERR | (25<<8))
 #define SQLITE_IOERR_CONVPATH          (SQLITE_IOERR | (26<<8))
 #define SQLITE_IOERR_VNODE             (SQLITE_IOERR | (27<<8))
-#define SQLITE_ABORT_ROLLBACK          (SQLITE_ABORT | (2<<8))
 #define SQLITE_CONSTRAINT_CHECK        (SQLITE_CONSTRAINT | (1<<8))
 #define SQLITE_CONSTRAINT_FOREIGNKEY   (SQLITE_CONSTRAINT | (3<<8))
 #define SQLITE_CONSTRAINT_FUNCTION     (SQLITE_CONSTRAINT | (4<<8))
@@ -675,30 +674,6 @@ enum sql_subtype {
 	SQL_SUBTYPE_MSGPACK = 77,
 };
 
-/**
- * Structure for internal usage during INSERT/UPDATE
- * statements compilation.
- */
-struct on_conflict {
-	/**
-	 * Represents an error action in queries like
-	 * INSERT/UPDATE OR <override_error>, which
-	 * overrides all space constraints error actions.
-	 * That kind of error action is strictly specified by
-	 * user and therefore have highest priority.
-	 */
-	enum on_conflict_action override_error;
-	/**
-	 * Represents an ON CONFLICT action which can be
-	 * optimized and executed without VDBE bytecode, by
-	 * Tarantool facilities. If optimization is not available,
-	 * then the value is ON_CONFLICT_ACTION_NONE, otherwise
-	 * it is ON_CONFLICT_ACTON_IGNORE or
-	 * ON_CONFLICT_ACTION_REPLACE.
-	 */
-	enum on_conflict_action optimized_action;
-};
-
 void *
 sqlite3_user_data(sqlite3_context *);
 
@@ -2009,12 +1984,6 @@ struct Index {
 	Table *pTable;
 	/** The next index associated with the same table. */
 	Index *pNext;
-	/**
-	 * Conflict resolution algorithm to employ whenever an
-	 * attempt is made to insert a non-unique element in
-	 * unique index.
-	 */
-	u8 onError;
 	/** Index definition. */
 	struct index_def *def;
 };
@@ -2904,6 +2873,7 @@ struct Parse {
 #define OPFLAG_EPHEM         0x01	/* OP_Column: Ephemeral output is ok */
 #define OPFLAG_OE_IGNORE    0x200	/* OP_IdxInsert: Ignore flag */
 #define OPFLAG_OE_FAIL      0x400	/* OP_IdxInsert: Fail flag */
+#define OPFLAG_OE_ROLLBACK  0x800	/* OP_IdxInsert: Rollback flag. */
 #define OPFLAG_LENGTHARG     0x40	/* OP_Column only used for length() */
 #define OPFLAG_TYPEOFARG     0x80	/* OP_Column only used for typeof() */
 #define OPFLAG_SEEKEQ        0x02	/* OP_Open** cursor uses EQ seek only */
@@ -3452,7 +3422,7 @@ void
 sql_column_add_nullable_action(struct Parse *parser,
 			       enum on_conflict_action nullable_action);
 
-void sqlite3AddPrimaryKey(Parse *, ExprList *, int, int, enum sort_order);
+void sqlite3AddPrimaryKey(Parse *, ExprList *, int, enum sort_order);
 
 /**
  * Add a new CHECK constraint to the table currently under
@@ -3580,8 +3550,6 @@ void sqlite3SrcListDelete(sqlite3 *, SrcList *);
  * @param token Index name. May be NULL.
  * @param tbl_name Table to index. Use pParse->pNewTable ifNULL.
  * @param col_list A list of columns to be indexed.
- * @param on_error One of ON_CONFLICT_ACTION_ABORT, _IGNORE,
- *        _REPLACE, or _NONE.
  * @param start The CREATE token that begins this statement.
  * @param sort_order Sort order of primary key when pList==NULL.
  * @param if_not_exist Omit error if index already exists.
@@ -3590,9 +3558,8 @@ void sqlite3SrcListDelete(sqlite3 *, SrcList *);
 void
 sql_create_index(struct Parse *parse, struct Token *token,
 		 struct SrcList *tbl_name, struct ExprList *col_list,
-		 enum on_conflict_action on_error, struct Token *start,
-		 enum sort_order sort_order, bool if_not_exist,
-		 enum sql_index_type idx_type);
+		 struct Token *start, enum sort_order sort_order,
+		 bool if_not_exist, enum sql_index_type idx_type);
 
 /**
  * This routine will drop an existing named index.  This routine
@@ -3760,7 +3727,7 @@ Vdbe *sqlite3GetVdbe(Parse *);
 void sqlite3PrngSaveState(void);
 void sqlite3PrngRestoreState(void);
 #endif
-void sqlite3RollbackAll(Vdbe *, int);
+void sqlite3RollbackAll(Vdbe *);
 
 /**
  * Generate opcodes which start new Tarantool transaction.
@@ -3909,26 +3876,88 @@ sql_generate_index_key(struct Parse *parse, struct Index *index, int cursor,
 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 *);
+/**
+ * Generate code to do constraint checks prior to an INSERT or
+ * an UPDATE on the given table.
+ *
+ * The @new_tuple_reg is the first register in a range that
+ * contains the data to be inserted or the data after the update.
+ * There will be field_count registers in this range.
+ * The first register in the range will contains the content of
+ * the first table column, and so forth.
+ *
+ * To test NULL, CHECK and statement (except for REPLACE)
+ * constraints we can avoid opening cursors on secondary indexes.
+ * However, to implement INSERT OR REPLACE or UPDATE OR REPLACE,
+ * we should
+ *
+ * Constraint
+ *    type       Action              What Happens
+ * ----------  ----------  --------------------------------------
+ *    any       ROLLBACK   The current transaction is rolled
+ *                         back and VDBE stops immediately
+ *                         with return code of SQLITE_CONSTRAINT.
+ *
+ *    any        ABORT     Back out changes from the current
+ *                         command only (do not do a complete
+ *                         rollback) then cause VDBE to return
+ *                         immediately with SQLITE_CONSTRAINT.
+ *
+ *    any        FAIL      VDBE returns immediately with a
+ *                         return code of SQLITE_CONSTRAINT. The
+ *                         transaction is not rolled back and any
+ *                         changes to prior rows are retained.
+ *
+ *    any       IGNORE     The attempt in insert or update the
+ *                         current row is skipped, without
+ *                         throwing an error. Processing
+ *                         continues with the next row.
+ *
+ *  NOT NULL    REPLACE    The NULL value is replace by the
+ *                         default value for that column. If the
+ *                         default value is NULL, the action is
+ *                         the same as ABORT.
+ *
+ *  UNIQUE      REPLACE    The other row that conflicts with the
+ *                         row being inserted is removed.
+ *                         Triggers are fired, foreign keys
+ *                         constraints are checked.
+ *
+ *  CHECK       REPLACE    Illegal. Results in an exception.
+ *
+ * @param parse_context Current parsing context.
+ * @param tab The table being inserted or updated.
+ * @param new_tuple_reg First register in a range holding values
+ *                      to insert.
+ * @param on_conflict On conflict error action of INSERT or
+ *        UPDATE statement (for example INSERT OR REPLACE).
+ * @param ignore_label Jump to this label on an IGNORE resolution.
+ * @param upd_cols Columns to be updated with the size of table's
+ *                 field count. NULL for INSERT operation.
+ */
+void vdbe_emit_constraint_checks(struct Parse *parse_context,
+				 struct Table *tab, int new_tuple_reg,
+				 enum on_conflict_action on_conflict,
+				 int ignore_label, int *upd_cols);
 /**
  * This routine generates code to finish the INSERT or UPDATE
  * operation that was started by a prior call to
- * sqlite3GenerateConstraintChecks.
+ * sqlite3GenerateConstraintChecks. It encodes raw data which
+ * is held in a range of registers starting from @raw_data_reg
+ * and length of @tuple_len and inserts this record to space
+ * using given @cursor_id.
+ *
  * @param v Virtual database engine.
  * @param cursor_id Primary index cursor.
- * @param tuple_id Register with data to insert.
- * @param on_conflict Structure, which contains override error
- * 	  action on failed insert/replaceand and optimized error
- * 	  action after generating bytecode for constraints checks.
+ * @param tuple_reg Register with raw data to insert.
+ * @param tuple_len Number of registers to hold the tuple.
+ * @param on_conflict On conflict action.
  */
 void
-vdbe_emit_insertion_completion(Vdbe *v, int cursor_id, int tuple_id,
-			       struct on_conflict *on_conflict);
+vdbe_emit_insertion_completion(struct Vdbe *v, int cursor_id, int raw_data_reg,
+			       uint32_t tuple_len,
+			       enum on_conflict_action on_conflict);
 
-int sqlite3OpenTableAndIndices(Parse *, Table *, int, u8, int, u8 *, int *,
-			       int *, u8, u8);
 void
 sql_set_multi_write(Parse *, bool);
 void sqlite3MayAbort(Parse *);
@@ -4535,8 +4564,6 @@ void sqlite3Analyze(Parse *, Token *);
 ssize_t
 sql_index_tuple_size(struct space *space, struct index *idx);
 
-int sqlite3InvokeBusyHandler(BusyHandler *);
-
 /**
  * Load the content of the _sql_stat1 and sql_stat4 tables. The
  * contents of _sql_stat1 are used to populate the tuple_stat1[]
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 3fdf5a9af..51cc5cef3 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -84,20 +84,12 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	int addrTop = 0;	/* VDBE instruction address of the start of the loop */
 	WhereInfo *pWInfo;	/* Information about the WHERE clause */
 	Vdbe *v;		/* The virtual database engine */
-	Index *pIdx;		/* For looping over indices */
 	Index *pPk;		/* The PRIMARY KEY index */
-	int nIdx;		/* Number of indices that need updating */
-	int iBaseCur;		/* Base cursor number */
-	int iDataCur;		/* Cursor for the canonical data btree */
-	int iIdxCur;		/* Cursor for the first index */
 	sqlite3 *db;		/* The database structure */
-	int *aRegIdx = 0;	/* One register assigned to each index to be updated */
 	int *aXRef = 0;		/* aXRef[i] is the index in pChanges->a[] of the
 				 * an expression for the i-th column of the table.
 				 * aXRef[i]==-1 if the i-th column is not changed.
 				 */
-	u8 *aToOpen;		/* 1 for tables and indices to be opened */
-	u8 chngPk;		/* PRIMARY KEY changed */
 	NameContext sNC;	/* The name-context to resolve expressions in */
 	int okOnePass;		/* True for one-pass algorithm without the FIFO */
 	int hasFK;		/* True if foreign key processing is required */
@@ -152,34 +144,17 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	}
 
 	struct space_def *def = pTab->def;
-
-	/* Allocate a cursors for the main database table and for all indices.
-	 * The index cursors might not be used, but if they are used they
-	 * need to occur right after the database cursor.  So go ahead and
-	 * allocate enough space, just in case.
-	 */
-	pTabList->a[0].iCursor = iBaseCur = iDataCur = pParse->nTab++;
-	iIdxCur = iDataCur + 1;
-	pPk = is_view ? 0 : sqlite3PrimaryKeyIndex(pTab);
-	for (nIdx = 0, pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext, nIdx++) {
-		if (sql_index_is_primary(pIdx) && pPk != 0) {
-			iDataCur = pParse->nTab;
-			pTabList->a[0].iCursor = iDataCur;
-		}
-		pParse->nTab++;
-	}
-
-	/* Allocate space for aXRef[], aRegIdx[], and aToOpen[].
-	 * Initialize aXRef[] and aToOpen[] to their default values.
-	 */
-	aXRef = sqlite3DbMallocRawNN(db, sizeof(int) *
-				     (def->field_count + nIdx) + nIdx + 2);
-	if (aXRef == 0)
+	int nIdx = 0;
+	for (struct Index *idx = pTab->pIndex; idx != NULL;
+	     idx = idx->pNext, nIdx++);
+	/* Allocate cursor on primary index. */
+	int pk_cursor = pParse->nTab++;
+	pTabList->a[0].iCursor = pk_cursor;
+	pPk = is_view ? NULL : sqlite3PrimaryKeyIndex(pTab);
+
+	aXRef = sqlite3DbMallocRawNN(db, sizeof(int) * def->field_count);
+	if (aXRef == NULL)
 		goto update_cleanup;
-	aRegIdx = aXRef + def->field_count;
-	aToOpen = (u8 *) (aRegIdx + nIdx);
-	memset(aToOpen, 1, nIdx + 1);
-	aToOpen[nIdx + 1] = 0;
 	for (i = 0; i < (int)def->field_count; i++)
 		aXRef[i] = -1;
 
@@ -192,7 +167,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	 * of the UPDATE statement.  Also find the column index
 	 * for each column to be updated in the pChanges array.
 	 */
-	chngPk = 0;
+	bool is_pk_modified = false;
 	for (i = 0; i < pChanges->nExpr; i++) {
 		if (sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr)) {
 			goto update_cleanup;
@@ -201,7 +176,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 			if (strcmp(def->fields[j].name,
 				   pChanges->a[i].zName) == 0) {
 				if (pPk && table_column_is_in_pk(pTab, j)) {
-					chngPk = 1;
+					is_pk_modified = true;
 				}
 				if (aXRef[j] != -1) {
 					sqlite3ErrorMsg(pParse,
@@ -220,8 +195,6 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 			goto update_cleanup;
 		}
 	}
-	assert(chngPk == 0 || chngPk == 1);
-
 	/*
 	 * The SET expressions are not actually used inside the
 	 * WHERE loop. So reset the colUsed mask.
@@ -230,35 +203,6 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 
 	hasFK = fkey_is_required(pTab->def->id, aXRef);
 
-	/* There is one entry in the aRegIdx[] array for each index on the table
-	 * being updated.  Fill in aRegIdx[] with a register number that will hold
-	 * the key for accessing each index.
-	 *
-	 * FIXME:  Be smarter about omitting indexes that use expressions.
-	 */
-	for (j = 0, pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext, j++) {
-		int reg;
-		uint32_t part_count = pIdx->def->key_def->part_count;
-		if (chngPk || hasFK || pIdx == pPk) {
-			reg = ++pParse->nMem;
-			pParse->nMem += part_count;
-		} else {
-			reg = 0;
-			for (uint32_t i = 0; i < part_count; i++) {
-				uint32_t fieldno =
-					pIdx->def->key_def->parts[i].fieldno;
-				if (aXRef[fieldno] >= 0) {
-					reg = ++pParse->nMem;
-					pParse->nMem += part_count;
-					break;
-				}
-			}
-		}
-		if (reg == 0)
-			aToOpen[j + 1] = 0;
-		aRegIdx[j] = reg;
-	}
-
 	/* Begin generating code. */
 	v = sqlite3GetVdbe(pParse);
 	if (v == NULL)
@@ -269,7 +213,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	/* Allocate required registers. */
 	regOldPk = regNewPk = ++pParse->nMem;
 
-	if (chngPk != 0 || trigger != NULL || hasFK != 0) {
+	if (is_pk_modified || trigger != NULL || hasFK != 0) {
 		regOld = pParse->nMem + 1;
 		pParse->nMem += def->field_count;
 		regNewPk = ++pParse->nMem;
@@ -280,10 +224,15 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	/* If we are trying to update a view, realize that view into
 	 * an ephemeral table.
 	 */
+	uint32_t pk_part_count;
 	if (is_view) {
-		sql_materialize_view(pParse, def->name, pWhere, iDataCur);
+		sql_materialize_view(pParse, def->name, pWhere, pk_cursor);
 		/* Number of columns from SELECT plus ID.*/
-		nKey = def->field_count + 1;
+		pk_part_count = nKey = def->field_count + 1;
+	} else {
+		vdbe_emit_open_cursor(pParse, pk_cursor, 0,
+				      space_by_id(pTab->def->id));
+		pk_part_count = pPk->def->key_def->part_count;
 	}
 
 	/* Resolve the column names in all the expressions in the
@@ -292,49 +241,28 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	if (sqlite3ResolveExprNames(&sNC, pWhere)) {
 		goto update_cleanup;
 	}
-	/* Begin the database scan
-	 * The only difference between VIEW and ordinary table is the fact that
-	 * VIEW is held in ephemeral table and doesn't have explicit PK.
-	 * In this case we have to manually load columns in order to make tuple.
-	 */
 	int iPk;	/* First of nPk memory cells holding PRIMARY KEY value */
-	/* Number of components of the PRIMARY KEY.  */
-	uint32_t pk_part_count;
 	int addrOpen;	/* Address of the OpenEphemeral instruction */
 
-	if (is_view) {
-		pk_part_count = nKey;
-	} else {
-		assert(pPk != 0);
-		pk_part_count = pPk->def->key_def->part_count;
-	}
 	iPk = pParse->nMem + 1;
 	pParse->nMem += pk_part_count;
 	regKey = ++pParse->nMem;
 	iEph = pParse->nTab++;
 	sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
 
-	if (is_view) {
-		addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph,
-					     nKey);
-	} else {
-		addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph,
-					     pk_part_count);
-		sql_vdbe_set_p4_key_def(pParse, pPk);
-	}
-
+	addrOpen = sqlite3VdbeAddOp2(v, OP_OpenTEphemeral, iEph, pk_part_count);
 	pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0,
-				   WHERE_ONEPASS_DESIRED, iIdxCur);
+				   WHERE_ONEPASS_DESIRED, pk_cursor);
 	if (pWInfo == 0)
 		goto update_cleanup;
 	okOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
 	if (is_view) {
 		for (i = 0; i < (int) pk_part_count; i++) {
-			sqlite3VdbeAddOp3(v, OP_Column, iDataCur, i, iPk + i);
+			sqlite3VdbeAddOp3(v, OP_Column, pk_cursor, i, iPk + i);
 		}
 	} else {
 		for (i = 0; i < (int) pk_part_count; i++) {
-			sqlite3ExprCodeGetColumnOfTable(v, def, iDataCur,
+			sqlite3ExprCodeGetColumnOfTable(v, def, pk_cursor,
 							pPk->def->key_def->
 								parts[i].fieldno,
 							iPk + i);
@@ -366,57 +294,22 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 		regRowCount = ++pParse->nMem;
 		sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
 	}
-
 	labelBreak = sqlite3VdbeMakeLabel(v);
-	if (!is_view) {
-		/*
-		 * Open every index that needs updating.  Note that if any
-		 * index could potentially invoke a REPLACE conflict resolution
-		 * action, then we need to open all indices because we might need
-		 * to be deleting some records.
-		 */
-		if (on_error == ON_CONFLICT_ACTION_REPLACE) {
-			memset(aToOpen, 1, nIdx + 1);
-		} else {
-			for (pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) {
-				if (pIdx->onError == ON_CONFLICT_ACTION_REPLACE) {
-					memset(aToOpen, 1, nIdx + 1);
-					break;
-				}
-			}
-		}
-		if (okOnePass) {
-			if (aiCurOnePass[0] >= 0)
-				aToOpen[aiCurOnePass[0] - iBaseCur] = 0;
-			if (aiCurOnePass[1] >= 0)
-				aToOpen[aiCurOnePass[1] - iBaseCur] = 0;
-		}
-		sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0,
-					   iBaseCur, aToOpen, 0, 0,
-					   on_error, 1);
-	}
-
 	/* Top of the update loop */
 	if (okOnePass) {
 		labelContinue = labelBreak;
-		sqlite3VdbeAddOp2(v, OP_IsNull, regKey,
-				  labelBreak);
-		if (aToOpen[iDataCur - iBaseCur] && !is_view) {
+		sqlite3VdbeAddOp2(v, OP_IsNull, regKey, labelBreak);
+		if (!is_view) {
 			assert(pPk);
-			sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur,
-					     labelBreak, regKey, nKey);
-			VdbeCoverageNeverTaken(v);
+			sqlite3VdbeAddOp4Int(v, OP_NotFound, pk_cursor,
+					     labelBreak, regKey, pk_part_count);
 		}
-		VdbeCoverageIf(v, pPk == 0);
-		VdbeCoverageIf(v, pPk != 0);
 	} else {
 		labelContinue = sqlite3VdbeMakeLabel(v);
 		sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak);
-		VdbeCoverage(v);
 		addrTop = sqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey);
-		sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue,
+		sqlite3VdbeAddOp4Int(v, OP_NotFound, pk_cursor, labelContinue,
 				     regKey, 0);
-		VdbeCoverage(v);
 	}
 
 	/* If the record number will change, set register regNewPk to
@@ -424,14 +317,13 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	 * then regNewPk is the same register as regOldPk, which is
 	 * already populated.
 	 */
-	assert(chngPk != 0 || trigger != NULL || hasFK != 0 ||
+	assert(is_pk_modified || trigger != NULL || hasFK != 0 ||
 	       regOldPk == regNewPk);
 
-
 	/* Compute the old pre-UPDATE content of the row being changed, if that
 	 * information is needed
 	 */
-	if (chngPk != 0 || hasFK != 0 || trigger != NULL) {
+	if (is_pk_modified || hasFK != 0 || trigger != NULL) {
 		struct space *space = space_by_id(pTab->def->id);
 		assert(space != NULL);
 		u32 oldmask = hasFK ? space->fkey_mask : 0;
@@ -444,7 +336,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 			    || table_column_is_in_pk(pTab, i)) {
 				testcase(oldmask != 0xffffffff && i == 31);
 				sqlite3ExprCodeGetColumnOfTable(v, def,
-								iDataCur, i,
+								pk_cursor, i,
 								regOld + i);
 			} else {
 				sqlite3VdbeAddOp2(v, OP_Null, 0, regOld + i);
@@ -483,8 +375,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 			testcase(i == 31);
 			testcase(i == 32);
 			sqlite3ExprCodeGetColumnToReg(pParse, def, i,
-						      iDataCur,
-						      regNew + i);
+						      pk_cursor, regNew + i);
 		} else {
 			sqlite3VdbeAddOp2(v, OP_Null, 0, regNew + i);
 		}
@@ -506,13 +397,13 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 		 * documentation.
 		 */
 		if (!is_view) {
-			sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur,
+			sqlite3VdbeAddOp4Int(v, OP_NotFound, pk_cursor,
 					     labelContinue, regKey, nKey);
-			VdbeCoverage(v);
 		} else {
-			sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur,
-					     labelContinue, regKey - nKey, nKey);
-			VdbeCoverage(v);
+			sqlite3VdbeAddOp4Int(v, OP_NotFound, pk_cursor,
+					     labelContinue,
+					     regKey - pk_part_count,
+					     pk_part_count);
 		}
 
 		/* If it did not delete it, the row-trigger may still have modified
@@ -523,62 +414,39 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 		for (i = 0; i < (int)def->field_count; i++) {
 			if (aXRef[i] < 0) {
 				sqlite3ExprCodeGetColumnOfTable(v, def,
-								iDataCur, i,
+								pk_cursor, i,
 								regNew + i);
 			}
 		}
 	}
 
 	if (!is_view) {
-		int addr1 = 0;	/* Address of jump instruction */
-		int bReplace = 0;	/* True if REPLACE conflict resolution might happen */
-
-		struct on_conflict on_conflict;
-		on_conflict.override_error = on_error;
-		on_conflict.optimized_action = ON_CONFLICT_ACTION_NONE;
-
-		/* Do constraint checks. */
 		assert(regOldPk > 0);
-		sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur,
-						iIdxCur, regNewPk,
-						regOldPk, chngPk, &on_conflict,
-						labelContinue, &bReplace,
-						aXRef);
-
+		vdbe_emit_constraint_checks(pParse, pTab, regNewPk + 1,
+					    on_error, labelContinue, aXRef);
 		/* Do FK constraint checks. */
 		if (hasFK)
 			fkey_emit_check(pParse, pTab, regOldPk, 0, aXRef);
-
-		/* Delete the index entries associated with the current record.  */
-		if (bReplace || chngPk) {
-			addr1 =
-				sqlite3VdbeAddOp4Int(v, OP_NotFound,
-						     iDataCur, 0, regKey,
-						     nKey);
-			VdbeCoverageNeverTaken(v);
-		}
-
-		/* If changing the PK value, or if there are foreign key constraints
-		 * to process, delete the old record.
+		int addr1 = 0;
+		/*
+		 * Delete the index entries associated with the
+		 * current record. It can be already be removed
+		 * by trigger or REPLACE conflict action.
 		 */
+		addr1 = sqlite3VdbeAddOp4Int(v, OP_NotFound, pk_cursor, 0,
+					     regKey, nKey);
 		assert(regNew == regNewPk + 1);
-		if (hasFK || chngPk || pPk != 0) {
-			sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, 0);
-		}
-		if (bReplace || chngPk) {
-			sqlite3VdbeJumpHere(v, addr1);
-		}
-
+		sqlite3VdbeAddOp2(v, OP_Delete, pk_cursor, 0);
+		sqlite3VdbeJumpHere(v, addr1);
 		if (hasFK)
 			fkey_emit_check(pParse, pTab, 0, regNewPk, aXRef);
-
-		/* Insert the new index entries and the new record. */
-		vdbe_emit_insertion_completion(v, iIdxCur, aRegIdx[0],
-					       &on_conflict);
-
-		/* 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 updated.
+		vdbe_emit_insertion_completion(v, pk_cursor, regNew,
+					       pTab->def->field_count,
+					       on_error);
+		/*
+		 * Do any ON CASCADE, SET NULL or SET DEFAULT
+		 * operations required to handle rows that refer
+		 * via a foreign key to the row just updated.
 		 */
 		if (hasFK)
 			fkey_emit_actions(pParse, pTab, regOldPk, aXRef);
@@ -617,7 +485,7 @@ sqlite3Update(Parse * pParse,		/* The parser context */
 	}
 
  update_cleanup:
-	sqlite3DbFree(db, aXRef);	/* Also frees aRegIdx[] and aToOpen[] */
+	sqlite3DbFree(db, aXRef);
 	sqlite3SrcListDelete(db, pTabList);
 	sql_expr_list_delete(db, pChanges);
 	sql_expr_delete(db, pWhere, false);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index e1eaf91a4..46beae24b 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -4227,7 +4227,9 @@ case OP_Next:          /* jump */
  * @param P2 Index of a register with MessagePack data to insert.
  * @param P5 Flags. If P5 contains OPFLAG_NCHANGE, then VDBE
  *        accounts the change in a case of successful insertion in
- *        nChange counter.
+ *        nChange counter. If P5 contains OPFLAG_OE_IGNORE, then
+ *        we are processing INSERT OR INGORE statement. Thus, in
+ *        case of conflict we don't raise an error.
  */
 /* Opcode: IdxReplace P1 P2 * * P5
  * Synopsis: key=r[P2]
@@ -4291,10 +4293,9 @@ case OP_IdxInsert: {        /* in2 */
 		}
 	} else if (pOp->p5 & OPFLAG_OE_FAIL) {
 		p->errorAction = ON_CONFLICT_ACTION_FAIL;
+	} else if (pOp->p5 & OPFLAG_OE_ROLLBACK) {
+		p->errorAction = ON_CONFLICT_ACTION_ROLLBACK;
 	}
-
-	assert(p->errorAction == ON_CONFLICT_ACTION_ABORT ||
-	       p->errorAction == ON_CONFLICT_ACTION_FAIL);
 	if (rc) goto abort_due_to_error;
 	break;
 }
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 242a6448e..5ab519683 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -2381,8 +2381,7 @@ sqlite3VdbeHalt(Vdbe * p)
 					 */
 					box_txn_rollback();
 					closeCursorsAndFree(p);
-					sqlite3RollbackAll(p,
-							   SQLITE_ABORT_ROLLBACK);
+					sqlite3RollbackAll(p);
 					sqlite3CloseSavepoints(p);
 					p->nChange = 0;
 				}
@@ -2433,7 +2432,7 @@ sqlite3VdbeHalt(Vdbe * p)
 					p->rc = rc;
 					box_txn_rollback();
 					closeCursorsAndFree(p);
-					sqlite3RollbackAll(p, SQLITE_OK);
+					sqlite3RollbackAll(p);
 					p->nChange = 0;
 				} else {
 					sqlite3CommitInternalChanges();
@@ -2441,7 +2440,7 @@ sqlite3VdbeHalt(Vdbe * p)
 			} else {
 				box_txn_rollback();
 				closeCursorsAndFree(p);
-				sqlite3RollbackAll(p, SQLITE_OK);
+				sqlite3RollbackAll(p);
 				p->nChange = 0;
 			}
 			p->anonymous_savepoint = NULL;
@@ -2453,7 +2452,7 @@ sqlite3VdbeHalt(Vdbe * p)
 			} else {
 				box_txn_rollback();
 				closeCursorsAndFree(p);
-				sqlite3RollbackAll(p, SQLITE_ABORT_ROLLBACK);
+				sqlite3RollbackAll(p);
 				sqlite3CloseSavepoints(p);
 				p->nChange = 0;
 			}
@@ -2476,7 +2475,7 @@ sqlite3VdbeHalt(Vdbe * p)
 					p->zErrMsg = 0;
 				}
 				closeCursorsAndFree(p);
-				sqlite3RollbackAll(p, SQLITE_ABORT_ROLLBACK);
+				sqlite3RollbackAll(p);
 				sqlite3CloseSavepoints(p);
 				p->nChange = 0;
 			}
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index 73fe070fd..1c205b664 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -2858,7 +2858,6 @@ whereLoopAddBtree(WhereLoopBuilder * pBuilder,	/* WHERE clause information */
 		 */
 		Index *pFirst;	/* First of real indices on the table */
 		memset(&fake_index, 0, sizeof(Index));
-		fake_index.onError = ON_CONFLICT_ACTION_REPLACE;
 		fake_index.pTable = pTab;
 
 		struct key_def *key_def = key_def_new(1);
@@ -4666,10 +4665,6 @@ sqlite3WhereBegin(Parse * pParse,	/* The parser context */
 			struct space *space = space_cache_find(pTabItem->pTab->def->id);
 			int iIndexCur;
 			int op = OP_OpenRead;
-			/* iAuxArg is always set if to a positive value if ONEPASS is possible */
-			assert(iAuxArg != 0
-			       || (pWInfo->
-				   wctrlFlags & WHERE_ONEPASS_DESIRED) == 0);
 			/* Check if index is primary. Either of
 			 * points should be true:
 			 * 1. struct Index is non-NULL and is
diff --git a/test/sql-tap/conflict3.test.lua b/test/sql-tap/conflict3.test.lua
deleted file mode 100755
index 9b555501e..000000000
--- a/test/sql-tap/conflict3.test.lua
+++ /dev/null
@@ -1,402 +0,0 @@
-#!/usr/bin/env tarantool
-test = require("sqltester")
-test:plan(29)
-
---!./tcltestrunner.lua
--- 2013-11-05
---
--- The author disclaims copyright to this source code.  In place of
--- a legal notice, here is a blessing:
---
---    May you do good and not evil.
---    May you find forgiveness for yourself and forgive others.
---    May you share freely, never taking more than you give.
---
--------------------------------------------------------------------------
--- This file implements regression tests for SQLite library.
---
--- This file implements tests for the conflict resolution extension
--- to SQLite.
---
--- This file focuses on making sure that combinations of REPLACE,
--- IGNORE, and FAIL conflict resolution play well together.
---
--- ["set","testdir",[["file","dirname",["argv0"]]]]
--- ["source",[["testdir"],"\/tester.tcl"]]
-
-
--- MUST_WORK_TEST
-test:do_execsql_test(
-    "conflict-1.1",
-    [[
-        CREATE TABLE t1(
-          a INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
-          b UNIQUE ON CONFLICT IGNORE,
-          c UNIQUE ON CONFLICT FAIL
-        );
-        INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4);
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-1.1>
-        1, 2, 3, 2, 3, 4
-        -- </conflict-1.1>
-    })
-
--- Insert a row that conflicts on column B.  The insert should be ignored.
---
-test:do_execsql_test(
-    "conflict-1.2",
-    [[
-        INSERT INTO t1(a,b,c) VALUES(3,2,5);
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-1.2>
-        1, 2, 3, 2, 3, 4
-        -- </conflict-1.2>
-    })
-
--- Insert two rows where the second conflicts on C.  The first row show go
--- and and then there should be a constraint error.
---
-test:do_catchsql_test(
-    "conflict-1.3",
-    [[
-        INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);
-    ]], {
-        -- <conflict-1.3>
-        1, "UNIQUE constraint failed: T1.C"
-        -- </conflict-1.3>
-    })
-
-test:do_execsql_test(
-    "conflict-1.4",
-    [[
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-1.4>
-        1, 2, 3, 2, 3, 4, 4, 5, 6
-        -- </conflict-1.4>
-    })
-
--- Replete the tests above, but this time on a table non-INTEGER primary key.
---
-test:do_execsql_test(
-    "conflict-2.1",
-    [[
-        DROP TABLE t1;
-        CREATE TABLE t1(
-          a INT PRIMARY KEY ON CONFLICT REPLACE, 
-          b UNIQUE ON CONFLICT IGNORE,
-          c UNIQUE ON CONFLICT FAIL
-        );
-        INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4);
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-2.1>
-        1, 2, 3, 2, 3, 4
-        -- </conflict-2.1>
-    })
-
--- Insert a row that conflicts on column B.  The insert should be ignored.
---
-test:do_execsql_test(
-    "conflict-2.2",
-    [[
-       INSERT INTO t1(a,b,c) VALUES(3,2,5);
-       SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-2.2>
-        1, 2, 3, 2, 3, 4
-        -- </conflict-2.2>
-    })
-
--- Insert two rows where the second conflicts on C.  The first row show go
--- and and then there should be a constraint error.
---
-test:do_catchsql_test(
-    "conflict-2.3",
-    [[
-        INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);
-    ]], {
-        -- <conflict-2.3>
-        1, "UNIQUE constraint failed: T1.C"
-        -- </conflict-2.3>
-    })
-
-test:do_execsql_test(
-    "conflict-2.4",
-    [[
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-2.4>
-        1, 2, 3, 2, 3, 4, 4, 5, 6
-        -- </conflict-2.4>
-    })
-
--- Replete again
---
-test:do_execsql_test(
-    "conflict-3.1",
-    [[
-        DROP TABLE t1;
-        CREATE TABLE t1(
-          a INT PRIMARY KEY ON CONFLICT REPLACE, 
-          b UNIQUE ON CONFLICT IGNORE,
-          c UNIQUE ON CONFLICT FAIL
-        );
-        INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4);
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-3.1>
-        1, 2, 3, 2, 3, 4
-        -- </conflict-3.1>
-    })
-
--- Insert a row that conflicts on column B.  The insert should be ignored.
---
-test:do_execsql_test(
-    "conflict-3.2",
-    [[
-        INSERT INTO t1(a,b,c) VALUES(3,2,5);
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-3.2>
-        1, 2, 3, 2, 3, 4
-        -- </conflict-3.2>
-    })
-
--- Insert two rows where the second conflicts on C.  The first row show go
--- and and then there should be a constraint error.
---
-test:do_catchsql_test(
-    "conflict-3.3",
-    [[
-        INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);
-    ]], {
-        -- <conflict-3.3>
-        1, "UNIQUE constraint failed: T1.C"
-        -- </conflict-3.3>
-    })
-
-test:do_execsql_test(
-    "conflict-3.4",
-    [[
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-3.4>
-        1, 2, 3, 2, 3, 4, 4, 5, 6
-        -- </conflict-3.4>
-    })
-
--- Arrange the table rows in a different order and repeat.
---
-test:do_execsql_test(
-    "conflict-4.1",
-    [[
-        DROP TABLE t1;
-        CREATE TABLE t1(
-          b UNIQUE ON CONFLICT IGNORE,
-          c UNIQUE ON CONFLICT FAIL,
-          a INT PRIMARY KEY ON CONFLICT REPLACE
-        );
-        INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4);
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-4.1>
-        1, 2, 3, 2, 3, 4
-        -- </conflict-4.1>
-    })
-
--- Insert a row that conflicts on column B.  The insert should be ignored.
---
-test:do_execsql_test(
-    "conflict-4.2",
-    [[
-        INSERT INTO t1(a,b,c) VALUES(3,2,5);
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-4.2>
-        1, 2, 3, 2, 3, 4
-        -- </conflict-4.2>
-    })
-
--- Insert two rows where the second conflicts on C.  The first row show go
--- and and then there should be a constraint error.
---
-test:do_catchsql_test(
-    "conflict-4.3",
-    [[
-        INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);
-    ]], {
-        -- <conflict-4.3>
-        1, "UNIQUE constraint failed: T1.C"
-        -- </conflict-4.3>
-    })
-
-test:do_execsql_test(
-    "conflict-4.4",
-    [[
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-4.4>
-        1, 2, 3, 2, 3, 4, 4, 5, 6
-        -- </conflict-4.4>
-    })
-
--- Arrange the table rows in a different order and repeat.
---
-test:do_execsql_test(
-    "conflict-5.1",
-    [[
-        DROP TABLE t1;
-        CREATE TABLE t1(
-          b UNIQUE ON CONFLICT IGNORE,
-          a INT PRIMARY KEY ON CONFLICT REPLACE,
-          c UNIQUE ON CONFLICT FAIL
-        );
-        INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4);
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-    -- <conflict-5.1>
-    1, 2, 3, 2, 3, 4
-        -- </conflict-5.1>
-    })
-
--- Insert a row that conflicts on column B.  The insert should be ignored.
---
-test:do_execsql_test(
-   "conflict-5.2",
-    [[
-        INSERT INTO t1(a,b,c) VALUES(3,2,5);
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-5.2>
-        1, 2, 3, 2, 3, 4
-        -- </conflict-5.2>
-    })
-
--- Insert two rows where the second conflicts on C.  The first row show go
--- and and then there should be a constraint error.
---
-test:do_catchsql_test(
-    "conflict-5.3",
-    [[
-        INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);
-    ]], {
-        -- <conflict-5.3>
-        1, "UNIQUE constraint failed: T1.C"
-        -- </conflict-5.3>
-    })
-
-test:do_execsql_test(
-    "conflict-5.4",
-    [[
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-5.4>
-        1, 2, 3, 2, 3, 4, 4, 5, 6
-        -- </conflict-5.4>
-    })
-
--- Arrange the table rows in a different order and repeat.
---
-test:do_execsql_test(
-    "conflict-6.1",
-    [[
-        DROP TABLE t1;
-        CREATE TABLE t1(
-          c UNIQUE ON CONFLICT FAIL,
-          a INT PRIMARY KEY ON CONFLICT REPLACE,
-          b UNIQUE ON CONFLICT IGNORE
-        );
-        INSERT INTO t1(a,b,c) VALUES(1,2,3), (2,3,4);
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-6.1>
-        1, 2, 3, 2, 3, 4
-        -- </conflict-6.1>
-    })
-
--- Insert a row that conflicts on column B.  The insert should be ignored.
---
-test:do_execsql_test(
-    "conflict-6.2",
-    [[
-        INSERT INTO t1(a,b,c) VALUES(3,2,5);
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-6.2>
-        1, 2, 3, 2, 3, 4
-        -- </conflict-6.2>
-    })
-
--- Insert two rows where the second conflicts on C.  The first row show go
--- and and then there should be a constraint error.
---
-test:do_catchsql_test(
-    "conflict-6.3",
-    [[
-        INSERT INTO t1(a,b,c) VALUES(4,5,6), (5,6,4);
-    ]], {
-        -- <conflict-6.3>
-        1, "UNIQUE constraint failed: T1.C"
-        -- </conflict-6.3>
-    })
-
-test:do_execsql_test(
-    "conflict-6.4",
-    [[
-        SELECT a,b,c FROM t1 ORDER BY a;
-    ]], {
-        -- <conflict-6.4>
-        1, 2, 3, 2, 3, 4, 4, 5, 6
-        -- </conflict-6.4>
-    })
-
-test:do_catchsql_test(
-    "conflict-7.1",
-    [[
-        CREATE TABLE t3(a PRIMARY KEY ON CONFLICT REPLACE,
-                        b UNIQUE ON CONFLICT REPLACE);
-    ]], {
-        1, "SQL error: only PRIMARY KEY constraint can have ON CONFLICT REPLACE clause - T3"
-    })
-
-test:do_catchsql_test(
-    "conflict-7.2",
-    [[
-        CREATE TABLE t3(a PRIMARY KEY,
-                        b UNIQUE ON CONFLICT REPLACE);
-    ]], {
-        1, "SQL error: only PRIMARY KEY constraint can have ON CONFLICT REPLACE clause - T3"
-    })
-
-test:do_catchsql_test(
-    "conflict-7.3",
-    [[
-        CREATE TABLE t3(a PRIMARY KEY,
-                        b UNIQUE ON CONFLICT REPLACE,
-                        c UNIQUE ON CONFLICT REPLACE);
-    ]], {
-        1, "SQL error: only PRIMARY KEY constraint can have ON CONFLICT REPLACE clause - T3"
-    })
-
-test:do_catchsql_test(
-    "conflict-7.4",
-    [[
-        CREATE TABLE t3(a PRIMARY KEY,
-                        b NOT NULL ON CONFLICT REPLACE DEFAULT 1488);
-    ]], {
-        1, "SQL error: only PRIMARY KEY constraint can have ON CONFLICT REPLACE clause - T3"
-    })
-
-test:do_catchsql_test(
-    "conflict-7.5",
-    [[
-        CREATE TABLE t3(a PRIMARY KEY ON CONFLICT REPLACE,
-                        b NOT NULL ON CONFLICT REPLACE DEFAULT 1488);
-    ]], {
-        1, "SQL error: only PRIMARY KEY constraint can have ON CONFLICT REPLACE clause - T3"
-    })
-
-test:finish_test()
diff --git a/test/sql-tap/gh-2931-savepoints.test.lua b/test/sql-tap/gh-2931-savepoints.test.lua
index 8491a3b69..2ce41459e 100755
--- a/test/sql-tap/gh-2931-savepoints.test.lua
+++ b/test/sql-tap/gh-2931-savepoints.test.lua
@@ -86,7 +86,7 @@ local testcases = {
 		{0,{1,2,10,11,1,2,4,10,11}}},
 	{"16",
 		[[insert or rollback into t1 values(4);]],
-		{1,"UNIQUE constraint failed: T2.A"}},
+		{1,"Duplicate key exists in unique index 'pk_unnamed_T2_1' in space 'T2'"}},
 	{"17",  -- should work as transaction is rolled back
 		[[insert or rollback into t1 values(4);
 		select * from t1 union all select * from t2;]],
diff --git a/test/sql-tap/gh2140-trans.test.lua b/test/sql-tap/gh2140-trans.test.lua
index 1ec162057..3c6f9042f 100755
--- a/test/sql-tap/gh2140-trans.test.lua
+++ b/test/sql-tap/gh2140-trans.test.lua
@@ -28,39 +28,33 @@ test:do_execsql_test('rollback1_check',
                      {1, 1, 2, 2})
 
 for _, verb in ipairs({'ROLLBACK', 'ABORT'}) do
-        box.sql.execute('DELETE FROM t2')
-	if verb == 'ROLLBACK' then
-		answer = 'UNIQUE constraint failed: T1.S1'
-	else
-		answer = "Duplicate key exists in unique index 'pk_unnamed_T1_1' in space 'T1'"
-	end
-        test:do_catchsql_test('insert1_'..verb,
-                              [[START TRANSACTION;
-                                  INSERT INTO t2 VALUES (2, 2);
-                                  INSERT OR ]]..verb..[[ INTO t1 VALUES (1,1);
-                              ]],
-                              {1, answer})
+    box.sql.execute('DELETE FROM t2')
+    answer = "Duplicate key exists in unique index 'pk_unnamed_T1_1' in space 'T1'"
+    test:do_catchsql_test('insert1_'..verb,
+                          [[START TRANSACTION;
+                            INSERT INTO t2 VALUES (2, 2);
+                            INSERT OR ]]..verb..[[ INTO t1 VALUES (1,1);
+                          ]],
+                          {1, answer})
 
-        local expect = {}
-        if verb == 'ABORT' then
-            box.sql.execute('COMMIT')
-            expect = {2, 2}
-        end
-        test:do_execsql_test('insert1_'..verb..'_check',
-                             'SELECT * FROM t2',
-                             expect)
+    local expect = {}
+    if verb == 'ABORT' then
+         box.sql.execute('COMMIT')
+         expect = {2, 2}
+    end
+    test:do_execsql_test('insert1_'..verb..'_check',
+                         'SELECT * FROM t2', expect)
 
-        box.sql.execute('DELETE FROM t2')
-        test:do_catchsql_test('update1_'..verb,
-                       [[START TRANSACTION;
-                           INSERT INTO t2 VALUES (2, 2);
-                           UPDATE OR ]]..verb..[[ t1 SET s1 = 1 WHERE s1 = 2;
-                         ]],
-                       {1, answer})
+    box.sql.execute('DELETE FROM t2')
+    test:do_catchsql_test('update1_'..verb,
+                          [[START TRANSACTION;
+                            INSERT INTO t2 VALUES (2, 2);
+                            UPDATE OR ]]..verb..[[ t1 SET s1 = 1 WHERE s1 = 2;
+                          ]],
+                          {1, answer})
 
-        test:do_execsql_test('update1_'..verb..'check',
-                             'SELECT * FROM t2',
-                             expect)
+    test:do_execsql_test('update1_'..verb..'check',
+                         'SELECT * FROM t2', expect)
 end
 
 box.sql.execute('COMMIT')
diff --git a/test/sql-tap/gh2964-abort.test.lua b/test/sql-tap/gh2964-abort.test.lua
index a596b0110..d4fcebc1b 100755
--- a/test/sql-tap/gh2964-abort.test.lua
+++ b/test/sql-tap/gh2964-abort.test.lua
@@ -12,17 +12,16 @@ test:do_catchsql_test(
     test_prefix.."1.0.2",
     "CREATE TABLE t2 (a int primary key);")
 
-local insert_err = {1, "UNIQUE constraint failed: T2.A"}
-local insert_err_PK = {1, "Duplicate key exists in unique index 'pk_unnamed_T2_1' in space 'T2'"}
+local insert_err = {1, "Duplicate key exists in unique index 'pk_unnamed_T2_1' in space 'T2'"}
 local data = {
 --id|TRIG TYPE|INSERT TYPE|insert error|commit error| result
- {1, "AFTER", "or abort",   insert_err_PK, {0},          {1,1,2}},
+ {1, "AFTER", "or abort",   insert_err, {0},          {1,1,2}},
  {2, "AFTER", "or rollback",insert_err, {1, "/no transaction is active/"}, {}},
- {3, "AFTER", "or fail",    insert_err_PK, {0},          {1,2,1,2}},
+ {3, "AFTER", "or fail",    insert_err, {0},          {1,2,1,2}},
  {4, "AFTER", "or ignore",  {0}       , {0},          {1,2,1,2}},
- {5, "BEFORE","or abort",   insert_err_PK, {0},          {1,1,2}},
+ {5, "BEFORE","or abort",   insert_err, {0},          {1,1,2}},
  {6, "BEFORE","or rollback",insert_err, {1, "/no transaction is active/"}, {}},
- {7, "BEFORE","or fail",    insert_err_PK, {0},          {1,1,2}},
+ {7, "BEFORE","or fail",    insert_err, {0},          {1,1,2}},
  {8, "BEFORE","or ignore",  {0}       , {0},          {1,2,1,2}}
 }
 
diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
index 731ecf0af..fdce2683c 100755
--- a/test/sql-tap/index1.test.lua
+++ b/test/sql-tap/index1.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(78)
+test:plan(70)
 
 --!./tcltestrunner.lua
 -- 2001 September 15
@@ -1048,115 +1048,6 @@ test:do_execsql_test(
     ]], {
     })
 
-
-    -- These tests ensure that if multiple table definition constraints are
-    -- implemented by a single indice, the correct ON CONFLICT policy applies.
-    test:do_execsql_test(
-        "index-19.1",
-        [[
-            CREATE TABLE t7(a UNIQUE PRIMARY KEY);
-            CREATE TABLE t8(a UNIQUE PRIMARY KEY ON CONFLICT ROLLBACK);
-            INSERT INTO t7 VALUES(1);
-            INSERT INTO t8 VALUES(1);
-        ]], {
-            -- <index-19.1>
-            
-            -- </index-19.1>
-        })
-
-    test:do_catchsql_test(
-        "index-19.2",
-        [[
-            START TRANSACTION;
-            INSERT INTO t7 VALUES(1);
-        ]], {
-            -- <index-19.2>
-            1, "Duplicate key exists in unique index 'unique_unnamed_T7_1' in space 'T7'"
-            -- </index-19.2>
-        })
-
-    test:do_catchsql_test(
-        "index-19.3",
-        [[
-            START TRANSACTION;
-        ]], {
-            -- <index-19.3>
-            1, "Operation is not permitted when there is an active transaction "
-            -- </index-19.3>
-        })
-
-    test:do_catchsql_test(
-        "index-19.4",
-        [[
-            INSERT INTO t8 VALUES(1);
-        ]], {
-            -- <index-19.4>
-            1, "UNIQUE constraint failed: T8.A"
-            -- </index-19.4>
-        })
-
-    test:do_catchsql_test(
-        "index-19.5",
-        [[
-            START TRANSACTION;
-            COMMIT;
-        ]], {
-            -- <index-19.5>
-            0
-            -- </index-19.5>
-        })
-
-    test:do_catchsql_test(
-        "index-19.6",
-        [[
-            DROP TABLE t7;
-            DROP TABLE t8;
-            CREATE TABLE t7(
-               a PRIMARY KEY ON CONFLICT FAIL, 
-               UNIQUE(a) ON CONFLICT IGNORE
-            );
-        ]], {
-            -- <index-19.6>
-            1, "conflicting ON CONFLICT clauses specified"
-            -- </index-19.6>
-        })
-
--- MUST_WORK_TEST
-if (0 > 0) then
-test:do_execsql_test(
-    "index-19.7",
-    [[
-        REINDEX
-    ]], {
-        -- <index-19.7>
-
-        -- </index-19.7>
-    })
-end
-
--- integrity_check index-19.8
--- Drop index with a quoted name. Ticket #695.
-test:do_execsql_test(
-    "index-20.1",
-    [[
-        CREATE INDEX "t6i2" ON t6(c);
-        DROP INDEX "t6i2" ON t6;
-    ]], {
-        -- <index-20.1>
-        
-        -- </index-20.1>
-    })
-
-test:do_execsql_test(
-    "index-20.2",
-    [[
-        DROP INDEX t6i1 ON t6;
-    ]], {
-        -- <index-20.2>
-        
-        -- </index-20.2>
-    })
-
 -- Try to create a TEMP index on a non-TEMP table. */
 --
 test:do_catchsql_test(
diff --git a/test/sql-tap/null.test.lua b/test/sql-tap/null.test.lua
index 65d57207e..5e1f8666e 100755
--- a/test/sql-tap/null.test.lua
+++ b/test/sql-tap/null.test.lua
@@ -316,11 +316,10 @@ test:do_catchsql_test(
 test:do_execsql_test(
     "null-7.1",
     [[
-        create table t2(a primary key, b unique on conflict ignore);
+        create table t2(a primary key, b unique);
         insert into t2 values(1,1);
         insert into t2 values(2,null);
         insert into t2 values(3,null);
-        insert into t2 values(4,1);
         select a from t2 order by a;
     ]], {
         -- <null-7.1>
@@ -331,11 +330,10 @@ test:do_execsql_test(
 test:do_execsql_test(
     "null-7.2",
     [[
-        create table t3(a primary key, b, c, unique(b,c) on conflict ignore);
+        create table t3(a primary key, b, c, unique(b,c));
         insert into t3 values(1,1,1);
         insert into t3 values(2,null,1);
         insert into t3 values(3,null,1);
-        insert into t3 values(4,1,1);
         select a from t3 order by a;
     ]], {
         -- <null-7.2>
diff --git a/test/sql-tap/tkt-4a03edc4c8.test.lua b/test/sql-tap/tkt-4a03edc4c8.test.lua
index cbee2b459..ea42d78c9 100755
--- a/test/sql-tap/tkt-4a03edc4c8.test.lua
+++ b/test/sql-tap/tkt-4a03edc4c8.test.lua
@@ -25,8 +25,8 @@ test:do_test(
     function()
         test:execsql [[
             CREATE TABLE t1(
-              a INTEGER PRIMARY KEY ON CONFLICT REPLACE,
-              b UNIQUE ON CONFLICT FAIL
+              a INTEGER PRIMARY KEY,
+              b UNIQUE
             );
             INSERT INTO t1 VALUES(1, 1);
             INSERT INTO t1 VALUES(2, 2);
@@ -38,7 +38,7 @@ test:do_test(
         ]]
     end, {
         -- <tkt-4a03ed-1.1>
-        1, "UNIQUE constraint failed: T1.B"
+        1, "Duplicate key exists in unique index 'pk_unnamed_T1_1' in space 'T1'"
         -- </tkt-4a03ed-1.1>
     })
 
diff --git a/test/sql-tap/triggerC.test.lua b/test/sql-tap/triggerC.test.lua
index d1fc82842..06e6e5bd2 100755
--- a/test/sql-tap/triggerC.test.lua
+++ b/test/sql-tap/triggerC.test.lua
@@ -242,7 +242,7 @@ test:do_catchsql_test(
         UPDATE OR ROLLBACK t1 SET a=100;
     ]], {
         -- <triggerC-1.15>
-        1, "UNIQUE constraint failed: T1.A"
+        1, "Duplicate key exists in unique index 'pk_unnamed_T1_1' in space 'T1'"
         -- </triggerC-1.15>
     })
 
diff --git a/test/sql/on-conflict.result b/test/sql/on-conflict.result
index eed06d582..4b5d2cd7e 100644
--- a/test/sql/on-conflict.result
+++ b/test/sql/on-conflict.result
@@ -1,121 +1,159 @@
 test_run = require('test_run').new()
 ---
 ...
+---
+...
+---
+...
 engine = test_run:get_cfg('engine')
 ---
 ...
+---
+...
+---
+...
 box.sql.execute('pragma sql_default_engine=\''..engine..'\'')
 ---
 ...
--- Create space
+---
+...
+---
+...
+-- Check that original SQLite ON CONFLICT clause is really
+-- disabled.
+--
 box.sql.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, v INTEGER UNIQUE ON CONFLICT ABORT)")
 ---
+- error: keyword "ON" is reserved
 ...
 box.sql.execute("CREATE TABLE q (id INTEGER PRIMARY KEY, v INTEGER UNIQUE ON CONFLICT FAIL)")
 ---
+- error: keyword "ON" is reserved
 ...
 box.sql.execute("CREATE TABLE p (id INTEGER PRIMARY KEY, v INTEGER UNIQUE ON CONFLICT IGNORE)")
 ---
+- error: keyword "ON" is reserved
+...
+box.sql.execute("CREATE TABLE g (id INTEGER PRIMARY KEY, v INTEGER UNIQUE ON CONFLICT REPLACE)")
+---
+- error: keyword "ON" is reserved
 ...
 box.sql.execute("CREATE TABLE e (id INTEGER PRIMARY KEY ON CONFLICT REPLACE, v INTEGER)")
 ---
+- error: keyword "ON" is reserved
 ...
--- Insert values and select them
-box.sql.execute("INSERT INTO t values (1, 1), (2, 2), (3, 1)")
+box.sql.execute("CREATE TABLE t1(a INT PRIMARY KEY ON CONFLICT REPLACE)")
 ---
-- error: Duplicate key exists in unique index 'unique_unnamed_T_2' in space 'T'
+- error: keyword "ON" is reserved
 ...
-box.sql.execute("SELECT * FROM t")
+box.sql.execute("CREATE TABLE t2(a INT PRIMARY KEY ON CONFLICT IGNORE)")
 ---
-- []
+- error: keyword "ON" is reserved
 ...
-box.sql.execute("INSERT INTO q values (1, 1), (2, 2), (3, 1)")
+-- CHECK constraint is illegal with REPLACE option.
+--
+box.sql.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, a CHECK (a > 5) ON CONFLICT REPLACE);")
 ---
-- error: 'UNIQUE constraint failed: Q.V'
+- error: keyword "ON" is reserved
 ...
-box.sql.execute("SELECT * FROM q")
+--
+-- gh-3473: Primary key can't be declared with NULL.
+--
+box.sql.execute("CREATE TABLE te17 (s1 INT NULL PRIMARY KEY NOT NULL);")
 ---
-- - [1, 1]
-  - [2, 2]
+- error: Primary index of the space 'TE17' can not contain nullable parts
 ...
-box.sql.execute("INSERT INTO p values (1, 1), (2, 2), (3, 1), (4, 5)")
+box.sql.execute("CREATE TABLE te17 (s1 INT NULL PRIMARY KEY);")
 ---
+- error: Primary index of the space 'TE17' can not contain nullable parts
 ...
-box.sql.execute("SELECT * FROM p")
+box.sql.execute("CREATE TABLE test (a int PRIMARY KEY, b int NULL ON CONFLICT IGNORE);")
 ---
-- - [1, 1]
-  - [2, 2]
-  - [4, 5]
+- error: 'SQL error: NULL declaration for column ''B'' of table ''TEST'' has been
+    already set to ''none'''
 ...
-box.sql.execute("INSERT INTO e values (1, 1), (2, 2), (1, 3)")
+box.sql.execute("CREATE TABLE test (a int, b int NULL, c int, PRIMARY KEY(a, b, c))")
 ---
+- error: Primary index of the space 'TEST' can not contain nullable parts
 ...
-box.sql.execute("SELECT * FROM e")
+-- Several NOT NULL REPLACE constraints work
+--
+box.sql.execute("CREATE TABLE a (id INT PRIMARY KEY, a NOT NULL ON CONFLICT REPLACE DEFAULT 1, b NOT NULL ON CONFLICT REPLACE DEFAULT 2);")
 ---
-- - [1, 3]
-  - [2, 2]
 ...
-box.sql.execute("CREATE TABLE t1(a INT PRIMARY KEY ON CONFLICT REPLACE)")
+box.sql.execute("INSERT INTO a VALUES(1, NULL, NULL);")
 ---
 ...
-box.sql.execute("INSERT INTO t1 VALUES (9)")
+box.sql.execute("INSERT INTO a VALUES(2, NULL, NULL);")
 ---
 ...
-box.sql.execute("INSERT INTO t1 VALUES (9)")
+box.sql.execute("SELECT * FROM a;")
 ---
+- - [1, 1, 2]
+  - [2, 1, 2]
 ...
-box.sql.execute("SELECT * FROM t1")
+box.sql.execute("DROP TABLE a;")
 ---
-- - [9]
 ...
-box.sql.execute("CREATE TABLE t2(a INT PRIMARY KEY ON CONFLICT IGNORE)")
+-- gh-3566: UPDATE OR IGNORE causes deletion of old entry.
+--
+box.sql.execute("CREATE TABLE tj (s1 INT PRIMARY KEY, s2 INT);")
 ---
 ...
-box.sql.execute("INSERT INTO t2 VALUES (9)")
+box.sql.execute("INSERT INTO tj VALUES (1, 2), (2, 3);")
 ---
 ...
-box.sql.execute("INSERT INTO t2 VALUES (9)")
+box.sql.execute("CREATE UNIQUE INDEX i ON tj (s2);")
 ---
 ...
-box.sql.execute("SELECT * FROM t2")
+box.sql.execute("UPDATE OR IGNORE tj SET s1 = s1 + 1;")
 ---
-- - [9]
 ...
-box.sql.execute('DROP TABLE t')
+box.sql.execute("SELECT * FROM tj;")
 ---
+- - [1, 2]
+  - [3, 3]
 ...
-box.sql.execute('DROP TABLE q')
+box.sql.execute("UPDATE OR IGNORE tj SET s2 = s2 + 1;")
 ---
 ...
-box.sql.execute('DROP TABLE p')
+box.sql.execute("SELECT * FROM tj;")
 ---
+- - [1, 2]
+  - [3, 4]
 ...
-box.sql.execute('DROP TABLE e')
+-- gh-3565: INSERT OR REPLACE causes assertion fault.
+--
+box.sql.execute("DROP TABLE tj;")
 ---
 ...
-box.sql.execute('DROP TABLE t1')
+box.sql.execute("CREATE TABLE tj (s1 INT PRIMARY KEY, s2 INT);")
 ---
 ...
-box.sql.execute('DROP TABLE t2')
+box.sql.execute("INSERT INTO tj VALUES (1, 2),(2, 3);")
 ---
 ...
---
--- gh-3473: Primary key can't be declared with NULL.
---
-box.sql.execute('CREATE TABLE te17 (s1 INT NULL PRIMARY KEY NOT NULL);')
+box.sql.execute("CREATE UNIQUE INDEX i ON tj (s2);")
 ---
-- error: Primary index of the space 'TE17' can not contain nullable parts
 ...
-box.sql.execute('CREATE TABLE te17 (s1 INT NULL PRIMARY KEY);')
+box.sql.execute("REPLACE INTO tj VALUES (1, 3);")
 ---
-- error: Primary index of the space 'TE17' can not contain nullable parts
 ...
-box.sql.execute("CREATE TABLE test (a int PRIMARY KEY, b int NULL ON CONFLICT IGNORE);")
+box.sql.execute("SELECT * FROM tj;")
 ---
-- error: 'SQL error: NULL declaration for column ''B'' of table ''TEST'' has been
-    already set to ''none'''
+- - [1, 3]
 ...
-box.sql.execute("CREATE TABLE test (a int, b int NULL, c int, PRIMARY KEY(a, b, c))")
+box.sql.execute("INSERT INTO tj VALUES (2, 4), (3, 5);")
+---
+...
+box.sql.execute("UPDATE OR REPLACE tj SET s2 = s2 + 1;")
+---
+...
+box.sql.execute("SELECT * FROM tj;")
+---
+- - [1, 4]
+  - [3, 6]
+...
+box.sql.execute("DROP TABLE tj;")
 ---
-- error: Primary index of the space 'TEST' can not contain nullable parts
 ...
diff --git a/test/sql/on-conflict.test.lua b/test/sql/on-conflict.test.lua
index 347245a8c..ab73249ef 100644
--- a/test/sql/on-conflict.test.lua
+++ b/test/sql/on-conflict.test.lua
@@ -1,48 +1,63 @@
 test_run = require('test_run').new()
+---
+...
 engine = test_run:get_cfg('engine')
+---
+...
 box.sql.execute('pragma sql_default_engine=\''..engine..'\'')
-
--- Create space
+---
+...
+-- Check that original SQLite ON CONFLICT clause is really
+-- disabled.
+--
 box.sql.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, v INTEGER UNIQUE ON CONFLICT ABORT)")
 box.sql.execute("CREATE TABLE q (id INTEGER PRIMARY KEY, v INTEGER UNIQUE ON CONFLICT FAIL)")
 box.sql.execute("CREATE TABLE p (id INTEGER PRIMARY KEY, v INTEGER UNIQUE ON CONFLICT IGNORE)")
+box.sql.execute("CREATE TABLE g (id INTEGER PRIMARY KEY, v INTEGER UNIQUE ON CONFLICT REPLACE)")
 box.sql.execute("CREATE TABLE e (id INTEGER PRIMARY KEY ON CONFLICT REPLACE, v INTEGER)")
-
--- Insert values and select them
-box.sql.execute("INSERT INTO t values (1, 1), (2, 2), (3, 1)")
-box.sql.execute("SELECT * FROM t")
-
-box.sql.execute("INSERT INTO q values (1, 1), (2, 2), (3, 1)")
-box.sql.execute("SELECT * FROM q")
-
-box.sql.execute("INSERT INTO p values (1, 1), (2, 2), (3, 1), (4, 5)")
-box.sql.execute("SELECT * FROM p")
-
-box.sql.execute("INSERT INTO e values (1, 1), (2, 2), (1, 3)")
-box.sql.execute("SELECT * FROM e")
-
 box.sql.execute("CREATE TABLE t1(a INT PRIMARY KEY ON CONFLICT REPLACE)")
-box.sql.execute("INSERT INTO t1 VALUES (9)")
-box.sql.execute("INSERT INTO t1 VALUES (9)")
-box.sql.execute("SELECT * FROM t1")
-
 box.sql.execute("CREATE TABLE t2(a INT PRIMARY KEY ON CONFLICT IGNORE)")
-box.sql.execute("INSERT INTO t2 VALUES (9)")
-box.sql.execute("INSERT INTO t2 VALUES (9)")
 
-box.sql.execute("SELECT * FROM t2")
-
-box.sql.execute('DROP TABLE t')
-box.sql.execute('DROP TABLE q')
-box.sql.execute('DROP TABLE p')
-box.sql.execute('DROP TABLE e')
-box.sql.execute('DROP TABLE t1')
-box.sql.execute('DROP TABLE t2')
+-- CHECK constraint is illegal with REPLACE option.
+--
+box.sql.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, a CHECK (a > 5) ON CONFLICT REPLACE);")
 
 --
 -- gh-3473: Primary key can't be declared with NULL.
 --
-box.sql.execute('CREATE TABLE te17 (s1 INT NULL PRIMARY KEY NOT NULL);')
-box.sql.execute('CREATE TABLE te17 (s1 INT NULL PRIMARY KEY);')
+box.sql.execute("CREATE TABLE te17 (s1 INT NULL PRIMARY KEY NOT NULL);")
+box.sql.execute("CREATE TABLE te17 (s1 INT NULL PRIMARY KEY);")
 box.sql.execute("CREATE TABLE test (a int PRIMARY KEY, b int NULL ON CONFLICT IGNORE);")
 box.sql.execute("CREATE TABLE test (a int, b int NULL, c int, PRIMARY KEY(a, b, c))")
+
+-- Several NOT NULL REPLACE constraints work
+--
+box.sql.execute("CREATE TABLE a (id INT PRIMARY KEY, a NOT NULL ON CONFLICT REPLACE DEFAULT 1, b NOT NULL ON CONFLICT REPLACE DEFAULT 2);")
+box.sql.execute("INSERT INTO a VALUES(1, NULL, NULL);")
+box.sql.execute("INSERT INTO a VALUES(2, NULL, NULL);")
+box.sql.execute("SELECT * FROM a;")
+box.sql.execute("DROP TABLE a;")
+
+-- gh-3566: UPDATE OR IGNORE causes deletion of old entry.
+--
+box.sql.execute("CREATE TABLE tj (s1 INT PRIMARY KEY, s2 INT);")
+box.sql.execute("INSERT INTO tj VALUES (1, 2), (2, 3);")
+box.sql.execute("CREATE UNIQUE INDEX i ON tj (s2);")
+box.sql.execute("UPDATE OR IGNORE tj SET s1 = s1 + 1;")
+box.sql.execute("SELECT * FROM tj;")
+box.sql.execute("UPDATE OR IGNORE tj SET s2 = s2 + 1;")
+box.sql.execute("SELECT * FROM tj;")
+
+-- gh-3565: INSERT OR REPLACE causes assertion fault.
+--
+box.sql.execute("DROP TABLE tj;")
+box.sql.execute("CREATE TABLE tj (s1 INT PRIMARY KEY, s2 INT);")
+box.sql.execute("INSERT INTO tj VALUES (1, 2),(2, 3);")
+box.sql.execute("CREATE UNIQUE INDEX i ON tj (s2);")
+box.sql.execute("REPLACE INTO tj VALUES (1, 3);")
+box.sql.execute("SELECT * FROM tj;")
+box.sql.execute("INSERT INTO tj VALUES (2, 4), (3, 5);")
+box.sql.execute("UPDATE OR REPLACE tj SET s2 = s2 + 1;")
+box.sql.execute("SELECT * FROM tj;")
+
+box.sql.execute("DROP TABLE tj;")
-- 
2.15.1





More information about the Tarantool-patches mailing list