[tarantool-patches] [PATCH v2 5/9] schema: add new system space for CHECK constraints

Kirill Shcherbatov kshcherbatov at tarantool.org
Wed Jan 30 11:59:12 MSK 2019


This patch introduces new system space to persist check
constraints. Format of the space:

_ck_constraint (space id = 357)
[<constraint name> STR, <space id> UINT, <expression string>STR]

CK constraint is local to space, so every pair <CK name, space id>
is unique (and it is PK in _ck_constraint space).

After insertion into this space, a new instance describing check
constraint is created. Check constraint held Expr tree.
Until space features check constraints, it isn't allowed to
be dropped. The :drop() space method firstly deletes all check
constraints and than removes entry from _space.

We use BuildCkConstraints Alter operation object because space
definition may be modified and check AST must be recreated.

Needed for #3691
---
 src/box/CMakeLists.txt                |   1 +
 src/box/alter.cc                      | 217 ++++++++++++++++++++++++--
 src/box/alter.h                       |   1 +
 src/box/bootstrap.snap                | Bin 1914 -> 1955 bytes
 src/box/ck_constraint.c               | 162 +++++++++++++++++++
 src/box/ck_constraint.h               | 173 ++++++++++++++++++++
 src/box/errcode.h                     |   4 +-
 src/box/lua/schema.lua                |   4 +
 src/box/lua/space.cc                  |   2 +
 src/box/lua/upgrade.lua               |  38 +++++
 src/box/schema.cc                     |   8 +
 src/box/schema_def.h                  |   9 ++
 src/box/space.c                       |   2 +
 src/box/space.h                       |   5 +
 src/box/space_def.c                   |  97 +-----------
 src/box/space_def.h                   |   2 -
 src/box/sql.c                         |  97 +-----------
 src/box/sql.h                         |  44 ------
 src/box/sql/build.c                   | 160 ++++++++++++++-----
 src/box/sql/insert.c                  |  52 +++---
 src/box/sql/parse.y                   |   4 +-
 src/box/sql/prepare.c                 |   1 +
 src/box/sql/select.c                  |   3 +-
 src/box/sql/sqliteInt.h               |  12 +-
 src/box/sql/vdbe.c                    |   6 +-
 test/app-tap/tarantoolctl.test.lua    |   4 +-
 test/box-py/bootstrap.result          |   4 +
 test/box/access.result                |   3 +
 test/box/access.test.lua              |   1 +
 test/box/access_misc.result           |   2 +
 test/box/access_sysview.result        |   6 +-
 test/box/alter.result                 |   6 +-
 test/box/misc.result                  |   2 +
 test/sql-tap/check.test.lua           |  40 ++---
 test/sql-tap/fkey2.test.lua           |   4 +-
 test/sql-tap/in1.test.lua             |   2 +-
 test/sql-tap/table.test.lua           |   8 +-
 test/sql/checks.result                |  96 +++++++-----
 test/sql/checks.test.lua              |  55 +++----
 test/sql/errinj.result                |  52 ++++++
 test/sql/errinj.test.lua              |  19 +++
 test/sql/gh-2981-check-autoinc.result |   8 +-
 test/sql/upgrade.result               |   9 +-
 test/wal_off/alter.result             |   2 +-
 44 files changed, 991 insertions(+), 436 deletions(-)
 create mode 100644 src/box/ck_constraint.c
 create mode 100644 src/box/ck_constraint.h

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 5521e489e..5019127eb 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -115,6 +115,7 @@ add_library(box STATIC
     applier.cc
     relay.cc
     journal.c
+    ck_constraint.c
     sql.c
     execute.c
     wal.c
diff --git a/src/box/alter.cc b/src/box/alter.cc
index eff3524cf..3ba604ca6 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -29,6 +29,7 @@
  * SUCH DAMAGE.
  */
 #include "alter.h"
+#include "ck_constraint.h"
 #include "schema.h"
 #include "user.h"
 #include "space.h"
@@ -532,17 +533,6 @@ space_def_new_from_tuple(struct tuple *tuple, uint32_t errcode,
 				 engine_name, engine_name_len, &opts, fields,
 				 field_count);
 	auto def_guard = make_scoped_guard([=] { space_def_delete(def); });
-	if (def->opts.checks != NULL &&
-	    sql_checks_resolve_space_def_reference(def->opts.checks,
-						   def) != 0) {
-		box_error_t *err = box_error_last();
-		if (box_error_code(err) != ENOMEM) {
-			tnt_raise(ClientError, errcode, def->name,
-				  box_error_message(err));
-		} else {
-			diag_raise();
-		}
-	}
 	struct engine *engine = engine_find_xc(def->engine_name);
 	engine_check_space_def_xc(engine, def);
 	def_guard.is_active = false;
@@ -1351,6 +1341,56 @@ TruncateIndex::commit(struct alter_space *alter, int64_t signature)
 	index_commit_create(new_index, signature);
 }
 
+/** BuildCkConstraints - rebuild ck_constraints on alter. */
+class BuildCkConstraints: public AlterSpaceOp
+{
+public:
+	BuildCkConstraints(struct alter_space *alter)
+		: AlterSpaceOp(alter),
+		ck_constraint(RLIST_HEAD_INITIALIZER(ck_constraint)) {}
+	struct rlist ck_constraint;
+	virtual void prepare(struct alter_space *alter);
+	virtual void alter(struct alter_space *alter);
+	virtual void rollback(struct alter_space *alter);
+	virtual ~BuildCkConstraints();
+};
+
+void
+BuildCkConstraints::prepare(struct alter_space *alter)
+{
+	struct ck_constraint *old_ck_constraint;
+	rlist_foreach_entry(old_ck_constraint, &alter->old_space->ck_constraint,
+			    link) {
+		struct ck_constraint *new_ck_constraint =
+			ck_constraint_new(old_ck_constraint->def,
+					  alter->new_space->def);
+		if (new_ck_constraint == NULL)
+			diag_raise();
+		rlist_add_entry(&ck_constraint, new_ck_constraint, link);
+	}
+}
+
+void
+BuildCkConstraints::alter(struct alter_space *alter)
+{
+	rlist_swap(&alter->new_space->ck_constraint, &ck_constraint);
+	rlist_swap(&ck_constraint, &alter->old_space->ck_constraint);
+}
+
+void
+BuildCkConstraints::rollback(struct alter_space *alter)
+{
+	rlist_swap(&alter->old_space->ck_constraint, &ck_constraint);
+	rlist_swap(&ck_constraint, &alter->new_space->ck_constraint);
+}
+
+BuildCkConstraints::~BuildCkConstraints()
+{
+	struct ck_constraint *old_ck_constraint, *tmp;
+	rlist_foreach_entry_safe(old_ck_constraint, &ck_constraint, link, tmp)
+		ck_constraint_delete(old_ck_constraint);
+}
+
 /**
  * UpdateSchemaVersion - increment schema_version. Used on
  * in alter_space_do(), i.e. when creating or dropping
@@ -1757,6 +1797,12 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 				  space_name(old_space),
 				  "the space has foreign key constraints");
 		}
+		/* Can't drop space having check constraints. */
+		if (!rlist_empty(&old_space->ck_constraint)) {
+			tnt_raise(ClientError, ER_DROP_SPACE,
+				  space_name(old_space),
+				  "the space has check constraints");
+		}
 		/**
 		 * The space must be deleted from the space
 		 * cache right away to achieve linearisable
@@ -1854,6 +1900,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 						     def->field_count);
 		(void) new CheckSpaceFormat(alter);
 		(void) new ModifySpace(alter, def);
+		/* Add an op to rebuild check constraints. */
+		(void) new BuildCkConstraints(alter);
 		def_guard.is_active = false;
 		/* Create MoveIndex ops for all space indexes. */
 		alter_space_move_indexes(alter, 0, old_space->index_id_max + 1);
@@ -2096,6 +2144,8 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 	 * old space.
 	 */
 	alter_space_move_indexes(alter, iid + 1, old_space->index_id_max + 1);
+	/* Add an op to rebuild check constraints. */
+	(void) new BuildCkConstraints(alter);
 	/* Add an op to update schema_version on commit. */
 	(void) new UpdateSchemaVersion(alter);
 	alter_space_do(txn, alter);
@@ -2163,7 +2213,8 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event)
 		struct index *old_index = old_space->index[i];
 		(void) new TruncateIndex(alter, old_index->def->iid);
 	}
-
+	/* Add an op to rebuild check constraints. */
+	(void) new BuildCkConstraints(alter);
 	alter_space_do(txn, alter);
 	scoped_guard.is_active = false;
 }
@@ -4051,6 +4102,144 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 	}
 }
 
+/**
+ * Create an instance of check constraint definition from tuple
+ * on region.
+ */
+static struct ck_constraint_def *
+ck_constraint_def_decode(const struct tuple *tuple, struct region *region)
+{
+	uint32_t name_len;
+	const char *name =
+		tuple_field_str_xc(tuple, BOX_CK_CONSTRAINT_FIELD_NAME,
+				   &name_len);
+	if (name_len > BOX_NAME_MAX) {
+		tnt_raise(ClientError, ER_CREATE_CK_CONSTRAINT,
+			  tt_cstr(name, BOX_INVALID_NAME_MAX),
+				  "check constraint name is too long");
+	}
+	identifier_check_xc(name, name_len);
+	uint32_t expr_str_len;
+	const char *expr_str =
+		tuple_field_str_xc(tuple, BOX_CK_CONSTRAINT_FIELD_EXPR_STR,
+				   &expr_str_len);
+	uint32_t name_offset, expr_str_offset;
+	uint32_t sz = ck_constraint_def_sizeof(name_len, expr_str_len,
+					       &name_offset, &expr_str_offset);
+	struct ck_constraint_def *ck_constraint_def =
+		(struct ck_constraint_def *)region_alloc_xc(region, sz);
+	ck_constraint_def_create(ck_constraint_def, name, name_len, expr_str,
+				 expr_str_len);
+	return ck_constraint_def;
+}
+
+/** Trigger invoked on rollback in the _ck_constraint space. */
+static void
+on_replace_ck_constraint_rollback(struct trigger *trigger, void *event)
+{
+	struct txn_stmt *stmt = txn_last_stmt((struct txn*) event);
+	struct ck_constraint *ck_constraint =
+		(struct ck_constraint *)trigger->data;
+	struct space *space = NULL;
+	if (ck_constraint != NULL)
+		space = space_by_id(ck_constraint->space_id);
+	if (stmt->old_tuple != NULL && stmt->new_tuple == NULL) {
+		/* Rollback DELETE check constraint. */
+		if (ck_constraint == NULL)
+			return;
+		assert(space != NULL);
+		rlist_add_entry(&space->ck_constraint, ck_constraint, link);
+	}  else if (stmt->new_tuple != NULL && stmt->old_tuple == NULL) {
+		/* Rollback INSERT check constraint. */
+		assert(space != NULL);
+		rlist_del_entry(ck_constraint, link);
+		ck_constraint_delete(ck_constraint);
+	} else {
+		/* Rollback REPLACE check constraint. */
+		assert(space != NULL);
+		const char *space_name = ck_constraint->def->name;
+		struct ck_constraint *new_ck_constraint =
+			space_ck_constraint_by_name(space, space_name,
+						    strlen(space_name));
+		assert(new_ck_constraint != NULL);
+		rlist_del_entry(new_ck_constraint, link);
+		rlist_add_entry(&space->ck_constraint, ck_constraint, link);
+		ck_constraint_delete(new_ck_constraint);
+	}
+}
+
+/**
+ * Trigger invoked on commit in the _ck_constraint space.
+ * Drop useless old check constraint object if exists.
+ */
+static void
+on_replace_ck_constraint_commit(struct trigger *trigger, void * /* event */)
+{
+	struct ck_constraint *old_ck_constraint =
+		(struct ck_constraint *)trigger->data;
+	if (old_ck_constraint != NULL)
+		ck_constraint_delete(old_ck_constraint);
+	++schema_version;
+}
+
+/** A trigger invoked on replace in the _ck_constraint space. */
+static void
+on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
+{
+	struct txn *txn = (struct txn *) event;
+	txn_check_singlestatement_xc(txn, "Space _ck_constraint");
+	struct txn_stmt *stmt = txn_current_stmt(txn);
+	struct tuple *old_tuple = stmt->old_tuple;
+	struct tuple *new_tuple = stmt->new_tuple;
+	uint32_t space_id =
+		tuple_field_u32_xc(old_tuple != NULL ? old_tuple : new_tuple,
+				   BOX_CK_CONSTRAINT_FIELD_SPACE_ID);
+	struct space *space = space_cache_find_xc(space_id);
+	access_check_ddl(space->def->name, space->def->id, space->def->uid,
+			 SC_SPACE, PRIV_A);
+
+	struct trigger *on_rollback =
+		txn_alter_trigger_new(on_replace_ck_constraint_rollback, NULL);
+	struct trigger *on_commit =
+		txn_alter_trigger_new(on_replace_ck_constraint_commit, NULL);
+
+	if (new_tuple != NULL) {
+		/* Create or replace check constraint. */
+		struct ck_constraint_def *ck_constraint_def =
+			ck_constraint_def_decode(new_tuple, &fiber()->gc);
+		struct ck_constraint *new_ck_constraint =
+			ck_constraint_new(ck_constraint_def, space->def);
+		if (new_ck_constraint == NULL)
+			diag_raise();
+		const char *space_name = new_ck_constraint->def->name;
+		struct ck_constraint *old_ck_constraint =
+			space_ck_constraint_by_name(space, space_name,
+						    strlen(space_name));
+		if (old_ck_constraint != NULL)
+			rlist_del_entry(old_ck_constraint, link);
+		rlist_add_entry(&space->ck_constraint, new_ck_constraint, link);
+		on_commit->data = old_ck_constraint;
+		on_rollback->data = old_tuple == NULL ? new_ck_constraint :
+							old_ck_constraint;
+	} else if (new_tuple == NULL && old_tuple != NULL) {
+		/* Drop check constraint. */
+		uint32_t name_len;
+		const char *name =
+			tuple_field_str_xc(old_tuple,
+					   BOX_CK_CONSTRAINT_FIELD_NAME,
+					   &name_len);
+		struct ck_constraint *old_ck_constraint =
+			space_ck_constraint_by_name(space, name, name_len);
+		assert(old_ck_constraint != NULL);
+		rlist_del_entry(old_ck_constraint, link);
+		on_commit->data = old_ck_constraint;
+		on_rollback->data = old_ck_constraint;
+	}
+
+	txn_on_rollback(txn, on_rollback);
+	txn_on_commit(txn, on_commit);
+}
+
 struct trigger alter_space_on_replace_space = {
 	RLIST_LINK_INITIALIZER, on_replace_dd_space, NULL, NULL
 };
@@ -4119,4 +4308,8 @@ struct trigger on_replace_fk_constraint = {
 	RLIST_LINK_INITIALIZER, on_replace_dd_fk_constraint, NULL, NULL
 };
 
+struct trigger on_replace_ck_constraint = {
+	RLIST_LINK_INITIALIZER, on_replace_dd_ck_constraint, NULL, NULL
+};
+
 /* vim: set foldmethod=marker */
diff --git a/src/box/alter.h b/src/box/alter.h
index 4108fa47c..b9ba7b846 100644
--- a/src/box/alter.h
+++ b/src/box/alter.h
@@ -46,6 +46,7 @@ extern struct trigger on_replace_sequence_data;
 extern struct trigger on_replace_space_sequence;
 extern struct trigger on_replace_trigger;
 extern struct trigger on_replace_fk_constraint;
+extern struct trigger on_replace_ck_constraint;
 extern struct trigger on_stmt_begin_space;
 extern struct trigger on_stmt_begin_index;
 extern struct trigger on_stmt_begin_truncate;
diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
index 662d6411544a3374ab97cf83dd8912ba7e05f6bd..2f5111e79d17a4fb9fd9aaf5093b5fe63d3dc240 100644
GIT binary patch
delta 1950

diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c
new file mode 100644
index 000000000..044416a4f
--- /dev/null
+++ b/src/box/ck_constraint.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <assert.h>
+#include "ck_constraint.h"
+#include "errcode.h"
+#include "small/rlist.h"
+#include "sql.h"
+#include "sql/sqliteInt.h"
+
+/**
+ * Resolve space_def references for check constraint via AST
+ * tree traversal.
+ * @param expr Check constraint expression AST to resolve column
+ *             references.
+ * @param ck_constraint_name Check constraint name to raise error.
+ * @param space_def Space definition to use.
+ * @retval 0 on success.
+ * @retval -1 on error.
+ */
+static int
+ck_constraint_resolve_column_reference(struct Expr *expr,
+				       const char *ck_constraint_name,
+				       const struct space_def *space_def)
+{
+	struct Parse parser;
+	sql_parser_create(&parser, sql_get());
+	parser.parse_only = true;
+
+	struct Table dummy_table;
+	memset(&dummy_table, 0, sizeof(dummy_table));
+	dummy_table.def = (struct space_def *)space_def;
+
+	sql_resolve_self_reference(&parser, &dummy_table, NC_IsCheck,
+				   expr, NULL);
+	int rc = 0;
+	if (parser.rc != SQLITE_OK) {
+		/* Tarantool error may be already set with diag. */
+		if (parser.rc != SQL_TARANTOOL_ERROR) {
+			diag_set(ClientError, ER_CREATE_CK_CONSTRAINT,
+				 ck_constraint_name, parser.zErrMsg);
+		}
+		rc = -1;
+	}
+	sql_parser_destroy(&parser);
+	return rc;
+}
+
+uint32_t
+ck_constraint_def_sizeof(uint32_t name_len, uint32_t expr_str_len,
+			 uint32_t *name_offset, uint32_t *expr_str_offset)
+{
+	*name_offset = sizeof(struct ck_constraint_def);
+	*expr_str_offset = *name_offset + (name_len != 0 ? name_len + 1 : 0);
+	return *expr_str_offset + (expr_str_len != 0 ? expr_str_len + 1 : 0);
+}
+
+void
+ck_constraint_def_create(struct ck_constraint_def *ck_constraint_def,
+			 const char *name, uint32_t name_len,
+			 const char *expr_str, uint32_t expr_str_len)
+{
+	uint32_t name_offset, expr_str_offset;
+	(void)ck_constraint_def_sizeof(name_len, expr_str_len, &name_offset,
+				       &expr_str_offset);
+	ck_constraint_def->name = (char *)ck_constraint_def + name_offset;
+	sprintf(ck_constraint_def->name, "%.*s", name_len, name);
+	ck_constraint_def->expr_str =
+		(char *)ck_constraint_def + expr_str_offset;
+	sprintf(ck_constraint_def->expr_str, "%.*s", expr_str_len, expr_str);
+	rlist_create(&ck_constraint_def->link);
+}
+
+struct ck_constraint *
+ck_constraint_new(const struct ck_constraint_def *ck_constraint_def,
+		  const struct space_def *space_def)
+{
+	uint32_t ck_constraint_name_len = strlen(ck_constraint_def->name);
+	uint32_t expr_str_len = strlen(ck_constraint_def->expr_str);
+	uint32_t name_offset, expr_str_offset;
+	uint32_t ck_constraint_def_sz =
+		ck_constraint_def_sizeof(ck_constraint_name_len, expr_str_len,
+					 &name_offset, &expr_str_offset);
+	uint32_t ck_constraint_sz = sizeof(struct ck_constraint) +
+				    ck_constraint_def_sz;
+	struct ck_constraint *ck_constraint = calloc(1, ck_constraint_sz);
+	if (ck_constraint == NULL) {
+		diag_set(OutOfMemory, ck_constraint_sz, "malloc",
+			 "ck_constraint");
+		return NULL;
+	}
+	rlist_create(&ck_constraint->link);
+	ck_constraint->space_id = space_def->id;
+	ck_constraint->def =
+		(struct ck_constraint_def *)((char *)ck_constraint +
+					     sizeof(struct ck_constraint));
+	ck_constraint_def_create(ck_constraint->def, ck_constraint_def->name,
+				 ck_constraint_name_len,
+				 ck_constraint_def->expr_str, expr_str_len);
+	struct Expr *expr =
+		sql_expr_compile(sql_get(), ck_constraint_def->expr_str,
+				 expr_str_len);
+	if (expr == NULL)
+		goto error;
+	if (ck_constraint_resolve_column_reference(expr, ck_constraint_def->name,
+						   space_def) != 0)
+		goto error;
+	ck_constraint->expr = expr;
+
+	return ck_constraint;
+error:
+	ck_constraint_delete(ck_constraint);
+	return NULL;
+}
+
+void
+ck_constraint_delete(struct ck_constraint *ck_constraint)
+{
+	sql_expr_delete(sql_get(), ck_constraint->expr, false);
+	TRASH(ck_constraint);
+	free(ck_constraint);
+}
+
+struct ck_constraint *
+space_ck_constraint_by_name(struct space *space, const char *name,
+			    uint32_t name_len)
+{
+	struct ck_constraint *ck_constraint = NULL;
+	rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) {
+		if (strlen(ck_constraint->def->name) == name_len &&
+		    memcmp(ck_constraint->def->name, name, name_len) == 0)
+			return ck_constraint;
+	}
+	return NULL;
+}
diff --git a/src/box/ck_constraint.h b/src/box/ck_constraint.h
new file mode 100644
index 000000000..b63c7cb43
--- /dev/null
+++ b/src/box/ck_constraint.h
@@ -0,0 +1,173 @@
+#ifndef INCLUDES_BOX_CK_CONSTRAINT_H
+#define INCLUDES_BOX_CK_CONSTRAINT_H
+/*
+ * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include "small/rlist.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct space;
+struct space_def;
+struct Expr;
+
+/**
+ * Definition of check constraint.
+ * The memory of size calculated with ck_constraint_def_sizeof
+ * must be allocated manually and must be initialized with routine
+ * ck_constraint_def_create.
+ */
+struct ck_constraint_def {
+	/**
+	 * The name of the check constraint is used for error
+	 * reporting. Must be unique for a given space.
+	 */
+	char *name;
+	/**
+	 * The string describing an check constraint expression.
+	 */
+	char *expr_str;
+	/**
+	 * Organize check_def structs into linked list with
+	 * Parse::new_ck_constraint.
+	 */
+	struct rlist link;
+};
+
+/* Structure representing check constraint object. */
+struct ck_constraint {
+	/**
+	 * The check constraint definition.
+	 */
+	struct ck_constraint_def *def;
+	/**
+	 * The check constraint expression AST is built for
+	 * ck_constraint::def::expr_str with sql_expr_compile
+	 * and resolved with sqlite3ResolveExprNames for
+	 * space with space[ck_constraint::space_id] definition.
+	 */
+	struct Expr *expr;
+	/**
+	 * The id of the space this check constraint is
+	 * built for.
+	 */
+	uint32_t space_id;
+	/**
+	 * Organize check constraint structs into linked list
+	 * with space::ck_constraint.
+	 */
+	struct rlist link;
+};
+
+/**
+ * Calculate check constraint definition memory size and fields
+ * offsets for given arguments.
+ * Alongside with struct ck_constraint_def itself, we reserve
+ * memory for string containing its name and expression string.
+ *
+ * Memory layout:
+ * +-----------------------------+ <- Allocated memory starts here
+ * |   struct ck_constraint_def  |
+ * |-----------------------------|
+ * |          name + \0          |
+ * |-----------------------------|
+ * |        expr_str + \0        |
+ * +-----------------------------+
+ * @param name_len The length of the name.
+ * @param expr_str_len The length of the expr_str.
+ * @param[out] name_offset The offset of the name string.
+ * @param[out] expr_str_offset The offset of the expr_str string.
+ */
+uint32_t
+ck_constraint_def_sizeof(uint32_t name_len, uint32_t expr_str_len,
+			 uint32_t *name_offset, uint32_t *expr_str_offset);
+
+/**
+ * Initialize specified memory chunk ck_constraint_def of size
+ * calculated with ck_constraint_def_sizeof for given arguments.
+ * @param ck_constraint_def Check constraint definition to
+ *                          initialize.
+ * @param name The check constraint name.
+ * @param name_len The length of the name.
+ * @param expr_str The string describing check constraint
+ *                 expression (optional).
+ * @param expr_str_len The length of the expr_str.
+ */
+void
+ck_constraint_def_create(struct ck_constraint_def *ck_constraint_def,
+			 const char *name, uint32_t name_len,
+			 const char *expr_str, uint32_t expr_str_len);
+
+/**
+ * Create a new object representing check constraint object
+ * for given check constraint definition and space definition
+ * this constraint is related to.
+ * This routine manually allocates own space_def structure as
+ * a part of new memory chunk.
+ * @param ck_constraint_def The check constraint definition object
+ *                          to use. Must be initialized with
+ *                          ck_constraint_def_new.
+ * @param space_def The space definition of the space this check
+ *                  constraint is constructed for.
+ * @retval not NULL Check constraint object on success,
+ *         NULL otherwise.
+*/
+struct ck_constraint *
+ck_constraint_new(const struct ck_constraint_def *ck_constraint_def,
+		  const struct space_def *space_def);
+
+/**
+ * Destroy check constraint memory, release acquired resources.
+ * @param ck_constraint The check constraint object to destroy.
+ */
+void
+ck_constraint_delete(struct ck_constraint *ck_constraint);
+
+/**
+ * Find check constraint object in space by given name and
+ * name_len.
+ * @param space The space to lookup check constarint.
+ * @param name The check constraint name.
+ * @param name_len The length of the name.
+ * @retval not NULL Check constrain if exists, NULL otherwise.
+ */
+struct ck_constraint *
+space_ck_constraint_by_name(struct space *space, const char *name,
+			    uint32_t name_len);
+
+#if defined(__cplusplus)
+} /* extern "C" { */
+#endif
+
+#endif /* INCLUDES_BOX_CK_CONSTRAINT_H */
diff --git a/src/box/errcode.h b/src/box/errcode.h
index f7dbb948e..bae6f71e7 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -200,12 +200,12 @@ struct errcode_record {
 	/*145 */_(ER_NO_SUCH_SEQUENCE,		"Sequence '%s' does not exist") \
 	/*146 */_(ER_SEQUENCE_EXISTS,		"Sequence '%s' already exists") \
 	/*147 */_(ER_SEQUENCE_OVERFLOW,		"Sequence '%s' has overflowed") \
-	/*148 */_(ER_UNUSED5,			"") \
+	/*148 */_(ER_CREATE_CK_CONSTRAINT,	"Failed to create check constraint '%s': %s") \
 	/*149 */_(ER_SPACE_FIELD_IS_DUPLICATE,	"Space field '%s' is duplicate") \
 	/*150 */_(ER_CANT_CREATE_COLLATION,	"Failed to initialize collation: %s.") \
 	/*151 */_(ER_WRONG_COLLATION_OPTIONS,	"Wrong collation options (field %u): %s") \
 	/*152 */_(ER_NULLABLE_PRIMARY,		"Primary index of the space '%s' can not contain nullable parts") \
-	/*153 */_(ER_UNUSED,			"") \
+	/*153 */_(ER_CK_CONSTRAINT_FAILED,	"Check constraint failed: %s") \
 	/*154 */_(ER_TRANSACTION_YIELD,		"Transaction has been aborted by a fiber yield") \
 	/*155 */_(ER_NO_SUCH_GROUP,		"Replication group '%s' does not exist") \
 	/*156 */_(ER_SQL_BIND_VALUE,            "Bind value for parameter %s is out of range for type %s") \
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 8a804f0ba..0e5feb7e6 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -513,6 +513,7 @@ box.schema.space.drop = function(space_id, space_name, opts)
     local _truncate = box.space[box.schema.TRUNCATE_ID]
     local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID]
     local _fk_constraint = box.space[box.schema.FK_CONSTRAINT_ID]
+    local _ck_constraint = box.space[box.schema.CK_CONSTRAINT_ID]
     local sequence_tuple = _space_sequence:delete{space_id}
     if sequence_tuple ~= nil and sequence_tuple[3] == true then
         -- Delete automatically generated sequence.
@@ -529,6 +530,9 @@ box.schema.space.drop = function(space_id, space_name, opts)
     for _, t in _fk_constraint.index.child_id:pairs({space_id}) do
         _fk_constraint:delete({t.name, space_id})
     end
+    for _, t in _ck_constraint.index.space_id:pairs({space_id}) do
+         _ck_constraint:delete({t.name, space_id})
+    end
     revoke_object_privs('space', space_id)
     _truncate:delete{space_id}
     if _space:delete{space_id} == nil then
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index 7cae436f1..a12ee1830 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -555,6 +555,8 @@ box_lua_space_init(struct lua_State *L)
 	lua_setfield(L, -2, "SQL_STAT4_ID");
 	lua_pushnumber(L, BOX_FK_CONSTRAINT_ID);
 	lua_setfield(L, -2, "FK_CONSTRAINT_ID");
+	lua_pushnumber(L, BOX_CK_CONSTRAINT_ID);
+	lua_setfield(L, -2, "CK_CONSTRAINT_ID");
 	lua_pushnumber(L, BOX_TRUNCATE_ID);
 	lua_setfield(L, -2, "TRUNCATE_ID");
 	lua_pushnumber(L, BOX_SEQUENCE_ID);
diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index cc172dc15..a304290a2 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -72,6 +72,7 @@ local function set_system_triggers(val)
     box.space._trigger:run_triggers(val)
     box.space._collation:run_triggers(val)
     box.space._fk_constraint:run_triggers(val)
+    box.space._ck_constraint:run_triggers(val)
 end
 
 --------------------------------------------------------------------------------
@@ -90,6 +91,7 @@ local function erase()
     truncate(box.space._collation)
     truncate(box.space._trigger)
     truncate(box.space._fk_constraint)
+    truncate(box.space._ck_constraint)
     --truncate(box.space._schema)
     box.space._schema:delete('version')
     box.space._schema:delete('max_id')
@@ -629,10 +631,46 @@ local function upgrade_priv_to_2_1_1()
                                         index.type, opts, index.parts}))
         end
     end
+    local _space = box.space[box.schema.SPACE_ID]
+    local _ck_constraint = box.space[box.schema.CK_CONSTRAINT_ID]
+    for _, space in _space:pairs() do
+        local flags = space.flags
+        if flags['checks'] ~= nil then
+            for i, check in pairs(flags['checks']) do
+                local expr_str = check.expr
+                local check_name = check.name or
+                                   "CK_CONSTRAINT_"..i.."_"..space.name
+                _ck_constraint:insert({check_name, space.id, expr_str})
+            end
+            flags['checks'] = nil
+            _space:replace(box.tuple.new({space.id, space.owner, space.name,
+                                          space.engine, space.field_count,
+                                          flags, space.format}))
+        end
+    end
 end
 
 local function upgrade_to_2_1_1()
     log.info("started upgrade_to_2_1_1")
+    local _space = box.space[box.schema.SPACE_ID]
+    local _index = box.space[box.schema.INDEX_ID]
+    local _ck_constraint = box.space[box.schema.CK_CONSTRAINT_ID]
+    local MAP = setmap({})
+
+    log.info("create space _ck_constraint")
+    local format = {{name='name', type='string'},
+                    {name='space_id', type='unsigned'},
+                    {name='expr_str', type='str'}}
+    _space:insert{_ck_constraint.id, ADMIN, '_ck_constraint', 'memtx', 0, MAP, format}
+
+    log.info("create index primary on _ck_constraint")
+    _index:insert{_ck_constraint.id, 0, 'primary', 'tree',
+                  {unique = true}, {{0, 'string'}, {1, 'unsigned'}}}
+
+    log.info("create secondary index child_id on _ck_constraint")
+    _index:insert{_ck_constraint.id, 1, 'space_id', 'tree',
+                  {unique = false}, {{1, 'unsigned'}}}
+
     upgrade_priv_to_2_1_1()
 end
 
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 8625d92ea..84875639a 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -468,6 +468,14 @@ schema_init()
 	sc_space_new(BOX_FK_CONSTRAINT_ID, "_fk_constraint", key_parts, 2,
 		     &on_replace_fk_constraint, NULL);
 
+	/* _ck_сonstraint - check constraints. */
+	key_parts[0].fieldno = 0; /* constraint name */
+	key_parts[0].type = FIELD_TYPE_STRING;
+	key_parts[1].fieldno = 1; /* space id */
+	key_parts[1].type = FIELD_TYPE_UNSIGNED;
+	sc_space_new(BOX_CK_CONSTRAINT_ID, "_ck_constraint", key_parts, 2,
+		     &on_replace_ck_constraint, NULL);
+
 	/*
 	 * _vinyl_deferred_delete - blackhole that is needed
 	 * for writing deferred DELETE statements generated by
diff --git a/src/box/schema_def.h b/src/box/schema_def.h
index a760ecc3f..920804a28 100644
--- a/src/box/schema_def.h
+++ b/src/box/schema_def.h
@@ -111,6 +111,8 @@ enum {
 	BOX_SQL_STAT4_ID = 349,
 	/** Space id of _fk_constraint. */
 	BOX_FK_CONSTRAINT_ID = 356,
+	/** Space id of _ck_contraint. */
+	BOX_CK_CONSTRAINT_ID = 357,
 	/** End of the reserved range of system spaces. */
 	BOX_SYSTEM_ID_MAX = 511,
 	BOX_ID_NIL = 2147483647
@@ -241,6 +243,13 @@ enum {
 	BOX_FK_CONSTRAINT_FIELD_PARENT_COLS = 8,
 };
 
+/** _ck_constraint fields. */
+enum {
+	BOX_CK_CONSTRAINT_FIELD_NAME = 0,
+	BOX_CK_CONSTRAINT_FIELD_SPACE_ID = 1,
+	BOX_CK_CONSTRAINT_FIELD_EXPR_STR = 2,
+};
+
 /*
  * Different objects which can be subject to access
  * control.
diff --git a/src/box/space.c b/src/box/space.c
index 316b34b92..6b3158a25 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -165,6 +165,7 @@ space_create(struct space *space, struct engine *engine,
 	space_fill_index_map(space);
 	rlist_create(&space->parent_fkey);
 	rlist_create(&space->child_fkey);
+	rlist_create(&space->ck_constraint);
 	return 0;
 
 fail_free_indexes:
@@ -225,6 +226,7 @@ space_delete(struct space *space)
 	assert(space->sql_triggers == NULL);
 	assert(rlist_empty(&space->parent_fkey));
 	assert(rlist_empty(&space->child_fkey));
+	assert(rlist_empty(&space->ck_constraint));
 	space->vtab->destroy(space);
 }
 
diff --git a/src/box/space.h b/src/box/space.h
index 7eb7ae292..fccca4b44 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -194,6 +194,11 @@ struct space {
 	 */
 	struct rlist parent_fkey;
 	struct rlist child_fkey;
+	/**
+	 * List of check constaints linked with
+	 * ck_constraint::link.
+	 */
+	struct rlist ck_constraint;
 	/**
 	 * Mask indicates which fields are involved in foreign
 	 * key constraint checking routine. Includes fields
diff --git a/src/box/space_def.c b/src/box/space_def.c
index c5b5ec295..cf2d29431 100644
--- a/src/box/space_def.c
+++ b/src/box/space_def.c
@@ -35,28 +35,12 @@
 #include "sql.h"
 #include "msgpuck.h"
 
-/**
- * Make checks from msgpack.
- * @param str pointer to array of maps
- *         e.g. [{"expr": "x < y", "name": "ONE"}, ..].
- * @param len array items count.
- * @param[out] opt pointer to store parsing result.
- * @param errcode Code of error to set if something is wrong.
- * @param field_no Field number of an option in a parent element.
- * @retval 0 on success.
- * @retval not 0 on error. Also set diag message.
- */
-static int
-checks_array_decode(const char **str, uint32_t len, char *opt, uint32_t errcode,
-		    uint32_t field_no);
-
 const struct space_opts space_opts_default = {
 	/* .group_id = */ 0,
 	/* .is_temporary = */ false,
 	/* .is_ephemeral = */ false,
 	/* .view = */ false,
 	/* .sql        = */ NULL,
-	/* .checks     = */ NULL,
 };
 
 const struct opt_def space_opts_reg[] = {
@@ -64,8 +48,7 @@ const struct opt_def space_opts_reg[] = {
 	OPT_DEF("temporary", OPT_BOOL, struct space_opts, is_temporary),
 	OPT_DEF("view", OPT_BOOL, struct space_opts, is_view),
 	OPT_DEF("sql", OPT_STRPTR, struct space_opts, sql),
-	OPT_DEF_ARRAY("checks", struct space_opts, checks,
-		      checks_array_decode),
+	OPT_DEF_LEGACY("checks"),
 	OPT_END,
 };
 
@@ -113,16 +96,6 @@ space_def_dup_opts(struct space_def *def, const struct space_opts *opts)
 			return -1;
 		}
 	}
-	if (opts->checks != NULL) {
-		def->opts.checks = sql_expr_list_dup(sql_get(), opts->checks, 0);
-		if (def->opts.checks == NULL) {
-			free(def->opts.sql);
-			diag_set(OutOfMemory, 0, "sql_expr_list_dup",
-				 "def->opts.checks");
-			return -1;
-		}
-		sql_checks_update_space_def_reference(def->opts.checks, def);
-	}
 	return 0;
 }
 
@@ -300,74 +273,6 @@ void
 space_opts_destroy(struct space_opts *opts)
 {
 	free(opts->sql);
-	sql_expr_list_delete(sql_get(), opts->checks);
 	TRASH(opts);
 }
 
-static int
-checks_array_decode(const char **str, uint32_t len, char *opt, uint32_t errcode,
-		    uint32_t field_no)
-{
-	char *errmsg = tt_static_buf();
-	struct ExprList *checks = NULL;
-	const char **map = str;
-	struct sqlite3 *db = sql_get();
-	for (uint32_t i = 0; i < len; i++) {
-		checks = sql_expr_list_append(db, checks, NULL);
-		if (checks == NULL) {
-			diag_set(OutOfMemory, 0, "sql_expr_list_append",
-				 "checks");
-			goto error;
-		}
-		const char *expr_name = NULL;
-		const char *expr_str = NULL;
-		uint32_t expr_name_len = 0;
-		uint32_t expr_str_len = 0;
-		uint32_t map_size = mp_decode_map(map);
-		for (uint32_t j = 0; j < map_size; j++) {
-			if (mp_typeof(**map) != MP_STR) {
-				diag_set(ClientError, errcode, field_no,
-					 "key must be a string");
-				goto error;
-			}
-			uint32_t key_len;
-			const char *key = mp_decode_str(map, &key_len);
-			if (mp_typeof(**map) != MP_STR) {
-				snprintf(errmsg, TT_STATIC_BUF_LEN,
-					 "invalid MsgPack map field '%.*s' type",
-					 key_len, key);
-				diag_set(ClientError, errcode, field_no, errmsg);
-				goto error;
-			}
-			if (key_len == 4 && memcmp(key, "expr", key_len) == 0) {
-				expr_str = mp_decode_str(map, &expr_str_len);
-			} else if (key_len == 4 &&
-				   memcmp(key, "name", key_len) == 0) {
-				expr_name = mp_decode_str(map, &expr_name_len);
-			} else {
-				snprintf(errmsg, TT_STATIC_BUF_LEN,
-					 "invalid MsgPack map field '%.*s'",
-					 key_len, key);
-				diag_set(ClientError, errcode, field_no, errmsg);
-				goto error;
-			}
-		}
-		if (sql_check_list_item_init(checks, i, expr_name, expr_name_len,
-					     expr_str, expr_str_len) != 0) {
-			box_error_t *err = box_error_last();
-			if (box_error_code(err) != ENOMEM) {
-				snprintf(errmsg, TT_STATIC_BUF_LEN,
-					 "invalid expression specified (%s)",
-					 box_error_message(err));
-				diag_set(ClientError, errcode, field_no,
-					 errmsg);
-			}
-			goto error;
-		}
-	}
-	*(struct ExprList **)opt = checks;
-	return 0;
-error:
-	sql_expr_list_delete(db, checks);
-	return  -1;
-}
diff --git a/src/box/space_def.h b/src/box/space_def.h
index 52ff56764..71d168342 100644
--- a/src/box/space_def.h
+++ b/src/box/space_def.h
@@ -71,8 +71,6 @@ struct space_opts {
 	bool is_view;
 	/** SQL statement that produced this space. */
 	char *sql;
-	/** SQL Checks expressions list. */
-	struct ExprList *checks;
 };
 
 extern const struct space_opts space_opts_default;
diff --git a/src/box/sql.c b/src/box/sql.c
index 387da7b3d..7755e6c09 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1041,16 +1041,9 @@ sql_encode_table_opts(struct region *region, struct Table *table,
 	bool is_error = false;
 	mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb,
 		      set_encode_error, &is_error);
-	int checks_cnt = 0;
-	struct ExprList_item *a;
 	bool is_view = table->def->opts.is_view;
-	struct ExprList *checks = table->def->opts.checks;
-	if (checks != NULL) {
-		checks_cnt = checks->nExpr;
-		a = checks->a;
-	}
 	assert(is_view || sql == NULL);
-	mpstream_encode_map(&stream, 2 * is_view + (checks_cnt > 0));
+	mpstream_encode_map(&stream, 2 * is_view);
 
 	if (is_view) {
 		mpstream_encode_str(&stream, "sql");
@@ -1058,23 +1051,6 @@ sql_encode_table_opts(struct region *region, struct Table *table,
 		mpstream_encode_str(&stream, "view");
 		mpstream_encode_bool(&stream, true);
 	}
-	if (checks_cnt > 0) {
-		mpstream_encode_str(&stream, "checks");
-		mpstream_encode_array(&stream, checks_cnt);
-	}
-	for (int i = 0; i < checks_cnt && !is_error; ++i, ++a) {
-		int items = (a->pExpr != NULL) + (a->zName != NULL);
-		mpstream_encode_map(&stream, items);
-		assert(a->pExpr != NULL);
-		struct Expr *pExpr = a->pExpr;
-		assert(pExpr->u.zToken != NULL);
-		mpstream_encode_str(&stream, "expr");
-		mpstream_encode_str(&stream, pExpr->u.zToken);
-		if (a->zName != NULL) {
-			mpstream_encode_str(&stream, "name");
-			mpstream_encode_str(&stream, a->zName);
-		}
-	}
 	mpstream_flush(&stream);
 	if (is_error) {
 		diag_set(OutOfMemory, stream.pos - stream.buf,
@@ -1302,74 +1278,3 @@ sql_table_def_rebuild(struct sqlite3 *db, struct Table *pTable)
 	pTable->def->opts.is_temporary = false;
 	return 0;
 }
-
-int
-sql_check_list_item_init(struct ExprList *expr_list, int column,
-			 const char *expr_name, uint32_t expr_name_len,
-			 const char *expr_str, uint32_t expr_str_len)
-{
-	assert(column < expr_list->nExpr);
-	struct ExprList_item *item = &expr_list->a[column];
-	memset(item, 0, sizeof(*item));
-	if (expr_name != NULL) {
-		item->zName = sqlite3DbStrNDup(db, expr_name, expr_name_len);
-		if (item->zName == NULL) {
-			diag_set(OutOfMemory, expr_name_len, "sqlite3DbStrNDup",
-				 "item->zName");
-			return -1;
-		}
-	}
-	if (expr_str != NULL) {
-		item->pExpr = sql_expr_compile(db, expr_str, expr_str_len);
-		/* The item->zName would be released later. */
-		if (item->pExpr == NULL)
-			return -1;
-	}
-	return 0;
-}
-
-static int
-update_space_def_callback(Walker *walker, Expr *expr)
-{
-	if (expr->op == TK_COLUMN && ExprHasProperty(expr, EP_Resolved))
-		expr->space_def = walker->u.space_def;
-	return WRC_Continue;
-}
-
-void
-sql_checks_update_space_def_reference(ExprList *expr_list,
-				      struct space_def *def)
-{
-	assert(expr_list != NULL);
-	Walker w;
-	memset(&w, 0, sizeof(w));
-	w.xExprCallback = update_space_def_callback;
-	w.u.space_def = def;
-	for (int i = 0; i < expr_list->nExpr; i++)
-		sqlite3WalkExpr(&w, expr_list->a[i].pExpr);
-}
-
-int
-sql_checks_resolve_space_def_reference(ExprList *expr_list,
-				       struct space_def *def)
-{
-	Parse parser;
-	sql_parser_create(&parser, sql_get());
-	parser.parse_only = true;
-
-	Table dummy_table;
-	memset(&dummy_table, 0, sizeof(dummy_table));
-	dummy_table.def = def;
-
-	sql_resolve_self_reference(&parser, &dummy_table, NC_IsCheck, NULL,
-				   expr_list);
-	int rc = 0;
-	if (parser.rc != SQLITE_OK) {
-		/* Tarantool error may be already set with diag. */
-		if (parser.rc != SQL_TARANTOOL_ERROR)
-			diag_set(ClientError, ER_SQL, parser.zErrMsg);
-		rc = -1;
-	}
-	sql_parser_destroy(&parser);
-	return rc;
-}
diff --git a/src/box/sql.h b/src/box/sql.h
index 028a15245..30bab3eb1 100644
--- a/src/box/sql.h
+++ b/src/box/sql.h
@@ -172,14 +172,6 @@ sql_expr_extract_select(struct Parse *parser, struct Select *select);
 struct Expr*
 space_column_default_expr(uint32_t space_id, uint32_t fieldno);
 
-/**
- * Get server checks list by space_id.
- * @param space_id Space ID.
- * @retval Checks list.
- */
-struct ExprList *
-space_checks_expr_list(uint32_t space_id);
-
 /**
  * Return the number of bytes required to create a duplicate of the
  * expression passed as the first argument. The second argument is a
@@ -310,42 +302,6 @@ void
 sql_resolve_self_reference(struct Parse *parser, struct Table *table, int type,
 			   struct Expr *expr, struct ExprList *expr_list);
 
-/**
- * Initialize check_list_item.
- * @param expr_list ExprList with item.
- * @param column index.
- * @param expr_name expression name (optional).
- * @param expr_name_len expresson name length (optional).
- * @param expr_str expression to build string.
- * @param expr_str_len expression to build string length.
- * @retval 0 on success.
- * @retval -1 on error.
- */
-int
-sql_check_list_item_init(struct ExprList *expr_list, int column,
-			 const char *expr_name, uint32_t expr_name_len,
-			 const char *expr_str, uint32_t expr_str_len);
-
-/**
- * Resolve space_def references checks for expr_list.
- * @param expr_list to modify.
- * @param def to refer to.
- * @retval 0 on success.
- * @retval -1 on error.
- */
-int
-sql_checks_resolve_space_def_reference(struct ExprList *expr_list,
-				       struct space_def *def);
-
-/**
- * Update space_def references for expr_list.
- * @param expr_list to modify.
- * @param def to refer to.
- */
-void
-sql_checks_update_space_def_reference(struct ExprList *expr_list,
-				      struct space_def *def);
-
 /**
  * Initialize a new parser object.
  * A number of service allocations are performed on the region,
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 49b90b5d0..235b78a31 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -275,8 +275,6 @@ table_delete(struct sqlite3 *db, struct Table *tab)
 	if (tab->def->opts.is_temporary) {
 		for (uint32_t i = 0; i < tab->space->index_count; ++i)
 			index_def_delete(tab->space->index[i]->def);
-		/* Do not delete table->def allocated on region. */
-		sql_expr_list_delete(db, tab->def->opts.checks);
 	} else if (tab->def->id == 0) {
 		space_def_delete(tab->def);
 	}
@@ -757,31 +755,57 @@ primary_key_exit:
 }
 
 void
-sql_add_check_constraint(struct Parse *parser, struct ExprSpan *span)
+sql_add_ck_constraint(struct Parse *parser, struct ExprSpan *span)
 {
 	struct Expr *expr = span->pExpr;
 	struct Table *table = parser->pNewTable;
-	if (table != NULL) {
-		expr->u.zToken =
-			sqlite3DbStrNDup(parser->db, (char *)span->zStart,
-					 (int)(span->zEnd - span->zStart));
-		if (expr->u.zToken == NULL)
-			goto release_expr;
-		table->def->opts.checks =
-			sql_expr_list_append(parser->db,
-					     table->def->opts.checks, expr);
-		if (table->def->opts.checks == NULL) {
-			sqlite3DbFree(parser->db, expr->u.zToken);
-			goto release_expr;
-		}
-		if (parser->constraintName.n) {
-			sqlite3ExprListSetName(parser, table->def->opts.checks,
-					       &parser->constraintName, 1);
+	assert(table != NULL);
+
+	struct region *region = &parser->region;
+	uint32_t expr_str_len = (uint32_t)(span->zEnd - span->zStart);
+	const char *expr_str = span->zStart;
+
+	const char *ck_constraint_name = NULL;
+	if (parser->constraintName.n != 0) {
+		ck_constraint_name =
+			region_alloc(region, parser->constraintName.n + 1);
+		if (ck_constraint_name == NULL) {
+			diag_set(OutOfMemory, parser->constraintName.n + 1,
+				 "region_alloc", "ck_constraint_name");
+			goto error;
 		}
+		sprintf((char *)ck_constraint_name, "%.*s",
+			parser->constraintName.n, parser->constraintName.z);
+		sqlite3NormalizeName((char *)ck_constraint_name);
 	} else {
-release_expr:
-		sql_expr_delete(parser->db, expr, false);
+		ck_constraint_name = tt_sprintf("CK_CONSTRAINT_%d_%s",
+						++parser->ck_constraint_count,
+						table->def->name);
+	}
+	uint32_t ck_constraint_name_len = strlen(ck_constraint_name);
+
+	uint32_t name_offset, expr_str_offset;
+	uint32_t ck_constraint_def_sz =
+		ck_constraint_def_sizeof(ck_constraint_name_len, expr_str_len,
+					 &name_offset, &expr_str_offset);
+	struct ck_constraint_def *ck_constraint_def =
+		region_alloc(region, ck_constraint_def_sz);
+	if (ck_constraint_def == NULL) {
+		diag_set(OutOfMemory, ck_constraint_def_sz, "region_alloc",
+			 "ck_constraint_def");
+		goto error;
 	}
+	ck_constraint_def_create(ck_constraint_def, ck_constraint_name,
+				 ck_constraint_name_len, expr_str,
+				 expr_str_len);
+	rlist_add_entry(&parser->new_ck_constraint, ck_constraint_def, link);
+out:
+	sql_expr_delete(parser->db, expr, false);
+	return;
+error:
+	parser->rc = SQL_TARANTOOL_ERROR;
+	parser->nErr++;
+	goto out;
 }
 
 /*
@@ -845,16 +869,6 @@ sql_column_collation(struct space_def *def, uint32_t column, uint32_t *coll_id)
 	return field->coll;
 }
 
-struct ExprList *
-space_checks_expr_list(uint32_t space_id)
-{
-	struct space *space;
-	space = space_by_id(space_id);
-	assert(space != NULL);
-	assert(space->def != NULL);
-	return space->def->opts.checks;
-}
-
 int
 vdbe_emit_open_cursor(struct Parse *parse_context, int cursor, int index_id,
 		      struct space *space)
@@ -1085,6 +1099,40 @@ emitNewSysSpaceSequenceRecord(Parse *pParse, int space_id, const char reg_seq_id
 	return first_col;
 }
 
+/**
+ * Generate opcodes to serialize check constraint definition into
+ * MsgPack and insert produced tuple into _ck_constraint space.
+ * @param parser Parsing context.
+ * @param ck_constraint_def Check constraint definition to be
+ *                          serialized.
+ * @param reg_space_id The VDBE containing space id.
+*/
+static void
+vdbe_emit_ck_constraint_create(struct Parse *parser,
+			       const struct ck_constraint_def *ck_constraint_def,
+			       uint32_t reg_space_id)
+{
+	struct sqlite3 *db = parser->db;
+	struct Vdbe *v = sqlite3GetVdbe(parser);
+	assert(v != NULL);
+	int ck_constraint_reg = sqlite3GetTempRange(parser, 4);
+	sqlite3VdbeAddOp4(v, OP_String8, 0, ck_constraint_reg, 0,
+			  sqlite3DbStrDup(db, ck_constraint_def->name),
+			  P4_DYNAMIC);
+	sqlite3VdbeAddOp2(v, OP_SCopy, reg_space_id, ck_constraint_reg + 1);
+	sqlite3VdbeAddOp4(v, OP_String8, 0, ck_constraint_reg + 2, 0,
+			  sqlite3DbStrDup(db, ck_constraint_def->expr_str),
+			  P4_DYNAMIC);
+	sqlite3VdbeAddOp3(v, OP_MakeRecord, ck_constraint_reg, 3,
+			  ck_constraint_reg + 3);
+	sqlite3VdbeAddOp3(v, OP_SInsert, BOX_CK_CONSTRAINT_ID, 0,
+			  ck_constraint_reg + 3);
+	save_record(parser, BOX_CK_CONSTRAINT_ID, ck_constraint_reg, 2,
+		    v->nOp - 1);
+	sqlite3ReleaseTempRange(parser, ck_constraint_reg, 4);
+	return;
+}
+
 /**
  * Generate opcodes to serialize foreign key into MsgPack and
  * insert produced tuple into _fk_constraint space.
@@ -1269,7 +1317,7 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 			sqlite3ErrorMsg(pParse,
 					"PRIMARY KEY missing on table %s",
 					p->def->name);
-			goto cleanup;
+			return;
 		}
 	}
 
@@ -1374,9 +1422,12 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 		fk->child_id = reg_space_id;
 		vdbe_emit_fkey_create(pParse, fk);
 	}
-cleanup:
-	sql_expr_list_delete(db, p->def->opts.checks);
-	p->def->opts.checks = NULL;
+	struct ck_constraint_def *ck_constraint_def;
+	rlist_foreach_entry(ck_constraint_def, &pParse->new_ck_constraint,
+			    link) {
+		vdbe_emit_ck_constraint_create(pParse, ck_constraint_def,
+					       reg_space_id);
+	}
 }
 
 void
@@ -1579,6 +1630,38 @@ vdbe_emit_fkey_drop(struct Parse *parse_context, char *constraint_name,
 	sqlite3ReleaseTempRange(parse_context, key_reg, 3);
 }
 
+/**
+ * Generate VDBE program to remove entry from _ck_constraint space.
+ *
+ * @param parser Parsing context.
+ * @param ck_constraint_name Name of CK constraint to be dropped.
+ * @param child_id Id of table which constraint belongs to.
+ */
+static void
+vdbe_emit_ck_constraint_drop(struct Parse *parser,
+			     const char *ck_constraint_name, uint32_t space_id)
+{
+	struct Vdbe *v = sqlite3GetVdbe(parser);
+	struct sqlite3 *db = v->db;
+	assert(v != NULL);
+	int key_reg = sqlite3GetTempRange(parser, 3);
+	sqlite3VdbeAddOp4(v, OP_String8, 0, key_reg, 0,
+			  sqlite3DbStrDup(db, ck_constraint_name),
+			  P4_DYNAMIC);
+	sqlite3VdbeAddOp2(v, OP_Integer, space_id,  key_reg + 1);
+	const char *error_msg =
+		tt_sprintf(tnt_errcode_desc(ER_NO_SUCH_CONSTRAINT),
+			   ck_constraint_name);
+	if (vdbe_emit_halt_with_presence_test(parser, BOX_CK_CONSTRAINT_ID, 0,
+					      key_reg, 2, ER_NO_SUCH_CONSTRAINT,
+					      error_msg, false,
+					      OP_Found) != 0)
+		return;
+	sqlite3VdbeAddOp3(v, OP_MakeRecord, key_reg, 2, key_reg + 2);
+	sqlite3VdbeAddOp2(v, OP_SDelete, BOX_CK_CONSTRAINT_ID, key_reg + 2);
+	sqlite3ReleaseTempRange(parser, key_reg, 3);
+}
+
 /**
  * Generate code to drop a table.
  * This routine includes dropping triggers, sequences,
@@ -1649,6 +1732,13 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space,
 			return;
 		vdbe_emit_fkey_drop(parse_context, fk_name_dup, space_id);
 	}
+	/* Delete all CK constraints. */
+	struct ck_constraint *ck_constraint;
+	rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) {
+		vdbe_emit_ck_constraint_drop(parse_context,
+					     ck_constraint->def->name,
+					     space_id);
+	}
 	/*
 	 * Drop all _space and _index entries that refer to the
 	 * table.
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index f147f6a50..786ac6990 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -935,34 +935,30 @@ vdbe_emit_constraint_checks(struct Parse *parse_context, struct Table *tab,
 	if (on_conflict == ON_CONFLICT_ACTION_DEFAULT)
 		on_conflict = ON_CONFLICT_ACTION_ABORT;
 	/* Test all CHECK constraints. */
-	struct ExprList *checks = space_checks_expr_list(def->id);
 	enum on_conflict_action on_conflict_check = on_conflict;
 	if (on_conflict == ON_CONFLICT_ACTION_REPLACE)
 		on_conflict_check = ON_CONFLICT_ACTION_ABORT;
-	if (checks != NULL) {
+	if (!rlist_empty(&space->ck_constraint))
 		parse_context->ckBase = new_tuple_reg;
-		for (int i = 0; i < checks->nExpr; i++) {
-			struct Expr *expr = checks->a[i].pExpr;
-			if (is_update &&
-			    checkConstraintUnchanged(expr, upd_cols))
-				continue;
-			int all_ok = sqlite3VdbeMakeLabel(v);
-			sqlite3ExprIfTrue(parse_context, expr, all_ok,
-					  SQLITE_JUMPIFNULL);
-			if (on_conflict == ON_CONFLICT_ACTION_IGNORE) {
-				sqlite3VdbeGoto(v, ignore_label);
-			} else {
-				char *name = checks->a[i].zName;
-				if (name == NULL)
-					name = def->name;
-				sqlite3HaltConstraint(parse_context,
-						      SQLITE_CONSTRAINT_CHECK,
-						      on_conflict_check, name,
-						      P4_TRANSIENT,
-						      P5_ConstraintCheck);
-			}
+	struct ck_constraint *ck_constraint;
+	rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) {
+		struct Expr *expr = ck_constraint->expr;
+		if (is_update && checkConstraintUnchanged(expr, upd_cols) != 0)
+			continue;
+		int all_ok = sqlite3VdbeMakeLabel(v);
+		sqlite3ExprIfTrue(parse_context, expr, all_ok,
+				  SQLITE_JUMPIFNULL);
+		if (on_conflict == ON_CONFLICT_ACTION_IGNORE) {
+			sqlite3VdbeGoto(v, ignore_label);
 			sqlite3VdbeResolveLabel(v, all_ok);
+		} else {
+			char *name = ck_constraint->def->name;
+			sqlite3HaltConstraint(parse_context,
+					      SQLITE_CONSTRAINT_CHECK,
+					      on_conflict_check, name,
+					      P4_TRANSIENT, P5_ConstraintCheck);
 		}
+		sqlite3VdbeResolveLabel(v, all_ok);
 	}
 	sql_emit_table_affinity(v, tab->def, new_tuple_reg);
 	/*
@@ -1248,14 +1244,12 @@ xferOptimization(Parse * pParse,	/* Parser context */
 		if (pSrcIdx == NULL)
 			return 0;
 	}
-	/* Get server checks. */
-	ExprList *pCheck_src = space_checks_expr_list(src->def->id);
-	ExprList *pCheck_dest = space_checks_expr_list(dest->def->id);
-	if (pCheck_dest != NULL &&
-	    sqlite3ExprListCompare(pCheck_src, pCheck_dest, -1) != 0) {
-		/* Tables have different CHECK constraints.  Ticket #2252 */
+	/*
+	 * Dissallow the transfer optimization if the destination
+	 * table contains any check constraints.
+	 */
+	if (!rlist_empty(&dest->ck_constraint))
 		return 0;
-	}
 	/* Disallow the transfer optimization if the destination table constains
 	 * any foreign key constraints.  This is more restrictive than necessary.
 	 * So the extra complication to make this rule less restrictive is probably
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 8e21b6fca..31715bf88 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -265,7 +265,7 @@ ccons ::= PRIMARY KEY sortorder(Z) autoinc(I).
 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 ::= CHECK LP expr(X) RP.   {sql_add_ck_constraint(pParse,&X);}
 ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R).
                                  {sql_create_foreign_key(pParse, NULL, NULL, NULL, &T, TA, false, R);}
 ccons ::= defer_subclause(D).    {fkey_change_defer_mode(pParse, D);}
@@ -317,7 +317,7 @@ tcons ::= UNIQUE LP sortlist(X) RP.
                                                    SORT_ORDER_ASC,false,
                                                    SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
 tcons ::= CHECK LP expr(E) RP onconf.
-                                 {sql_add_check_constraint(pParse,&E);}
+                                 {sql_add_ck_constraint(pParse,&E);}
 tcons ::= FOREIGN KEY LP eidlist(FA) RP
           REFERENCES nm(T) eidlist_opt(TA) refargs(R) defer_subclause_opt(D). {
     sql_create_foreign_key(pParse, NULL, NULL, FA, &T, TA, D, R);
diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index 824578e45..703ec5aae 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -274,6 +274,7 @@ sql_parser_create(struct Parse *parser, sqlite3 *db)
 	memset(parser, 0, sizeof(struct Parse));
 	parser->db = db;
 	rlist_create(&parser->new_fkey);
+	rlist_create(&parser->new_ck_constraint);
 	rlist_create(&parser->record_list);
 	region_create(&parser->region, &cord()->slabc);
 }
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 02ee225f1..22ddbb50c 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -6408,6 +6408,5 @@ sql_expr_extract_select(struct Parse *parser, struct Select *select)
 	assert(expr_list->nExpr == 1);
 	parser->parsed_ast_type = AST_TYPE_EXPR;
 	parser->parsed_ast.expr = sqlite3ExprDup(parser->db,
-						 expr_list->a->pExpr,
-						 EXPRDUP_REDUCE);
+						 expr_list->a->pExpr, 0);
 }
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index ee24e0337..c6678e884 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -69,6 +69,7 @@
 
 #include "box/field_def.h"
 #include "box/sql.h"
+#include "box/ck_constraint.h"
 #include "box/txn.h"
 #include "trivia/util.h"
 
@@ -2790,6 +2791,15 @@ struct Parse {
 	 * Foreign key constraint appeared in CREATE TABLE stmt.
 	 */
 	struct rlist new_fkey;
+	/**
+	 * Number of check constraints declared within
+	 * CREATE TABLE statement.
+	 */
+	uint32_t ck_constraint_count;
+	/**
+	 * Check constraint appeared in CREATE TABLE stmt.
+	 */
+	struct rlist new_ck_constraint;
 	/**
 	 * List of all records that were inserted in system spaces
 	 * in current statement.
@@ -3385,7 +3395,7 @@ void sqlite3AddPrimaryKey(Parse *, ExprList *, int, enum sort_order);
  * @param span Expression span object.
  */
 void
-sql_add_check_constraint(Parse *parser, ExprSpan *span);
+sql_add_ck_constraint(struct Parse *parser, struct ExprSpan *span);
 
 void sqlite3AddDefaultValue(Parse *, ExprSpan *);
 void sqlite3AddCollateType(Parse *, Token *);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 9fc362f0a..4b2b45766 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1011,7 +1011,7 @@ case OP_HaltIfNull: {      /* in3 */
  *    0:  (no change)
  *    1:  NOT NULL contraint failed: P4
  *    2:  UNIQUE constraint failed: P4
- *    3:  CHECK constraint failed: P4
+ *    3:  Check constraint failed: P4
  *    4:  FOREIGN KEY constraint failed: P4
  *
  * If P5 is not zero and  P4 is  NULL, then everything after the
@@ -1060,8 +1060,8 @@ case OP_Halt: {
 					      pOp->p4.z);
 			}
 		} else if (pOp->p5 != 0) {
-			static const char * const azType[] = { "NOT NULL", "UNIQUE", "CHECK",
-							       "FOREIGN KEY" };
+			static const char * const azType[] =
+				{"NOT NULL", "UNIQUE", "Check", "FOREIGN KEY" };
 			testcase( pOp->p5==1);
 			testcase( pOp->p5==2);
 			testcase( pOp->p5==3);
diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua
index db046e03f..cb373dae0 100755
--- a/test/app-tap/tarantoolctl.test.lua
+++ b/test/app-tap/tarantoolctl.test.lua
@@ -388,8 +388,8 @@ do
             check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1", "\n", 3)
             check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1 --replica 2", "\n", 3)
             check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 2", "\n", 0)
-            check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 23)
-            check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 49)
+            check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 24)
+            check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 51)
         end)
     end)
 
diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
index 2532b704a..0cb492904 100644
--- a/test/box-py/bootstrap.result
+++ b/test/box-py/bootstrap.result
@@ -84,6 +84,8 @@ box.space._space:select{}
       {'name': 'is_deferred', 'type': 'boolean'}, {'name': 'match', 'type': 'string'},
       {'name': 'on_delete', 'type': 'string'}, {'name': 'on_update', 'type': 'string'},
       {'name': 'child_cols', 'type': 'array'}, {'name': 'parent_cols', 'type': 'array'}]]
+  - [357, 1, '_ck_constraint', 'memtx', 0, {}, [{'name': 'name', 'type': 'string'},
+      {'name': 'space_id', 'type': 'unsigned'}, {'name': 'expr_str', 'type': 'str'}]]
 ...
 box.space._index:select{}
 ---
@@ -139,6 +141,8 @@ box.space._index:select{}
         5, 'scalar']]]
   - [356, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]]
   - [356, 1, 'child_id', 'tree', {'unique': false}, [[1, 'unsigned']]]
+  - [357, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]]
+  - [357, 1, 'space_id', 'tree', {'unique': false}, [[1, 'unsigned']]]
 ...
 box.space._user:select{}
 ---
diff --git a/test/box/access.result b/test/box/access.result
index 9c190240f..801112799 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -1487,6 +1487,9 @@ box.schema.user.grant('tester', 'read', 'space', '_trigger')
 box.schema.user.grant('tester', 'read', 'space', '_fk_constraint')
 ---
 ...
+box.schema.user.grant('tester', 'read', 'space', '_ck_constraint')
+---
+...
 box.session.su("tester")
 ---
 ...
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index 4baeb2ef6..2f62d6f53 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -554,6 +554,7 @@ box.schema.user.grant('tester', 'create' , 'sequence')
 box.schema.user.grant('tester', 'read', 'space', '_sequence')
 box.schema.user.grant('tester', 'read', 'space', '_trigger')
 box.schema.user.grant('tester', 'read', 'space', '_fk_constraint')
+box.schema.user.grant('tester', 'read', 'space', '_ck_constraint')
 box.session.su("tester")
 -- successful create
 s1 = box.schema.space.create("test_space")
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index 4ffeb386a..93d4c4cc2 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -823,6 +823,8 @@ box.space._space:select()
       {'name': 'is_deferred', 'type': 'boolean'}, {'name': 'match', 'type': 'string'},
       {'name': 'on_delete', 'type': 'string'}, {'name': 'on_update', 'type': 'string'},
       {'name': 'child_cols', 'type': 'array'}, {'name': 'parent_cols', 'type': 'array'}]]
+  - [357, 1, '_ck_constraint', 'memtx', 0, {}, [{'name': 'name', 'type': 'string'},
+      {'name': 'space_id', 'type': 'unsigned'}, {'name': 'expr_str', 'type': 'str'}]]
 ...
 box.space._func:select()
 ---
diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result
index fd8b14248..c0b75b235 100644
--- a/test/box/access_sysview.result
+++ b/test/box/access_sysview.result
@@ -230,11 +230,11 @@ box.session.su('guest')
 ...
 #box.space._vspace:select{}
 ---
-- 24
+- 25
 ...
 #box.space._vindex:select{}
 ---
-- 50
+- 52
 ...
 #box.space._vuser:select{}
 ---
@@ -262,7 +262,7 @@ box.session.su('guest')
 ...
 #box.space._vindex:select{}
 ---
-- 50
+- 52
 ...
 #box.space._vuser:select{}
 ---
diff --git a/test/box/alter.result b/test/box/alter.result
index 9a1086e0c..9c4262687 100644
--- a/test/box/alter.result
+++ b/test/box/alter.result
@@ -107,7 +107,7 @@ space = box.space[t[1]]
 ...
 space.id
 ---
-- 357
+- 358
 ...
 space.field_count
 ---
@@ -152,7 +152,7 @@ space_deleted
 ...
 space:replace{0}
 ---
-- error: Space '357' does not exist
+- error: Space '358' does not exist
 ...
 _index:insert{_space.id, 0, 'primary', 'tree', {unique=true}, {{0, 'unsigned'}}}
 ---
@@ -233,6 +233,8 @@ _index:select{}
         5, 'scalar']]]
   - [356, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]]
   - [356, 1, 'child_id', 'tree', {'unique': false}, [[1, 'unsigned']]]
+  - [357, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]]
+  - [357, 1, 'space_id', 'tree', {'unique': false}, [[1, 'unsigned']]]
 ...
 -- modify indexes of a system space
 _index:delete{_index.id, 0}
diff --git a/test/box/misc.result b/test/box/misc.result
index c3cabcc8a..dd51539a6 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -477,10 +477,12 @@ t;
   145: box.error.NO_SUCH_SEQUENCE
   146: box.error.SEQUENCE_EXISTS
   147: box.error.SEQUENCE_OVERFLOW
+  148: box.error.CREATE_CK_CONSTRAINT
   149: box.error.SPACE_FIELD_IS_DUPLICATE
   150: box.error.CANT_CREATE_COLLATION
   151: box.error.WRONG_COLLATION_OPTIONS
   152: box.error.NULLABLE_PRIMARY
+  153: box.error.CK_CONSTRAINT_FAILED
   154: box.error.TRANSACTION_YIELD
   155: box.error.NO_SUCH_GROUP
   156: box.error.SQL_BIND_VALUE
diff --git a/test/sql-tap/check.test.lua b/test/sql-tap/check.test.lua
index 1f369fb02..c419e535d 100755
--- a/test/sql-tap/check.test.lua
+++ b/test/sql-tap/check.test.lua
@@ -55,7 +55,7 @@ test:do_catchsql_test(
         INSERT INTO t1 VALUES(6,7, 2);
     ]], {
         -- <check-1.3>
-        1, "CHECK constraint failed: T1"
+        1, "Check constraint failed: CK_CONSTRAINT_1_T1"
         -- </check-1.3>
     })
 
@@ -75,7 +75,7 @@ test:do_catchsql_test(
         INSERT INTO t1 VALUES(4,3, 2);
     ]], {
         -- <check-1.5>
-        1, "CHECK constraint failed: T1"
+        1, "Check constraint failed: CK_CONSTRAINT_2_T1"
         -- </check-1.5>
     })
 
@@ -147,7 +147,7 @@ test:do_catchsql_test(
         UPDATE t1 SET x=7 WHERE x==2
     ]], {
         -- <check-1.12>
-        1, "CHECK constraint failed: T1"
+        1, "Check constraint failed: CK_CONSTRAINT_1_T1"
         -- </check-1.12>
     })
 
@@ -167,7 +167,7 @@ test:do_catchsql_test(
         UPDATE t1 SET x=5 WHERE x==2
     ]], {
         -- <check-1.14>
-        1, "CHECK constraint failed: T1"
+        1, "Check constraint failed: CK_CONSTRAINT_1_T1"
         -- </check-1.14>
     })
 
@@ -246,7 +246,7 @@ test:do_catchsql_test(
         INSERT INTO t2 VALUES(3, 1.1, NULL, NULL);
     ]], {
         -- <check-2.4>
-        1, "CHECK constraint failed: ONE"
+        1, "Check constraint failed: ONE"
         -- </check-2.4>
     })
 
@@ -256,7 +256,7 @@ test:do_catchsql_test(
         INSERT INTO t2 VALUES(4, NULL, 5, NULL);
     ]], {
         -- <check-2.5>
-        1, "CHECK constraint failed: TWO"
+        1, "Check constraint failed: TWO"
         -- </check-2.5>
     })
 
@@ -266,7 +266,7 @@ test:do_catchsql_test(
         INSERT INTO t2 VALUES(5, NULL, NULL, 3.14159);
     ]], {
         -- <check-2.6>
-        1, "CHECK constraint failed: THREE"
+        1, "Check constraint failed: THREE"
         -- </check-2.6>
     })
 
@@ -319,7 +319,7 @@ test:do_catchsql_test(
         );
     ]], {
         -- <check-3.1>
-        1, "Failed to create space 'T3': SQL error: subqueries prohibited in CHECK constraints"
+        1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': subqueries prohibited in CHECK constraints"
         -- </check-3.1>
     })
 
@@ -344,7 +344,7 @@ test:do_catchsql_test(
         );
     ]], {
         -- <check-3.3>
-        1, "Failed to create space 'T3': SQL error: no such column: Q"
+        1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': no such column: Q"
         -- </check-3.3>
     })
 
@@ -368,7 +368,7 @@ test:do_catchsql_test(
         );
     ]], {
         -- <check-3.5>
-        1, "Failed to create space 'T3': SQL error: no such column: T2.X"
+        1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': no such column: T2.X"
         -- </check-3.5>
     })
 
@@ -413,7 +413,7 @@ test:do_catchsql_test(
         INSERT INTO t3 VALUES(111,222,333);
     ]], {
         -- <check-3.9>
-        1, "CHECK constraint failed: T3"
+        1, "Check constraint failed: CK_CONSTRAINT_1_T3"
         -- </check-3.9>
     })
 
@@ -484,7 +484,7 @@ test:do_catchsql_test(
         UPDATE t4 SET x=0, y=1;
     ]], {
         -- <check-4.6>
-        1, "CHECK constraint failed: T4"
+        1, "Check constraint failed: CK_CONSTRAINT_1_T4"
         -- </check-4.6>
     })
 
@@ -504,7 +504,7 @@ test:do_catchsql_test(
         UPDATE t4 SET x=0, y=2;
     ]], {
         -- <check-4.9>
-        1, "CHECK constraint failed: T4"
+        1, "Check constraint failed: CK_CONSTRAINT_1_T4"
         -- </check-4.9>
     })
 
@@ -516,7 +516,7 @@ test:do_catchsql_test(
         );
     ]], {
         -- <check-5.1>
-        1, "Wrong space options (field 5): invalid expression specified (SQL error: bindings are not allowed in DDL)"
+        1, "SQL error: bindings are not allowed in DDL"
         -- </check-5.1>
     })
 
@@ -528,7 +528,7 @@ test:do_catchsql_test(
         );
     ]], {
         -- <check-5.2>
-        1, "Wrong space options (field 5): invalid expression specified (SQL error: bindings are not allowed in DDL)"
+        1, "SQL error: bindings are not allowed in DDL"
         -- </check-5.2>
     })
 
@@ -581,7 +581,7 @@ test:do_catchsql_test(
         UPDATE OR FAIL t1 SET x=7-x, y=y+1;
     ]], {
         -- <check-6.5>
-        1, "CHECK constraint failed: T1"
+        1, "Check constraint failed: CK_CONSTRAINT_1_T1"
         -- </check-6.5>
     })
 
@@ -603,7 +603,7 @@ test:do_catchsql_test(
         INSERT OR ROLLBACK INTO t1 VALUES(8,40.0, 10);
     ]], {
         -- <check-6.7>
-        1, "CHECK constraint failed: T1"
+        1, "Check constraint failed: CK_CONSTRAINT_1_T1"
         -- </check-6.7>
     })
 
@@ -636,7 +636,7 @@ test:do_catchsql_test(
         REPLACE INTO t1 VALUES(6,7, 11);
     ]], {
         -- <check-6.12>
-        1, "CHECK constraint failed: T1"
+        1, "Check constraint failed: CK_CONSTRAINT_1_T1"
         -- </check-6.12>
     })
 
@@ -700,7 +700,7 @@ test:do_catchsql_test(
     7.3,
     " INSERT INTO t6 VALUES(11) ", {
         -- <7.3>
-        1, "CHECK constraint failed: T6"
+        1, "Check constraint failed: CK_CONSTRAINT_1_T6"
         -- </7.3>
     })
 
@@ -755,7 +755,7 @@ test:do_test(
         return test:catchsql(" INSERT INTO t6 VALUES(12) ", "db2")
     end, {
         -- <7.8>
-        1, "CHECK constraint failed: T6"
+        1, "Check constraint failed: T6"
         -- </7.8>
     })
 end
diff --git a/test/sql-tap/fkey2.test.lua b/test/sql-tap/fkey2.test.lua
index 03bf025f3..72b301713 100755
--- a/test/sql-tap/fkey2.test.lua
+++ b/test/sql-tap/fkey2.test.lua
@@ -362,7 +362,7 @@ test:do_catchsql_test(
         UPDATE ab SET a = 5;
     ]], {
         -- <fkey2-3.2>
-        1, "CHECK constraint failed: EF"
+        1, "Check constraint failed: CK_CONSTRAINT_1_EF"
         -- </fkey2-3.2>
     })
 
@@ -382,7 +382,7 @@ test:do_catchsql_test(
         UPDATE ab SET a = 5;
     ]], {
         -- <fkey2-3.4>
-        1, "CHECK constraint failed: EF"
+        1, "Check constraint failed: CK_CONSTRAINT_1_EF"
         -- </fkey2-3.4>
     })
 
diff --git a/test/sql-tap/in1.test.lua b/test/sql-tap/in1.test.lua
index b938ff17f..2729fcc0a 100755
--- a/test/sql-tap/in1.test.lua
+++ b/test/sql-tap/in1.test.lua
@@ -615,7 +615,7 @@ test:do_catchsql_test(
 --   catchsql {
 --     INSERT INTO t5 VALUES(4);
 --   }
--- } {1 {CHECK constraint failed: t5}}
+-- } {1 {Check constraint failed: t5}}
 -- Ticket #1821
 --
 -- Type affinity applied to the right-hand side of an IN operator.
diff --git a/test/sql-tap/table.test.lua b/test/sql-tap/table.test.lua
index 7057f6b0f..cb54e719c 100755
--- a/test/sql-tap/table.test.lua
+++ b/test/sql-tap/table.test.lua
@@ -1217,7 +1217,7 @@ test:do_catchsql_test(
         INSERT INTO T21 VALUES(1, -1, 1);
     ]], {
         -- <table-21.3>
-        1, "CHECK constraint failed: T21"
+        1, "Check constraint failed: CK_CONSTRAINT_1_T21"
         -- </table-21.3>
     })
 
@@ -1227,7 +1227,7 @@ test:do_catchsql_test(
         INSERT INTO T21 VALUES(1, 1, -1);
     ]], {
         -- <table-21.4>
-        1, "CHECK constraint failed: T21"
+        1, "Check constraint failed: CK_CONSTRAINT_2_T21"
         -- </table-21.4>
     })
 
@@ -1368,7 +1368,7 @@ test:do_catchsql_test(
         INSERT INTO T28 VALUES(0);
     ]], {
         -- <table-22.10>
-        1, "CHECK constraint failed: CHECK1"
+        1, "Check constraint failed: CHECK1"
         -- </table-22.10>
     })
 
@@ -1378,7 +1378,7 @@ test:do_catchsql_test(
         INSERT INTO T28 VALUES(9);
     ]], {
         -- <table-22.11>
-        1, "CHECK constraint failed: CHECK2"
+        1, "Check constraint failed: CHECK2"
         -- </table-22.11>
     })
 
diff --git a/test/sql/checks.result b/test/sql/checks.result
index 12a3aa14c..636fa5b5f 100644
--- a/test/sql/checks.result
+++ b/test/sql/checks.result
@@ -17,8 +17,8 @@ box.sql.execute('pragma sql_default_engine=\''..engine..'\'')
 --
 -- gh-3272: Move SQL CHECK into server
 --
--- invalid expression
-opts = {checks = {{expr = 'X><5'}}}
+-- Legacy data in _space (insertion on bootrap) test.
+opts = {checks = {{expr = 'X>5'}}}
 ---
 ...
 format = {{name = 'X', type = 'unsigned'}}
@@ -29,89 +29,106 @@ t = {513, 1, 'test', 'memtx', 0, opts, format}
 ...
 s = box.space._space:insert(t)
 ---
-- error: 'Wrong space options (field 5): invalid expression specified (SQL error:
-    near "<": syntax error)'
 ...
-opts = {checks = {{expr = 'X>5'}}}
+box.space.test:create_index('pk')
 ---
+- unique: true
+  parts:
+  - type: unsigned
+    is_nullable: false
+    fieldno: 1
+  id: 0
+  space_id: 513
+  name: pk
+  type: TREE
 ...
-format = {{name = 'X', type = 'unsigned'}}
+-- Invalid expression test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X><5'})
 ---
+- error: 'SQL error: near "<": syntax error'
 ...
-t = {513, 1, 'test', 'memtx', 0, opts, format}
+-- Unexistent space test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 'X<5'})
 ---
+- error: Space '550' does not exist
 ...
-s = box.space._space:insert(t)
+-- Field type test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 666})
 ---
+- error: 'Tuple field 3 type does not match one required by operation: expected string'
 ...
-box.space._space:delete(513)
+-- Check constraints LUA creation test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X<5'})
 ---
-- [513, 1, 'test', 'memtx', 0, {'checks': [{'expr': 'X>5'}]}, [{'name': 'X', 'type': 'unsigned'}]]
+- ['CK_CONSTRAINT_01', 513, 'X<5']
 ...
-opts = {checks = {{expr = 'X>5', name = 'ONE'}}}
+box.space._ck_constraint:count({})
 ---
+- 1
 ...
-format = {{name = 'X', type = 'unsigned'}}
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
 ---
+- error: 'Check constraint failed: CK_CONSTRAINT_01'
 ...
-t = {513, 1, 'test', 'memtx', 0, opts, format}
+box.space._ck_constraint:replace({'CK_CONSTRAINT_01', 513, 'X<=5'})
 ---
+- ['CK_CONSTRAINT_01', 513, 'X<=5']
 ...
-s = box.space._space:insert(t)
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
 ---
 ...
-box.space._space:delete(513)
+box.sql.execute("INSERT INTO \"test\" VALUES(6);")
 ---
-- [513, 1, 'test', 'memtx', 0, {'checks': [{'name': 'ONE', 'expr': 'X>5'}]}, [{'name': 'X',
-      'type': 'unsigned'}]]
+- error: 'Check constraint failed: CK_CONSTRAINT_01'
 ...
--- extra invlalid field name
-opts = {checks = {{expr = 'X>5', name = 'ONE', extra = 'TWO'}}}
+-- Can't drop table with check constraints.
+box.space.test:delete({5})
 ---
+- [5]
 ...
-format = {{name = 'X', type = 'unsigned'}}
+box.space.test.index.pk:drop()
 ---
 ...
-t = {513, 1, 'test', 'memtx', 0, opts, format}
+box.space._space:delete({513})
 ---
+- error: 'Can''t drop space ''test'': the space has check constraints'
 ...
-s = box.space._space:insert(t)
+box.space._ck_constraint:delete({'CK_CONSTRAINT_01', 513})
 ---
-- error: 'Wrong space options (field 5): invalid MsgPack map field ''extra'''
+- ['CK_CONSTRAINT_01', 513, 'X<=5']
 ...
-opts = {checks = {{expr_invalid_label = 'X>5'}}}
+box.space.test:drop()
 ---
 ...
-format = {{name = 'X', type = 'unsigned'}}
+-- Create table with checks in sql.
+box.sql.execute("CREATE TABLE t1(x INTEGER CONSTRAINT ONE CHECK( x<5 ), y REAL CONSTRAINT TWO CHECK( y>x ), z INTEGER PRIMARY KEY);")
 ---
 ...
-t = {513, 1, 'test', 'memtx', 0, opts, format}
+box.space._ck_constraint:count()
 ---
+- 2
 ...
-s = box.space._space:insert(t)
+box.sql.execute("INSERT INTO t1 VALUES (7, 1, 1)")
 ---
-- error: 'Wrong space options (field 5): invalid MsgPack map field ''expr_invalid_label'''
+- error: 'Check constraint failed: ONE'
 ...
--- invalid field type
-opts = {checks = {{name = 123}}}
+box.sql.execute("INSERT INTO t1 VALUES (2, 1, 1)")
 ---
+- error: 'Check constraint failed: TWO'
 ...
-format = {{name = 'X', type = 'unsigned'}}
+box.sql.execute("INSERT INTO t1 VALUES (2, 4, 1)")
 ---
 ...
-t = {513, 1, 'test', 'memtx', 0, opts, format}
----
-...
-s = box.space._space:insert(t)
+box.sql.execute("DROP TABLE t1")
 ---
-- error: 'Wrong space options (field 5): invalid MsgPack map field ''name'' type'
 ...
 --
 -- gh-3611: Segfault on table creation with check referencing this table
 --
 box.sql.execute("CREATE TABLE w2 (s1 INT PRIMARY KEY, CHECK ((SELECT COUNT(*) FROM w2) = 0));")
 ---
-- error: 'Failed to create space ''W2'': SQL error: no such table: W2'
+- error: 'Failed to create check constraint ''CK_CONSTRAINT_1_W2'': subqueries prohibited
+    in CHECK constraints'
 ...
 box.sql.execute("DROP TABLE w2;")
 ---
@@ -122,8 +139,7 @@ box.sql.execute("DROP TABLE w2;")
 --
 box.sql.execute("CREATE TABLE t5(x INT PRIMARY KEY, y INT, CHECK( x*y < ? ));")
 ---
-- error: 'Wrong space options (field 5): invalid expression specified (SQL error:
-    bindings are not allowed in DDL)'
+- error: 'SQL error: bindings are not allowed in DDL'
 ...
 opts = {checks = {{expr = '?>5', name = 'ONE'}}}
 ---
@@ -136,8 +152,6 @@ t = {513, 1, 'test', 'memtx', 0, opts, format}
 ...
 s = box.space._space:insert(t)
 ---
-- error: 'Wrong space options (field 5): invalid expression specified (SQL error:
-    bindings are not allowed in DDL)'
 ...
 test_run:cmd("clear filter")
 ---
diff --git a/test/sql/checks.test.lua b/test/sql/checks.test.lua
index 0582bbb63..0de0b5e75 100644
--- a/test/sql/checks.test.lua
+++ b/test/sql/checks.test.lua
@@ -8,41 +8,42 @@ box.sql.execute('pragma sql_default_engine=\''..engine..'\'')
 -- gh-3272: Move SQL CHECK into server
 --
 
--- invalid expression
-opts = {checks = {{expr = 'X><5'}}}
-format = {{name = 'X', type = 'unsigned'}}
-t = {513, 1, 'test', 'memtx', 0, opts, format}
-s = box.space._space:insert(t)
-
+-- Legacy data in _space (insertion on bootrap) test.
 opts = {checks = {{expr = 'X>5'}}}
 format = {{name = 'X', type = 'unsigned'}}
 t = {513, 1, 'test', 'memtx', 0, opts, format}
 s = box.space._space:insert(t)
-box.space._space:delete(513)
+box.space.test:create_index('pk')
 
-opts = {checks = {{expr = 'X>5', name = 'ONE'}}}
-format = {{name = 'X', type = 'unsigned'}}
-t = {513, 1, 'test', 'memtx', 0, opts, format}
-s = box.space._space:insert(t)
-box.space._space:delete(513)
+-- Invalid expression test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X><5'})
+-- Unexistent space test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 'X<5'})
+-- Field type test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 666})
 
--- extra invlalid field name
-opts = {checks = {{expr = 'X>5', name = 'ONE', extra = 'TWO'}}}
-format = {{name = 'X', type = 'unsigned'}}
-t = {513, 1, 'test', 'memtx', 0, opts, format}
-s = box.space._space:insert(t)
-
-opts = {checks = {{expr_invalid_label = 'X>5'}}}
-format = {{name = 'X', type = 'unsigned'}}
-t = {513, 1, 'test', 'memtx', 0, opts, format}
-s = box.space._space:insert(t)
+-- Check constraints LUA creation test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X<5'})
+box.space._ck_constraint:count({})
 
--- invalid field type
-opts = {checks = {{name = 123}}}
-format = {{name = 'X', type = 'unsigned'}}
-t = {513, 1, 'test', 'memtx', 0, opts, format}
-s = box.space._space:insert(t)
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
+box.space._ck_constraint:replace({'CK_CONSTRAINT_01', 513, 'X<=5'})
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
+box.sql.execute("INSERT INTO \"test\" VALUES(6);")
+-- Can't drop table with check constraints.
+box.space.test:delete({5})
+box.space.test.index.pk:drop()
+box.space._space:delete({513})
+box.space._ck_constraint:delete({'CK_CONSTRAINT_01', 513})
+box.space.test:drop()
 
+-- Create table with checks in sql.
+box.sql.execute("CREATE TABLE t1(x INTEGER CONSTRAINT ONE CHECK( x<5 ), y REAL CONSTRAINT TWO CHECK( y>x ), z INTEGER PRIMARY KEY);")
+box.space._ck_constraint:count()
+box.sql.execute("INSERT INTO t1 VALUES (7, 1, 1)")
+box.sql.execute("INSERT INTO t1 VALUES (2, 1, 1)")
+box.sql.execute("INSERT INTO t1 VALUES (2, 4, 1)")
+box.sql.execute("DROP TABLE t1")
 
 --
 -- gh-3611: Segfault on table creation with check referencing this table
diff --git a/test/sql/errinj.result b/test/sql/errinj.result
index acce52e8a..56e103e4a 100644
--- a/test/sql/errinj.result
+++ b/test/sql/errinj.result
@@ -340,3 +340,55 @@ errinj.set("ERRINJ_WAL_IO", false)
 box.sql.execute("DROP TABLE t3;")
 ---
 ...
+-- Tests which are aimed at verifying work of commit/rollback
+-- triggers on _ck_constraint space.
+--
+errinj = box.error.injection
+---
+...
+s = box.schema.space.create('test', {format = {{name = 'X', type = 'unsigned'}}})
+---
+...
+pk = box.space.test:create_index('pk')
+---
+...
+errinj.set("ERRINJ_WAL_IO", true)
+---
+- ok
+...
+_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'})
+---
+- error: Failed to write to disk
+...
+errinj.set("ERRINJ_WAL_IO", false)
+---
+- ok
+...
+_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'})
+---
+...
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
+---
+- error: 'Check constraint failed: CK_CONSTRAINT_01'
+...
+errinj.set("ERRINJ_WAL_IO", true)
+---
+- ok
+...
+_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'})
+---
+- error: Failed to write to disk
+...
+errinj.set("ERRINJ_WAL_IO", false)
+---
+- ok
+...
+_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'})
+---
+...
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
+---
+...
+box.space.test:drop()
+---
+...
diff --git a/test/sql/errinj.test.lua b/test/sql/errinj.test.lua
index fc19c859b..e4f064ff1 100644
--- a/test/sql/errinj.test.lua
+++ b/test/sql/errinj.test.lua
@@ -117,3 +117,22 @@ box.sql.execute("ALTER TABLE t3 DROP CONSTRAINT fk1;")
 box.sql.execute("INSERT INTO t3 VALUES(1, 1, 3);")
 errinj.set("ERRINJ_WAL_IO", false)
 box.sql.execute("DROP TABLE t3;")
+
+-- Tests which are aimed at verifying work of commit/rollback
+-- triggers on _ck_constraint space.
+--
+errinj = box.error.injection
+s = box.schema.space.create('test', {format = {{name = 'X', type = 'unsigned'}}})
+pk = box.space.test:create_index('pk')
+
+errinj.set("ERRINJ_WAL_IO", true)
+_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'})
+errinj.set("ERRINJ_WAL_IO", false)
+_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'})
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
+errinj.set("ERRINJ_WAL_IO", true)
+_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'})
+errinj.set("ERRINJ_WAL_IO", false)
+_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'})
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
+box.space.test:drop()
diff --git a/test/sql/gh-2981-check-autoinc.result b/test/sql/gh-2981-check-autoinc.result
index b0f55e61d..69c722064 100644
--- a/test/sql/gh-2981-check-autoinc.result
+++ b/test/sql/gh-2981-check-autoinc.result
@@ -24,28 +24,28 @@ box.sql.execute("insert into t1 values (18, null);")
 ...
 box.sql.execute("insert into t1(s2) values (null);")
 ---
-- error: 'CHECK constraint failed: T1'
+- error: 'Check constraint failed: CK_CONSTRAINT_1_T1'
 ...
 box.sql.execute("insert into t2 values (18, null);")
 ---
 ...
 box.sql.execute("insert into t2(s2) values (null);")
 ---
-- error: 'CHECK constraint failed: T2'
+- error: 'Check constraint failed: CK_CONSTRAINT_1_T2'
 ...
 box.sql.execute("insert into t2 values (24, null);")
 ---
 ...
 box.sql.execute("insert into t2(s2) values (null);")
 ---
-- error: 'CHECK constraint failed: T2'
+- error: 'Check constraint failed: CK_CONSTRAINT_1_T2'
 ...
 box.sql.execute("insert into t3 values (9, null)")
 ---
 ...
 box.sql.execute("insert into t3(s2) values (null)")
 ---
-- error: 'CHECK constraint failed: T3'
+- error: 'Check constraint failed: CK_CONSTRAINT_1_T3'
 ...
 box.sql.execute("DROP TABLE t1")
 ---
diff --git a/test/sql/upgrade.result b/test/sql/upgrade.result
index d3392e88b..7b9126a82 100644
--- a/test/sql/upgrade.result
+++ b/test/sql/upgrade.result
@@ -180,11 +180,10 @@ s = box.space._space.index['name']:get('T2')
 s
 ---
 - [512, 1, 'T2', 'memtx', 3, {'sql': 'CREATE TABLE t2(x INTEGER CONSTRAINT aaa CHECK
-      ( x<5 ), y REAL CHECK( y>x ), z  INT primary key)', 'checks': [{'name': 'AAA',
-        'expr': 'x<5'}, {'expr': 'y>x'}]}, [{'affinity': 68, 'type': 'scalar', 'nullable_action': 'none',
-      'name': 'X', 'is_nullable': true}, {'affinity': 69, 'type': 'scalar', 'nullable_action': 'none',
-      'name': 'Y', 'is_nullable': true}, {'affinity': 68, 'type': 'integer', 'nullable_action': 'abort',
-      'name': 'Z', 'is_nullable': false}]]
+      ( x<5 ), y REAL CHECK( y>x ), z  INT primary key)'}, [{'affinity': 68, 'type': 'scalar',
+      'is_nullable': true, 'name': 'X', 'nullable_action': 'none'}, {'affinity': 69,
+      'type': 'scalar', 'is_nullable': true, 'name': 'Y', 'nullable_action': 'none'},
+    {'affinity': 68, 'type': 'integer', 'is_nullable': false, 'name': 'Z', 'nullable_action': 'abort'}]]
 ...
 i = box.space._index:select(s.id)
 ---
diff --git a/test/wal_off/alter.result b/test/wal_off/alter.result
index b4c6a928a..d62cc8e19 100644
--- a/test/wal_off/alter.result
+++ b/test/wal_off/alter.result
@@ -28,7 +28,7 @@ end;
 ...
 #spaces;
 ---
-- 65509
+- 65508
 ...
 -- cleanup
 for k, v in pairs(spaces) do
-- 
2.19.2





More information about the Tarantool-patches mailing list