Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v2 0/2] Add constraint names hash table to space
@ 2019-11-28 18:34 Roman Khabibov
  2019-11-28 18:34 ` [Tarantool-patches] [PATCH v2 1/2] box: introduce constraint names hash table Roman Khabibov
                   ` (2 more replies)
  0 siblings, 3 replies; 29+ messages in thread
From: Roman Khabibov @ 2019-11-28 18:34 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

I have made essential changes to the previous patch, so I decided
to send it as v2.

1) I still don't understand, why we need to store constraint id. If we have the
query "ALTER TABLE T DROP CONSTRAINT C", we just get struct space by its name,
then find the corresponding constraint_def node by the name and emit opcode on
replace in _index/_fk_constraint/_ck_constraint depending on
constraint_def->type.

2) space_delete()
I don't know how to check, if the hash table is empty, because it jsut freed.

salad/mhash.h
void
_mh(delete)(struct _mh(t) *h)
{
	if (h->shadow->p) {
		free(h->shadow->p);
		free(h->shadow->b);
		memset(h->shadow, 0, sizeof(*h->shadow));
	}
	free(h->shadow);
	free(h->b);
	free(h->p);
	free(h);
}

3) I copy names of constraints instead of use index_def->name or
ck_constraint_def->name or fk_constraint_def->name, because in some cases these
defs can be freed. I discovered it during degugging, when I haven't use name copy
yet: there was node in the hash table with name length and hash, but without name
itself. Now, I decided to store duplicates in struct constraint_def.

Issue: https://github.com/tarantool/tarantool/issues/3503
Branch: https://github.com/tarantool/tarantool/tree/romanhabibov/gh-3503-constr-names_v3

Roman Khabibov (2):
  box: introduce constraint names hash table
  sql: make constraint operations transactional

 src/box/CMakeLists.txt       |   1 +
 src/box/alter.cc             | 427 +++++++++++++++++++++++++++++++----
 src/box/constraint_def.c     |  59 +++++
 src/box/constraint_def.h     |  83 +++++++
 src/box/space.c              |  56 +++++
 src/box/space.h              |  39 ++++
 test/sql/constraint.result   | 190 ++++++++++++++++
 test/sql/constraint.test.lua |  84 +++++++
 8 files changed, 892 insertions(+), 47 deletions(-)
 create mode 100755 src/box/constraint_def.c
 create mode 100755 src/box/constraint_def.h
 create mode 100644 test/sql/constraint.result
 create mode 100755 test/sql/constraint.test.lua

-- 
2.21.0 (Apple Git-122)

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

* [Tarantool-patches] [PATCH v2 1/2] box: introduce constraint names hash table
  2019-11-28 18:34 [Tarantool-patches] [PATCH v2 0/2] Add constraint names hash table to space Roman Khabibov
@ 2019-11-28 18:34 ` Roman Khabibov
  2019-11-30  1:03   ` Vladislav Shpilevoy
  2019-11-28 18:34 ` [Tarantool-patches] [PATCH v2 2/2] sql: make constraint operations transactional Roman Khabibov
  2019-11-30  1:03 ` [Tarantool-patches] [PATCH v2 0/2] Add constraint names hash table to space Vladislav Shpilevoy
  2 siblings, 1 reply; 29+ messages in thread
From: Roman Khabibov @ 2019-11-28 18:34 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

Add hash table and API for interaction with it to struct space.
This hash table is needed to keep constraint of a table together
and check their duplicates by name.

Part of #3503
---
 src/box/CMakeLists.txt   |  1 +
 src/box/constraint_def.c | 59 ++++++++++++++++++++++++++++
 src/box/constraint_def.h | 83 ++++++++++++++++++++++++++++++++++++++++
 src/box/space.c          | 56 +++++++++++++++++++++++++++
 src/box/space.h          | 39 +++++++++++++++++++
 5 files changed, 238 insertions(+)
 create mode 100755 src/box/constraint_def.c
 create mode 100755 src/box/constraint_def.h

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 5cd5cba81..bf3895262 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -102,6 +102,7 @@ add_library(box STATIC
     sequence.c
     ck_constraint.c
     fk_constraint.c
+    constraint_def.c
     func.c
     func_def.c
     key_list.c
diff --git a/src/box/constraint_def.c b/src/box/constraint_def.c
new file mode 100755
index 000000000..914b36da3
--- /dev/null
+++ b/src/box/constraint_def.c
@@ -0,0 +1,59 @@
+/*
+ * 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 "constraint_def.h"
+#include "assoc.h"
+#include "errcode.h"
+#include "diag.h"
+#include <string.h>
+#include <stdlib.h>
+
+struct constraint_def *
+constraint_def_new(uint32_t space_id, enum constraint_type type,
+		   const char *name) {
+	uint32_t len = strlen(name);
+	uint32_t size = sizeof(struct constraint_def) + len + 1;
+	struct constraint_def *ret = malloc(size);
+	if (ret == NULL) {
+		diag_set(OutOfMemory, size, "malloc", "constraint_def");
+		return NULL;
+	}
+	ret->space_id = space_id;
+	ret->type = type;
+	memcpy(ret->name, name, len + 1);
+	return ret;
+}
+
+void
+constraint_def_free(struct constraint_def *def) {
+	free(def);
+}
diff --git a/src/box/constraint_def.h b/src/box/constraint_def.h
new file mode 100755
index 000000000..ef238eb46
--- /dev/null
+++ b/src/box/constraint_def.h
@@ -0,0 +1,83 @@
+#ifndef INCLUDES_BOX_CONSTRAINT_DEF_H
+#define INCLUDES_BOX_CONSTRAINT_DEF_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>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+enum constraint_type {
+	CONSTRAINT_TYPE_PK = 0,
+	CONSTRAINT_TYPE_UNIQUE = 1,
+	CONSTRAINT_TYPE_FK = 2,
+	CONSTRAINT_TYPE_CK = 3
+};
+
+struct constraint_def {
+	/** Space id. */
+	uint32_t space_id;
+	/** Constraint type. */
+	enum constraint_type type;
+	/** Zero-terminated string with name. */
+	char name[0];
+};
+
+/**
+ * Allocate memory and construct constraint def object.
+ *
+ * @param space_id Space id.
+ * @param type     Constraint type.
+ * @param name     Constraint name.
+ *
+ * @retval ptr  Constraint def object.
+ * @retval NULL Memory allocation error.
+ */
+struct constraint_def *
+constraint_def_new(uint32_t space_id, enum constraint_type type,
+		   const char *name);
+
+/**
+ * Free memory of constraint def object.
+ *
+ * @param def Constraint def.
+ */
+void
+constraint_def_free(struct constraint_def *def);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
diff --git a/src/box/space.c b/src/box/space.c
index 94716a414..91a7d575b 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -45,6 +45,8 @@
 #include "iproto_constants.h"
 #include "schema.h"
 #include "ck_constraint.h"
+#include "assoc.h"
+#include "constraint_def.h"
 
 int
 access_check_space(struct space *space, user_access_t access)
@@ -202,6 +204,13 @@ space_create(struct space *space, struct engine *engine,
 			}
 		}
 	}
+
+	space->constraint_names = mh_strnptr_new();
+	if (space->constraint_names == NULL) {
+		diag_set(OutOfMemory, sizeof(*space->constraint_names),
+			 "malloc", "constraint_names");
+		goto fail;
+	}
 	return 0;
 
 fail_free_indexes:
@@ -257,6 +266,20 @@ space_delete(struct space *space)
 	trigger_destroy(&space->before_replace);
 	trigger_destroy(&space->on_replace);
 	space_def_delete(space->def);
+
+	/**
+	 * Free memory of the constraint hash table. Destroy every
+	 * constraint def object.
+	 */
+	struct mh_strnptr_t *mh = space->constraint_names;
+	while (mh_size(mh) > 0) {
+		mh_int_t i = mh_first(mh);
+		struct constraint_def *def =
+			(struct constraint_def *) mh_strnptr_node(mh, i)->val;
+		constraint_def_free(def);
+	}
+	mh_strnptr_delete(mh);
+
 	/*
 	 * SQL Triggers should be deleted with
 	 * on_replace_dd_trigger on deletion from _trigger.
@@ -617,6 +640,39 @@ space_remove_ck_constraint(struct space *space, struct ck_constraint *ck)
 	}
 }
 
+struct constraint_def *
+space_constraint_def_by_name(struct space *space, const char *name) {
+	uint32_t len = strlen(name);
+	mh_int_t pos = mh_strnptr_find_inp(space->constraint_names, name, len);
+	if (pos == mh_end(space->constraint_names))
+		return NULL;
+	return (struct constraint_def *)
+		mh_strnptr_node(space->constraint_names, pos)->val;
+}
+
+int
+space_put_constraint(struct space *space, struct constraint_def *def)
+{
+	uint32_t len = strlen(def->name);
+	uint32_t hash = mh_strn_hash(def->name, len);
+	const struct mh_strnptr_node_t name_node = { def->name, len, hash, def};
+	if (mh_strnptr_put(space->constraint_names, &name_node, NULL, NULL) ==
+	    mh_end(space->constraint_names)) {
+		diag_set(OutOfMemory, sizeof(name_node), "malloc",
+			 "constraint_names");
+		return -1;
+	}
+	return 0;
+}
+
+void
+space_drop_constraint(struct space *space, const char *name)
+{
+	mh_int_t pos = mh_strnptr_find_inp(space->constraint_names, name,
+					   strlen(name));
+	mh_strnptr_del(space->constraint_names, pos, NULL);
+}
+
 /* {{{ Virtual method stubs */
 
 size_t
diff --git a/src/box/space.h b/src/box/space.h
index 7926aa65e..96396311e 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -52,6 +52,7 @@ struct port;
 struct tuple;
 struct tuple_format;
 struct ck_constraint;
+struct constraint_def;
 
 struct space_vtab {
 	/** Free a space instance. */
@@ -234,6 +235,8 @@ struct space {
 	 * of parent constraints as well as child ones.
 	 */
 	uint64_t fk_constraint_mask;
+	/** Hash table with constraint names. */
+	struct mh_strnptr_t *constraint_names;
 };
 
 /** Initialize a base space instance. */
@@ -516,6 +519,42 @@ space_add_ck_constraint(struct space *space, struct ck_constraint *ck);
 void
 space_remove_ck_constraint(struct space *space, struct ck_constraint *ck);
 
+/**
+ * Find node with @a name in the constraint hash table of @a
+ * space.
+ *
+ * @param space Space.
+ * @param name  Constraint name.
+ *
+ * @retval constraint_def object.
+ * @retval NULL Constraint doesn't exist.
+ */
+struct constraint_def *
+space_constraint_def_by_name(struct space *space, const char *name);
+
+/**
+ * Put node with @a def to the constraint hash table of @a space.
+ *
+ * @param space Space.
+ * @param def   Constraint def.
+ *
+ * @retval  0 Success.
+ * @retval -1 Memory allocation error.
+ */
+int
+space_put_constraint(struct space *space, struct constraint_def *def);
+
+/**
+ * Remove node with @a name from the constraint hash table of @a
+ * space. But don't destroy the constraint def object binded to
+ * this @a name.
+ *
+ * @param space Space.
+ * @param name  Constraint name.
+ */
+void
+space_drop_constraint(struct space *space, const char *name);
+
 /*
  * Virtual method stubs.
  */
-- 
2.21.0 (Apple Git-122)

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

* [Tarantool-patches] [PATCH v2 2/2] sql: make constraint operations transactional
  2019-11-28 18:34 [Tarantool-patches] [PATCH v2 0/2] Add constraint names hash table to space Roman Khabibov
  2019-11-28 18:34 ` [Tarantool-patches] [PATCH v2 1/2] box: introduce constraint names hash table Roman Khabibov
@ 2019-11-28 18:34 ` Roman Khabibov
  2019-11-29  7:38   ` Roman Khabibov
  2019-11-30  1:03   ` Vladislav Shpilevoy
  2019-11-30  1:03 ` [Tarantool-patches] [PATCH v2 0/2] Add constraint names hash table to space Vladislav Shpilevoy
  2 siblings, 2 replies; 29+ messages in thread
From: Roman Khabibov @ 2019-11-28 18:34 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

Put constraint names into the space's hash table and drop them on
replace in correspondig system spaces (_index, _fk_constraint,
_ck_constraint).

Closes #3503
---
 src/box/alter.cc             | 427 +++++++++++++++++++++++++++++++----
 test/sql/constraint.result   | 190 ++++++++++++++++
 test/sql/constraint.test.lua |  84 +++++++
 3 files changed, 654 insertions(+), 47 deletions(-)
 create mode 100644 test/sql/constraint.result
 create mode 100755 test/sql/constraint.test.lua

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 4a3241a79..4bb8b8556 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -57,6 +57,7 @@
 #include "version.h"
 #include "sequence.h"
 #include "sql.h"
+#include "constraint_def.h"
 
 /* {{{ Auxiliary functions and methods. */
 
@@ -1765,6 +1766,38 @@ MoveCkConstraints::rollback(struct alter_space *alter)
 	space_swap_ck_constraint(alter->new_space, alter->old_space);
 }
 
+/**
+ * Move constraint names hash table from old space to a new one.
+ */
+class MoveConstraints: public AlterSpaceOp
+{
+	inline void space_swap_constraint(struct space *old_space,
+				   struct space *new_space);
+public:
+	MoveConstraints(struct alter_space *alter) : AlterSpaceOp(alter) {}
+	virtual void alter(struct alter_space *alter);
+	virtual void rollback(struct alter_space *alter);
+};
+
+inline void
+MoveConstraints::space_swap_constraint(struct space *old_space,
+				       struct space *new_space)
+{
+	SWAP(new_space->constraint_names, old_space->constraint_names);
+}
+
+void
+MoveConstraints::alter(struct alter_space *alter)
+{
+	space_swap_constraint(alter->old_space, alter->new_space);
+}
+
+void
+MoveConstraints::rollback(struct alter_space *alter)
+{
+	space_swap_constraint(alter->new_space, alter->old_space);
+}
+
 /* }}} */
 
 /**
@@ -2307,6 +2340,7 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 		def_guard.is_active = false;
 		/* Create MoveIndex ops for all space indexes. */
 		alter_space_move_indexes(alter, 0, old_space->index_id_max + 1);
+		(void) new MoveConstraints(alter);
 		/* Remember to update schema_version. */
 		(void) new UpdateSchemaVersion(alter);
 		try {
@@ -2341,6 +2375,150 @@ index_is_used_by_fk_constraint(struct rlist *fk_list, uint32_t iid)
 	return false;
 }
 
+/**
+ * Bring back the node of an unique index to the constraint hash
+ * table of a space.
+ *
+ * Rollback DROP index.
+ */
+static int
+on_drop_index_rollback(struct trigger *trigger, void * /* event */)
+{
+	struct constraint_def *def = (struct constraint_def *)trigger->data;
+	struct space *space = space_by_id(def->space_id);
+	assert(space != NULL);
+	if (space_put_constraint(space, def) != 0) {
+		panic("Can't recover after index drop rollback (out of "
+		      "memory)");
+	}
+	return 0;
+}
+
+/**
+ * Drop the node of an unique index from the constraint hash table
+ * of a space and clean the unnecessary constraint def memory.
+ *
+ * Rollback CREATE index.
+ */
+static int
+on_create_index_rollback(struct trigger *trigger, void * /* event */)
+{
+	struct constraint_def *def = (struct constraint_def *)trigger->data;
+	struct space *space = space_by_id(def->space_id);
+	assert(space != NULL);
+	space_drop_constraint(space, def->name);
+	constraint_def_free(def);
+	return 0;
+}
+
+/**
+ * Clean the unnecessary constraint def memory.
+ *
+ * Commit REPLACE index.
+ */
+static int
+on_replace_index_commit(struct trigger *trigger, void * /* event */)
+{
+	struct constraint_def **defs = (struct constraint_def **)trigger->data;
+	constraint_def_free(defs[0]);
+	return 0;
+}
+
+/**
+ * Bring back the new node of a unique index to the constraint
+ * hash table of a space and drop the old node respectively. Clean
+ * the unnecessary constraint def memory.
+ *
+ * Rollback REPLACE index.
+ */
+static int
+on_replace_index_rollback(struct trigger *trigger, void * /* event */)
+{
+	struct constraint_def **defs = (struct constraint_def **)trigger->data;
+	struct constraint_def *old_def = defs[0];
+	struct constraint_def *new_def = defs[1];
+	struct space *space = space_by_id(old_def->space_id);
+	assert(space != NULL);
+	space_drop_constraint(space, new_def->name);
+	constraint_def_free(new_def);
+	if (space_put_constraint(space, old_def) != 0) {
+		panic("Can't recover after index replace rollback (out of "
+		      "memory)");
+	}
+	return 0;
+}
+
+/**
+ * Put the node of an unique index to the constraint hash table of
+ * @a space and create trigger on rollback.
+ *
+ * This function is needed to wrap the duplicated piece of code
+ * inside on_replace_dd_index().
+ *
+ * @param stmt  Statement.
+ * @param space Space.
+ * @param def   Index def.
+ *
+ * @retval 0  Success.
+ * @retval -1 Constraint already exists or out of memory.
+ */
+static int
+create_index_as_constraint(struct txn_stmt *stmt, struct space *space,
+			   struct index_def *def) {
+	assert(def->opts.is_unique);
+	if (space_constraint_def_by_name(space, def->name) != NULL) {
+		diag_set(ClientError, ER_CONSTRAINT_EXISTS, def->name);
+		return -1;
+	}
+	struct constraint_def *constr_def =
+		constraint_def_new(space->def->id, def->iid == 0 ?
+				   CONSTRAINT_TYPE_PK : CONSTRAINT_TYPE_UNIQUE,
+				   def->name);
+	if (constr_def == NULL)
+		return -1;
+	struct trigger *on_rollback = txn_alter_trigger_new(NULL, NULL);
+	if (space_put_constraint(space, constr_def) != 0 ||
+	    on_rollback == NULL) {
+		constraint_def_free(constr_def);
+		return -1;
+	}
+	on_rollback->data = constr_def;
+	on_rollback->run = on_create_index_rollback;
+	txn_stmt_on_rollback(stmt, on_rollback);
+	return 0;
+}
+
+/**
+ * Drop the node of an unique index from the constraint hash table
+ * of @a space and create trigger on rollback.
+ *
+ * This function is needed to wrap the duplicated piece of code
+ * inside on_replace_dd_index().
+ *
+ * @param stmt  Statement.
+ * @param space Space.
+ * @param def   Index def.
+ *
+ * @retval 0  Success.
+ * @retval -1 Out of memory.
+ */
+static int
+drop_index_as_constraint(struct txn_stmt *stmt, struct space *space,
+			 struct index_def *def) {
+	assert(def->opts.is_unique);
+	struct constraint_def *constr_def =
+		space_constraint_def_by_name(space, def->name);
+	assert(constr_def != NULL);
+	space_drop_constraint(space, def->name);
+	struct trigger *on_rollback = txn_alter_trigger_new(NULL, NULL);
+	if (on_rollback == NULL)
+		return -1;
+	on_rollback->data = constr_def;
+	on_rollback->run = on_drop_index_rollback;
+	txn_stmt_on_rollback(stmt, on_rollback);
+	return 0;
+}
+
 /**
  * Just like with _space, 3 major cases:
  *
@@ -2391,21 +2569,21 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 	if (tuple_field_u32(old_tuple ? old_tuple : new_tuple,
 			    BOX_INDEX_FIELD_ID, &iid) != 0)
 		return -1;
-	struct space *old_space = space_cache_find(id);
-	if (old_space == NULL)
+	struct space *space = space_cache_find(id);
+	if (space == NULL)
 		return -1;
-	if (old_space->def->opts.is_view) {
-		diag_set(ClientError, ER_ALTER_SPACE, space_name(old_space),
+	if (space->def->opts.is_view) {
+		diag_set(ClientError, ER_ALTER_SPACE, space_name(space),
 			  "can not add index on a view");
 		return -1;
 	}
 	enum priv_type priv_type = new_tuple ? PRIV_C : PRIV_D;
 	if (old_tuple && new_tuple)
 		priv_type = PRIV_A;
-	if (access_check_ddl(old_space->def->name, old_space->def->id,
-			 old_space->def->uid, SC_SPACE, priv_type) != 0)
+	if (access_check_ddl(space->def->name, space->def->id, space->def->uid,
+			     SC_SPACE, priv_type) != 0)
 		return -1;
-	struct index *old_index = space_index(old_space, iid);
+	struct index *old_index = space_index(space, iid);
 
 	/*
 	 * Deal with various cases of dropping of the primary key.
@@ -2414,43 +2592,41 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 		/*
 		 * Dropping the primary key in a system space: off limits.
 		 */
-		if (space_is_system(old_space)) {
+		if (space_is_system(space)) {
 			diag_set(ClientError, ER_LAST_DROP,
-				  space_name(old_space));
+				 space_name(space));
 			return -1;
 		}
 		/*
 		 * Can't drop primary key before secondary keys.
 		 */
-		if (old_space->index_count > 1) {
+		if (space->index_count > 1) {
 			diag_set(ClientError, ER_DROP_PRIMARY_KEY,
-				  space_name(old_space));
+				 space_name(space));
 			return -1;
 		}
 		/*
 		 * Can't drop primary key before space sequence.
 		 */
-		if (old_space->sequence != NULL) {
-			diag_set(ClientError, ER_ALTER_SPACE,
-				  space_name(old_space),
+		if (space->sequence != NULL) {
+			diag_set(ClientError, ER_ALTER_SPACE, space_name(space),
 				  "can not drop primary key while "
 				  "space sequence exists");
 			return -1;
 		}
 	}
 
-	if (iid != 0 && space_index(old_space, 0) == NULL) {
+	if (iid != 0 && space_index(space, 0) == NULL) {
 		/*
 		 * A secondary index can not be created without
 		 * a primary key.
 		 */
-		diag_set(ClientError, ER_ALTER_SPACE,
-			  space_name(old_space),
+		diag_set(ClientError, ER_ALTER_SPACE, space_name(space),
 			  "can not add a secondary key before primary");
 		return -1;
 	}
 
-	struct alter_space *alter = alter_space_new(old_space);
+	struct alter_space *alter = alter_space_new(space);
 	if (alter == NULL)
 		return -1;
 	auto scoped_guard =
@@ -2469,35 +2645,111 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 		 * Can't drop index if foreign key constraints
 		 * references this index.
 		 */
-		if (index_is_used_by_fk_constraint(&old_space->parent_fk_constraint,
+		if (index_is_used_by_fk_constraint(&space->parent_fk_constraint,
 						   iid)) {
-			diag_set(ClientError, ER_ALTER_SPACE,
-				  space_name(old_space),
+			diag_set(ClientError, ER_ALTER_SPACE, space_name(space),
 				  "can not drop a referenced index");
 			return -1;
 		}
 		alter_space_move_indexes(alter, 0, iid);
 		(void) new DropIndex(alter, old_index);
+		if (old_index->def->opts.is_unique &&
+		    drop_index_as_constraint(stmt, space, old_index->def) != 0)
+			return -1;
 	}
 	/* Case 2: create an index, if it is simply created. */
 	if (old_index == NULL && new_tuple != NULL) {
+		struct index_def *def =
+			index_def_new_from_tuple(new_tuple, space);
+		if (def == NULL)
+			return -1;
+		if (def->opts.is_unique) {
+			if (create_index_as_constraint(stmt, space, def) != 0) {
+				index_def_delete(def);
+				return -1;
+			}
+		}
 		alter_space_move_indexes(alter, 0, iid);
 		CreateIndex *create_index = new CreateIndex(alter);
-		create_index->new_index_def =
-			index_def_new_from_tuple(new_tuple, old_space);
-		if (create_index->new_index_def == NULL)
-			return -1;
-		index_def_update_optionality(create_index->new_index_def,
-					     alter->new_min_field_count);
+		create_index->new_index_def = def;
+		index_def_update_optionality(def, alter->new_min_field_count);
 	}
 	/* Case 3 and 4: check if we need to rebuild index data. */
 	if (old_index != NULL && new_tuple != NULL) {
 		struct index_def *index_def;
-		index_def = index_def_new_from_tuple(new_tuple, old_space);
+		index_def = index_def_new_from_tuple(new_tuple, space);
 		if (index_def == NULL)
 			return -1;
 		auto index_def_guard =
 			make_scoped_guard([=] { index_def_delete(index_def); });
+		struct index_def *old_def = old_index->def;
+		/**
+		 * We put a new name either an index is becoming
+		 * unique (i.e. constraint), or when an unique
+		 * index's name is under change.
+		 */
+		if (!old_def->opts.is_unique && index_def->opts.is_unique) {
+			if (create_index_as_constraint(stmt, space, index_def)
+			    != 0)
+				return -1;
+		}
+		if (old_def->opts.is_unique && index_def->opts.is_unique &&
+		    strcmp(index_def->name, old_def->name) != 0) {
+			if (space_constraint_def_by_name(space, index_def->name)
+			    != NULL) {
+				diag_set(ClientError, ER_CONSTRAINT_EXISTS,
+					 index_def->name);
+				return -1;
+			}
+			struct constraint_def *old_constr_def =
+				space_constraint_def_by_name(space,
+							     old_def->name);
+			assert(old_constr_def != NULL);
+			struct constraint_def *new_constr_def =
+				constraint_def_new(old_constr_def->space_id,
+						   old_constr_def->type,
+						   index_def->name);
+			if (new_constr_def == NULL)
+				return -1;
+			auto new_constraint_def_guard =
+				make_scoped_guard([=] { constraint_def_free(new_constr_def); });
+			if (space_put_constraint(space, new_constr_def) != 0)
+				return -1;
+			space_drop_constraint(space, old_def->name);
+			/**
+			 * Array with the pair of 2
+			 * index_def structures: old and
+			 * new.
+			 */
+			uint32_t size = sizeof(struct constraint_def *) * 2;
+			struct region *r = &fiber()->gc;
+			struct constraint_def **defs =
+				(struct constraint_def **)region_alloc(r, size);
+			if (defs == NULL) {
+				diag_set(OutOfMemory, size, "region",
+					 "new slab");
+				return -1;
+			}
+			defs[0] = old_constr_def;
+			defs[1] = new_constr_def;
+			struct trigger *on_commit =
+				txn_alter_trigger_new(NULL, NULL);
+			struct trigger *on_rollback =
+				txn_alter_trigger_new(NULL, NULL);
+			if (on_commit == NULL || on_rollback == NULL)
+				return -1;
+			on_commit->data = defs;
+			on_commit->run = on_replace_index_commit;
+			on_rollback->data = defs;
+			on_rollback->run = on_replace_index_rollback;
+			txn_stmt_on_commit(stmt, on_commit);
+			txn_stmt_on_rollback(stmt, on_rollback);
+			new_constraint_def_guard.is_active = false;
+		}
+		if (old_def->opts.is_unique && !index_def->opts.is_unique) {
+			if (drop_index_as_constraint(stmt, space, old_def) != 0)
+				return -1;
+		}
 		/*
 		 * To detect which key parts are optional,
 		 * min_field_count is required. But
@@ -2508,30 +2760,29 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 		 * index on a non-nullable second field. Min field
 		 * count here is 2. Now alter the secondary index
 		 * to make its part be nullable. In the
-		 * 'old_space' min_field_count is still 2, but
+		 * 'space' min_field_count is still 2, but
 		 * actually it is already 1. Actual
 		 * min_field_count must be calculated using old
 		 * unchanged indexes, NEW definition of an updated
 		 * index and a space format, defined by a user.
 		 */
 		struct key_def **keys;
-		size_t bsize = old_space->index_count * sizeof(keys[0]);
+		size_t bsize = space->index_count * sizeof(keys[0]);
 		keys = (struct key_def **) region_alloc(&fiber()->gc, bsize);
 		if (keys == NULL) {
 			diag_set(OutOfMemory, bsize, "region", "new slab");
 			return -1;
 		}
-		for (uint32_t i = 0, j = 0; i < old_space->index_count; ++i) {
-			struct index_def *d = old_space->index[i]->def;
+		for (uint32_t i = 0, j = 0; i < space->index_count; ++i) {
+			struct index_def *d = space->index[i]->def;
 			if (d->iid != index_def->iid)
 				keys[j++] = d->key_def;
 			else
 				keys[j++] = index_def->key_def;
 		}
-		struct space_def *def = old_space->def;
+		struct space_def *def = space->def;
 		alter->new_min_field_count =
-			tuple_format_min_field_count(keys,
-						     old_space->index_count,
+			tuple_format_min_field_count(keys, space->index_count,
 						     def->fields,
 						     def->field_count);
 		index_def_update_optionality(index_def,
@@ -2542,10 +2793,10 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 			(void) new MoveIndex(alter, old_index->def->iid);
 		} else if (index_def_change_requires_rebuild(old_index,
 							     index_def)) {
-			if (index_is_used_by_fk_constraint(&old_space->parent_fk_constraint,
+			if (index_is_used_by_fk_constraint(&space->parent_fk_constraint,
 							   iid)) {
 				diag_set(ClientError, ER_ALTER_SPACE,
-					  space_name(old_space),
+					  space_name(space),
 					  "can not alter a referenced index");
 				return -1;
 			}
@@ -2570,8 +2821,9 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 	 * Create MoveIndex ops for the remaining indexes in the
 	 * old space.
 	 */
-	alter_space_move_indexes(alter, iid + 1, old_space->index_id_max + 1);
+	alter_space_move_indexes(alter, iid + 1, space->index_id_max + 1);
 	(void) new MoveCkConstraints(alter);
+	(void) new MoveConstraints(alter);
 	/* Add an op to update schema_version on commit. */
 	(void) new UpdateSchemaVersion(alter);
 	try {
@@ -2663,6 +2915,7 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event)
 	}
 
 	(void) new MoveCkConstraints(alter);
+	(void) new MoveConstraints(alter);
 	try {
 		alter_space_do(stmt, alter);
 	} catch (Exception *e) {
@@ -4951,8 +5204,15 @@ on_create_fk_constraint_rollback(struct trigger *trigger, void *event)
 	struct fk_constraint *fk = (struct fk_constraint *)trigger->data;
 	rlist_del_entry(fk, in_parent_space);
 	rlist_del_entry(fk, in_child_space);
+	struct space *child = space_by_id(fk->def->child_id);
+	const char *name = fk->def->name;
+	struct constraint_def *constr_def =
+		space_constraint_def_by_name(child, name);
+	assert(constr_def != NULL);
+	space_drop_constraint(child, name);
+	constraint_def_free(constr_def);
 	space_reset_fk_constraint_mask(space_by_id(fk->def->parent_id));
-	space_reset_fk_constraint_mask(space_by_id(fk->def->child_id));
+	space_reset_fk_constraint_mask(child);
 	fk_constraint_delete(fk);
 	return 0;
 }
@@ -4986,11 +5246,24 @@ on_drop_fk_constraint_rollback(struct trigger *trigger, void *event)
 	struct space *child = space_by_id(old_fk->def->child_id);
 	rlist_add_entry(&child->child_fk_constraint, old_fk, in_child_space);
 	rlist_add_entry(&parent->parent_fk_constraint, old_fk, in_parent_space);
+	const char *name = old_fk->def->name;
+	struct constraint_def *constr_def =
+		constraint_def_new(child->def->id, CONSTRAINT_TYPE_FK, name);
+	if (constr_def == NULL)
+		goto panic;
+	if (space_put_constraint(child, constr_def) != 0) {
+		constraint_def_free(constr_def);
+		goto panic;
+	}
 	fk_constraint_set_mask(old_fk, &child->fk_constraint_mask,
 			       FIELD_LINK_CHILD);
 	fk_constraint_set_mask(old_fk, &parent->fk_constraint_mask,
 			       FIELD_LINK_PARENT);
 	return 0;
+
+panic:
+	panic("Can't recover after FK constraint drop rollback (out of "
+	      "memory)");
 }
 
 /**
@@ -5165,15 +5438,31 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 		fk->def = fk_def;
 		fk->index_id = fk_index->def->iid;
 		if (old_tuple == NULL) {
+			if (space_constraint_def_by_name(child_space,
+							 fk_def->name)
+			    != NULL) {
+				diag_set(ClientError, ER_CONSTRAINT_EXISTS,
+					 fk_def->name);
+				return -1;
+			}
 			rlist_add_entry(&child_space->child_fk_constraint,
 					fk, in_child_space);
 			rlist_add_entry(&parent_space->parent_fk_constraint,
 					fk, in_parent_space);
+			struct constraint_def *constr_def =
+				constraint_def_new(child_space->def->id,
+						   CONSTRAINT_TYPE_FK,
+						   fk_def->name);
+			if (constr_def == NULL)
+				return -1;
 			struct trigger *on_rollback =
 				txn_alter_trigger_new(on_create_fk_constraint_rollback,
 						      fk);
-			if (on_rollback == NULL)
+			if (space_put_constraint(child_space, constr_def) != 0
+			    || on_rollback == NULL) {
+				constraint_def_free(constr_def);
 				return -1;
+			}
 			txn_stmt_on_rollback(stmt, on_rollback);
 			fk_constraint_set_mask(fk,
 					       &parent_space->fk_constraint_mask,
@@ -5221,6 +5510,12 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 		struct fk_constraint *old_fk=
 			fk_constraint_remove(&child_space->child_fk_constraint,
 					     fk_def->name);
+		const char *name = fk_def->name;
+		struct constraint_def *constr_def =
+			space_constraint_def_by_name(child_space, name);
+		assert(constr_def != NULL);
+		space_drop_constraint(child_space, name);
+		constraint_def_free(constr_def);
 		struct trigger *on_commit =
 			txn_alter_trigger_new(on_drop_or_replace_fk_constraint_commit,
 					      old_fk);
@@ -5297,9 +5592,13 @@ on_create_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
 	assert(ck != NULL);
 	struct space *space = space_by_id(ck->def->space_id);
 	assert(space != NULL);
-	assert(space_ck_constraint_by_name(space, ck->def->name,
-					   strlen(ck->def->name)) != NULL);
+	const char *name = ck->def->name;
+	assert(space_ck_constraint_by_name(space, name, strlen(name)) != NULL);
 	space_remove_ck_constraint(space, ck);
+	struct constraint_def *constr_def =
+		space_constraint_def_by_name(space, name);
+	space_drop_constraint(space, name);
+	constraint_def_free(constr_def);
 	ck_constraint_delete(ck);
 	if (trigger_run(&on_alter_space, space) != 0)
 		return -1;
@@ -5324,13 +5623,23 @@ on_drop_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
 	assert(ck != NULL);
 	struct space *space = space_by_id(ck->def->space_id);
 	assert(space != NULL);
-	assert(space_ck_constraint_by_name(space, ck->def->name,
-					   strlen(ck->def->name)) == NULL);
-	if (space_add_ck_constraint(space, ck) != 0)
-		panic("Can't recover after CK constraint drop rollback");
+	const char *name = ck->def->name;
+	assert(space_ck_constraint_by_name(space, name, strlen(name)) == NULL);
+	struct constraint_def *constr_def =
+		constraint_def_new(space->def->id, CONSTRAINT_TYPE_CK, name);
+	if (constr_def == NULL)
+		goto panic;
+	if (space_add_ck_constraint(space, ck) != 0 ||
+	    space_put_constraint(space, constr_def) != 0) {
+		constraint_def_free(constr_def);
+		goto panic;
+	}
 	if (trigger_run(&on_alter_space, space) != 0)
 		return -1;
 	return 0;
+
+panic:
+	panic("Can't recover after CK constraint drop rollback");
 }
 
 /** Commit REPLACE check constraint. */
@@ -5443,11 +5752,28 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		auto ck_guard = make_scoped_guard([=] {
 			ck_constraint_delete(new_ck_constraint);
 		});
-		if (old_ck_constraint != NULL)
+
+		if (old_ck_constraint != NULL) {
 			rlist_del_entry(old_ck_constraint, link);
+		} else if (space_constraint_def_by_name(space, name) != NULL) {
+			diag_set(ClientError, ER_CONSTRAINT_EXISTS,
+				 name);
+			return -1;
+		}
 		if (space_add_ck_constraint(space, new_ck_constraint) != 0)
 			return -1;
 		ck_guard.is_active = false;
+		if (old_ck_constraint == NULL) {
+			struct constraint_def *constr_def =
+				constraint_def_new(space->def->id,
+						   CONSTRAINT_TYPE_CK, name);
+			if (constr_def == NULL)
+				return -1;
+			if (space_put_constraint(space, constr_def) != 0) {
+				constraint_def_free(constr_def);
+				return -1;
+			}
+		}
 		if (old_tuple != NULL) {
 			on_rollback->data = old_ck_constraint;
 			on_rollback->run = on_replace_ck_constraint_rollback;
@@ -5467,7 +5793,13 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 			return -1;
 		struct ck_constraint *old_ck_constraint =
 			space_ck_constraint_by_name(space, name, name_len);
+		struct constraint_def *constr_def =
+			space_constraint_def_by_name(space,
+						     old_ck_constraint->def->name);
+		assert(constr_def != NULL);
 		assert(old_ck_constraint != NULL);
+		space_drop_constraint(space, old_ck_constraint->def->name);
+		constraint_def_free(constr_def);
 		space_remove_ck_constraint(space, old_ck_constraint);
 		on_commit->data = old_ck_constraint;
 		on_commit->run = on_drop_ck_constraint_commit;
@@ -5567,6 +5899,7 @@ on_replace_dd_func_index(struct trigger *trigger, void *event)
 	alter_space_move_indexes(alter, index->def->iid + 1,
 				 space->index_id_max + 1);
 	(void) new MoveCkConstraints(alter);
+	(void) new MoveConstraints(alter);
 	(void) new UpdateSchemaVersion(alter);
 	try {
 		alter_space_do(stmt, alter);
diff --git a/test/sql/constraint.result b/test/sql/constraint.result
new file mode 100644
index 000000000..d8a4bc1c4
--- /dev/null
+++ b/test/sql/constraint.result
@@ -0,0 +1,190 @@
+-- test-run result file version 2
+test_run = require('test_run').new()
+ | ---
+ | ...
+engine = test_run:get_cfg('engine')
+ | ---
+ | ...
+box.execute('pragma sql_default_engine=\''..engine..'\'')
+ | ---
+ | - row_count: 0
+ | ...
+test_run:cmd("setopt delimiter ';'")
+ | ---
+ | - true
+ | ...
+
+--
+-- Check a constraint name for duplicate within a single
+-- <CREATE TABLE> statement.
+--
+box.execute('CREATE TABLE t1 (i INT PRIMARY KEY);');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c UNIQUE (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c UNIQUE (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c CHECK (i < 0));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c CHECK (i > 0));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+
+--
+-- Check a constraint name for duplicate using
+-- <ALTER TABLE> statement.
+--
+box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);');
+ | ---
+ | - null
+ | - Index 'C' already exists in space 'T2'
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);');
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+
+--
+-- Make sure that a constraint's name isn't kept after the
+-- constraint drop.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('DROP INDEX d ON t2;');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);');
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.ck_constraint.D:drop();
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;');
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- The same inside a transaction.
+--
+box.begin()
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+box.space.T2.ck_constraint.D:drop()
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+box.commit();
+ | ---
+ | ...
+
+--
+-- Make sure, that altering of an index name affect to its record
+-- in the space's constraint hash table.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);');
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.D:alter({name = 'E'});
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+ | ---
+ | - null
+ | - Constraint E already exists
+ | ...
+
+--
+-- Make sure, that altering of an index uniqueness puts/drops
+-- its name to/from the space's constraint hash table.
+--
+box.space.T2.index.E:alter({unique = false});
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.E:alter({unique = true});
+ | ---
+ | - error: Constraint E already exists
+ | ...
+box.space.T2.ck_constraint.E:drop();
+ | ---
+ | ...
+box.space.T2.index.E:alter({unique = true});
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+ | ---
+ | - null
+ | - Constraint E already exists
+ | ...
+
+-- Alter name and uniqueness of an unique index simultaneously.
+box.space.T2.index.E:alter({name = 'D', unique = false});
+ | ---
+ | ...
+box.execute('CREATE UNIQUE INDEX e ON t2(i);');
+ | ---
+ | - row_count: 1
+ | ...
diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
new file mode 100755
index 000000000..e80aa4387
--- /dev/null
+++ b/test/sql/constraint.test.lua
@@ -0,0 +1,84 @@
+test_run = require('test_run').new()
+engine = test_run:get_cfg('engine')
+box.execute('pragma sql_default_engine=\''..engine..'\'')
+test_run:cmd("setopt delimiter ';'")
+
+--
+-- Check a constraint name for duplicate within a single
+-- <CREATE TABLE> statement.
+--
+box.execute('CREATE TABLE t1 (i INT PRIMARY KEY);');
+box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+box.execute([[CREATE TABLE t2 (i INT,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c UNIQUE (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c UNIQUE (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c CHECK (i < 0));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c CHECK (i > 0));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
+
+--
+-- Check a constraint name for duplicate using
+-- <ALTER TABLE> statement.
+--
+box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);');
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);');
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);');
+
+--
+-- Make sure that a constraint's name isn't kept after the
+-- constraint drop.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);');
+box.execute('DROP INDEX d ON t2;');
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);');
+box.space.T2.ck_constraint.D:drop();
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);');
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;');
+
+--
+-- The same inside a transaction.
+--
+box.begin()
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+box.space.T2.ck_constraint.D:drop()
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+box.commit();
+
+--
+-- Make sure, that altering of an index name affect to its record
+-- in the space's constraint hash table.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);');
+box.space.T2.index.D:alter({name = 'E'});
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+
+--
+-- Make sure, that altering of an index uniqueness puts/drops
+-- its name to/from the space's constraint hash table.
+--
+box.space.T2.index.E:alter({unique = false});
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+box.space.T2.index.E:alter({unique = true});
+box.space.T2.ck_constraint.E:drop();
+box.space.T2.index.E:alter({unique = true});
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+
+-- Alter name and uniqueness of an unique index simultaneously.
+box.space.T2.index.E:alter({name = 'D', unique = false});
+box.execute('CREATE UNIQUE INDEX e ON t2(i);');
-- 
2.21.0 (Apple Git-122)

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

* Re: [Tarantool-patches] [PATCH v2 2/2] sql: make constraint operations transactional
  2019-11-28 18:34 ` [Tarantool-patches] [PATCH v2 2/2] sql: make constraint operations transactional Roman Khabibov
@ 2019-11-29  7:38   ` Roman Khabibov
  2019-11-30  1:03   ` Vladislav Shpilevoy
  1 sibling, 0 replies; 29+ messages in thread
From: Roman Khabibov @ 2019-11-29  7:38 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

Forgot: I can’t space name in ER_CONSTRAINT_EXISTS (build.c), because I can’t get any info
about space in build.c during ALTER TABLE ADD CONSTRAINT.

> On Nov 28, 2019, at 21:34, Roman Khabibov <roman.habibov@tarantool.org> wrote:
> 
> Put constraint names into the space's hash table and drop them on
> replace in correspondig system spaces (_index, _fk_constraint,
> _ck_constraint).
> 
> Closes #3503
> ---
> src/box/alter.cc             | 427 +++++++++++++++++++++++++++++++----
> test/sql/constraint.result   | 190 ++++++++++++++++
> test/sql/constraint.test.lua |  84 +++++++
> 3 files changed, 654 insertions(+), 47 deletions(-)
> create mode 100644 test/sql/constraint.result
> create mode 100755 test/sql/constraint.test.lua
> 
> diff --git a/src/box/alter.cc b/src/box/alter.cc
> index 4a3241a79..4bb8b8556 100644
> --- a/src/box/alter.cc
> +++ b/src/box/alter.cc
> @@ -57,6 +57,7 @@
> #include "version.h"
> #include "sequence.h"
> #include "sql.h"
> +#include "constraint_def.h"
> 
> /* {{{ Auxiliary functions and methods. */
> 
> @@ -1765,6 +1766,38 @@ MoveCkConstraints::rollback(struct alter_space *alter)
> 	space_swap_ck_constraint(alter->new_space, alter->old_space);
> }
> 
> +/**
> + * Move constraint names hash table from old space to a new one.
> + */
> +class MoveConstraints: public AlterSpaceOp
> +{
> +	inline void space_swap_constraint(struct space *old_space,
> +				   struct space *new_space);
> +public:
> +	MoveConstraints(struct alter_space *alter) : AlterSpaceOp(alter) {}
> +	virtual void alter(struct alter_space *alter);
> +	virtual void rollback(struct alter_space *alter);
> +};
> +
> +inline void
> +MoveConstraints::space_swap_constraint(struct space *old_space,
> +				       struct space *new_space)
> +{
> +	SWAP(new_space->constraint_names, old_space->constraint_names);
> +}
> +
> +void
> +MoveConstraints::alter(struct alter_space *alter)
> +{
> +	space_swap_constraint(alter->old_space, alter->new_space);
> +}
> +
> +void
> +MoveConstraints::rollback(struct alter_space *alter)
> +{
> +	space_swap_constraint(alter->new_space, alter->old_space);
> +}
> +
> /* }}} */
> 
> /**
> @@ -2307,6 +2340,7 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
> 		def_guard.is_active = false;
> 		/* Create MoveIndex ops for all space indexes. */
> 		alter_space_move_indexes(alter, 0, old_space->index_id_max + 1);
> +		(void) new MoveConstraints(alter);
> 		/* Remember to update schema_version. */
> 		(void) new UpdateSchemaVersion(alter);
> 		try {
> @@ -2341,6 +2375,150 @@ index_is_used_by_fk_constraint(struct rlist *fk_list, uint32_t iid)
> 	return false;
> }
> 
> +/**
> + * Bring back the node of an unique index to the constraint hash
> + * table of a space.
> + *
> + * Rollback DROP index.
> + */
> +static int
> +on_drop_index_rollback(struct trigger *trigger, void * /* event */)
> +{
> +	struct constraint_def *def = (struct constraint_def *)trigger->data;
> +	struct space *space = space_by_id(def->space_id);
> +	assert(space != NULL);
> +	if (space_put_constraint(space, def) != 0) {
> +		panic("Can't recover after index drop rollback (out of "
> +		      "memory)");
> +	}
> +	return 0;
> +}
> +
> +/**
> + * Drop the node of an unique index from the constraint hash table
> + * of a space and clean the unnecessary constraint def memory.
> + *
> + * Rollback CREATE index.
> + */
> +static int
> +on_create_index_rollback(struct trigger *trigger, void * /* event */)
> +{
> +	struct constraint_def *def = (struct constraint_def *)trigger->data;
> +	struct space *space = space_by_id(def->space_id);
> +	assert(space != NULL);
> +	space_drop_constraint(space, def->name);
> +	constraint_def_free(def);
> +	return 0;
> +}
> +
> +/**
> + * Clean the unnecessary constraint def memory.
> + *
> + * Commit REPLACE index.
> + */
> +static int
> +on_replace_index_commit(struct trigger *trigger, void * /* event */)
> +{
> +	struct constraint_def **defs = (struct constraint_def **)trigger->data;
> +	constraint_def_free(defs[0]);
> +	return 0;
> +}
> +
> +/**
> + * Bring back the new node of a unique index to the constraint
> + * hash table of a space and drop the old node respectively. Clean
> + * the unnecessary constraint def memory.
> + *
> + * Rollback REPLACE index.
> + */
> +static int
> +on_replace_index_rollback(struct trigger *trigger, void * /* event */)
> +{
> +	struct constraint_def **defs = (struct constraint_def **)trigger->data;
> +	struct constraint_def *old_def = defs[0];
> +	struct constraint_def *new_def = defs[1];
> +	struct space *space = space_by_id(old_def->space_id);
> +	assert(space != NULL);
> +	space_drop_constraint(space, new_def->name);
> +	constraint_def_free(new_def);
> +	if (space_put_constraint(space, old_def) != 0) {
> +		panic("Can't recover after index replace rollback (out of "
> +		      "memory)");
> +	}
> +	return 0;
> +}
> +
> +/**
> + * Put the node of an unique index to the constraint hash table of
> + * @a space and create trigger on rollback.
> + *
> + * This function is needed to wrap the duplicated piece of code
> + * inside on_replace_dd_index().
> + *
> + * @param stmt  Statement.
> + * @param space Space.
> + * @param def   Index def.
> + *
> + * @retval 0  Success.
> + * @retval -1 Constraint already exists or out of memory.
> + */
> +static int
> +create_index_as_constraint(struct txn_stmt *stmt, struct space *space,
> +			   struct index_def *def) {
> +	assert(def->opts.is_unique);
> +	if (space_constraint_def_by_name(space, def->name) != NULL) {
> +		diag_set(ClientError, ER_CONSTRAINT_EXISTS, def->name);
> +		return -1;
> +	}
> +	struct constraint_def *constr_def =
> +		constraint_def_new(space->def->id, def->iid == 0 ?
> +				   CONSTRAINT_TYPE_PK : CONSTRAINT_TYPE_UNIQUE,
> +				   def->name);
> +	if (constr_def == NULL)
> +		return -1;
> +	struct trigger *on_rollback = txn_alter_trigger_new(NULL, NULL);
> +	if (space_put_constraint(space, constr_def) != 0 ||
> +	    on_rollback == NULL) {
> +		constraint_def_free(constr_def);
> +		return -1;
> +	}
> +	on_rollback->data = constr_def;
> +	on_rollback->run = on_create_index_rollback;
> +	txn_stmt_on_rollback(stmt, on_rollback);
> +	return 0;
> +}
> +
> +/**
> + * Drop the node of an unique index from the constraint hash table
> + * of @a space and create trigger on rollback.
> + *
> + * This function is needed to wrap the duplicated piece of code
> + * inside on_replace_dd_index().
> + *
> + * @param stmt  Statement.
> + * @param space Space.
> + * @param def   Index def.
> + *
> + * @retval 0  Success.
> + * @retval -1 Out of memory.
> + */
> +static int
> +drop_index_as_constraint(struct txn_stmt *stmt, struct space *space,
> +			 struct index_def *def) {
> +	assert(def->opts.is_unique);
> +	struct constraint_def *constr_def =
> +		space_constraint_def_by_name(space, def->name);
> +	assert(constr_def != NULL);
> +	space_drop_constraint(space, def->name);
> +	struct trigger *on_rollback = txn_alter_trigger_new(NULL, NULL);
> +	if (on_rollback == NULL)
> +		return -1;
> +	on_rollback->data = constr_def;
> +	on_rollback->run = on_drop_index_rollback;
> +	txn_stmt_on_rollback(stmt, on_rollback);
> +	return 0;
> +}
> +
> /**
>  * Just like with _space, 3 major cases:
>  *
> @@ -2391,21 +2569,21 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
> 	if (tuple_field_u32(old_tuple ? old_tuple : new_tuple,
> 			    BOX_INDEX_FIELD_ID, &iid) != 0)
> 		return -1;
> -	struct space *old_space = space_cache_find(id);
> -	if (old_space == NULL)
> +	struct space *space = space_cache_find(id);
> +	if (space == NULL)
> 		return -1;
> -	if (old_space->def->opts.is_view) {
> -		diag_set(ClientError, ER_ALTER_SPACE, space_name(old_space),
> +	if (space->def->opts.is_view) {
> +		diag_set(ClientError, ER_ALTER_SPACE, space_name(space),
> 			  "can not add index on a view");
> 		return -1;
> 	}
> 	enum priv_type priv_type = new_tuple ? PRIV_C : PRIV_D;
> 	if (old_tuple && new_tuple)
> 		priv_type = PRIV_A;
> -	if (access_check_ddl(old_space->def->name, old_space->def->id,
> -			 old_space->def->uid, SC_SPACE, priv_type) != 0)
> +	if (access_check_ddl(space->def->name, space->def->id, space->def->uid,
> +			     SC_SPACE, priv_type) != 0)
> 		return -1;
> -	struct index *old_index = space_index(old_space, iid);
> +	struct index *old_index = space_index(space, iid);
> 
> 	/*
> 	 * Deal with various cases of dropping of the primary key.
> @@ -2414,43 +2592,41 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
> 		/*
> 		 * Dropping the primary key in a system space: off limits.
> 		 */
> -		if (space_is_system(old_space)) {
> +		if (space_is_system(space)) {
> 			diag_set(ClientError, ER_LAST_DROP,
> -				  space_name(old_space));
> +				 space_name(space));
> 			return -1;
> 		}
> 		/*
> 		 * Can't drop primary key before secondary keys.
> 		 */
> -		if (old_space->index_count > 1) {
> +		if (space->index_count > 1) {
> 			diag_set(ClientError, ER_DROP_PRIMARY_KEY,
> -				  space_name(old_space));
> +				 space_name(space));
> 			return -1;
> 		}
> 		/*
> 		 * Can't drop primary key before space sequence.
> 		 */
> -		if (old_space->sequence != NULL) {
> -			diag_set(ClientError, ER_ALTER_SPACE,
> -				  space_name(old_space),
> +		if (space->sequence != NULL) {
> +			diag_set(ClientError, ER_ALTER_SPACE, space_name(space),
> 				  "can not drop primary key while "
> 				  "space sequence exists");
> 			return -1;
> 		}
> 	}
> 
> -	if (iid != 0 && space_index(old_space, 0) == NULL) {
> +	if (iid != 0 && space_index(space, 0) == NULL) {
> 		/*
> 		 * A secondary index can not be created without
> 		 * a primary key.
> 		 */
> -		diag_set(ClientError, ER_ALTER_SPACE,
> -			  space_name(old_space),
> +		diag_set(ClientError, ER_ALTER_SPACE, space_name(space),
> 			  "can not add a secondary key before primary");
> 		return -1;
> 	}
> 
> -	struct alter_space *alter = alter_space_new(old_space);
> +	struct alter_space *alter = alter_space_new(space);
> 	if (alter == NULL)
> 		return -1;
> 	auto scoped_guard =
> @@ -2469,35 +2645,111 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
> 		 * Can't drop index if foreign key constraints
> 		 * references this index.
> 		 */
> -		if (index_is_used_by_fk_constraint(&old_space->parent_fk_constraint,
> +		if (index_is_used_by_fk_constraint(&space->parent_fk_constraint,
> 						   iid)) {
> -			diag_set(ClientError, ER_ALTER_SPACE,
> -				  space_name(old_space),
> +			diag_set(ClientError, ER_ALTER_SPACE, space_name(space),
> 				  "can not drop a referenced index");
> 			return -1;
> 		}
> 		alter_space_move_indexes(alter, 0, iid);
> 		(void) new DropIndex(alter, old_index);
> +		if (old_index->def->opts.is_unique &&
> +		    drop_index_as_constraint(stmt, space, old_index->def) != 0)
> +			return -1;
> 	}
> 	/* Case 2: create an index, if it is simply created. */
> 	if (old_index == NULL && new_tuple != NULL) {
> +		struct index_def *def =
> +			index_def_new_from_tuple(new_tuple, space);
> +		if (def == NULL)
> +			return -1;
> +		if (def->opts.is_unique) {
> +			if (create_index_as_constraint(stmt, space, def) != 0) {
> +				index_def_delete(def);
> +				return -1;
> +			}
> +		}
> 		alter_space_move_indexes(alter, 0, iid);
> 		CreateIndex *create_index = new CreateIndex(alter);
> -		create_index->new_index_def =
> -			index_def_new_from_tuple(new_tuple, old_space);
> -		if (create_index->new_index_def == NULL)
> -			return -1;
> -		index_def_update_optionality(create_index->new_index_def,
> -					     alter->new_min_field_count);
> +		create_index->new_index_def = def;
> +		index_def_update_optionality(def, alter->new_min_field_count);
> 	}
> 	/* Case 3 and 4: check if we need to rebuild index data. */
> 	if (old_index != NULL && new_tuple != NULL) {
> 		struct index_def *index_def;
> -		index_def = index_def_new_from_tuple(new_tuple, old_space);
> +		index_def = index_def_new_from_tuple(new_tuple, space);
> 		if (index_def == NULL)
> 			return -1;
> 		auto index_def_guard =
> 			make_scoped_guard([=] { index_def_delete(index_def); });
> +		struct index_def *old_def = old_index->def;
> +		/**
> +		 * We put a new name either an index is becoming
> +		 * unique (i.e. constraint), or when an unique
> +		 * index's name is under change.
> +		 */
> +		if (!old_def->opts.is_unique && index_def->opts.is_unique) {
> +			if (create_index_as_constraint(stmt, space, index_def)
> +			    != 0)
> +				return -1;
> +		}
> +		if (old_def->opts.is_unique && index_def->opts.is_unique &&
> +		    strcmp(index_def->name, old_def->name) != 0) {
> +			if (space_constraint_def_by_name(space, index_def->name)
> +			    != NULL) {
> +				diag_set(ClientError, ER_CONSTRAINT_EXISTS,
> +					 index_def->name);
> +				return -1;
> +			}
> +			struct constraint_def *old_constr_def =
> +				space_constraint_def_by_name(space,
> +							     old_def->name);
> +			assert(old_constr_def != NULL);
> +			struct constraint_def *new_constr_def =
> +				constraint_def_new(old_constr_def->space_id,
> +						   old_constr_def->type,
> +						   index_def->name);
> +			if (new_constr_def == NULL)
> +				return -1;
> +			auto new_constraint_def_guard =
> +				make_scoped_guard([=] { constraint_def_free(new_constr_def); });
> +			if (space_put_constraint(space, new_constr_def) != 0)
> +				return -1;
> +			space_drop_constraint(space, old_def->name);
> +			/**
> +			 * Array with the pair of 2
> +			 * index_def structures: old and
> +			 * new.
> +			 */
> +			uint32_t size = sizeof(struct constraint_def *) * 2;
> +			struct region *r = &fiber()->gc;
> +			struct constraint_def **defs =
> +				(struct constraint_def **)region_alloc(r, size);
> +			if (defs == NULL) {
> +				diag_set(OutOfMemory, size, "region",
> +					 "new slab");
> +				return -1;
> +			}
> +			defs[0] = old_constr_def;
> +			defs[1] = new_constr_def;
> +			struct trigger *on_commit =
> +				txn_alter_trigger_new(NULL, NULL);
> +			struct trigger *on_rollback =
> +				txn_alter_trigger_new(NULL, NULL);
> +			if (on_commit == NULL || on_rollback == NULL)
> +				return -1;
> +			on_commit->data = defs;
> +			on_commit->run = on_replace_index_commit;
> +			on_rollback->data = defs;
> +			on_rollback->run = on_replace_index_rollback;
> +			txn_stmt_on_commit(stmt, on_commit);
> +			txn_stmt_on_rollback(stmt, on_rollback);
> +			new_constraint_def_guard.is_active = false;
> +		}
> +		if (old_def->opts.is_unique && !index_def->opts.is_unique) {
> +			if (drop_index_as_constraint(stmt, space, old_def) != 0)
> +				return -1;
> +		}
> 		/*
> 		 * To detect which key parts are optional,
> 		 * min_field_count is required. But
> @@ -2508,30 +2760,29 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
> 		 * index on a non-nullable second field. Min field
> 		 * count here is 2. Now alter the secondary index
> 		 * to make its part be nullable. In the
> -		 * 'old_space' min_field_count is still 2, but
> +		 * 'space' min_field_count is still 2, but
> 		 * actually it is already 1. Actual
> 		 * min_field_count must be calculated using old
> 		 * unchanged indexes, NEW definition of an updated
> 		 * index and a space format, defined by a user.
> 		 */
> 		struct key_def **keys;
> -		size_t bsize = old_space->index_count * sizeof(keys[0]);
> +		size_t bsize = space->index_count * sizeof(keys[0]);
> 		keys = (struct key_def **) region_alloc(&fiber()->gc, bsize);
> 		if (keys == NULL) {
> 			diag_set(OutOfMemory, bsize, "region", "new slab");
> 			return -1;
> 		}
> -		for (uint32_t i = 0, j = 0; i < old_space->index_count; ++i) {
> -			struct index_def *d = old_space->index[i]->def;
> +		for (uint32_t i = 0, j = 0; i < space->index_count; ++i) {
> +			struct index_def *d = space->index[i]->def;
> 			if (d->iid != index_def->iid)
> 				keys[j++] = d->key_def;
> 			else
> 				keys[j++] = index_def->key_def;
> 		}
> -		struct space_def *def = old_space->def;
> +		struct space_def *def = space->def;
> 		alter->new_min_field_count =
> -			tuple_format_min_field_count(keys,
> -						     old_space->index_count,
> +			tuple_format_min_field_count(keys, space->index_count,
> 						     def->fields,
> 						     def->field_count);
> 		index_def_update_optionality(index_def,
> @@ -2542,10 +2793,10 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
> 			(void) new MoveIndex(alter, old_index->def->iid);
> 		} else if (index_def_change_requires_rebuild(old_index,
> 							     index_def)) {
> -			if (index_is_used_by_fk_constraint(&old_space->parent_fk_constraint,
> +			if (index_is_used_by_fk_constraint(&space->parent_fk_constraint,
> 							   iid)) {
> 				diag_set(ClientError, ER_ALTER_SPACE,
> -					  space_name(old_space),
> +					  space_name(space),
> 					  "can not alter a referenced index");
> 				return -1;
> 			}
> @@ -2570,8 +2821,9 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
> 	 * Create MoveIndex ops for the remaining indexes in the
> 	 * old space.
> 	 */
> -	alter_space_move_indexes(alter, iid + 1, old_space->index_id_max + 1);
> +	alter_space_move_indexes(alter, iid + 1, space->index_id_max + 1);
> 	(void) new MoveCkConstraints(alter);
> +	(void) new MoveConstraints(alter);
> 	/* Add an op to update schema_version on commit. */
> 	(void) new UpdateSchemaVersion(alter);
> 	try {
> @@ -2663,6 +2915,7 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event)
> 	}
> 
> 	(void) new MoveCkConstraints(alter);
> +	(void) new MoveConstraints(alter);
> 	try {
> 		alter_space_do(stmt, alter);
> 	} catch (Exception *e) {
> @@ -4951,8 +5204,15 @@ on_create_fk_constraint_rollback(struct trigger *trigger, void *event)
> 	struct fk_constraint *fk = (struct fk_constraint *)trigger->data;
> 	rlist_del_entry(fk, in_parent_space);
> 	rlist_del_entry(fk, in_child_space);
> +	struct space *child = space_by_id(fk->def->child_id);
> +	const char *name = fk->def->name;
> +	struct constraint_def *constr_def =
> +		space_constraint_def_by_name(child, name);
> +	assert(constr_def != NULL);
> +	space_drop_constraint(child, name);
> +	constraint_def_free(constr_def);
> 	space_reset_fk_constraint_mask(space_by_id(fk->def->parent_id));
> -	space_reset_fk_constraint_mask(space_by_id(fk->def->child_id));
> +	space_reset_fk_constraint_mask(child);
> 	fk_constraint_delete(fk);
> 	return 0;
> }
> @@ -4986,11 +5246,24 @@ on_drop_fk_constraint_rollback(struct trigger *trigger, void *event)
> 	struct space *child = space_by_id(old_fk->def->child_id);
> 	rlist_add_entry(&child->child_fk_constraint, old_fk, in_child_space);
> 	rlist_add_entry(&parent->parent_fk_constraint, old_fk, in_parent_space);
> +	const char *name = old_fk->def->name;
> +	struct constraint_def *constr_def =
> +		constraint_def_new(child->def->id, CONSTRAINT_TYPE_FK, name);
> +	if (constr_def == NULL)
> +		goto panic;
> +	if (space_put_constraint(child, constr_def) != 0) {
> +		constraint_def_free(constr_def);
> +		goto panic;
> +	}
> 	fk_constraint_set_mask(old_fk, &child->fk_constraint_mask,
> 			       FIELD_LINK_CHILD);
> 	fk_constraint_set_mask(old_fk, &parent->fk_constraint_mask,
> 			       FIELD_LINK_PARENT);
> 	return 0;
> +
> +panic:
> +	panic("Can't recover after FK constraint drop rollback (out of "
> +	      "memory)");
> }
> 
> /**
> @@ -5165,15 +5438,31 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
> 		fk->def = fk_def;
> 		fk->index_id = fk_index->def->iid;
> 		if (old_tuple == NULL) {
> +			if (space_constraint_def_by_name(child_space,
> +							 fk_def->name)
> +			    != NULL) {
> +				diag_set(ClientError, ER_CONSTRAINT_EXISTS,
> +					 fk_def->name);
> +				return -1;
> +			}
> 			rlist_add_entry(&child_space->child_fk_constraint,
> 					fk, in_child_space);
> 			rlist_add_entry(&parent_space->parent_fk_constraint,
> 					fk, in_parent_space);
> +			struct constraint_def *constr_def =
> +				constraint_def_new(child_space->def->id,
> +						   CONSTRAINT_TYPE_FK,
> +						   fk_def->name);
> +			if (constr_def == NULL)
> +				return -1;
> 			struct trigger *on_rollback =
> 				txn_alter_trigger_new(on_create_fk_constraint_rollback,
> 						      fk);
> -			if (on_rollback == NULL)
> +			if (space_put_constraint(child_space, constr_def) != 0
> +			    || on_rollback == NULL) {
> +				constraint_def_free(constr_def);
> 				return -1;
> +			}
> 			txn_stmt_on_rollback(stmt, on_rollback);
> 			fk_constraint_set_mask(fk,
> 					       &parent_space->fk_constraint_mask,
> @@ -5221,6 +5510,12 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
> 		struct fk_constraint *old_fk=
> 			fk_constraint_remove(&child_space->child_fk_constraint,
> 					     fk_def->name);
> +		const char *name = fk_def->name;
> +		struct constraint_def *constr_def =
> +			space_constraint_def_by_name(child_space, name);
> +		assert(constr_def != NULL);
> +		space_drop_constraint(child_space, name);
> +		constraint_def_free(constr_def);
> 		struct trigger *on_commit =
> 			txn_alter_trigger_new(on_drop_or_replace_fk_constraint_commit,
> 					      old_fk);
> @@ -5297,9 +5592,13 @@ on_create_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
> 	assert(ck != NULL);
> 	struct space *space = space_by_id(ck->def->space_id);
> 	assert(space != NULL);
> -	assert(space_ck_constraint_by_name(space, ck->def->name,
> -					   strlen(ck->def->name)) != NULL);
> +	const char *name = ck->def->name;
> +	assert(space_ck_constraint_by_name(space, name, strlen(name)) != NULL);
> 	space_remove_ck_constraint(space, ck);
> +	struct constraint_def *constr_def =
> +		space_constraint_def_by_name(space, name);
> +	space_drop_constraint(space, name);
> +	constraint_def_free(constr_def);
> 	ck_constraint_delete(ck);
> 	if (trigger_run(&on_alter_space, space) != 0)
> 		return -1;
> @@ -5324,13 +5623,23 @@ on_drop_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
> 	assert(ck != NULL);
> 	struct space *space = space_by_id(ck->def->space_id);
> 	assert(space != NULL);
> -	assert(space_ck_constraint_by_name(space, ck->def->name,
> -					   strlen(ck->def->name)) == NULL);
> -	if (space_add_ck_constraint(space, ck) != 0)
> -		panic("Can't recover after CK constraint drop rollback");
> +	const char *name = ck->def->name;
> +	assert(space_ck_constraint_by_name(space, name, strlen(name)) == NULL);
> +	struct constraint_def *constr_def =
> +		constraint_def_new(space->def->id, CONSTRAINT_TYPE_CK, name);
> +	if (constr_def == NULL)
> +		goto panic;
> +	if (space_add_ck_constraint(space, ck) != 0 ||
> +	    space_put_constraint(space, constr_def) != 0) {
> +		constraint_def_free(constr_def);
> +		goto panic;
> +	}
> 	if (trigger_run(&on_alter_space, space) != 0)
> 		return -1;
> 	return 0;
> +
> +panic:
> +	panic("Can't recover after CK constraint drop rollback");
> }
> 
> /** Commit REPLACE check constraint. */
> @@ -5443,11 +5752,28 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
> 		auto ck_guard = make_scoped_guard([=] {
> 			ck_constraint_delete(new_ck_constraint);
> 		});
> -		if (old_ck_constraint != NULL)
> +
> +		if (old_ck_constraint != NULL) {
> 			rlist_del_entry(old_ck_constraint, link);
> +		} else if (space_constraint_def_by_name(space, name) != NULL) {
> +			diag_set(ClientError, ER_CONSTRAINT_EXISTS,
> +				 name);
> +			return -1;
> +		}
> 		if (space_add_ck_constraint(space, new_ck_constraint) != 0)
> 			return -1;
> 		ck_guard.is_active = false;
> +		if (old_ck_constraint == NULL) {
> +			struct constraint_def *constr_def =
> +				constraint_def_new(space->def->id,
> +						   CONSTRAINT_TYPE_CK, name);
> +			if (constr_def == NULL)
> +				return -1;
> +			if (space_put_constraint(space, constr_def) != 0) {
> +				constraint_def_free(constr_def);
> +				return -1;
> +			}
> +		}
> 		if (old_tuple != NULL) {
> 			on_rollback->data = old_ck_constraint;
> 			on_rollback->run = on_replace_ck_constraint_rollback;
> @@ -5467,7 +5793,13 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
> 			return -1;
> 		struct ck_constraint *old_ck_constraint =
> 			space_ck_constraint_by_name(space, name, name_len);
> +		struct constraint_def *constr_def =
> +			space_constraint_def_by_name(space,
> +						     old_ck_constraint->def->name);
> +		assert(constr_def != NULL);
> 		assert(old_ck_constraint != NULL);
> +		space_drop_constraint(space, old_ck_constraint->def->name);
> +		constraint_def_free(constr_def);
> 		space_remove_ck_constraint(space, old_ck_constraint);
> 		on_commit->data = old_ck_constraint;
> 		on_commit->run = on_drop_ck_constraint_commit;
> @@ -5567,6 +5899,7 @@ on_replace_dd_func_index(struct trigger *trigger, void *event)
> 	alter_space_move_indexes(alter, index->def->iid + 1,
> 				 space->index_id_max + 1);
> 	(void) new MoveCkConstraints(alter);
> +	(void) new MoveConstraints(alter);
> 	(void) new UpdateSchemaVersion(alter);
> 	try {
> 		alter_space_do(stmt, alter);
> diff --git a/test/sql/constraint.result b/test/sql/constraint.result
> new file mode 100644
> index 000000000..d8a4bc1c4
> --- /dev/null
> +++ b/test/sql/constraint.result
> @@ -0,0 +1,190 @@
> +-- test-run result file version 2
> +test_run = require('test_run').new()
> + | ---
> + | ...
> +engine = test_run:get_cfg('engine')
> + | ---
> + | ...
> +box.execute('pragma sql_default_engine=\''..engine..'\'')
> + | ---
> + | - row_count: 0
> + | ...
> +test_run:cmd("setopt delimiter ';'")
> + | ---
> + | - true
> + | ...
> +
> +--
> +-- Check a constraint name for duplicate within a single
> +-- <CREATE TABLE> statement.
> +--
> +box.execute('CREATE TABLE t1 (i INT PRIMARY KEY);');
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
> +                               CONSTRAINT c PRIMARY KEY (i));]]);
> + | ---
> + | - null
> + | - Constraint C already exists
> + | ...
> +box.execute([[CREATE TABLE t2 (i INT,
> +                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
> +                               CONSTRAINT c PRIMARY KEY (i));]]);
> + | ---
> + | - null
> + | - Constraint C already exists
> + | ...
> +box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
> +                               CONSTRAINT c CHECK (i > 0),
> +                               CONSTRAINT c UNIQUE (i));]]);
> + | ---
> + | - null
> + | - Constraint C already exists
> + | ...
> +box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
> +                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
> +                               CONSTRAINT c UNIQUE (i));]]);
> + | ---
> + | - null
> + | - Constraint C already exists
> + | ...
> +box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
> +                               CONSTRAINT c CHECK (i > 0),
> +                               CONSTRAINT c CHECK (i < 0));]]);
> + | ---
> + | - null
> + | - Constraint C already exists
> + | ...
> +box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
> +                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
> +                               CONSTRAINT c CHECK (i > 0));]]);
> + | ---
> + | - null
> + | - Constraint C already exists
> + | ...
> +box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
> +                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
> + | ---
> + | - null
> + | - Constraint C already exists
> + | ...
> +
> +--
> +-- Check a constraint name for duplicate using
> +-- <ALTER TABLE> statement.
> +--
> +box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);');
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
> + | ---
> + | - null
> + | - Constraint C already exists
> + | ...
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);');
> + | ---
> + | - null
> + | - Index 'C' already exists in space 'T2'
> + | ...
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);');
> + | ---
> + | - null
> + | - Constraint C already exists
> + | ...
> +
> +--
> +-- Make sure that a constraint's name isn't kept after the
> +-- constraint drop.
> +--
> +box.execute('CREATE UNIQUE INDEX d ON t2(i);');
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute('DROP INDEX d ON t2;');
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);');
> + | ---
> + | - row_count: 1
> + | ...
> +box.space.T2.ck_constraint.D:drop();
> + | ---
> + | ...
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);');
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute('ALTER TABLE t2 DROP CONSTRAINT d;');
> + | ---
> + | - row_count: 1
> + | ...
> +
> +--
> +-- The same inside a transaction.
> +--
> +box.begin()
> +box.execute('CREATE UNIQUE INDEX d ON t2(i);')
> +box.execute('DROP INDEX d ON t2;')
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
> +box.space.T2.ck_constraint.D:drop()
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
> +box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
> +box.commit();
> + | ---
> + | ...
> +
> +--
> +-- Make sure, that altering of an index name affect to its record
> +-- in the space's constraint hash table.
> +--
> +box.execute('CREATE UNIQUE INDEX d ON t2(i);');
> + | ---
> + | - row_count: 1
> + | ...
> +box.space.T2.index.D:alter({name = 'E'});
> + | ---
> + | ...
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
> + | ---
> + | - null
> + | - Constraint E already exists
> + | ...
> +
> +--
> +-- Make sure, that altering of an index uniqueness puts/drops
> +-- its name to/from the space's constraint hash table.
> +--
> +box.space.T2.index.E:alter({unique = false});
> + | ---
> + | ...
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
> + | ---
> + | - row_count: 1
> + | ...
> +box.space.T2.index.E:alter({unique = true});
> + | ---
> + | - error: Constraint E already exists
> + | ...
> +box.space.T2.ck_constraint.E:drop();
> + | ---
> + | ...
> +box.space.T2.index.E:alter({unique = true});
> + | ---
> + | ...
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
> + | ---
> + | - null
> + | - Constraint E already exists
> + | ...
> +
> +-- Alter name and uniqueness of an unique index simultaneously.
> +box.space.T2.index.E:alter({name = 'D', unique = false});
> + | ---
> + | ...
> +box.execute('CREATE UNIQUE INDEX e ON t2(i);');
> + | ---
> + | - row_count: 1
> + | ...
> diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
> new file mode 100755
> index 000000000..e80aa4387
> --- /dev/null
> +++ b/test/sql/constraint.test.lua
> @@ -0,0 +1,84 @@
> +test_run = require('test_run').new()
> +engine = test_run:get_cfg('engine')
> +box.execute('pragma sql_default_engine=\''..engine..'\'')
> +test_run:cmd("setopt delimiter ';'")
> +
> +--
> +-- Check a constraint name for duplicate within a single
> +-- <CREATE TABLE> statement.
> +--
> +box.execute('CREATE TABLE t1 (i INT PRIMARY KEY);');
> +box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
> +                               CONSTRAINT c PRIMARY KEY (i));]]);
> +box.execute([[CREATE TABLE t2 (i INT,
> +                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
> +                               CONSTRAINT c PRIMARY KEY (i));]]);
> +box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
> +                               CONSTRAINT c CHECK (i > 0),
> +                               CONSTRAINT c UNIQUE (i));]]);
> +box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
> +                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
> +                               CONSTRAINT c UNIQUE (i));]]);
> +box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
> +                               CONSTRAINT c CHECK (i > 0),
> +                               CONSTRAINT c CHECK (i < 0));]]);
> +box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
> +                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
> +                               CONSTRAINT c CHECK (i > 0));]]);
> +box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
> +                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
> +
> +--
> +-- Check a constraint name for duplicate using
> +-- <ALTER TABLE> statement.
> +--
> +box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);');
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);');
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);');
> +
> +--
> +-- Make sure that a constraint's name isn't kept after the
> +-- constraint drop.
> +--
> +box.execute('CREATE UNIQUE INDEX d ON t2(i);');
> +box.execute('DROP INDEX d ON t2;');
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);');
> +box.space.T2.ck_constraint.D:drop();
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);');
> +box.execute('ALTER TABLE t2 DROP CONSTRAINT d;');
> +
> +--
> +-- The same inside a transaction.
> +--
> +box.begin()
> +box.execute('CREATE UNIQUE INDEX d ON t2(i);')
> +box.execute('DROP INDEX d ON t2;')
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
> +box.space.T2.ck_constraint.D:drop()
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
> +box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
> +box.commit();
> +
> +--
> +-- Make sure, that altering of an index name affect to its record
> +-- in the space's constraint hash table.
> +--
> +box.execute('CREATE UNIQUE INDEX d ON t2(i);');
> +box.space.T2.index.D:alter({name = 'E'});
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
> +
> +--
> +-- Make sure, that altering of an index uniqueness puts/drops
> +-- its name to/from the space's constraint hash table.
> +--
> +box.space.T2.index.E:alter({unique = false});
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
> +box.space.T2.index.E:alter({unique = true});
> +box.space.T2.ck_constraint.E:drop();
> +box.space.T2.index.E:alter({unique = true});
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
> +
> +-- Alter name and uniqueness of an unique index simultaneously.
> +box.space.T2.index.E:alter({name = 'D', unique = false});
> +box.execute('CREATE UNIQUE INDEX e ON t2(i);');
> -- 
> 2.21.0 (Apple Git-122)
> 

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

* Re: [Tarantool-patches] [PATCH v2 0/2] Add constraint names hash table to space
  2019-11-28 18:34 [Tarantool-patches] [PATCH v2 0/2] Add constraint names hash table to space Roman Khabibov
  2019-11-28 18:34 ` [Tarantool-patches] [PATCH v2 1/2] box: introduce constraint names hash table Roman Khabibov
  2019-11-28 18:34 ` [Tarantool-patches] [PATCH v2 2/2] sql: make constraint operations transactional Roman Khabibov
@ 2019-11-30  1:03 ` Vladislav Shpilevoy
  2019-12-04 16:23   ` [Tarantool-patches] [PATCH v2 0/3] " Roman Khabibov
  2 siblings, 1 reply; 29+ messages in thread
From: Vladislav Shpilevoy @ 2019-11-30  1:03 UTC (permalink / raw)
  To: Roman Khabibov, tarantool-patches

Hi! Thanks for the patch!

See 2 comments below!

On 28/11/2019 19:34, Roman Khabibov wrote:
> I have made essential changes to the previous patch, so I decided
> to send it as v2.
> 
> 1) I still don't understand, why we need to store constraint id. If we have the
> query "ALTER TABLE T DROP CONSTRAINT C", we just get struct space by its name,
> then find the corresponding constraint_def node by the name and emit opcode on
> replace in _index/_fk_constraint/_ck_constraint depending on
> constraint_def->type.

1. ID is a primary index. You can delete by ID. Is it always
possible to delete by name? Do all the constraint spaces have a
unique index over name consisting of one column?

> 
> 2) space_delete()
> I don't know how to check, if the hash table is empty, because it jsut freed.

2. Mhash provides mh_size() method. Add an assert, that it
is it == 0.

> 
> salad/mhash.h
> void
> _mh(delete)(struct _mh(t) *h)
> {
> 	if (h->shadow->p) {
> 		free(h->shadow->p);
> 		free(h->shadow->b);
> 		memset(h->shadow, 0, sizeof(*h->shadow));
> 	}
> 	free(h->shadow);
> 	free(h->b);
> 	free(h->p);
> 	free(h);
> }
> Roman Khabibov (2):
>   box: introduce constraint names hash table
>   sql: make constraint operations transactional
> 
>  src/box/CMakeLists.txt       |   1 +
>  src/box/alter.cc             | 427 +++++++++++++++++++++++++++++++----
>  src/box/constraint_def.c     |  59 +++++
>  src/box/constraint_def.h     |  83 +++++++
>  src/box/space.c              |  56 +++++
>  src/box/space.h              |  39 ++++
>  test/sql/constraint.result   | 190 ++++++++++++++++
>  test/sql/constraint.test.lua |  84 +++++++
>  8 files changed, 892 insertions(+), 47 deletions(-)
>  create mode 100755 src/box/constraint_def.c
>  create mode 100755 src/box/constraint_def.h
>  create mode 100644 test/sql/constraint.result
>  create mode 100755 test/sql/constraint.test.lua
> 

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

* Re: [Tarantool-patches] [PATCH v2 2/2] sql: make constraint operations transactional
  2019-11-28 18:34 ` [Tarantool-patches] [PATCH v2 2/2] sql: make constraint operations transactional Roman Khabibov
  2019-11-29  7:38   ` Roman Khabibov
@ 2019-11-30  1:03   ` Vladislav Shpilevoy
  2019-12-04 16:23     ` [Tarantool-patches] [PATCH v2 2/3] " Roman Khabibov
  2019-12-05 18:43     ` Roman Khabibov
  1 sibling, 2 replies; 29+ messages in thread
From: Vladislav Shpilevoy @ 2019-11-30  1:03 UTC (permalink / raw)
  To: Roman Khabibov, tarantool-patches

Thanks for the patch!

The commit below (or at least the commit message) was outdated.
I see a different version on the branch. Below I copypaste the
branch's version, and review it.

See 14 comments below.
    
>     Put constraint names into the space's hash table and drop them on
>     replace in correspondig system spaces (_index, _fk_constraint,

1. correspondig -> corresponding

>     _ck_constraint).
>     
>     Closes #3503
>     
>     @TarantoolBot document
>     Title: Constraints
>     Now, names of constraints must be unique within one space.

2. Now, imagine that you are a technical writer. You don't
run and test Tarantool every day. And you don't know what,
how, when, and where do we support. What are the problems,
features, plans. Your task is simple - you take a ticket
from tarantool/doc, and transform it into something that is
ok to be placed on the site. Into a good English text, with
rich details, with omission of not needed information. You
may fix some of the old documentation affected by that ticket.
You may add pictures, diagrams.

And then imagine you got such a ticket as you provided here.
Sorry, but this is just nothing. That is like if I would ask
you to 'fix an assertion in box'. What assertion? Where in
the box? What is a reproducer?

You need to explain, what do you mean as constraints; what
if uniqueness is violated; does letter case matter; what are
examples; does language matter (Lua, SQL, binary) etc.

> 
> diff --git a/src/box/alter.cc b/src/box/alter.cc
> index 4a3241a79..4bb8b8556 100644
> --- a/src/box/alter.cc
> +++ b/src/box/alter.cc
> @@ -1765,6 +1766,38 @@ MoveCkConstraints::rollback(struct alter_space *alter)
>  	space_swap_ck_constraint(alter->new_space, alter->old_space);
>  }
>  
> +/**
> + * Move constraint names hash table from old space to a new one.
> + */
> +class MoveConstraints: public AlterSpaceOp
> +{
> +	inline void space_swap_constraint(struct space *old_space,
> +				   struct space *new_space);
> +public:
> +	MoveConstraints(struct alter_space *alter) : AlterSpaceOp(alter) {}
> +	virtual void alter(struct alter_space *alter);
> +	virtual void rollback(struct alter_space *alter);
> +};
> +
> +inline void
> +MoveConstraints::space_swap_constraint(struct space *old_space,
> +				       struct space *new_space)
> +{
> +	SWAP(new_space->constraint_names, old_space->constraint_names);

3. So this is not inlined as a I asked. Perhaps that was
misunderstanding. I meant literally inline it. Not add
'inline' keyword. That is a one-liner function, which calls
another one liner. Even with the same number of arguments.
What is a point of having it? It will never even be extended,
and does not make the code more compact.

> +}
> +
> +void
> +MoveConstraints::alter(struct alter_space *alter)
> +{
> +	space_swap_constraint(alter->old_space, alter->new_space);
> +}
> +
> +void
> +MoveConstraints::rollback(struct alter_space *alter)
> +{
> +	space_swap_constraint(alter->new_space, alter->old_space);
> +}
> @@ -2341,6 +2375,150 @@ index_is_used_by_fk_constraint(struct rlist *fk_list, uint32_t iid)
> +
> +/**
> + * Put the node of an unique index to the constraint hash table of
> + * @a space and create trigger on rollback.
> + *
> + * This function is needed to wrap the duplicated piece of code
> + * inside on_replace_dd_index().
> + *
> + * @param stmt  Statement.
> + * @param space Space.
> + * @param def   Index def.
> + *
> + * @retval 0  Success.
> + * @retval -1 Constraint already exists or out of memory.

4. Once again - please, don't write comments just for
comments. 'stmt Statement.' really does not make anything
clearer or easier to understand. Either drop these
useless formal @param @retval, or provide more *useful*
details about these arguments. Although I doubt, that
it is needed. The function is trivial and is not public
anyway. The same about other similar places.

The comment's part before first @param is good, keep it.

> + */
> +static int
> +create_index_as_constraint(struct txn_stmt *stmt, struct space *space,
> +			   struct index_def *def) {

5. I propose you to make it `const struct index_def *`.

> +	assert(def->opts.is_unique);
> +	if (space_constraint_def_by_name(space, def->name) != NULL) {
> +		diag_set(ClientError, ER_CONSTRAINT_EXISTS, def->name);

6. AFAIR, I asked to add constraint type into the error message, didn't I?
Why didn't you add it?

> +		return -1;
> +	}
> +	struct constraint_def *constr_def =
> +		constraint_def_new(space->def->id, def->iid == 0 ?
> +				   CONSTRAINT_TYPE_PK : CONSTRAINT_TYPE_UNIQUE,
> +				   def->name);
> +	if (constr_def == NULL)
> +		return -1;
> +	struct trigger *on_rollback = txn_alter_trigger_new(NULL, NULL);
> +	if (space_put_constraint(space, constr_def) != 0 ||
> +	    on_rollback == NULL) {
> +		constraint_def_free(constr_def);
> +		return -1;
> +	}
> +	on_rollback->data = constr_def;
> +	on_rollback->run = on_create_index_rollback;
> +	txn_stmt_on_rollback(stmt, on_rollback);
> +	return 0;
> +}
> +
> +/**
> + * Drop the node of an unique index from the constraint hash table
> + * of @a space and create trigger on rollback.
> + *
> + * This function is needed to wrap the duplicated piece of code
> + * inside on_replace_dd_index().
> + *
> + * @param stmt  Statement.
> + * @param space Space.
> + * @param def   Index def.
> + *
> + * @retval 0  Success.
> + * @retval -1 Out of memory.
> + */
> +static int
> +drop_index_as_constraint(struct txn_stmt *stmt, struct space *space,
> +			 struct index_def *def) {
> +	assert(def->opts.is_unique);
> +	struct constraint_def *constr_def =
> +		space_constraint_def_by_name(space, def->name);
> +	assert(constr_def != NULL);
> +	space_drop_constraint(space, def->name);
> +	struct trigger *on_rollback = txn_alter_trigger_new(NULL, NULL);
> +	if (on_rollback == NULL)
> +		return -1;

7. In case on_rollback creation is failed, you don't restore
the constraint back.

> +	on_rollback->data = constr_def;
> +	on_rollback->run = on_drop_index_rollback;
> +	txn_stmt_on_rollback(stmt, on_rollback);
> +	return 0;
> +}
> +
>  /**
>   * Just like with _space, 3 major cases:
>   *> @@ -2469,35 +2645,111 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
>  		 * Can't drop index if foreign key constraints
>  		 * references this index.
>  		 */
> -		if (index_is_used_by_fk_constraint(&old_space->parent_fk_constraint,
> +		if (index_is_used_by_fk_constraint(&space->parent_fk_constraint,
>  						   iid)) {
> -			diag_set(ClientError, ER_ALTER_SPACE,
> -				  space_name(old_space),
> +			diag_set(ClientError, ER_ALTER_SPACE, space_name(space),
>  				  "can not drop a referenced index");
>  			return -1;
>  		}
>  		alter_space_move_indexes(alter, 0, iid);
>  		(void) new DropIndex(alter, old_index);
> +		if (old_index->def->opts.is_unique &&
> +		    drop_index_as_constraint(stmt, space, old_index->def) != 0)
> +			return -1;
>  	}
>  	/* Case 2: create an index, if it is simply created. */
>  	if (old_index == NULL && new_tuple != NULL) {
> +		struct index_def *def =
> +			index_def_new_from_tuple(new_tuple, space);
> +		if (def == NULL)
> +			return -1;
> +		if (def->opts.is_unique) {
> +			if (create_index_as_constraint(stmt, space, def) != 0) {
> +				index_def_delete(def);
> +				return -1;
> +			}
> +		}
>  		alter_space_move_indexes(alter, 0, iid);
>  		CreateIndex *create_index = new CreateIndex(alter);
> -		create_index->new_index_def =
> -			index_def_new_from_tuple(new_tuple, old_space);
> -		if (create_index->new_index_def == NULL)
> -			return -1;
> -		index_def_update_optionality(create_index->new_index_def,
> -					     alter->new_min_field_count);
> +		create_index->new_index_def = def;
> +		index_def_update_optionality(def, alter->new_min_field_count);
>  	}
>  	/* Case 3 and 4: check if we need to rebuild index data. */
>  	if (old_index != NULL && new_tuple != NULL) {
>  		struct index_def *index_def;
> -		index_def = index_def_new_from_tuple(new_tuple, old_space);
> +		index_def = index_def_new_from_tuple(new_tuple, space);
>  		if (index_def == NULL)
>  			return -1;
>  		auto index_def_guard =
>  			make_scoped_guard([=] { index_def_delete(index_def); });
> +		struct index_def *old_def = old_index->def;
> +		/**

8. We don't use /** inside functions.

> +		 * We put a new name either an index is becoming
> +		 * unique (i.e. constraint), or when an unique
> +		 * index's name is under change.
> +		 */
> +		if (!old_def->opts.is_unique && index_def->opts.is_unique) {
> +			if (create_index_as_constraint(stmt, space, index_def)

9. When you have multiple conditions, it is shorter
and easier to check them all via && instead of 'if if if ... '.

> +			    != 0)
> +				return -1;
> +		}
> +		if (old_def->opts.is_unique && index_def->opts.is_unique &&
> +		    strcmp(index_def->name, old_def->name) != 0) {
> +			if (space_constraint_def_by_name(space, index_def->name)
> +			    != NULL) {
> +				diag_set(ClientError, ER_CONSTRAINT_EXISTS,
> +					 index_def->name);
> +				return -1;
> +			}
> +			struct constraint_def *old_constr_def =
> +				space_constraint_def_by_name(space,
> +							     old_def->name);
> +			assert(old_constr_def != NULL);
> +			struct constraint_def *new_constr_def =
> +				constraint_def_new(old_constr_def->space_id,
> +						   old_constr_def->type,
> +						   index_def->name);
> +			if (new_constr_def == NULL)
> +				return -1;
> +			auto new_constraint_def_guard =
> +				make_scoped_guard([=] { constraint_def_free(new_constr_def); });
> +			if (space_put_constraint(space, new_constr_def) != 0)
> +				return -1;
> +			space_drop_constraint(space, old_def->name);
> +			/**
> +			 * Array with the pair of 2
> +			 * index_def structures: old and
> +			 * new.
> +			 */
> +			uint32_t size = sizeof(struct constraint_def *) * 2;
> +			struct region *r = &fiber()->gc;
> +			struct constraint_def **defs =
> +				(struct constraint_def **)region_alloc(r, size);
> +			if (defs == NULL) {
> +				diag_set(OutOfMemory, size, "region",
> +					 "new slab");

10. Please, take a look at what OutOfMemory expects, and how is
it used in other places. It takes size, allocator function name,
and result variable name. So both 'region' and 'new slab' are
wrong here. You should use 'region_alloc' and 'defs'.

11. old_constr_def leaks here. Moreover, it is dropped already,
and is it not added back.

> +				return -1;
> +			}
> +			defs[0] = old_constr_def;
> +			defs[1] = new_constr_def;
> +			struct trigger *on_commit =
> +				txn_alter_trigger_new(NULL, NULL);
> +			struct trigger *on_rollback =
> +				txn_alter_trigger_new(NULL, NULL);
> +			if (on_commit == NULL || on_rollback == NULL)
> +				return -1;

12. The same here.

> +			on_commit->data = defs;
> +			on_commit->run = on_replace_index_commit;
> +			on_rollback->data = defs;
> +			on_rollback->run = on_replace_index_rollback;
> +			txn_stmt_on_commit(stmt, on_commit);
> +			txn_stmt_on_rollback(stmt, on_rollback);
> +			new_constraint_def_guard.is_active = false;
> +		}
> +		if (old_def->opts.is_unique && !index_def->opts.is_unique) {
> +			if (drop_index_as_constraint(stmt, space, old_def) != 0)
> +				return -1;
> +		}
>  		/*
>  		 * To detect which key parts are optional,
>  		 * min_field_count is required. But
> @@ -2570,8 +2821,9 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
>  	 * Create MoveIndex ops for the remaining indexes in the
>  	 * old space.
>  	 */
> -	alter_space_move_indexes(alter, iid + 1, old_space->index_id_max + 1);
> +	alter_space_move_indexes(alter, iid + 1, space->index_id_max + 1);

13. I remember, you said that 'old_space' -> 'space' is going
to save some lines, but man. This rename diff is huge. This is
really comparable with the functional diff in this function. I
didn't think, that this rename will affect so much code. Please,
return old_space back. It is not worth introducing such a big
diff.


14. Talking of the super mess with on_replace_dd_index, and
how tight it is now with all these struggling about replacing
constraints, and on_commit/on_rollback. I think, that this is
a dead end. Too complex.

How about introducing a couple of new AlterSpaceOp classes?
AddConstraint, and DropConstraint. AlterSpaceOp looks good
because
- it will allow you to not set on_commit/on_rollback triggers
  manually - it already provides these methods;
- you will be able to move this huge pieces of code out of
  on_replace_dd_index and other on_replace_*;
- it is consistent with MoveIndex/DropIndex/etc classes.

All you will need to do is to add to on_replace_*
(void) new AddConstraint(), and (void) new DropConstraint.
And you will keep (void) new MoveConstraints().

Also that should eliminate code duplication between different
on_replace_* functions about adding/dropping constraints.

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

* Re: [Tarantool-patches] [PATCH v2 1/2] box: introduce constraint names hash table
  2019-11-28 18:34 ` [Tarantool-patches] [PATCH v2 1/2] box: introduce constraint names hash table Roman Khabibov
@ 2019-11-30  1:03   ` Vladislav Shpilevoy
  2019-12-04 16:23     ` [Tarantool-patches] [PATCH v2 1/3] " Roman Khabibov
  0 siblings, 1 reply; 29+ messages in thread
From: Vladislav Shpilevoy @ 2019-11-30  1:03 UTC (permalink / raw)
  To: Roman Khabibov, tarantool-patches

Thanks for the patch!

See 6 comments below!

On 28/11/2019 19:34, Roman Khabibov wrote:
> Add hash table and API for interaction with it to struct space.
> This hash table is needed to keep constraint of a table together
> and check their duplicates by name.
> 
> Part of #3503
> ---
>  src/box/CMakeLists.txt   |  1 +
>  src/box/constraint_def.c | 59 ++++++++++++++++++++++++++++
>  src/box/constraint_def.h | 83 ++++++++++++++++++++++++++++++++++++++++
>  src/box/space.c          | 56 +++++++++++++++++++++++++++
>  src/box/space.h          | 39 +++++++++++++++++++
>  5 files changed, 238 insertions(+)
>  create mode 100755 src/box/constraint_def.c
>  create mode 100755 src/box/constraint_def.h
> 
> diff --git a/src/box/constraint_def.c b/src/box/constraint_def.c
> new file mode 100755
> index 000000000..914b36da3
> --- /dev/null
> +++ b/src/box/constraint_def.c
> +#include "constraint_def.h"
> +#include "assoc.h"
> +#include "errcode.h"
> +#include "diag.h"
> +#include <string.h>
> +#include <stdlib.h>
> +
> +struct constraint_def *
> +constraint_def_new(uint32_t space_id, enum constraint_type type,
> +		   const char *name) {

1. Please, put { on a separate line. In all the other places too.

> +	uint32_t len = strlen(name);
> +	uint32_t size = sizeof(struct constraint_def) + len + 1;
> +	struct constraint_def *ret = malloc(size);
> +	if (ret == NULL) {
> +		diag_set(OutOfMemory, size, "malloc", "constraint_def");
> +		return NULL;
> +	}
> +	ret->space_id = space_id;
> +	ret->type = type;
> +	memcpy(ret->name, name, len + 1);
> +	return ret;
> +}
> +
> +void
> +constraint_def_free(struct constraint_def *def) {

2. Please, rename it to _delete(). We always use new/delete() for
constructor/destructor managing memory. Free() is used only by a
few obsolete places, and by subsystem destructors.

> +	free(def);
> +}
> diff --git a/src/box/constraint_def.h b/src/box/constraint_def.h
> new file mode 100755
> index 000000000..ef238eb46
> --- /dev/null
> +++ b/src/box/constraint_def.h
> @@ -0,0 +1,83 @@
> +#ifndef INCLUDES_BOX_CONSTRAINT_DEF_H
> +#define INCLUDES_BOX_CONSTRAINT_DEF_H

3. Please, take a look at other header guards. They
have a certain naming policy. For example, tuple.h in
box/ has a guard TARANTOOL_BOX_TUPLE_H_INCLUDED. I guess
you could use TARANTOOL_BOX_CONSTRAINT_DEF_H_INCLUDED.

But even better would be to use #pragma once. I don't
know whether they are allowed in our code though.

> +/*
> + * 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>
> +
> +#if defined(__cplusplus)
> +extern "C" {
> +#endif
> +
> +enum constraint_type {
> +	CONSTRAINT_TYPE_PK = 0,

4. You can omit the assignments, if they does not matter anyway.

> +	CONSTRAINT_TYPE_UNIQUE = 1,
> +	CONSTRAINT_TYPE_FK = 2,
> +	CONSTRAINT_TYPE_CK = 3
> +};
> diff --git a/src/box/space.c b/src/box/space.c
> index 94716a414..91a7d575b 100644
> --- a/src/box/space.c
> +++ b/src/box/space.c
> @@ -257,6 +266,20 @@ space_delete(struct space *space)
>  	trigger_destroy(&space->before_replace);
>  	trigger_destroy(&space->on_replace);
>  	space_def_delete(space->def);
> +
> +	/**
> +	 * Free memory of the constraint hash table. Destroy every
> +	 * constraint def object.

5. There should not be any objects by that time. Why
is not the hash table empty?

> +	 */
> +	struct mh_strnptr_t *mh = space->constraint_names;
> +	while (mh_size(mh) > 0) {
> +		mh_int_t i = mh_first(mh);
> +		struct constraint_def *def =
> +			(struct constraint_def *) mh_strnptr_node(mh, i)->val;
> +		constraint_def_free(def);
> +	}
> +	mh_strnptr_delete(mh);
> +
>  	/*
>  	 * SQL Triggers should be deleted with
>  	 * on_replace_dd_trigger on deletion from _trigger.
> @@ -617,6 +640,39 @@ space_remove_ck_constraint(struct space *space, struct ck_constraint *ck)> diff --git a/src/box/space.h b/src/box/space.h
> index 7926aa65e..96396311e 100644
> --- a/src/box/space.h
> +++ b/src/box/space.h
> @@ -516,6 +519,42 @@ space_add_ck_constraint(struct space *space, struct ck_constraint *ck);
> +struct constraint_def *
> +space_constraint_def_by_name(struct space *space, const char *name);
> +
> +/**
> + * Put node with @a def to the constraint hash table of @a space.
> + *
> + * @param space Space.
> + * @param def   Constraint def.
> + *
> + * @retval  0 Success.
> + * @retval -1 Memory allocation error.
> + */
> +int
> +space_put_constraint(struct space *space, struct constraint_def *def);
> +
> +/**
> + * Remove node with @a name from the constraint hash table of @a
> + * space. But don't destroy the constraint def object binded to
> + * this @a name.
> + *
> + * @param space Space.
> + * @param name  Constraint name.
> + */
> +void
> +space_drop_constraint(struct space *space, const char *name);

6. That function is a bit confusing. I understand, why
do you need to not destroy a constraint right here. But
drop() name really assumes that. Moreover, in most of the
places you do space_constraint_def_by_name() before
space_drop_constraint(). So this is a double search. I
propose you to rename drop to pop, and make it return the
old constraint. It will make clear, that the constraint is
not deleted here, and will eliminate the double search.

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

* Re: [Tarantool-patches] [PATCH v2 2/3] sql: make constraint operations transactional
  2019-11-30  1:03   ` Vladislav Shpilevoy
@ 2019-12-04 16:23     ` Roman Khabibov
  2019-12-05 18:43     ` Roman Khabibov
  1 sibling, 0 replies; 29+ messages in thread
From: Roman Khabibov @ 2019-12-04 16:23 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy



> On Nov 30, 2019, at 04:03, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Thanks for the patch!
> 
> The commit below (or at least the commit message) was outdated.
> I see a different version on the branch. Below I copypaste the
> branch's version, and review it.
> 
> See 14 comments below.
> 
>>    Put constraint names into the space's hash table and drop them on
>>    replace in correspondig system spaces (_index, _fk_constraint,
> 
> 1. correspondig -> corresponding
Done.

>>    _ck_constraint).
>> 
>>    Closes #3503
>> 
>>    @TarantoolBot document
>>    Title: Constraints
>>    Now, names of constraints must be unique within one space.
> 
> 2. Now, imagine that you are a technical writer. You don't
> run and test Tarantool every day. And you don't know what,
> how, when, and where do we support. What are the problems,
> features, plans. Your task is simple - you take a ticket
> from tarantool/doc, and transform it into something that is
> ok to be placed on the site. Into a good English text, with
> rich details, with omission of not needed information. You
> may fix some of the old documentation affected by that ticket.
> You may add pictures, diagrams.
> 
> And then imagine you got such a ticket as you provided here.
> Sorry, but this is just nothing. That is like if I would ask
> you to 'fix an assertion in box'. What assertion? Where in
> the box? What is a reproducer?
> 
> You need to explain, what do you mean as constraints; what
> if uniqueness is violated; does letter case matter; what are
> examples; does language matter (Lua, SQL, binary) etc.
Done.

>> 
>> diff --git a/src/box/alter.cc b/src/box/alter.cc
>> index 4a3241a79..4bb8b8556 100644
>> --- a/src/box/alter.cc
>> +++ b/src/box/alter.cc
>> @@ -1765,6 +1766,38 @@ MoveCkConstraints::rollback(struct alter_space *alter)
>> 	space_swap_ck_constraint(alter->new_space, alter->old_space);
>> }
>> 
>> +/**
>> + * Move constraint names hash table from old space to a new one.
>> + */
>> +class MoveConstraints: public AlterSpaceOp
>> +{
>> +	inline void space_swap_constraint(struct space *old_space,
>> +				   struct space *new_space);
>> +public:
>> +	MoveConstraints(struct alter_space *alter) : AlterSpaceOp(alter) {}
>> +	virtual void alter(struct alter_space *alter);
>> +	virtual void rollback(struct alter_space *alter);
>> +};
>> +
>> +inline void
>> +MoveConstraints::space_swap_constraint(struct space *old_space,
>> +				       struct space *new_space)
>> +{
>> +	SWAP(new_space->constraint_names, old_space->constraint_names);
> 
> 3. So this is not inlined as a I asked. Perhaps that was
> misunderstanding. I meant literally inline it. Not add
> 'inline' keyword. That is a one-liner function, which calls
> another one liner. Even with the same number of arguments.
> What is a point of having it? It will never even be extended,
> and does not make the code more compact.
Isn’t it better to SWAP() them in alter_space_do() and alter_space_rollback()
without new class adding?

+/**
+ * Move constraint names hash table from old space to a new one.
+ */
+class MoveConstraintDefs: public AlterSpaceOp
+{
+public:
+	MoveConstraintDefs(struct alter_space *alter) : AlterSpaceOp(alter) {}
+	virtual void alter(struct alter_space *alter);
+	virtual void rollback(struct alter_space *alter);
+};
+
+void
+MoveConstraintDefs::alter(struct alter_space *alter)
+{
+	SWAP(alter->new_space->constraint_names,
+	     alter->old_space->constraint_names);
+}
+
+void
+MoveConstraintDefs::rollback(struct alter_space *alter)
+{
+	SWAP(alter->new_space->constraint_names,
+	     alter->old_space->constraint_names);
+}
+

>> +}
>> +
>> +void
>> +MoveConstraints::alter(struct alter_space *alter)
>> +{
>> +	space_swap_constraint(alter->old_space, alter->new_space);
>> +}
>> +
>> +void
>> +MoveConstraints::rollback(struct alter_space *alter)
>> +{
>> +	space_swap_constraint(alter->new_space, alter->old_space);
>> +}
>> @@ -2341,6 +2375,150 @@ index_is_used_by_fk_constraint(struct rlist *fk_list, uint32_t iid)
>> +
>> +/**
>> + * Put the node of an unique index to the constraint hash table of
>> + * @a space and create trigger on rollback.
>> + *
>> + * This function is needed to wrap the duplicated piece of code
>> + * inside on_replace_dd_index().
>> + *
>> + * @param stmt  Statement.
>> + * @param space Space.
>> + * @param def   Index def.
>> + *
>> + * @retval 0  Success.
>> + * @retval -1 Constraint already exists or out of memory.
> 
> 4. Once again - please, don't write comments just for
> comments. 'stmt Statement.' really does not make anything
> clearer or easier to understand. Either drop these
> useless formal @param @retval, or provide more *useful*
> details about these arguments. Although I doubt, that
> it is needed. The function is trivial and is not public
> anyway. The same about other similar places.
> 
> The comment's part before first @param is good, keep it.
> 
>> + */
>> +static int
>> +create_index_as_constraint(struct txn_stmt *stmt, struct space *space,
>> +			   struct index_def *def) {
> 
> 5. I propose you to make it `const struct index_def *`.
> 
>> +	assert(def->opts.is_unique);
>> +	if (space_constraint_def_by_name(space, def->name) != NULL) {
>> +		diag_set(ClientError, ER_CONSTRAINT_EXISTS, def->name);
+/**
+ * Put the node of an unique index to the constraint hash table of
+ * @a space.
+ *
+ * This function is needed to wrap the duplicated piece of code
+ * inside on_replace_dd_index().
+ */
+static int
+create_index_as_constraint(struct alter_space *alter,
+			   const struct index_def *def)
+{
+	assert(def->opts.is_unique);
+	struct space *space = alter->old_space;
+	if (space_constraint_def_by_name(space, def->name) != NULL) {
+		diag_set(ClientError, ER_CONSTRAINT_EXISTS, def->name);
+		return -1;
+	}
+	struct constraint_def *constr_def =
+		constraint_def_new(space->def->id, def->iid == 0 ?
+				   CONSTRAINT_TYPE_PK : CONSTRAINT_TYPE_UNIQUE,
+				   def->name);
+	if (constr_def == NULL)
+		return -1;
+	(void) new CreateConstraintDef(alter, constr_def);
+	return 0;
+}
+

> 6. AFAIR, I asked to add constraint type into the error message, didn't I?
> Why didn't you add it?
I have done the commit to the patchset, but IMO, I jumped out of the frying pan
into the fire. Because of, e.g.:

 box.execute("CREATE TABLE t2(id INT PRIMARY KEY, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t2, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t2);")
 ---
 - null
-- Constraint FK1 already exists
+- Duplicate key exists in unique index 'primary' in space ‘_fk_constraint'

Tuple duplication is checked before the corresponding on_replace_dd_...

>> +		return -1;
>> +	}
>> +	struct constraint_def *constr_def =
>> +		constraint_def_new(space->def->id, def->iid == 0 ?
>> +				   CONSTRAINT_TYPE_PK : CONSTRAINT_TYPE_UNIQUE,
>> +				   def->name);
>> +	if (constr_def == NULL)
>> +		return -1;
>> +	struct trigger *on_rollback = txn_alter_trigger_new(NULL, NULL);
>> +	if (space_put_constraint(space, constr_def) != 0 ||
>> +	    on_rollback == NULL) {
>> +		constraint_def_free(constr_def);
>> +		return -1;
>> +	}
>> +	on_rollback->data = constr_def;
>> +	on_rollback->run = on_create_index_rollback;
>> +	txn_stmt_on_rollback(stmt, on_rollback);
>> +	return 0;
>> +}
>> +
>> +/**
>> + * Drop the node of an unique index from the constraint hash table
>> + * of @a space and create trigger on rollback.
>> + *
>> + * This function is needed to wrap the duplicated piece of code
>> + * inside on_replace_dd_index().
>> + *
>> + * @param stmt  Statement.
>> + * @param space Space.
>> + * @param def   Index def.
>> + *
>> + * @retval 0  Success.
>> + * @retval -1 Out of memory.
>> + */
>> +static int
>> +drop_index_as_constraint(struct txn_stmt *stmt, struct space *space,
>> +			 struct index_def *def) {
>> +	assert(def->opts.is_unique);
>> +	struct constraint_def *constr_def =
>> +		space_constraint_def_by_name(space, def->name);
>> +	assert(constr_def != NULL);
>> +	space_drop_constraint(space, def->name);
>> +	struct trigger *on_rollback = txn_alter_trigger_new(NULL, NULL);
>> +	if (on_rollback == NULL)
>> +		return -1;
> 
> 7. In case on_rollback creation is failed, you don't restore
> the constraint back.
> 
>> +	on_rollback->data = constr_def;
>> +	on_rollback->run = on_drop_index_rollback;
>> +	txn_stmt_on_rollback(stmt, on_rollback);
>> +	return 0;
>> +}
>> +
>> /**
>>  * Just like with _space, 3 major cases:
>>  *> @@ -2469,35 +2645,111 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
>> 		 * Can't drop index if foreign key constraints
>> 		 * references this index.
>> 		 */
>> -		if (index_is_used_by_fk_constraint(&old_space->parent_fk_constraint,
>> +		if (index_is_used_by_fk_constraint(&space->parent_fk_constraint,
>> 						   iid)) {
>> -			diag_set(ClientError, ER_ALTER_SPACE,
>> -				  space_name(old_space),
>> +			diag_set(ClientError, ER_ALTER_SPACE, space_name(space),
>> 				  "can not drop a referenced index");
>> 			return -1;
>> 		}
>> 		alter_space_move_indexes(alter, 0, iid);
>> 		(void) new DropIndex(alter, old_index);
>> +		if (old_index->def->opts.is_unique &&
>> +		    drop_index_as_constraint(stmt, space, old_index->def) != 0)
>> +			return -1;
>> 	}
>> 	/* Case 2: create an index, if it is simply created. */
>> 	if (old_index == NULL && new_tuple != NULL) {
>> +		struct index_def *def =
>> +			index_def_new_from_tuple(new_tuple, space);
>> +		if (def == NULL)
>> +			return -1;
>> +		if (def->opts.is_unique) {
>> +			if (create_index_as_constraint(stmt, space, def) != 0) {
>> +				index_def_delete(def);
>> +				return -1;
>> +			}
>> +		}
>> 		alter_space_move_indexes(alter, 0, iid);
>> 		CreateIndex *create_index = new CreateIndex(alter);
>> -		create_index->new_index_def =
>> -			index_def_new_from_tuple(new_tuple, old_space);
>> -		if (create_index->new_index_def == NULL)
>> -			return -1;
>> -		index_def_update_optionality(create_index->new_index_def,
>> -					     alter->new_min_field_count);
>> +		create_index->new_index_def = def;
>> +		index_def_update_optionality(def, alter->new_min_field_count);
>> 	}
>> 	/* Case 3 and 4: check if we need to rebuild index data. */
>> 	if (old_index != NULL && new_tuple != NULL) {
>> 		struct index_def *index_def;
>> -		index_def = index_def_new_from_tuple(new_tuple, old_space);
>> +		index_def = index_def_new_from_tuple(new_tuple, space);
>> 		if (index_def == NULL)
>> 			return -1;
>> 		auto index_def_guard =
>> 			make_scoped_guard([=] { index_def_delete(index_def); });
>> +		struct index_def *old_def = old_index->def;
>> +		/**
> 
> 8. We don't use /** inside functions.
Ok. Removed.

>> +		 * We put a new name either an index is becoming
>> +		 * unique (i.e. constraint), or when an unique
>> +		 * index's name is under change.
>> +		 */
>> +		if (!old_def->opts.is_unique && index_def->opts.is_unique) {
>> +			if (create_index_as_constraint(stmt, space, index_def)
> 
> 9. When you have multiple conditions, it is shorter
> and easier to check them all via && instead of 'if if if ... ‘.
Ok. I thought it was more obvious.

> 
> 13. I remember, you said that 'old_space' -> 'space' is going
> to save some lines, but man. This rename diff is huge. This is
> really comparable with the functional diff in this function. I
> didn't think, that this rename will affect so much code. Please,
> return old_space back. It is not worth introducing such a big
> diff.
Returned.

> 
> 14. Talking of the super mess with on_replace_dd_index, and
> how tight it is now with all these struggling about replacing
> constraints, and on_commit/on_rollback. I think, that this is
> a dead end. Too complex.
> 
> How about introducing a couple of new AlterSpaceOp classes?
> AddConstraint, and DropConstraint. AlterSpaceOp looks good
> because
> - it will allow you to not set on_commit/on_rollback triggers
>  manually - it already provides these methods;
> - you will be able to move this huge pieces of code out of
>  on_replace_dd_index and other on_replace_*;
> - it is consistent with MoveIndex/DropIndex/etc classes.
> 
> All you will need to do is to add to on_replace_*
> (void) new AddConstraint(), and (void) new DropConstraint.
> And you will keep (void) new MoveConstraints().
> 
> Also that should eliminate code duplication between different
> on_replace_* functions about adding/dropping constraints.
+/**
+ * CreateConstraintDef - add a new constraint def to the space.
+ */
+class CreateConstraintDef: public AlterSpaceOp
+{
+private:
+	struct constraint_def *new_constraint_def;
+public:
+	CreateConstraintDef(struct alter_space *alter,
+			    struct constraint_def *def)
+		:AlterSpaceOp(alter), new_constraint_def(def)
+	{}
+	virtual void alter(struct alter_space *alter);
+	virtual void rollback(struct alter_space *alter);
+};
+
+void
+CreateConstraintDef::alter(struct alter_space *alter)
+{
+	if (space_put_constraint(alter->old_space, new_constraint_def) != 0)
+		panic("Can't recover after constraint alter (out of memory)");
+}
+
+void
+CreateConstraintDef::rollback(struct alter_space *alter)
+{
+	/*
+	 * Pop from new_space, because hash tables was swapped by
+	 * MoveConstraintDefs::alter().
+	 */
+	assert(space_pop_constraint(alter->new_space,
+				    new_constraint_def->name) ==
+	       new_constraint_def);
+	constraint_def_delete(new_constraint_def);
+}
+
+/** DropConstraintDef - drop a constraint def from the space. */
+class DropConstraintDef: public AlterSpaceOp
+{
+private:
+	struct constraint_def *old_constraint_def;
+	const char *name;
+public:
+	DropConstraintDef(struct alter_space *alter, const char *name)
+		:AlterSpaceOp(alter), old_constraint_def(NULL), name(name)
+	{}
+	virtual void alter(struct alter_space *alter);
+	virtual void commit(struct alter_space *alter , int64_t signature);
+	virtual void rollback(struct alter_space *alter);
+};
+
+void
+DropConstraintDef::alter(struct alter_space *alter)
+{
+	old_constraint_def =
+		space_constraint_def_by_name(alter->old_space, name);
+	assert(old_constraint_def != NULL);
+	space_pop_constraint(alter->old_space, name);
+}
+
+void
+DropConstraintDef::commit(struct alter_space *alter, int64_t signature)
+{
+	(void) alter;
+	(void) signature;
+	constraint_def_delete(old_constraint_def);
+}
+
+void
+DropConstraintDef::rollback(struct alter_space *alter)
+{
+	assert(old_constraint_def != NULL);
+	/*
+	 * Put to new_space, because hash tables was swapped by
+	 * MoveConstraintDefs::alter().
+	 */
+	if (space_put_constraint(alter->new_space, old_constraint_def) != 0) {
+		panic("Can't recover after constraint drop rollback (out of "
+		      "memory)");
+	}
+}
+

commit 5bad13b4756bff553d1107616aa3de3dd4bbe124
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Wed Oct 23 15:54:16 2019 +0300

    sql: make constraint operations transactional
    
    Put constraint names into the space's hash table and drop them on
    replace in corresponding system spaces (_index, _fk_constraint,
    _ck_constraint).
    
    Closes #3503
    
    @TarantoolBot document
    Title: Table constraints in SQL
    
    SQL:
    According to ANSI SQL, table constraint is one of the following
    entities: PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK. Every
    constraint have its own name passed by user or automatically
    generated. And these names must be unique within one table/space.
    Naming in SQL is case-insensitive, so "CONSTRAINT c" and
    "CONSTRAINT C" are the same. For example, you will get error, if
    you try to:
    
    CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
                     CONSTRAINT c PRIMARY KEY (i));
    
    or
    
    CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);
    ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
    
    The same is for any other constraint types.
    
    Lua/box:
    
    You also can create/drop table constraints from box. See
    space_object:create_check_constraint() and space_object:create_index()
    (if an index is unique). Naming in box is case-sensitive, so 'c' and
    'C' are not the same (see naming policy). For example, an unique
    index is a constraint, but a non-unique index is not. So, a non-unique
    index can have the same name with a check/foreign key constraint
    within one space:
    
    box.execute('CREATE TABLE t2 (i INT PRIMARY KEY);');
    box.execute('CREATE INDEX e ON t2(i);');
    box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
    
    But, if you try to:
    
    box.space.T2.index.E:alter({unique = true});
    
    You will get error, beacuse index 'e' is becoming unique.

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 4a3241a79..9583bfd13 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -57,6 +57,7 @@
 #include "version.h"
 #include "sequence.h"
 #include "sql.h"
+#include "constraint_def.h"
 
 /* {{{ Auxiliary functions and methods. */
 
@@ -1765,6 +1766,113 @@ MoveCkConstraints::rollback(struct alter_space *alter)
 	space_swap_ck_constraint(alter->new_space, alter->old_space);
 }
 
+/**
+ * CreateConstraintDef - add a new constraint def to the space.
+ */
+class CreateConstraintDef: public AlterSpaceOp
+{
+private:
+	struct constraint_def *new_constraint_def;
+public:
+	CreateConstraintDef(struct alter_space *alter,
+			    struct constraint_def *def)
+		:AlterSpaceOp(alter), new_constraint_def(def)
+	{}
+	virtual void alter(struct alter_space *alter);
+	virtual void rollback(struct alter_space *alter);
+};
+
+void
+CreateConstraintDef::alter(struct alter_space *alter)
+{
+	if (space_put_constraint(alter->old_space, new_constraint_def) != 0)
+		panic("Can't recover after constraint alter (out of memory)");
+}
+
+void
+CreateConstraintDef::rollback(struct alter_space *alter)
+{
+	/*
+	 * Pop from new_space, because hash tables was swapped by
+	 * MoveConstraintDefs::alter().
+	 */
+	assert(space_pop_constraint(alter->new_space,
+				    new_constraint_def->name) ==
+	       new_constraint_def);
+	constraint_def_delete(new_constraint_def);
+}
+
+/** DropConstraintDef - drop a constraint def from the space. */
+class DropConstraintDef: public AlterSpaceOp
+{
+private:
+	struct constraint_def *old_constraint_def;
+	const char *name;
+public:
+	DropConstraintDef(struct alter_space *alter, const char *name)
+		:AlterSpaceOp(alter), old_constraint_def(NULL), name(name)
+	{}
+	virtual void alter(struct alter_space *alter);
+	virtual void commit(struct alter_space *alter , int64_t signature);
+	virtual void rollback(struct alter_space *alter);
+};
+
+void
+DropConstraintDef::alter(struct alter_space *alter)
+{
+	old_constraint_def =
+		space_constraint_def_by_name(alter->old_space, name);
+	assert(old_constraint_def != NULL);
+	space_pop_constraint(alter->old_space, name);
+}
+
+void
+DropConstraintDef::commit(struct alter_space *alter, int64_t signature)
+{
+	(void) alter;
+	(void) signature;
+	constraint_def_delete(old_constraint_def);
+}
+
+void
+DropConstraintDef::rollback(struct alter_space *alter)
+{
+	assert(old_constraint_def != NULL);
+	/*
+	 * Put to new_space, because hash tables was swapped by
+	 * MoveConstraintDefs::alter().
+	 */
+	if (space_put_constraint(alter->new_space, old_constraint_def) != 0) {
+		panic("Can't recover after constraint drop rollback (out of "
+		      "memory)");
+	}
+}
+
+/**
+ * Move constraint names hash table from old space to a new one.
+ */
+class MoveConstraintDefs: public AlterSpaceOp
+{
+public:
+	MoveConstraintDefs(struct alter_space *alter) : AlterSpaceOp(alter) {}
+	virtual void alter(struct alter_space *alter);
+	virtual void rollback(struct alter_space *alter);
+};
+
+void
+MoveConstraintDefs::alter(struct alter_space *alter)
+{
+	SWAP(alter->new_space->constraint_names,
+	     alter->old_space->constraint_names);
+}
+
+void
+MoveConstraintDefs::rollback(struct alter_space *alter)
+{
+	SWAP(alter->new_space->constraint_names,
+	     alter->old_space->constraint_names);
+}
+
 /* }}} */
 
 /**
@@ -2307,6 +2415,7 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 		def_guard.is_active = false;
 		/* Create MoveIndex ops for all space indexes. */
 		alter_space_move_indexes(alter, 0, old_space->index_id_max + 1);
+		(void) new MoveConstraintDefs(alter);
 		/* Remember to update schema_version. */
 		(void) new UpdateSchemaVersion(alter);
 		try {
@@ -2341,6 +2450,33 @@ index_is_used_by_fk_constraint(struct rlist *fk_list, uint32_t iid)
 	return false;
 }
 
+/**
+ * Put the node of an unique index to the constraint hash table of
+ * @a space.
+ *
+ * This function is needed to wrap the duplicated piece of code
+ * inside on_replace_dd_index().
+ */
+static int
+create_index_as_constraint(struct alter_space *alter,
+			   const struct index_def *def)
+{
+	assert(def->opts.is_unique);
+	struct space *space = alter->old_space;
+	if (space_constraint_def_by_name(space, def->name) != NULL) {
+		diag_set(ClientError, ER_CONSTRAINT_EXISTS, def->name);
+		return -1;
+	}
+	struct constraint_def *constr_def =
+		constraint_def_new(space->def->id, def->iid == 0 ?
+				   CONSTRAINT_TYPE_PK : CONSTRAINT_TYPE_UNIQUE,
+				   def->name);
+	if (constr_def == NULL)
+		return -1;
+	(void) new CreateConstraintDef(alter, constr_def);
+	return 0;
+}
+
 /**
  * Just like with _space, 3 major cases:
  *
@@ -2476,19 +2612,28 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 				  "can not drop a referenced index");
 			return -1;
 		}
+		struct index_def *def = old_index->def;
 		alter_space_move_indexes(alter, 0, iid);
 		(void) new DropIndex(alter, old_index);
+		if (old_index->def->opts.is_unique)
+			(void) new DropConstraintDef(alter, def->name);
 	}
 	/* Case 2: create an index, if it is simply created. */
 	if (old_index == NULL && new_tuple != NULL) {
-		alter_space_move_indexes(alter, 0, iid);
-		CreateIndex *create_index = new CreateIndex(alter);
-		create_index->new_index_def =
+		struct index_def *def =
 			index_def_new_from_tuple(new_tuple, old_space);
-		if (create_index->new_index_def == NULL)
+		if (def == NULL)
 			return -1;
-		index_def_update_optionality(create_index->new_index_def,
-					     alter->new_min_field_count);
+		if (def->opts.is_unique) {
+			if (create_index_as_constraint(alter, def) != 0) {
+				index_def_delete(def);
+				return -1;
+			}
+		}
+		alter_space_move_indexes(alter, 0, iid);
+		CreateIndex *create_index = new CreateIndex(alter);
+		create_index->new_index_def = def;
+		index_def_update_optionality(def, alter->new_min_field_count);
 	}
 	/* Case 3 and 4: check if we need to rebuild index data. */
 	if (old_index != NULL && new_tuple != NULL) {
@@ -2498,6 +2643,23 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 			return -1;
 		auto index_def_guard =
 			make_scoped_guard([=] { index_def_delete(index_def); });
+		struct index_def *old_def = old_index->def;
+		/*
+		 * We put a new name either an index is becoming
+		 * unique (i.e. constraint), or when an unique
+		 * index's name is under change.
+		 */
+		if (!old_def->opts.is_unique && index_def->opts.is_unique &&
+		    create_index_as_constraint(alter, index_def) != 0)
+				return -1;
+		if (old_def->opts.is_unique && index_def->opts.is_unique &&
+		    strcmp(index_def->name, old_def->name) != 0) {
+			if (create_index_as_constraint(alter, index_def) != 0)
+				return -1;
+			(void) new DropConstraintDef(alter, old_def->name);
+		}
+		if (old_def->opts.is_unique && !index_def->opts.is_unique)
+			(void) new DropConstraintDef(alter, old_def->name);
 		/*
 		 * To detect which key parts are optional,
 		 * min_field_count is required. But
@@ -2572,6 +2734,7 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 	 */
 	alter_space_move_indexes(alter, iid + 1, old_space->index_id_max + 1);
 	(void) new MoveCkConstraints(alter);
+	(void) new MoveConstraintDefs(alter);
 	/* Add an op to update schema_version on commit. */
 	(void) new UpdateSchemaVersion(alter);
 	try {
@@ -2663,6 +2826,7 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event)
 	}
 
 	(void) new MoveCkConstraints(alter);
+	(void) new MoveConstraintDefs(alter);
 	try {
 		alter_space_do(stmt, alter);
 	} catch (Exception *e) {
@@ -4951,8 +5115,15 @@ on_create_fk_constraint_rollback(struct trigger *trigger, void *event)
 	struct fk_constraint *fk = (struct fk_constraint *)trigger->data;
 	rlist_del_entry(fk, in_parent_space);
 	rlist_del_entry(fk, in_child_space);
+	struct space *child = space_by_id(fk->def->child_id);
+	const char *name = fk->def->name;
+	struct constraint_def *constr_def =
+		space_constraint_def_by_name(child, name);
+	assert(constr_def != NULL);
+	space_pop_constraint(child, name);
+	constraint_def_delete(constr_def);
 	space_reset_fk_constraint_mask(space_by_id(fk->def->parent_id));
-	space_reset_fk_constraint_mask(space_by_id(fk->def->child_id));
+	space_reset_fk_constraint_mask(child);
 	fk_constraint_delete(fk);
 	return 0;
 }
@@ -4986,11 +5157,24 @@ on_drop_fk_constraint_rollback(struct trigger *trigger, void *event)
 	struct space *child = space_by_id(old_fk->def->child_id);
 	rlist_add_entry(&child->child_fk_constraint, old_fk, in_child_space);
 	rlist_add_entry(&parent->parent_fk_constraint, old_fk, in_parent_space);
+	const char *name = old_fk->def->name;
+	struct constraint_def *constr_def =
+		constraint_def_new(child->def->id, CONSTRAINT_TYPE_FK, name);
+	if (constr_def == NULL)
+		goto panic;
+	if (space_put_constraint(child, constr_def) != 0) {
+		constraint_def_delete(constr_def);
+		goto panic;
+	}
 	fk_constraint_set_mask(old_fk, &child->fk_constraint_mask,
 			       FIELD_LINK_CHILD);
 	fk_constraint_set_mask(old_fk, &parent->fk_constraint_mask,
 			       FIELD_LINK_PARENT);
 	return 0;
+
+panic:
+	panic("Can't recover after FK constraint drop rollback (out of "
+	      "memory)");
 }
 
 /**
@@ -5165,15 +5349,31 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 		fk->def = fk_def;
 		fk->index_id = fk_index->def->iid;
 		if (old_tuple == NULL) {
+			if (space_constraint_def_by_name(child_space,
+							 fk_def->name)
+			    != NULL) {
+				diag_set(ClientError, ER_CONSTRAINT_EXISTS,
+					 fk_def->name);
+				return -1;
+			}
 			rlist_add_entry(&child_space->child_fk_constraint,
 					fk, in_child_space);
 			rlist_add_entry(&parent_space->parent_fk_constraint,
 					fk, in_parent_space);
+			struct constraint_def *constr_def =
+				constraint_def_new(child_space->def->id,
+						   CONSTRAINT_TYPE_FK,
+						   fk_def->name);
+			if (constr_def == NULL)
+				return -1;
 			struct trigger *on_rollback =
 				txn_alter_trigger_new(on_create_fk_constraint_rollback,
 						      fk);
-			if (on_rollback == NULL)
+			if (space_put_constraint(child_space, constr_def) != 0
+			    || on_rollback == NULL) {
+				constraint_def_delete(constr_def);
 				return -1;
+			}
 			txn_stmt_on_rollback(stmt, on_rollback);
 			fk_constraint_set_mask(fk,
 					       &parent_space->fk_constraint_mask,
@@ -5221,6 +5421,12 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 		struct fk_constraint *old_fk=
 			fk_constraint_remove(&child_space->child_fk_constraint,
 					     fk_def->name);
+		const char *name = fk_def->name;
+		struct constraint_def *constr_def =
+			space_constraint_def_by_name(child_space, name);
+		assert(constr_def != NULL);
+		space_pop_constraint(child_space, name);
+		constraint_def_delete(constr_def);
 		struct trigger *on_commit =
 			txn_alter_trigger_new(on_drop_or_replace_fk_constraint_commit,
 					      old_fk);
@@ -5297,9 +5503,14 @@ on_create_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
 	assert(ck != NULL);
 	struct space *space = space_by_id(ck->def->space_id);
 	assert(space != NULL);
-	assert(space_ck_constraint_by_name(space, ck->def->name,
-					   strlen(ck->def->name)) != NULL);
+	const char *name = ck->def->name;
+	assert(space_ck_constraint_by_name(space, name, strlen(name)) != NULL);
 	space_remove_ck_constraint(space, ck);
+	struct constraint_def *constr_def =
+		space_constraint_def_by_name(space, name);
+	assert(constr_def != NULL);
+	space_pop_constraint(space, name);
+	constraint_def_delete(constr_def);
 	ck_constraint_delete(ck);
 	if (trigger_run(&on_alter_space, space) != 0)
 		return -1;
@@ -5324,13 +5535,23 @@ on_drop_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
 	assert(ck != NULL);
 	struct space *space = space_by_id(ck->def->space_id);
 	assert(space != NULL);
-	assert(space_ck_constraint_by_name(space, ck->def->name,
-					   strlen(ck->def->name)) == NULL);
-	if (space_add_ck_constraint(space, ck) != 0)
-		panic("Can't recover after CK constraint drop rollback");
+	const char *name = ck->def->name;
+	assert(space_ck_constraint_by_name(space, name, strlen(name)) == NULL);
+	struct constraint_def *constr_def =
+		constraint_def_new(space->def->id, CONSTRAINT_TYPE_CK, name);
+	if (constr_def == NULL)
+		goto panic;
+	if (space_add_ck_constraint(space, ck) != 0 ||
+	    space_put_constraint(space, constr_def) != 0) {
+		constraint_def_delete(constr_def);
+		goto panic;
+	}
 	if (trigger_run(&on_alter_space, space) != 0)
 		return -1;
 	return 0;
+
+panic:
+	panic("Can't recover after CK constraint drop rollback");
 }
 
 /** Commit REPLACE check constraint. */
@@ -5443,11 +5664,27 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		auto ck_guard = make_scoped_guard([=] {
 			ck_constraint_delete(new_ck_constraint);
 		});
-		if (old_ck_constraint != NULL)
+		if (old_ck_constraint != NULL) {
 			rlist_del_entry(old_ck_constraint, link);
+		} else if (space_constraint_def_by_name(space, name) != NULL) {
+			diag_set(ClientError, ER_CONSTRAINT_EXISTS,
+				 name);
+			return -1;
+		}
 		if (space_add_ck_constraint(space, new_ck_constraint) != 0)
 			return -1;
 		ck_guard.is_active = false;
+		if (old_ck_constraint == NULL) {
+			struct constraint_def *constr_def =
+				constraint_def_new(space->def->id,
+						   CONSTRAINT_TYPE_CK, name);
+			if (constr_def == NULL)
+				return -1;
+			if (space_put_constraint(space, constr_def) != 0) {
+				constraint_def_delete(constr_def);
+				return -1;
+			}
+		}
 		if (old_tuple != NULL) {
 			on_rollback->data = old_ck_constraint;
 			on_rollback->run = on_replace_ck_constraint_rollback;
@@ -5467,7 +5704,13 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 			return -1;
 		struct ck_constraint *old_ck_constraint =
 			space_ck_constraint_by_name(space, name, name_len);
+		struct constraint_def *constr_def =
+			space_constraint_def_by_name(space,
+						     old_ck_constraint->def->name);
+		assert(constr_def != NULL);
 		assert(old_ck_constraint != NULL);
+		space_pop_constraint(space, old_ck_constraint->def->name);
+		constraint_def_delete(constr_def);
 		space_remove_ck_constraint(space, old_ck_constraint);
 		on_commit->data = old_ck_constraint;
 		on_commit->run = on_drop_ck_constraint_commit;
@@ -5567,6 +5810,7 @@ on_replace_dd_func_index(struct trigger *trigger, void *event)
 	alter_space_move_indexes(alter, index->def->iid + 1,
 				 space->index_id_max + 1);
 	(void) new MoveCkConstraints(alter);
+	(void) new MoveConstraintDefs(alter);
 	(void) new UpdateSchemaVersion(alter);
 	try {
 		alter_space_do(stmt, alter);
diff --git a/test/sql/constraint.result b/test/sql/constraint.result
new file mode 100644
index 000000000..ba262182b
--- /dev/null
+++ b/test/sql/constraint.result
@@ -0,0 +1,190 @@
+-- test-run result file version 2
+test_run = require('test_run').new()
+ | ---
+ | ...
+engine = test_run:get_cfg('engine')
+ | ---
+ | ...
+box.execute('pragma sql_default_engine=\''..engine..'\'')
+ | ---
+ | - row_count: 0
+ | ...
+test_run:cmd("setopt delimiter ';'")
+ | ---
+ | - true
+ | ...
+
+--
+-- Check a constraint name for duplicate within a single
+-- <CREATE TABLE> statement.
+--
+box.execute('CREATE TABLE t1 (i INT PRIMARY KEY);');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c UNIQUE (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c UNIQUE (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c CHECK (i < 0));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c CHECK (i > 0));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+
+--
+-- Check a constraint name for duplicate using <ALTER TABLE>
+-- statement.
+--
+box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);');
+ | ---
+ | - null
+ | - Index 'C' already exists in space 'T2'
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);');
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+
+--
+-- Make sure that a constraint's name isn't kept after the
+-- constraint drop.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('DROP INDEX d ON t2;');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);');
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.ck_constraint.D:drop();
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;');
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- The same inside a transaction.
+--
+box.begin()
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+box.space.T2.ck_constraint.D:drop()
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+box.commit();
+ | ---
+ | ...
+
+--
+-- Make sure, that altering of an index name affect to its record
+-- in the space's constraint hash table.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);');
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.D:alter({name = 'E'});
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+ | ---
+ | - null
+ | - Constraint E already exists
+ | ...
+
+--
+-- Make sure, that altering of an index uniqueness puts/drops
+-- its name to/from the space's constraint hash table.
+--
+box.space.T2.index.E:alter({unique = false});
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.E:alter({unique = true});
+ | ---
+ | - error: Constraint E already exists
+ | ...
+box.space.T2.ck_constraint.E:drop();
+ | ---
+ | ...
+box.space.T2.index.E:alter({unique = true});
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+ | ---
+ | - null
+ | - Constraint E already exists
+ | ...
+
+-- Alter name and uniqueness of an unique index simultaneously.
+box.space.T2.index.E:alter({name = 'D', unique = false});
+ | ---
+ | ...
+box.execute('CREATE UNIQUE INDEX e ON t2(i);');
+ | ---
+ | - row_count: 1
+ | ...
diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
new file mode 100755
index 000000000..50162e47d
--- /dev/null
+++ b/test/sql/constraint.test.lua
@@ -0,0 +1,84 @@
+test_run = require('test_run').new()
+engine = test_run:get_cfg('engine')
+box.execute('pragma sql_default_engine=\''..engine..'\'')
+test_run:cmd("setopt delimiter ';'")
+
+--
+-- Check a constraint name for duplicate within a single
+-- <CREATE TABLE> statement.
+--
+box.execute('CREATE TABLE t1 (i INT PRIMARY KEY);');
+box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+box.execute([[CREATE TABLE t2 (i INT,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c UNIQUE (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c UNIQUE (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c CHECK (i < 0));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c CHECK (i > 0));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
+
+--
+-- Check a constraint name for duplicate using <ALTER TABLE>
+-- statement.
+--
+box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);');
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);');
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);');
+
+--
+-- Make sure that a constraint's name isn't kept after the
+-- constraint drop.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);');
+box.execute('DROP INDEX d ON t2;');
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);');
+box.space.T2.ck_constraint.D:drop();
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);');
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;');
+
+--
+-- The same inside a transaction.
+--
+box.begin()
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+box.space.T2.ck_constraint.D:drop()
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+box.commit();
+
+--
+-- Make sure, that altering of an index name affect to its record
+-- in the space's constraint hash table.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);');
+box.space.T2.index.D:alter({name = 'E'});
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+
+--
+-- Make sure, that altering of an index uniqueness puts/drops
+-- its name to/from the space's constraint hash table.
+--
+box.space.T2.index.E:alter({unique = false});
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+box.space.T2.index.E:alter({unique = true});
+box.space.T2.ck_constraint.E:drop();
+box.space.T2.index.E:alter({unique = true});
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+
+-- Alter name and uniqueness of an unique index simultaneously.
+box.space.T2.index.E:alter({name = 'D', unique = false});
+box.execute('CREATE UNIQUE INDEX e ON t2(i);');

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

* Re: [Tarantool-patches] [PATCH v2 1/3] box: introduce constraint names hash table
  2019-11-30  1:03   ` Vladislav Shpilevoy
@ 2019-12-04 16:23     ` Roman Khabibov
  2019-12-07 16:34       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 29+ messages in thread
From: Roman Khabibov @ 2019-12-04 16:23 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

Hi! Thanks for the review.

> On Nov 30, 2019, at 04:03, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Thanks for the patch!
> 
> See 6 comments below!
> 
> On 28/11/2019 19:34, Roman Khabibov wrote:
>> Add hash table and API for interaction with it to struct space.
>> This hash table is needed to keep constraint of a table together
>> and check their duplicates by name.
>> 
>> Part of #3503
>> ---
>> src/box/CMakeLists.txt   |  1 +
>> src/box/constraint_def.c | 59 ++++++++++++++++++++++++++++
>> src/box/constraint_def.h | 83 ++++++++++++++++++++++++++++++++++++++++
>> src/box/space.c          | 56 +++++++++++++++++++++++++++
>> src/box/space.h          | 39 +++++++++++++++++++
>> 5 files changed, 238 insertions(+)
>> create mode 100755 src/box/constraint_def.c
>> create mode 100755 src/box/constraint_def.h
>> 
>> diff --git a/src/box/constraint_def.c b/src/box/constraint_def.c
>> new file mode 100755
>> index 000000000..914b36da3
>> --- /dev/null
>> +++ b/src/box/constraint_def.c
>> +#include "constraint_def.h"
>> +#include "assoc.h"
>> +#include "errcode.h"
>> +#include "diag.h"
>> +#include <string.h>
>> +#include <stdlib.h>
>> +
>> +struct constraint_def *
>> +constraint_def_new(uint32_t space_id, enum constraint_type type,
>> +		   const char *name) {
> 
> 1. Please, put { on a separate line. In all the other places too.
Done.

>> +	uint32_t len = strlen(name);
>> +	uint32_t size = sizeof(struct constraint_def) + len + 1;
>> +	struct constraint_def *ret = malloc(size);
>> +	if (ret == NULL) {
>> +		diag_set(OutOfMemory, size, "malloc", "constraint_def");
>> +		return NULL;
>> +	}
>> +	ret->space_id = space_id;
>> +	ret->type = type;
>> +	memcpy(ret->name, name, len + 1);
>> +	return ret;
>> +}
>> +
>> +void
>> +constraint_def_free(struct constraint_def *def) {
> 
> 2. Please, rename it to _delete(). We always use new/delete() for
> constructor/destructor managing memory. Free() is used only by a
> few obsolete places, and by subsystem destructors.
Done.

>> +	free(def);
>> +}
>> diff --git a/src/box/constraint_def.h b/src/box/constraint_def.h
>> new file mode 100755
>> index 000000000..ef238eb46
>> --- /dev/null
>> +++ b/src/box/constraint_def.h
>> @@ -0,0 +1,83 @@
>> +#ifndef INCLUDES_BOX_CONSTRAINT_DEF_H
>> +#define INCLUDES_BOX_CONSTRAINT_DEF_H
> 
> 3. Please, take a look at other header guards. They
> have a certain naming policy. For example, tuple.h in
> box/ has a guard TARANTOOL_BOX_TUPLE_H_INCLUDED. I guess
> you could use TARANTOOL_BOX_CONSTRAINT_DEF_H_INCLUDED.
> 
> But even better would be to use #pragma once. I don't
> know whether they are allowed in our code though.
I think #pragma is disallowed, because I didn’t found it by grep in the
src.

>> +/*
>> + * 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>
>> +
>> +#if defined(__cplusplus)
>> +extern "C" {
>> +#endif
>> +
>> +enum constraint_type {
>> +	CONSTRAINT_TYPE_PK = 0,
> 
> 4. You can omit the assignments, if they does not matter anyway.
+enum constraint_type {
+	CONSTRAINT_TYPE_PK = 0,
+	CONSTRAINT_TYPE_UNIQUE,
+	CONSTRAINT_TYPE_FK,
+	CONSTRAINT_TYPE_CK,
+};

>> +	CONSTRAINT_TYPE_UNIQUE = 1,
>> +	CONSTRAINT_TYPE_FK = 2,
>> +	CONSTRAINT_TYPE_CK = 3
>> +};
>> diff --git a/src/box/space.c b/src/box/space.c
>> index 94716a414..91a7d575b 100644
>> --- a/src/box/space.c
>> +++ b/src/box/space.c
>> @@ -257,6 +266,20 @@ space_delete(struct space *space)
>> 	trigger_destroy(&space->before_replace);
>> 	trigger_destroy(&space->on_replace);
>> 	space_def_delete(space->def);
>> +
>> +	/**
>> +	 * Free memory of the constraint hash table. Destroy every
>> +	 * constraint def object.
> 
> 5. There should not be any objects by that time. Why
> is not the hash table empty?
Yes, the table must be empty.

@@ -258,9 +267,12 @@ space_delete(struct space *space)
 	trigger_destroy(&space->on_replace);
 	space_def_delete(space->def);
 	/*
-	 * SQL Triggers should be deleted with
-	 * on_replace_dd_trigger on deletion from _trigger.
+	 * SQL triggers and constraints should be deleted with
+	 * on_replace_dd_ triggers on deletion from corresponding
+	 * system space.
 	 */
+	assert(mh_size(space->constraint_names) == 0);
+	mh_strnptr_delete(space->constraint_names);
 	assert(space->sql_triggers == NULL);
 	assert(rlist_empty(&space->parent_fk_constraint));
 	assert(rlist_empty(&space->child_fk_constraint));

>> +	 */
>> +	struct mh_strnptr_t *mh = space->constraint_names;
>> +	while (mh_size(mh) > 0) {
>> +		mh_int_t i = mh_first(mh);
>> +		struct constraint_def *def =
>> +			(struct constraint_def *) mh_strnptr_node(mh, i)->val;
>> +		constraint_def_free(def);
>> +	}
>> +	mh_strnptr_delete(mh);
>> +
>> 	/*
>> 	 * SQL Triggers should be deleted with
>> 	 * on_replace_dd_trigger on deletion from _trigger.
>> @@ -617,6 +640,39 @@ space_remove_ck_constraint(struct space *space, struct ck_constraint *ck)> diff --git a/src/box/space.h b/src/box/space.h
>> index 7926aa65e..96396311e 100644
>> --- a/src/box/space.h
>> +++ b/src/box/space.h
>> @@ -516,6 +519,42 @@ space_add_ck_constraint(struct space *space, struct ck_constraint *ck);
>> +struct constraint_def *
>> +space_constraint_def_by_name(struct space *space, const char *name);
>> +
>> +/**
>> + * Put node with @a def to the constraint hash table of @a space.
>> + *
>> + * @param space Space.
>> + * @param def   Constraint def.
>> + *
>> + * @retval  0 Success.
>> + * @retval -1 Memory allocation error.
>> + */
>> +int
>> +space_put_constraint(struct space *space, struct constraint_def *def);
>> +
>> +/**
>> + * Remove node with @a name from the constraint hash table of @a
>> + * space. But don't destroy the constraint def object binded to
>> + * this @a name.
>> + *
>> + * @param space Space.
>> + * @param name  Constraint name.
>> + */
>> +void
>> +space_drop_constraint(struct space *space, const char *name);
> 
> 6. That function is a bit confusing. I understand, why
> do you need to not destroy a constraint right here. But
> drop() name really assumes that. Moreover, in most of the
> places you do space_constraint_def_by_name() before
> space_drop_constraint(). So this is a double search. I
> propose you to rename drop to pop, and make it return the
> old constraint. It will make clear, that the constraint is
> not deleted here, and will eliminate the double search.
But the first one is almost copy of the second one. Is it ok?

+struct constraint_def *
+space_constraint_def_by_name(struct space *space, const char *name)
+{
+	uint32_t len = strlen(name);
+	mh_int_t pos = mh_strnptr_find_inp(space->constraint_names, name, len);
+	if (pos == mh_end(space->constraint_names))
+		return NULL;
+	return (struct constraint_def *)
+		mh_strnptr_node(space->constraint_names, pos)->val;
+}

+struct constraint_def *
+space_pop_constraint(struct space *space, const char *name)
+{
+	uint32_t len = strlen(name);
+	mh_int_t pos = mh_strnptr_find_inp(space->constraint_names, name, len);
+	if (pos == mh_end(space->constraint_names))
+		return NULL;
+	struct constraint_def *def = (struct constraint_def *)
+		mh_strnptr_node(space->constraint_names, pos)->val;
+	mh_strnptr_del(space->constraint_names, pos, NULL);
+	return def;
+}
+

commit f044d9a99b627e888cef0b935f55c8ad7f1386b1
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Tue Nov 5 18:06:59 2019 +0300

    box: introduce constraint names hash table
    
    Add hash table and API for interaction with it to struct space.
    This hash table is needed to keep constraint of a table together
    and check their duplicates by name.
    
    Part of #3503

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 5cd5cba81..bf3895262 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -102,6 +102,7 @@ add_library(box STATIC
     sequence.c
     ck_constraint.c
     fk_constraint.c
+    constraint_def.c
     func.c
     func_def.c
     key_list.c
diff --git a/src/box/constraint_def.c b/src/box/constraint_def.c
new file mode 100755
index 000000000..1d4367532
--- /dev/null
+++ b/src/box/constraint_def.c
@@ -0,0 +1,61 @@
+/*
+ * 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 "constraint_def.h"
+#include "assoc.h"
+#include "errcode.h"
+#include "diag.h"
+#include <string.h>
+#include <stdlib.h>
+
+struct constraint_def *
+constraint_def_new(uint32_t space_id, enum constraint_type type,
+		   const char *name)
+{
+	uint32_t len = strlen(name);
+	uint32_t size = sizeof(struct constraint_def) + len + 1;
+	struct constraint_def *ret = malloc(size);
+	if (ret == NULL) {
+		diag_set(OutOfMemory, size, "malloc", "ret");
+		return NULL;
+	}
+	ret->space_id = space_id;
+	ret->type = type;
+	memcpy(ret->name, name, len + 1);
+	return ret;
+}
+
+void
+constraint_def_delete(struct constraint_def *def)
+{
+	free(def);
+}
diff --git a/src/box/constraint_def.h b/src/box/constraint_def.h
new file mode 100755
index 000000000..c740bd2f0
--- /dev/null
+++ b/src/box/constraint_def.h
@@ -0,0 +1,83 @@
+#ifndef TARANTOOL_BOX_CONSTRAINT_DEF_H_INCLUDED
+#define TARANTOOL_BOX_CONSTRAINT_DEF_H_INCLUDED
+/*
+ * 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>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+enum constraint_type {
+	CONSTRAINT_TYPE_PK = 0,
+	CONSTRAINT_TYPE_UNIQUE,
+	CONSTRAINT_TYPE_FK,
+	CONSTRAINT_TYPE_CK,
+};
+
+struct constraint_def {
+	/** Space id. */
+	uint32_t space_id;
+	/** Constraint type. */
+	enum constraint_type type;
+	/** Zero-terminated string with name. */
+	char name[0];
+};
+
+/**
+ * Allocate memory and construct constraint def object.
+ *
+ * @param space_id Space id.
+ * @param type     Constraint type.
+ * @param name     Constraint name.
+ *
+ * @retval ptr  Constraint def object.
+ * @retval NULL Memory allocation error.
+ */
+struct constraint_def *
+constraint_def_new(uint32_t space_id, enum constraint_type type,
+		   const char *name);
+
+/**
+ * Free memory of constraint def object.
+ *
+ * @param def Constraint def.
+ */
+void
+constraint_def_delete(struct constraint_def *def);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
diff --git a/src/box/space.c b/src/box/space.c
index 94716a414..7e1a197a1 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -45,6 +45,8 @@
 #include "iproto_constants.h"
 #include "schema.h"
 #include "ck_constraint.h"
+#include "assoc.h"
+#include "constraint_def.h"
 
 int
 access_check_space(struct space *space, user_access_t access)
@@ -202,6 +204,13 @@ space_create(struct space *space, struct engine *engine,
 			}
 		}
 	}
+
+	space->constraint_names = mh_strnptr_new();
+	if (space->constraint_names == NULL) {
+		diag_set(OutOfMemory, sizeof(*space->constraint_names),
+			 "malloc", "constraint_names");
+		goto fail;
+	}
 	return 0;
 
 fail_free_indexes:
@@ -258,9 +267,12 @@ space_delete(struct space *space)
 	trigger_destroy(&space->on_replace);
 	space_def_delete(space->def);
 	/*
-	 * SQL Triggers should be deleted with
-	 * on_replace_dd_trigger on deletion from _trigger.
+	 * SQL triggers and constraints should be deleted with
+	 * on_replace_dd_ triggers on deletion from corresponding
+	 * system space.
 	 */
+	assert(mh_size(space->constraint_names) == 0);
+	mh_strnptr_delete(space->constraint_names);
 	assert(space->sql_triggers == NULL);
 	assert(rlist_empty(&space->parent_fk_constraint));
 	assert(rlist_empty(&space->child_fk_constraint));
@@ -617,6 +629,45 @@ space_remove_ck_constraint(struct space *space, struct ck_constraint *ck)
 	}
 }
 
+struct constraint_def *
+space_constraint_def_by_name(struct space *space, const char *name)
+{
+	uint32_t len = strlen(name);
+	mh_int_t pos = mh_strnptr_find_inp(space->constraint_names, name, len);
+	if (pos == mh_end(space->constraint_names))
+		return NULL;
+	return (struct constraint_def *)
+		mh_strnptr_node(space->constraint_names, pos)->val;
+}
+
+int
+space_put_constraint(struct space *space, struct constraint_def *def)
+{
+	uint32_t len = strlen(def->name);
+	uint32_t hash = mh_strn_hash(def->name, len);
+	const struct mh_strnptr_node_t name_node = { def->name, len, hash, def};
+	if (mh_strnptr_put(space->constraint_names, &name_node, NULL, NULL) ==
+	    mh_end(space->constraint_names)) {
+		diag_set(OutOfMemory, sizeof(name_node), "malloc",
+			 "constraint_names");
+		return -1;
+	}
+	return 0;
+}
+
+struct constraint_def *
+space_pop_constraint(struct space *space, const char *name)
+{
+	uint32_t len = strlen(name);
+	mh_int_t pos = mh_strnptr_find_inp(space->constraint_names, name, len);
+	if (pos == mh_end(space->constraint_names))
+		return NULL;
+	struct constraint_def *def = (struct constraint_def *)
+		mh_strnptr_node(space->constraint_names, pos)->val;
+	mh_strnptr_del(space->constraint_names, pos, NULL);
+	return def;
+}
+
 /* {{{ Virtual method stubs */
 
 size_t
diff --git a/src/box/space.h b/src/box/space.h
index 7926aa65e..0c23c224c 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -52,6 +52,7 @@ struct port;
 struct tuple;
 struct tuple_format;
 struct ck_constraint;
+struct constraint_def;
 
 struct space_vtab {
 	/** Free a space instance. */
@@ -234,6 +235,8 @@ struct space {
 	 * of parent constraints as well as child ones.
 	 */
 	uint64_t fk_constraint_mask;
+	/** Hash table with constraint names. */
+	struct mh_strnptr_t *constraint_names;
 };
 
 /** Initialize a base space instance. */
@@ -516,6 +519,45 @@ space_add_ck_constraint(struct space *space, struct ck_constraint *ck);
 void
 space_remove_ck_constraint(struct space *space, struct ck_constraint *ck);
 
+/**
+ * Find node with @a name in the constraint hash table of @a
+ * space.
+ *
+ * @param space Space.
+ * @param name  Constraint name.
+ *
+ * @retval constraint_def object.
+ * @retval NULL Constraint doesn't exist.
+ */
+struct constraint_def *
+space_constraint_def_by_name(struct space *space, const char *name);
+
+/**
+ * Put node with @a def to the constraint hash table of @a space.
+ *
+ * @param space Space.
+ * @param def   Constraint def.
+ *
+ * @retval  0 Success.
+ * @retval -1 Memory allocation error.
+ */
+int
+space_put_constraint(struct space *space, struct constraint_def *def);
+
+/**
+ * Remove node with @a name from the constraint hash table of @a
+ * space. But don't destroy the constraint def object binded to
+ * this @a name.
+ *
+ * @param space Space.
+ * @param name  Constraint name.
+ *
+ * @retval constraint_def object.
+ * @retval NULL Constraint doesn't exist.
+ */
+struct constraint_def *
+space_pop_constraint(struct space *space, const char *name);
+
 /*
  * Virtual method stubs.
  */

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

* Re: [Tarantool-patches] [PATCH v2 0/3] Add constraint names hash table to space
  2019-11-30  1:03 ` [Tarantool-patches] [PATCH v2 0/2] Add constraint names hash table to space Vladislav Shpilevoy
@ 2019-12-04 16:23   ` Roman Khabibov
  2019-12-07 16:34     ` Vladislav Shpilevoy
  0 siblings, 1 reply; 29+ messages in thread
From: Roman Khabibov @ 2019-12-04 16:23 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy



> On Nov 30, 2019, at 04:03, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Hi! Thanks for the patch!
> 
> See 2 comments below!
> 
> On 28/11/2019 19:34, Roman Khabibov wrote:
>> I have made essential changes to the previous patch, so I decided
>> to send it as v2.
>> 
>> 1) I still don't understand, why we need to store constraint id. If we have the
>> query "ALTER TABLE T DROP CONSTRAINT C", we just get struct space by its name,
>> then find the corresponding constraint_def node by the name and emit opcode on
>> replace in _index/_fk_constraint/_ck_constraint depending on
>> constraint_def->type.
> 
> 1. ID is a primary index. You can delete by ID. Is it always
> possible to delete by name? Do all the constraint spaces have a
> unique index over name consisting of one column?
I know a space_id. So, I will delete tuples from _index, _ck, _fk by their unique
composite indexes:
+----------+------------+
| space_id | name/iid   |
+----------+------------+

If we want to delete by ID, let’s add it to _ck and _fk and keep it in struct constraint_def also. But, IMO, it is not about my patchset.

>> 
>> 2) space_delete()
>> I don't know how to check, if the hash table is empty, because it jsut freed.
> 
> 2. Mhash provides mh_size() method. Add an assert, that it
> is it == 0.
Done.

>> 
>> salad/mhash.h
>> void
>> _mh(delete)(struct _mh(t) *h)
>> {
>> 	if (h->shadow->p) {
>> 		free(h->shadow->p);
>> 		free(h->shadow->b);
>> 		memset(h->shadow, 0, sizeof(*h->shadow));
>> 	}
>> 	free(h->shadow);
>> 	free(h->b);
>> 	free(h->p);
>> 	free(h);
>> }
>> Roman Khabibov (2):
>>  box: introduce constraint names hash table
>>  sql: make constraint operations transactional
>> 
>> src/box/CMakeLists.txt       |   1 +
>> src/box/alter.cc             | 427 +++++++++++++++++++++++++++++++----
>> src/box/constraint_def.c     |  59 +++++
>> src/box/constraint_def.h     |  83 +++++++
>> src/box/space.c              |  56 +++++
>> src/box/space.h              |  39 ++++
>> test/sql/constraint.result   | 190 ++++++++++++++++
>> test/sql/constraint.test.lua |  84 +++++++
>> 8 files changed, 892 insertions(+), 47 deletions(-)
>> create mode 100755 src/box/constraint_def.c
>> create mode 100755 src/box/constraint_def.h
>> create mode 100644 test/sql/constraint.result
>> create mode 100755 test/sql/constraint.test.lua

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

* Re: [Tarantool-patches] [PATCH v2 2/3] sql: make constraint operations transactional
  2019-11-30  1:03   ` Vladislav Shpilevoy
  2019-12-04 16:23     ` [Tarantool-patches] [PATCH v2 2/3] " Roman Khabibov
@ 2019-12-05 18:43     ` Roman Khabibov
  2019-12-07 16:35       ` Vladislav Shpilevoy
  1 sibling, 1 reply; 29+ messages in thread
From: Roman Khabibov @ 2019-12-05 18:43 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

Please, don’t see the previous answer.

> 
> On Nov 30, 2019, at 04:03, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Thanks for the patch!
> 
> The commit below (or at least the commit message) was outdated.
> I see a different version on the branch. Below I copypaste the
> branch's version, and review it.
> 
> See 14 comments below.
> 
>>   Put constraint names into the space's hash table and drop them on
>>   replace in correspondig system spaces (_index, _fk_constraint,
> 
> 1. correspondig -> corresponding
Done.

> 2. Now, imagine that you are a technical writer. You don't
> run and test Tarantool every day. And you don't know what,
> how, when, and where do we support. What are the problems,
> features, plans. Your task is simple - you take a ticket
> from tarantool/doc, and transform it into something that is
> ok to be placed on the site. Into a good English text, with
> rich details, with omission of not needed information. You
> may fix some of the old documentation affected by that ticket.
> You may add pictures, diagrams.
> 
> And then imagine you got such a ticket as you provided here.
> Sorry, but this is just nothing. That is like if I would ask
> you to 'fix an assertion in box'. What assertion? Where in
> the box? What is a reproducer?
> 
> You need to explain, what do you mean as constraints; what
> if uniqueness is violated; does letter case matter; what are
> examples; does language matter (Lua, SQL, binary) etc.
Done.

> 3. So this is not inlined as a I asked. Perhaps that was
> misunderstanding. I meant literally inline it. Not add
> 'inline' keyword. That is a one-liner function, which calls
> another one liner. Even with the same number of arguments.
> What is a point of having it? It will never even be extended,
> and does not make the code more compact.
Isn’t it better to SWAP() them in alter_space_do() and alter_space_rollback()?

@@ -1033,10 +1034,13 @@ alter_space_rollback(struct trigger *trigger, void * /* event */)
 	space_fill_index_map(alter->old_space);
 	space_fill_index_map(alter->new_space);
 	/*
-	 * Don't forget about space triggers and foreign keys.
+	 * Don't forget about space triggers, foreign keys and
+	 * constraints.
 	 */
 	space_swap_triggers(alter->new_space, alter->old_space);
 	space_swap_fk_constraints(alter->new_space, alter->old_space);
+	SWAP(alter->new_space->constraint_names,
+	     alter->old_space->constraint_names);
 	space_cache_replace(alter->new_space, alter->old_space);
 	alter_space_delete(alter);
 	return 0;
@@ -1143,10 +1147,13 @@ alter_space_do(struct txn_stmt *stmt, struct alter_space *alter)
 	space_fill_index_map(alter->old_space);
 	space_fill_index_map(alter->new_space);
 	/*
-	 * Don't forget about space triggers and foreign keys.
+	 * Don't forget about space triggers, foreign keys and
+	 * constraints.
 	 */
 	space_swap_triggers(alter->new_space, alter->old_space);
 	space_swap_fk_constraints(alter->new_space, alter->old_space);
+	SWAP(alter->new_space->constraint_names,
+	     alter->old_space->constraint_names);
 	/*
 	 * The new space is ready. Time to update the space
 	 * cache with it.

> 4. Once again - please, don't write comments just for
> comments. 'stmt Statement.' really does not make anything
> clearer or easier to understand. Either drop these
> useless formal @param @retval, or provide more *useful*
> details about these arguments. Although I doubt, that
> it is needed. The function is trivial and is not public
> anyway. The same about other similar places.
> 
> The comment's part before first @param is good, keep it.
> 
>> + */
>> +static int
>> +create_index_as_constraint(struct txn_stmt *stmt, struct space *space,
>> +			   struct index_def *def) {
> 
> 5. I propose you to make it `const struct index_def *`.
> 
>> +	assert(def->opts.is_unique);
>> +	if (space_constraint_def_by_name(space, def->name) != NULL) {
>> +		diag_set(ClientError, ER_CONSTRAINT_EXISTS, def->name);

+/**
+ * Put the node of an unique index to the constraint hash table of
+ * @a space.
+ *
+ * This function is needed to wrap the duplicated piece of code
+ * inside on_replace_dd_index().
+ */
+static int
+create_index_as_constraint(struct alter_space *alter,
+			   const struct index_def *def)
+{
+	assert(def->opts.is_unique);
+	struct space *space = alter->old_space;
+	if (space_constraint_def_by_name(space, def->name) != NULL) {
+		diag_set(ClientError, ER_CONSTRAINT_EXISTS, def->name);
+		return -1;
+	}
+	struct constraint_def *constr_def =
+		constraint_def_new(space->def->id, def->iid == 0 ?
+				   CONSTRAINT_TYPE_PK : CONSTRAINT_TYPE_UNIQUE,
+				   def->name);
+	if (constr_def == NULL)
+		return -1;
+	try {
+		(void) new CreateConstraintDef(alter, constr_def);
+	} catch (Exception *e) {
+		constraint_def_delete(constr_def);
+		return -1;
+	}
+	return 0;
+}
+
+/**
+ * Drop the node of an unique index from the constraint hash table
+ * of @a space.
+ *
+ * This function is needed to wrap the duplicated piece of code
+ * inside on_replace_dd_index().
+ */
+static int
+drop_index_as_constraint(struct alter_space *alter, const struct index_def *def)
+{
+	assert(def->opts.is_unique);
+	try {
+		(void) new DropConstraintDef(alter, def->name);
+	} catch (Exception *e) {
+		return -1;
+	}
+	return 0;
+}
+
> 
> 6. AFAIR, I asked to add constraint type into the error message, didn't I?
> Why didn't you add it?
I have done the commit to the patchset, but IMO, I jumped out of the frying pan
into the fire. Because of, e.g.:

box.execute("CREATE TABLE t2(id INT PRIMARY KEY, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t2, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t2);")
---
- null
-- Constraint FK1 already exists
+- Duplicate key exists in unique index 'primary' in space ‘_fk_constraint'

Tuple duplication is checked before the corresponding on_replace_dd_

> 8. We don't use /** inside functions.
Removed.

> 9. When you have multiple conditions, it is shorter
> and easier to check them all via && instead of 'if if if ... ‘.
Ok. I thought it was more obvious.

> 
> 13. I remember, you said that 'old_space' -> 'space' is going
> to save some lines, but man. This rename diff is huge. This is
> really comparable with the functional diff in this function. I
> didn't think, that this rename will affect so much code. Please,
> return old_space back. It is not worth introducing such a big
> diff.
Returned.

> 
> 14. Talking of the super mess with on_replace_dd_index, and
> how tight it is now with all these struggling about replacing
> constraints, and on_commit/on_rollback. I think, that this is
> a dead end. Too complex.
> 
> How about introducing a couple of new AlterSpaceOp classes?
> AddConstraint, and DropConstraint. AlterSpaceOp looks good
> because
> - it will allow you to not set on_commit/on_rollback triggers
> manually - it already provides these methods;
> - you will be able to move this huge pieces of code out of
> on_replace_dd_index and other on_replace_*;
> - it is consistent with MoveIndex/DropIndex/etc classes.
> 
> All you will need to do is to add to on_replace_*
> (void) new AddConstraint(), and (void) new DropConstraint.
> And you will keep (void) new MoveConstraints().
> 
> Also that should eliminate code duplication between different
> on_replace_* functions about adding/dropping constraints.
+/**
+ * CreateConstraintDef - add a new constraint def to the space.
+ */
+class CreateConstraintDef: public AlterSpaceOp
+{
+private:
+	struct constraint_def *new_constraint_def;
+public:
+	CreateConstraintDef(struct alter_space *alter,
+			    struct constraint_def *def)
+		:AlterSpaceOp(alter), new_constraint_def(def)
+	{}
+	virtual void alter(struct alter_space *alter);
+	virtual void rollback(struct alter_space *alter);
+};
+
+void
+CreateConstraintDef::alter(struct alter_space *alter)
+{
+	assert(new_constraint_def != NULL);
+	if (space_put_constraint(alter->old_space, new_constraint_def) != 0)
+		panic("Can't recover after constraint alter (out of memory)");
+}
+
+void
+CreateConstraintDef::rollback(struct alter_space *alter)
+{
+	assert(space_pop_constraint(alter->new_space,
+				    new_constraint_def->name) ==
+	       new_constraint_def);
+	(void) alter;
+	constraint_def_delete(new_constraint_def);
+}
+
+/** DropConstraintDef - drop a constraint def from the space. */
+class DropConstraintDef: public AlterSpaceOp
+{
+private:
+	struct constraint_def *old_constraint_def;
+	const char *name;
+public:
+	DropConstraintDef(struct alter_space *alter, const char *name)
+		:AlterSpaceOp(alter), old_constraint_def(NULL), name(name)
+	{}
+	virtual void alter(struct alter_space *alter);
+	virtual void commit(struct alter_space *alter , int64_t signature);
+	virtual void rollback(struct alter_space *alter);
+};
+
+void
+DropConstraintDef::alter(struct alter_space *alter)
+{
+	assert(name != NULL);
+	old_constraint_def =
+		space_constraint_def_by_name(alter->old_space, name);
+	assert(old_constraint_def != NULL);
+	space_pop_constraint(alter->old_space, name);
+}
+
+void
+DropConstraintDef::commit(struct alter_space *alter, int64_t signature)
+{
+	(void) alter;
+	(void) signature;
+	assert(old_constraint_def != NULL);
+	constraint_def_delete(old_constraint_def);
+}
+
+void
+DropConstraintDef::rollback(struct alter_space *alter)
+{
+	assert(old_constraint_def != NULL);
+	if (space_put_constraint(alter->new_space, old_constraint_def) != 0) {
+		panic("Can't recover after constraint drop rollback (out of "
+		      "memory)");
+	}
+}
+

I know, that I can combine these ifs to the one, but I did so, because I believe that
it is more readable.

+		struct index_def *old_def = old_index->def;
+		/*
+		 * We put a new name either an index is becoming
+		 * unique (i.e. constraint), or when an unique
+		 * index's name is under change.
+		 */
+		if (!old_def->opts.is_unique && index_def->opts.is_unique &&
+		    create_index_as_constraint(alter, index_def) != 0)
+			return -1;
+		if (old_def->opts.is_unique && index_def->opts.is_unique &&
+		    strcmp(index_def->name, old_def->name) != 0 &&
+		    (create_index_as_constraint(alter, index_def) != 0 ||
+		     drop_index_as_constraint(alter, old_def) != 0))
+			return -1;
+		if (old_def->opts.is_unique && !index_def->opts.is_unique &&
+		    drop_index_as_constraint(alter, old_def) != 0)
+			return -1;


commit c54d2e89d660d2f0b07f1e6917327131362a6568
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Wed Oct 23 15:54:16 2019 +0300

    sql: make constraint operations transactional
    
    Put constraint names into the space's hash table and drop them on
    replace in corresponding system spaces (_index, _fk_constraint,
    _ck_constraint).
    
    Closes #3503
    
    @TarantoolBot document
    Title: Table constraints in SQL
    
    SQL:
    According to ANSI SQL, table constraint is one of the following
    entities: PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK. Every
    constraint have its own name passed by user or automatically
    generated. And these names must be unique within one table/space.
    Naming in SQL is case-insensitive, so "CONSTRAINT c" and
    "CONSTRAINT C" are the same. For example, you will get error, if
    you try to:
    
    CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
                     CONSTRAINT c PRIMARY KEY (i));
    
    or
    
    CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);
    ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
    
    The same is for any other constraint types.
    
    Lua/box:
    
    You also can create/drop table constraints from box. See
    space_object:create_check_constraint() and space_object:create_index()
    (if an index is unique). Naming in box is case-sensitive, so 'c' and
    'C' are not the same (see naming policy). For example, an unique
    index is a constraint, but a non-unique index is not. So, a non-unique
    index can have the same name with a check/foreign key constraint
    within one space:
    
    box.execute('CREATE TABLE t2 (i INT PRIMARY KEY);');
    box.execute('CREATE INDEX e ON t2(i);');
    box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
    
    But, if you try to:
    
    box.space.T2.index.E:alter({unique = true});
    
    You will get error, beacuse index 'e' is becoming unique.

diff --git a/src/box/alter.cc b/src/box/alter.cc
index bef25b605..c03bad589 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -57,6 +57,7 @@
 #include "version.h"
 #include "sequence.h"
 #include "sql.h"
+#include "constraint_def.h"
 
 /* {{{ Auxiliary functions and methods. */
 
@@ -1033,10 +1034,13 @@ alter_space_rollback(struct trigger *trigger, void * /* event */)
 	space_fill_index_map(alter->old_space);
 	space_fill_index_map(alter->new_space);
 	/*
-	 * Don't forget about space triggers and foreign keys.
+	 * Don't forget about space triggers, foreign keys and
+	 * constraints.
 	 */
 	space_swap_triggers(alter->new_space, alter->old_space);
 	space_swap_fk_constraints(alter->new_space, alter->old_space);
+	SWAP(alter->new_space->constraint_names,
+	     alter->old_space->constraint_names);
 	space_cache_replace(alter->new_space, alter->old_space);
 	alter_space_delete(alter);
 	return 0;
@@ -1143,10 +1147,13 @@ alter_space_do(struct txn_stmt *stmt, struct alter_space *alter)
 	space_fill_index_map(alter->old_space);
 	space_fill_index_map(alter->new_space);
 	/*
-	 * Don't forget about space triggers and foreign keys.
+	 * Don't forget about space triggers, foreign keys and
+	 * constraints.
 	 */
 	space_swap_triggers(alter->new_space, alter->old_space);
 	space_swap_fk_constraints(alter->new_space, alter->old_space);
+	SWAP(alter->new_space->constraint_names,
+	     alter->old_space->constraint_names);
 	/*
 	 * The new space is ready. Time to update the space
 	 * cache with it.
@@ -1764,6 +1771,84 @@ MoveCkConstraints::rollback(struct alter_space *alter)
 	space_swap_ck_constraint(alter->new_space, alter->old_space);
 }
 
+/**
+ * CreateConstraintDef - add a new constraint def to the space.
+ */
+class CreateConstraintDef: public AlterSpaceOp
+{
+private:
+	struct constraint_def *new_constraint_def;
+public:
+	CreateConstraintDef(struct alter_space *alter,
+			    struct constraint_def *def)
+		:AlterSpaceOp(alter), new_constraint_def(def)
+	{}
+	virtual void alter(struct alter_space *alter);
+	virtual void rollback(struct alter_space *alter);
+};
+
+void
+CreateConstraintDef::alter(struct alter_space *alter)
+{
+	assert(new_constraint_def != NULL);
+	if (space_put_constraint(alter->old_space, new_constraint_def) != 0)
+		panic("Can't recover after constraint alter (out of memory)");
+}
+
+void
+CreateConstraintDef::rollback(struct alter_space *alter)
+{
+	assert(space_pop_constraint(alter->new_space,
+				    new_constraint_def->name) ==
+	       new_constraint_def);
+	(void) alter;
+	constraint_def_delete(new_constraint_def);
+}
+
+/** DropConstraintDef - drop a constraint def from the space. */
+class DropConstraintDef: public AlterSpaceOp
+{
+private:
+	struct constraint_def *old_constraint_def;
+	const char *name;
+public:
+	DropConstraintDef(struct alter_space *alter, const char *name)
+		:AlterSpaceOp(alter), old_constraint_def(NULL), name(name)
+	{}
+	virtual void alter(struct alter_space *alter);
+	virtual void commit(struct alter_space *alter , int64_t signature);
+	virtual void rollback(struct alter_space *alter);
+};
+
+void
+DropConstraintDef::alter(struct alter_space *alter)
+{
+	assert(name != NULL);
+	old_constraint_def =
+		space_constraint_def_by_name(alter->old_space, name);
+	assert(old_constraint_def != NULL);
+	space_pop_constraint(alter->old_space, name);
+}
+
+void
+DropConstraintDef::commit(struct alter_space *alter, int64_t signature)
+{
+	(void) alter;
+	(void) signature;
+	assert(old_constraint_def != NULL);
+	constraint_def_delete(old_constraint_def);
+}
+
+void
+DropConstraintDef::rollback(struct alter_space *alter)
+{
+	assert(old_constraint_def != NULL);
+	if (space_put_constraint(alter->new_space, old_constraint_def) != 0) {
+		panic("Can't recover after constraint drop rollback (out of "
+		      "memory)");
+	}
+}
+
 /* }}} */
 
 /**
@@ -2363,6 +2448,57 @@ index_is_used_by_fk_constraint(struct rlist *fk_list, uint32_t iid)
 	return false;
 }
 
+/**
+ * Put the node of an unique index to the constraint hash table of
+ * @a space.
+ *
+ * This function is needed to wrap the duplicated piece of code
+ * inside on_replace_dd_index().
+ */
+static int
+create_index_as_constraint(struct alter_space *alter,
+			   const struct index_def *def)
+{
+	assert(def->opts.is_unique);
+	struct space *space = alter->old_space;
+	if (space_constraint_def_by_name(space, def->name) != NULL) {
+		diag_set(ClientError, ER_CONSTRAINT_EXISTS, def->name);
+		return -1;
+	}
+	struct constraint_def *constr_def =
+		constraint_def_new(space->def->id, def->iid == 0 ?
+				   CONSTRAINT_TYPE_PK : CONSTRAINT_TYPE_UNIQUE,
+				   def->name);
+	if (constr_def == NULL)
+		return -1;
+	try {
+		(void) new CreateConstraintDef(alter, constr_def);
+	} catch (Exception *e) {
+		constraint_def_delete(constr_def);
+		return -1;
+	}
+	return 0;
+}
+
+/**
+ * Drop the node of an unique index from the constraint hash table
+ * of @a space.
+ *
+ * This function is needed to wrap the duplicated piece of code
+ * inside on_replace_dd_index().
+ */
+static int
+drop_index_as_constraint(struct alter_space *alter, const struct index_def *def)
+{
+	assert(def->opts.is_unique);
+	try {
+		(void) new DropConstraintDef(alter, def->name);
+	} catch (Exception *e) {
+		return -1;
+	}
+	return 0;
+}
+
 /**
  * Just like with _space, 3 major cases:
  *
@@ -2500,6 +2636,9 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 		}
 		if (alter_space_move_indexes(alter, 0, iid) != 0)
 			return -1;
+		if (old_index->def->opts.is_unique &&
+		    drop_index_as_constraint(alter, old_index->def) != 0)
+			return -1;
 		try {
 			(void) new DropIndex(alter, old_index);
 		} catch (Exception *e) {
@@ -2510,18 +2649,23 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 	if (old_index == NULL && new_tuple != NULL) {
 		if (alter_space_move_indexes(alter, 0, iid))
 			return -1;
+		struct index_def *def =
+			index_def_new_from_tuple(new_tuple, old_space);
+		if (def == NULL)
+			return -1;
+		if (def->opts.is_unique &&
+		    create_index_as_constraint(alter, def) != 0) {
+			index_def_delete(def);
+			return -1;
+		}
 		CreateIndex *create_index;
 		try {
 			create_index = new CreateIndex(alter);
 		} catch (Exception *e) {
 			return -1;
 		}
-		create_index->new_index_def =
-			index_def_new_from_tuple(new_tuple, old_space);
-		if (create_index->new_index_def == NULL)
-			return -1;
-		index_def_update_optionality(create_index->new_index_def,
-					     alter->new_min_field_count);
+		create_index->new_index_def = def;
+		index_def_update_optionality(def, alter->new_min_field_count);
 	}
 	/* Case 3 and 4: check if we need to rebuild index data. */
 	if (old_index != NULL && new_tuple != NULL) {
@@ -2531,6 +2675,23 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 			return -1;
 		auto index_def_guard =
 			make_scoped_guard([=] { index_def_delete(index_def); });
+		struct index_def *old_def = old_index->def;
+		/*
+		 * We put a new name either an index is becoming
+		 * unique (i.e. constraint), or when an unique
+		 * index's name is under change.
+		 */
+		if (!old_def->opts.is_unique && index_def->opts.is_unique &&
+		    create_index_as_constraint(alter, index_def) != 0)
+			return -1;
+		if (old_def->opts.is_unique && index_def->opts.is_unique &&
+		    strcmp(index_def->name, old_def->name) != 0 &&
+		    (create_index_as_constraint(alter, index_def) != 0 ||
+		     drop_index_as_constraint(alter, old_def) != 0))
+			return -1;
+		if (old_def->opts.is_unique && !index_def->opts.is_unique &&
+		    drop_index_as_constraint(alter, old_def) != 0)
+			return -1;
 		/*
 		 * To detect which key parts are optional,
 		 * min_field_count is required. But
@@ -4996,8 +5157,15 @@ on_create_fk_constraint_rollback(struct trigger *trigger, void *event)
 	struct fk_constraint *fk = (struct fk_constraint *)trigger->data;
 	rlist_del_entry(fk, in_parent_space);
 	rlist_del_entry(fk, in_child_space);
+	struct space *child = space_by_id(fk->def->child_id);
+	const char *name = fk->def->name;
+	struct constraint_def *constr_def =
+		space_constraint_def_by_name(child, name);
+	assert(constr_def != NULL);
+	space_pop_constraint(child, name);
+	constraint_def_delete(constr_def);
 	space_reset_fk_constraint_mask(space_by_id(fk->def->parent_id));
-	space_reset_fk_constraint_mask(space_by_id(fk->def->child_id));
+	space_reset_fk_constraint_mask(child);
 	fk_constraint_delete(fk);
 	return 0;
 }
@@ -5031,11 +5199,24 @@ on_drop_fk_constraint_rollback(struct trigger *trigger, void *event)
 	struct space *child = space_by_id(old_fk->def->child_id);
 	rlist_add_entry(&child->child_fk_constraint, old_fk, in_child_space);
 	rlist_add_entry(&parent->parent_fk_constraint, old_fk, in_parent_space);
+	const char *name = old_fk->def->name;
+	struct constraint_def *constr_def =
+		constraint_def_new(child->def->id, CONSTRAINT_TYPE_FK, name);
+	if (constr_def == NULL)
+		goto panic;
+	if (space_put_constraint(child, constr_def) != 0) {
+		constraint_def_delete(constr_def);
+		goto panic;
+	}
 	fk_constraint_set_mask(old_fk, &child->fk_constraint_mask,
 			       FIELD_LINK_CHILD);
 	fk_constraint_set_mask(old_fk, &parent->fk_constraint_mask,
 			       FIELD_LINK_PARENT);
 	return 0;
+
+panic:
+	panic("Can't recover after FK constraint drop rollback (out of "
+	      "memory)");
 }
 
 /**
@@ -5210,6 +5391,13 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 		fk->def = fk_def;
 		fk->index_id = fk_index->def->iid;
 		if (old_tuple == NULL) {
+			if (space_constraint_def_by_name(child_space,
+							 fk_def->name)
+			    != NULL) {
+				diag_set(ClientError, ER_CONSTRAINT_EXISTS,
+					 fk_def->name);
+				return -1;
+			}
 			rlist_add_entry(&child_space->child_fk_constraint,
 					fk, in_child_space);
 			rlist_add_entry(&parent_space->parent_fk_constraint,
@@ -5219,6 +5407,16 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 						      fk);
 			if (on_rollback == NULL)
 				return -1;
+			struct constraint_def *constr_def =
+				constraint_def_new(child_space->def->id,
+						   CONSTRAINT_TYPE_FK,
+						   fk_def->name);
+			if (constr_def == NULL)
+				return -1;
+			if (space_put_constraint(child_space, constr_def) != 0) {
+				constraint_def_delete(constr_def);
+				return -1;
+			}
 			txn_stmt_on_rollback(stmt, on_rollback);
 			fk_constraint_set_mask(fk,
 					       &parent_space->fk_constraint_mask,
@@ -5279,6 +5477,12 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 					      old_fk);
 		if (on_rollback == NULL)
 			return -1;
+		const char *name = fk_def->name;
+		struct constraint_def *constr_def =
+			space_constraint_def_by_name(child_space, name);
+		assert(constr_def != NULL);
+		space_pop_constraint(child_space, name);
+		constraint_def_delete(constr_def);
 		txn_stmt_on_rollback(stmt, on_rollback);
 		space_reset_fk_constraint_mask(child_space);
 		space_reset_fk_constraint_mask(parent_space);
@@ -5344,9 +5548,14 @@ on_create_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
 	assert(ck != NULL);
 	struct space *space = space_by_id(ck->def->space_id);
 	assert(space != NULL);
-	assert(space_ck_constraint_by_name(space, ck->def->name,
-					   strlen(ck->def->name)) != NULL);
+	const char *name = ck->def->name;
+	assert(space_ck_constraint_by_name(space, name, strlen(name)) != NULL);
 	space_remove_ck_constraint(space, ck);
+	struct constraint_def *constr_def =
+		space_constraint_def_by_name(space, name);
+	assert(constr_def != NULL);
+	space_pop_constraint(space, name);
+	constraint_def_delete(constr_def);
 	ck_constraint_delete(ck);
 	if (trigger_run(&on_alter_space, space) != 0)
 		return -1;
@@ -5371,13 +5580,23 @@ on_drop_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
 	assert(ck != NULL);
 	struct space *space = space_by_id(ck->def->space_id);
 	assert(space != NULL);
-	assert(space_ck_constraint_by_name(space, ck->def->name,
-					   strlen(ck->def->name)) == NULL);
-	if (space_add_ck_constraint(space, ck) != 0)
-		panic("Can't recover after CK constraint drop rollback");
+	const char *name = ck->def->name;
+	assert(space_ck_constraint_by_name(space, name, strlen(name)) == NULL);
+	struct constraint_def *constr_def =
+		constraint_def_new(space->def->id, CONSTRAINT_TYPE_CK, name);
+	if (constr_def == NULL)
+		goto panic;
+	if (space_add_ck_constraint(space, ck) != 0 ||
+	    space_put_constraint(space, constr_def) != 0) {
+		constraint_def_delete(constr_def);
+		goto panic;
+	}
 	if (trigger_run(&on_alter_space, space) != 0)
 		return -1;
 	return 0;
+
+panic:
+	panic("Can't recover after CK constraint drop rollback");
 }
 
 /** Commit REPLACE check constraint. */
@@ -5491,11 +5710,27 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		auto ck_guard = make_scoped_guard([=] {
 			ck_constraint_delete(new_ck_constraint);
 		});
-		if (old_ck_constraint != NULL)
+		if (old_ck_constraint != NULL) {
 			rlist_del_entry(old_ck_constraint, link);
+		} else if (space_constraint_def_by_name(space, name) != NULL) {
+			diag_set(ClientError, ER_CONSTRAINT_EXISTS,
+				 name);
+			return -1;
+		}
 		if (space_add_ck_constraint(space, new_ck_constraint) != 0)
 			return -1;
 		ck_guard.is_active = false;
+		if (old_ck_constraint == NULL) {
+			struct constraint_def *constr_def =
+				constraint_def_new(space->def->id,
+						   CONSTRAINT_TYPE_CK, name);
+			if (constr_def == NULL)
+				return -1;
+			if (space_put_constraint(space, constr_def) != 0) {
+				constraint_def_delete(constr_def);
+				return -1;
+			}
+		}
 		if (old_tuple != NULL) {
 			on_rollback->data = old_ck_constraint;
 			on_rollback->run = on_replace_ck_constraint_rollback;
@@ -5515,7 +5750,13 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 			return -1;
 		struct ck_constraint *old_ck_constraint =
 			space_ck_constraint_by_name(space, name, name_len);
+		struct constraint_def *constr_def =
+			space_constraint_def_by_name(space,
+						     old_ck_constraint->def->name);
+		assert(constr_def != NULL);
 		assert(old_ck_constraint != NULL);
+		space_pop_constraint(space, old_ck_constraint->def->name);
+		constraint_def_delete(constr_def);
 		space_remove_ck_constraint(space, old_ck_constraint);
 		on_commit->data = old_ck_constraint;
 		on_commit->run = on_drop_ck_constraint_commit;
diff --git a/test/sql/constraint.result b/test/sql/constraint.result
new file mode 100644
index 000000000..ba262182b
--- /dev/null
+++ b/test/sql/constraint.result
@@ -0,0 +1,190 @@
+-- test-run result file version 2
+test_run = require('test_run').new()
+ | ---
+ | ...
+engine = test_run:get_cfg('engine')
+ | ---
+ | ...
+box.execute('pragma sql_default_engine=\''..engine..'\'')
+ | ---
+ | - row_count: 0
+ | ...
+test_run:cmd("setopt delimiter ';'")
+ | ---
+ | - true
+ | ...
+
+--
+-- Check a constraint name for duplicate within a single
+-- <CREATE TABLE> statement.
+--
+box.execute('CREATE TABLE t1 (i INT PRIMARY KEY);');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c UNIQUE (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c UNIQUE (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c CHECK (i < 0));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c CHECK (i > 0));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+
+--
+-- Check a constraint name for duplicate using <ALTER TABLE>
+-- statement.
+--
+box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);');
+ | ---
+ | - null
+ | - Index 'C' already exists in space 'T2'
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);');
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+
+--
+-- Make sure that a constraint's name isn't kept after the
+-- constraint drop.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('DROP INDEX d ON t2;');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);');
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.ck_constraint.D:drop();
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);');
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;');
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- The same inside a transaction.
+--
+box.begin()
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+box.space.T2.ck_constraint.D:drop()
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+box.commit();
+ | ---
+ | ...
+
+--
+-- Make sure, that altering of an index name affect to its record
+-- in the space's constraint hash table.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);');
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.D:alter({name = 'E'});
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+ | ---
+ | - null
+ | - Constraint E already exists
+ | ...
+
+--
+-- Make sure, that altering of an index uniqueness puts/drops
+-- its name to/from the space's constraint hash table.
+--
+box.space.T2.index.E:alter({unique = false});
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.E:alter({unique = true});
+ | ---
+ | - error: Constraint E already exists
+ | ...
+box.space.T2.ck_constraint.E:drop();
+ | ---
+ | ...
+box.space.T2.index.E:alter({unique = true});
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+ | ---
+ | - null
+ | - Constraint E already exists
+ | ...
+
+-- Alter name and uniqueness of an unique index simultaneously.
+box.space.T2.index.E:alter({name = 'D', unique = false});
+ | ---
+ | ...
+box.execute('CREATE UNIQUE INDEX e ON t2(i);');
+ | ---
+ | - row_count: 1
+ | ...
diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
new file mode 100755
index 000000000..50162e47d
--- /dev/null
+++ b/test/sql/constraint.test.lua
@@ -0,0 +1,84 @@
+test_run = require('test_run').new()
+engine = test_run:get_cfg('engine')
+box.execute('pragma sql_default_engine=\''..engine..'\'')
+test_run:cmd("setopt delimiter ';'")
+
+--
+-- Check a constraint name for duplicate within a single
+-- <CREATE TABLE> statement.
+--
+box.execute('CREATE TABLE t1 (i INT PRIMARY KEY);');
+box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+box.execute([[CREATE TABLE t2 (i INT,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c UNIQUE (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c UNIQUE (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c CHECK (i < 0));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c CHECK (i > 0));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
+
+--
+-- Check a constraint name for duplicate using <ALTER TABLE>
+-- statement.
+--
+box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);');
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);');
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);');
+
+--
+-- Make sure that a constraint's name isn't kept after the
+-- constraint drop.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);');
+box.execute('DROP INDEX d ON t2;');
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);');
+box.space.T2.ck_constraint.D:drop();
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);');
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;');
+
+--
+-- The same inside a transaction.
+--
+box.begin()
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+box.space.T2.ck_constraint.D:drop()
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+box.commit();
+
+--
+-- Make sure, that altering of an index name affect to its record
+-- in the space's constraint hash table.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);');
+box.space.T2.index.D:alter({name = 'E'});
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+
+--
+-- Make sure, that altering of an index uniqueness puts/drops
+-- its name to/from the space's constraint hash table.
+--
+box.space.T2.index.E:alter({unique = false});
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+box.space.T2.index.E:alter({unique = true});
+box.space.T2.ck_constraint.E:drop();
+box.space.T2.index.E:alter({unique = true});
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
+
+-- Alter name and uniqueness of an unique index simultaneously.
+box.space.T2.index.E:alter({name = 'D', unique = false});
+box.execute('CREATE UNIQUE INDEX e ON t2(i);');

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

* Re: [Tarantool-patches] [PATCH v2 0/3] Add constraint names hash table to space
  2019-12-04 16:23   ` [Tarantool-patches] [PATCH v2 0/3] " Roman Khabibov
@ 2019-12-07 16:34     ` Vladislav Shpilevoy
  0 siblings, 0 replies; 29+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-07 16:34 UTC (permalink / raw)
  To: Roman Khabibov, tarantool-patches

Hi! Thanks for the fixes!

On 04/12/2019 17:23, Roman Khabibov wrote:
> 
> 
>> On Nov 30, 2019, at 04:03, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
>>
>> Hi! Thanks for the patch!
>>
>> See 2 comments below!
>>
>> On 28/11/2019 19:34, Roman Khabibov wrote:
>>> I have made essential changes to the previous patch, so I decided
>>> to send it as v2.
>>>
>>> 1) I still don't understand, why we need to store constraint id. If we have the
>>> query "ALTER TABLE T DROP CONSTRAINT C", we just get struct space by its name,
>>> then find the corresponding constraint_def node by the name and emit opcode on
>>> replace in _index/_fk_constraint/_ck_constraint depending on
>>> constraint_def->type.
>>
>> 1. ID is a primary index. You can delete by ID. Is it always
>> possible to delete by name? Do all the constraint spaces have a
>> unique index over name consisting of one column?
> I know a space_id. So, I will delete tuples from _index, _ck, _fk by their unique
> composite indexes:
> +----------+------------+
> | space_id | name/iid   |
> +----------+------------+
> 
> If we want to delete by ID, let’s add it to _ck and _fk and keep it in struct constraint_def also. But, IMO, it is not about my patchset.
> 

You are right - ID won't help here. _index/_ck/_fk anyway
either have a unique index by <space_id, name>, or does not
have their own ID. So constraint ID won't help.

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

* Re: [Tarantool-patches] [PATCH v2 1/3] box: introduce constraint names hash table
  2019-12-04 16:23     ` [Tarantool-patches] [PATCH v2 1/3] " Roman Khabibov
@ 2019-12-07 16:34       ` Vladislav Shpilevoy
  2019-12-10 12:48         ` Roman Khabibov
  0 siblings, 1 reply; 29+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-07 16:34 UTC (permalink / raw)
  To: Roman Khabibov, tarantool-patches

Thanks for the fixes!

>>> diff --git a/src/box/constraint_def.h b/src/box/constraint_def.h
>>> new file mode 100755
>>> index 000000000..ef238eb46
>>> --- /dev/null
>>> +++ b/src/box/constraint_def.h
>>> @@ -0,0 +1,83 @@
>>> +#ifndef INCLUDES_BOX_CONSTRAINT_DEF_H
>>> +#define INCLUDES_BOX_CONSTRAINT_DEF_H
>>
>> 3. Please, take a look at other header guards. They
>> have a certain naming policy. For example, tuple.h in
>> box/ has a guard TARANTOOL_BOX_TUPLE_H_INCLUDED. I guess
>> you could use TARANTOOL_BOX_CONSTRAINT_DEF_H_INCLUDED.
>>
>> But even better would be to use #pragma once. I don't
>> know whether they are allowed in our code though.
> I think #pragma is disallowed, because I didn’t found it by grep in the
> src.
> 

I asked Kirill. He said that pragma is a great idea. Please,
use it in all new files, including this one.

>>> --- a/src/box/space.h
>>> +++ b/src/box/space.h
>>> @@ -516,6 +519,42 @@ space_add_ck_constraint(struct space *space, struct ck_constraint *ck);
>>> +struct constraint_def *
>>> +space_constraint_def_by_name(struct space *space, const char *name);
>>> +
>>> +/**
>>> + * Put node with @a def to the constraint hash table of @a space.
>>> + *
>>> + * @param space Space.
>>> + * @param def   Constraint def.
>>> + *
>>> + * @retval  0 Success.
>>> + * @retval -1 Memory allocation error.
>>> + */
>>> +int
>>> +space_put_constraint(struct space *space, struct constraint_def *def);
>>> +
>>> +/**
>>> + * Remove node with @a name from the constraint hash table of @a
>>> + * space. But don't destroy the constraint def object binded to
>>> + * this @a name.
>>> + *
>>> + * @param space Space.
>>> + * @param name  Constraint name.
>>> + */
>>> +void
>>> +space_drop_constraint(struct space *space, const char *name);
>>
>> 6. That function is a bit confusing. I understand, why
>> do you need to not destroy a constraint right here. But
>> drop() name really assumes that. Moreover, in most of the
>> places you do space_constraint_def_by_name() before
>> space_drop_constraint(). So this is a double search. I
>> propose you to rename drop to pop, and make it return the
>> old constraint. It will make clear, that the constraint is
>> not deleted here, and will eliminate the double search.
> But the first one is almost copy of the second one. Is it ok?

Yes. These two functions are small enough. I tried to make
a third one which they would use, but it did not make the
code smaller.

> diff --git a/src/box/space.h b/src/box/space.h
> index 7926aa65e..0c23c224c 100644
> --- a/src/box/space.h
> +++ b/src/box/space.h
> @@ -234,6 +235,8 @@ struct space {
>  	 * of parent constraints as well as child ones.
>  	 */
>  	uint64_t fk_constraint_mask;
> +	/** Hash table with constraint names. */
> +	struct mh_strnptr_t *constraint_names;

Please, rename to 'constraints'. These are not just 'names'
anymore. Sorry for the nit.

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

* Re: [Tarantool-patches] [PATCH v2 2/3] sql: make constraint operations transactional
  2019-12-05 18:43     ` Roman Khabibov
@ 2019-12-07 16:35       ` Vladislav Shpilevoy
  2019-12-10 12:49         ` [Tarantool-patches] [PATCH v2 2/3] box: " Roman Khabibov
  0 siblings, 1 reply; 29+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-07 16:35 UTC (permalink / raw)
  To: Roman Khabibov, tarantool-patches

Thanks for the fixes!

See 9 comments below.

>> 14. Talking of the super mess with on_replace_dd_index, and
>> how tight it is now with all these struggling about replacing
>> constraints, and on_commit/on_rollback. I think, that this is
>> a dead end. Too complex.
>>
>> How about introducing a couple of new AlterSpaceOp classes?
>> AddConstraint, and DropConstraint. AlterSpaceOp looks good
>> because
>> - it will allow you to not set on_commit/on_rollback triggers
>> manually - it already provides these methods;
>> - you will be able to move this huge pieces of code out of
>> on_replace_dd_index and other on_replace_*;
>> - it is consistent with MoveIndex/DropIndex/etc classes.
>>
>> All you will need to do is to add to on_replace_*
>> (void) new AddConstraint(), and (void) new DropConstraint.
>> And you will keep (void) new MoveConstraints().
>>
>> Also that should eliminate code duplication between different
>> on_replace_* functions about adding/dropping constraints.
> +/**
> + * CreateConstraintDef - add a new constraint def to the space.
> + */
> +class CreateConstraintDef: public AlterSpaceOp
> +{
> +private:
> +	struct constraint_def *new_constraint_def;
> +public:
> +	CreateConstraintDef(struct alter_space *alter,
> +			    struct constraint_def *def)
> +		:AlterSpaceOp(alter), new_constraint_def(def)
> +	{}
> +	virtual void alter(struct alter_space *alter);
> +	virtual void rollback(struct alter_space *alter);
> +};
> +
> +void
> +CreateConstraintDef::alter(struct alter_space *alter)
> +{
> +	assert(new_constraint_def != NULL);
> +	if (space_put_constraint(alter->old_space, new_constraint_def) != 0)
> +		panic("Can't recover after constraint alter (out of memory)");

1. Wow, why panic? Alter can safely fail. It it is not a commit or
rollback trigger. Maybe it can't return an error, but it can throw
it.

(Although I would like to be able to panic on malloc fails, but it
does not seem to happen in the nearest future.)

> +}
> +
> +void
> +CreateConstraintDef::rollback(struct alter_space *alter)
> +{
> +	assert(space_pop_constraint(alter->new_space,
> +				    new_constraint_def->name) ==
> +	       new_constraint_def);

2. This will fail in Release build mode, because
assert arguments are not calculated in Release. So you
delete the object, but it is still in the hash table.

> +	(void) alter;
> +	constraint_def_delete(new_constraint_def);
> +}
> +
> 
> I know, that I can combine these ifs to the one, but I did so, because I believe that
> it is more readable.

3. Yes, here I agree.

> 
> +		struct index_def *old_def = old_index->def;
> +		/*
> +		 * We put a new name either an index is becoming
> +		 * unique (i.e. constraint), or when an unique
> +		 * index's name is under change.
> +		 */
> +		if (!old_def->opts.is_unique && index_def->opts.is_unique &&
> +		    create_index_as_constraint(alter, index_def) != 0)
> +			return -1;
> +		if (old_def->opts.is_unique && index_def->opts.is_unique &&
> +		    strcmp(index_def->name, old_def->name) != 0 &&
> +		    (create_index_as_constraint(alter, index_def) != 0 ||
> +		     drop_index_as_constraint(alter, old_def) != 0))
> +			return -1;
> +		if (old_def->opts.is_unique && !index_def->opts.is_unique &&
> +		    drop_index_as_constraint(alter, old_def) != 0)
> +			return -1;
> 
> 
> commit c54d2e89d660d2f0b07f1e6917327131362a6568
> Author: Roman Khabibov <roman.habibov@tarantool.org>
> Date:   Wed Oct 23 15:54:16 2019 +0300
> 
>     sql: make constraint operations transactional
>     
>     Put constraint names into the space's hash table and drop them on
>     replace in corresponding system spaces (_index, _fk_constraint,
>     _ck_constraint).
>     
>     Closes #3503
>     
>     @TarantoolBot document
>     Title: Table constraints in SQL
>     
>     SQL:
>     According to ANSI SQL, table constraint is one of the following
>     entities: PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK. Every
>     constraint have its own name passed by user or automatically
>     generated. And these names must be unique within one table/space.
>     Naming in SQL is case-insensitive, so "CONSTRAINT c" and
>     "CONSTRAINT C" are the same. For example, you will get error, if
>     you try to:
>     
>     CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
>                      CONSTRAINT c PRIMARY KEY (i));
>     
>     or
>     
>     CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);
>     ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
>     
>     The same is for any other constraint types.
>     
>     Lua/box:
>     
>     You also can create/drop table constraints from box. See
>     space_object:create_check_constraint() and space_object:create_index()
>     (if an index is unique). Naming in box is case-sensitive, so 'c' and
>     'C' are not the same (see naming policy). For example, an unique
>     index is a constraint, but a non-unique index is not. So, a non-unique
>     index can have the same name with a check/foreign key constraint
>     within one space:
>     
>     box.execute('CREATE TABLE t2 (i INT PRIMARY KEY);');
>     box.execute('CREATE INDEX e ON t2(i);');
>     box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
>     
>     But, if you try to:
>     
>     box.space.T2.index.E:alter({unique = true});
>     
>     You will get error, beacuse index 'e' is becoming unique.

4. That is a good comment. (But typo: beacuse -> because.)

> 
> diff --git a/src/box/alter.cc b/src/box/alter.cc
> index bef25b605..c03bad589 100644
> --- a/src/box/alter.cc
> +++ b/src/box/alter.cc> @@ -2510,18 +2649,23 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
>  	if (old_index == NULL && new_tuple != NULL) {
>  		if (alter_space_move_indexes(alter, 0, iid))
>  			return -1;
> +		struct index_def *def =
> +			index_def_new_from_tuple(new_tuple, old_space);
> +		if (def == NULL)
> +			return -1;
> +		if (def->opts.is_unique &&
> +		    create_index_as_constraint(alter, def) != 0) {
> +			index_def_delete(def);
> +			return -1;
> +		}
>  		CreateIndex *create_index;
>  		try {
>  			create_index = new CreateIndex(alter);
>  		} catch (Exception *e) {
>  			return -1;

5. 'def' leaks here.

>  		}
> -		create_index->new_index_def =
> -			index_def_new_from_tuple(new_tuple, old_space);
> -		if (create_index->new_index_def == NULL)
> -			return -1;
> -		index_def_update_optionality(create_index->new_index_def,
> -					     alter->new_min_field_count);
> +		create_index->new_index_def = def;
> +		index_def_update_optionality(def, alter->new_min_field_count);
>  	}
>  	/* Case 3 and 4: check if we need to rebuild index data. */
>  	if (old_index != NULL && new_tuple != NULL) {
> @@ -2531,6 +2675,23 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
>  			return -1;
>  		auto index_def_guard =
>  			make_scoped_guard([=] { index_def_delete(index_def); });
> +		struct index_def *old_def = old_index->def;
> +		/*
> +		 * We put a new name either an index is becoming

6. 'either' -> 'when either'.

> +		 * unique (i.e. constraint), or when an unique
> +		 * index's name is under change.
> +		 */
> +		if (!old_def->opts.is_unique && index_def->opts.is_unique &&
> +		    create_index_as_constraint(alter, index_def) != 0)
> +			return -1;
> +		if (old_def->opts.is_unique && index_def->opts.is_unique &&
> +		    strcmp(index_def->name, old_def->name) != 0 &&
> +		    (create_index_as_constraint(alter, index_def) != 0 ||
> +		     drop_index_as_constraint(alter, old_def) != 0))
> +			return -1;
> +		if (old_def->opts.is_unique && !index_def->opts.is_unique &&
> +		    drop_index_as_constraint(alter, old_def) != 0)
> +			return -1;
>  		/*
>  		 * To detect which key parts are optional,
>  		 * min_field_count is required. But> @@ -5219,6 +5407,16 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
>  						      fk);
>  			if (on_rollback == NULL)
>  				return -1;
> +			struct constraint_def *constr_def =
> +				constraint_def_new(child_space->def->id,
> +						   CONSTRAINT_TYPE_FK,
> +						   fk_def->name);
> +			if (constr_def == NULL)
> +				return -1;
> +			if (space_put_constraint(child_space, constr_def) != 0) {
> +				constraint_def_delete(constr_def);
> +				return -1;
> +			}

7. Sequence constraint_def_new -> space_put_constraint is repeated 4
times. I think this is worth wrapping into a function. The same about
space_constraint_def_by_name -> space_pop_constraint ->
constraint_def_delete.

>  			txn_stmt_on_rollback(stmt, on_rollback);
>  			fk_constraint_set_mask(fk,
>  					       &parent_space->fk_constraint_mask,
> diff --git a/test/sql/constraint.result b/test/sql/constraint.result
> new file mode 100644
> index 000000000..ba262182b
> --- /dev/null
> +++ b/test/sql/constraint.result
> @@ -0,0 +1,190 @@
> +-- test-run result file version 2
> +test_run = require('test_run').new()
> + | ---
> + | ...
> +engine = test_run:get_cfg('engine')
> + | ---
> + | ...
> +box.execute('pragma sql_default_engine=\''..engine..'\'')
> + | ---
> + | - row_count: 0
> + | ...
> +test_run:cmd("setopt delimiter ';'")

8. Please, don't. There is only one small place where you need
multiple lines. You can either use ';' just for it, or use /.

> diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
> new file mode 100755
> index 000000000..50162e47d
> --- /dev/null
> +++ b/test/sql/constraint.test.lua

9. Your test leaves lots of not deleted data. Please, do
cleanup.

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

* Re: [Tarantool-patches] [PATCH v2 1/3] box: introduce constraint names hash table
  2019-12-07 16:34       ` Vladislav Shpilevoy
@ 2019-12-10 12:48         ` Roman Khabibov
  0 siblings, 0 replies; 29+ messages in thread
From: Roman Khabibov @ 2019-12-10 12:48 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Thanks for the review.

> On Dec 7, 2019, at 19:34, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Thanks for the fixes!
> 
>>>> diff --git a/src/box/constraint_def.h b/src/box/constraint_def.h
>>>> new file mode 100755
>>>> index 000000000..ef238eb46
>>>> --- /dev/null
>>>> +++ b/src/box/constraint_def.h
>>>> @@ -0,0 +1,83 @@
>>>> +#ifndef INCLUDES_BOX_CONSTRAINT_DEF_H
>>>> +#define INCLUDES_BOX_CONSTRAINT_DEF_H
>>> 
>>> 3. Please, take a look at other header guards. They
>>> have a certain naming policy. For example, tuple.h in
>>> box/ has a guard TARANTOOL_BOX_TUPLE_H_INCLUDED. I guess
>>> you could use TARANTOOL_BOX_CONSTRAINT_DEF_H_INCLUDED.
>>> 
>>> But even better would be to use #pragma once. I don't
>>> know whether they are allowed in our code though.
>> I think #pragma is disallowed, because I didn’t found it by grep in the
>> src.
>> 
> 
> I asked Kirill. He said that pragma is a great idea. Please,
> use it in all new files, including this one.
+++ b/src/box/constraint_def.h
@@ -0,0 +1,83 @@
+#pragma once
+

>>>> --- a/src/box/space.h
>>>> +++ b/src/box/space.h
>>>> @@ -516,6 +519,42 @@ space_add_ck_constraint(struct space *space, struct ck_constraint *ck);
>>>> +struct constraint_def *
>>>> +space_constraint_def_by_name(struct space *space, const char *name);
>>>> +
>>>> +/**
>>>> + * Put node with @a def to the constraint hash table of @a space.
>>>> + *
>>>> + * @param space Space.
>>>> + * @param def   Constraint def.
>>>> + *
>>>> + * @retval  0 Success.
>>>> + * @retval -1 Memory allocation error.
>>>> + */
>>>> +int
>>>> +space_put_constraint(struct space *space, struct constraint_def *def);
>>>> +
>>>> +/**
>>>> + * Remove node with @a name from the constraint hash table of @a
>>>> + * space. But don't destroy the constraint def object binded to
>>>> + * this @a name.
>>>> + *
>>>> + * @param space Space.
>>>> + * @param name  Constraint name.
>>>> + */
>>>> +void
>>>> +space_drop_constraint(struct space *space, const char *name);
>>> 
>>> 6. That function is a bit confusing. I understand, why
>>> do you need to not destroy a constraint right here. But
>>> drop() name really assumes that. Moreover, in most of the
>>> places you do space_constraint_def_by_name() before
>>> space_drop_constraint(). So this is a double search. I
>>> propose you to rename drop to pop, and make it return the
>>> old constraint. It will make clear, that the constraint is
>>> not deleted here, and will eliminate the double search.
>> But the first one is almost copy of the second one. Is it ok?
> 
> Yes. These two functions are small enough. I tried to make
> a third one which they would use, but it did not make the
> code smaller.
> 
>> diff --git a/src/box/space.h b/src/box/space.h
>> index 7926aa65e..0c23c224c 100644
>> --- a/src/box/space.h
>> +++ b/src/box/space.h
>> @@ -234,6 +235,8 @@ struct space {
>> 	 * of parent constraints as well as child ones.
>> 	 */
>> 	uint64_t fk_constraint_mask;
>> +	/** Hash table with constraint names. */
>> +	struct mh_strnptr_t *constraint_names;
> 
> Please, rename to 'constraints'. These are not just 'names'
> anymore. Sorry for the nit.
Done.

commit aa06c0665af78a66036da88aad8001ffbd773551
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Tue Nov 5 18:06:59 2019 +0300

    box: introduce constraint names hash table
    
    Add hash table and API for interaction with it to struct space.
    This hash table is needed to keep constraint of a table together
    and check their duplicates by name.
    
    Part of #3503

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 5cd5cba81..bf3895262 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -102,6 +102,7 @@ add_library(box STATIC
     sequence.c
     ck_constraint.c
     fk_constraint.c
+    constraint_def.c
     func.c
     func_def.c
     key_list.c
diff --git a/src/box/constraint_def.c b/src/box/constraint_def.c
new file mode 100755
index 000000000..1d4367532
--- /dev/null
+++ b/src/box/constraint_def.c
@@ -0,0 +1,61 @@
+/*
+ * 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 "constraint_def.h"
+#include "assoc.h"
+#include "errcode.h"
+#include "diag.h"
+#include <string.h>
+#include <stdlib.h>
+
+struct constraint_def *
+constraint_def_new(uint32_t space_id, enum constraint_type type,
+		   const char *name)
+{
+	uint32_t len = strlen(name);
+	uint32_t size = sizeof(struct constraint_def) + len + 1;
+	struct constraint_def *ret = malloc(size);
+	if (ret == NULL) {
+		diag_set(OutOfMemory, size, "malloc", "ret");
+		return NULL;
+	}
+	ret->space_id = space_id;
+	ret->type = type;
+	memcpy(ret->name, name, len + 1);
+	return ret;
+}
+
+void
+constraint_def_delete(struct constraint_def *def)
+{
+	free(def);
+}
diff --git a/src/box/constraint_def.h b/src/box/constraint_def.h
new file mode 100755
index 000000000..d1b1a725a
--- /dev/null
+++ b/src/box/constraint_def.h
@@ -0,0 +1,83 @@
+#pragma once
+
+/*
+ * 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>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+enum constraint_type {
+	CONSTRAINT_TYPE_PK = 0,
+	CONSTRAINT_TYPE_UNIQUE,
+	CONSTRAINT_TYPE_FK,
+	CONSTRAINT_TYPE_CK,
+
+	CONSTRAINT_TYPE_MAX,
+};
+
+struct constraint_def {
+	/** Space id. */
+	uint32_t space_id;
+	/** Constraint type. */
+	enum constraint_type type;
+	/** Zero-terminated string with name. */
+	char name[0];
+};
+
+/**
+ * Allocate memory and construct constraint def object.
+ *
+ * @param space_id Space id.
+ * @param type     Constraint type.
+ * @param name     Constraint name.
+ *
+ * @retval ptr  Constraint def object.
+ * @retval NULL Memory allocation error.
+ */
+struct constraint_def *
+constraint_def_new(uint32_t space_id, enum constraint_type type,
+		   const char *name);
+
+/**
+ * Free memory of constraint def object.
+ *
+ * @param def Constraint def.
+ */
+void
+constraint_def_delete(struct constraint_def *def);
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/src/box/space.c b/src/box/space.c
index 94716a414..cfae93cf1 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -45,6 +45,8 @@
 #include "iproto_constants.h"
 #include "schema.h"
 #include "ck_constraint.h"
+#include "assoc.h"
+#include "constraint_def.h"
 
 int
 access_check_space(struct space *space, user_access_t access)
@@ -202,6 +204,13 @@ space_create(struct space *space, struct engine *engine,
 			}
 		}
 	}
+
+	space->constraints = mh_strnptr_new();
+	if (space->constraints == NULL) {
+		diag_set(OutOfMemory, sizeof(*space->constraints), "malloc",
+			 "constraints");
+		goto fail;
+	}
 	return 0;
 
 fail_free_indexes:
@@ -258,9 +267,12 @@ space_delete(struct space *space)
 	trigger_destroy(&space->on_replace);
 	space_def_delete(space->def);
 	/*
-	 * SQL Triggers should be deleted with
-	 * on_replace_dd_trigger on deletion from _trigger.
+	 * SQL triggers and constraints should be deleted with
+	 * on_replace_dd_ triggers on deletion from corresponding
+	 * system space.
 	 */
+	assert(mh_size(space->constraints) == 0);
+	mh_strnptr_delete(space->constraints);
 	assert(space->sql_triggers == NULL);
 	assert(rlist_empty(&space->parent_fk_constraint));
 	assert(rlist_empty(&space->child_fk_constraint));
@@ -617,6 +629,45 @@ space_remove_ck_constraint(struct space *space, struct ck_constraint *ck)
 	}
 }
 
+struct constraint_def *
+space_constraint_def_by_name(struct space *space, const char *name)
+{
+	uint32_t len = strlen(name);
+	mh_int_t pos = mh_strnptr_find_inp(space->constraints, name, len);
+	if (pos == mh_end(space->constraints))
+		return NULL;
+	return (struct constraint_def *)
+		mh_strnptr_node(space->constraints, pos)->val;
+}
+
+int
+space_put_constraint(struct space *space, struct constraint_def *def)
+{
+	uint32_t len = strlen(def->name);
+	uint32_t hash = mh_strn_hash(def->name, len);
+	const struct mh_strnptr_node_t name_node = { def->name, len, hash, def};
+	if (mh_strnptr_put(space->constraints, &name_node, NULL, NULL) ==
+	    mh_end(space->constraints)) {
+		diag_set(OutOfMemory, sizeof(name_node), "malloc",
+			 "constraints");
+		return -1;
+	}
+	return 0;
+}
+
+struct constraint_def *
+space_pop_constraint(struct space *space, const char *name)
+{
+	uint32_t len = strlen(name);
+	mh_int_t pos = mh_strnptr_find_inp(space->constraints, name, len);
+	if (pos == mh_end(space->constraints))
+		return NULL;
+	struct constraint_def *def = (struct constraint_def *)
+		mh_strnptr_node(space->constraints, pos)->val;
+	mh_strnptr_del(space->constraints, pos, NULL);
+	return def;
+}
+
 /* {{{ Virtual method stubs */
 
 size_t
diff --git a/src/box/space.h b/src/box/space.h
index 7926aa65e..08b5b4777 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -52,6 +52,7 @@ struct port;
 struct tuple;
 struct tuple_format;
 struct ck_constraint;
+struct constraint_def;
 
 struct space_vtab {
 	/** Free a space instance. */
@@ -234,6 +235,8 @@ struct space {
 	 * of parent constraints as well as child ones.
 	 */
 	uint64_t fk_constraint_mask;
+	/** Hash table with constraint def objects by name. */
+	struct mh_strnptr_t *constraints;
 };
 
 /** Initialize a base space instance. */
@@ -516,6 +519,45 @@ space_add_ck_constraint(struct space *space, struct ck_constraint *ck);
 void
 space_remove_ck_constraint(struct space *space, struct ck_constraint *ck);
 
+/**
+ * Find node with @a name in the constraint hash table of @a
+ * space.
+ *
+ * @param space Space.
+ * @param name  Constraint name.
+ *
+ * @retval constraint_def object.
+ * @retval NULL Constraint doesn't exist.
+ */
+struct constraint_def *
+space_constraint_def_by_name(struct space *space, const char *name);
+
+/**
+ * Put node with @a def to the constraint hash table of @a space.
+ *
+ * @param space Space.
+ * @param def   Constraint def.
+ *
+ * @retval  0 Success.
+ * @retval -1 Memory allocation error.
+ */
+int
+space_put_constraint(struct space *space, struct constraint_def *def);
+
+/**
+ * Remove node with @a name from the constraint hash table of @a
+ * space. But don't destroy the constraint def object binded to
+ * this @a name.
+ *
+ * @param space Space.
+ * @param name  Constraint name.
+ *
+ * @retval constraint_def object.
+ * @retval NULL Constraint doesn't exist.
+ */
+struct constraint_def *
+space_pop_constraint(struct space *space, const char *name);
+
 /*
  * Virtual method stubs.
  */

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

* Re: [Tarantool-patches] [PATCH v2 2/3] box: make constraint operations transactional
  2019-12-07 16:35       ` Vladislav Shpilevoy
@ 2019-12-10 12:49         ` Roman Khabibov
  2019-12-15 22:26           ` Vladislav Shpilevoy
  2019-12-21 20:54           ` Sergey Ostanevich
  0 siblings, 2 replies; 29+ messages in thread
From: Roman Khabibov @ 2019-12-10 12:49 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches



> On Dec 7, 2019, at 19:35, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Thanks for the fixes!
> 
> See 9 comments below.
> 
>>> 14. Talking of the super mess with on_replace_dd_index, and
>>> how tight it is now with all these struggling about replacing
>>> constraints, and on_commit/on_rollback. I think, that this is
>>> a dead end. Too complex.
>>> 
>>> How about introducing a couple of new AlterSpaceOp classes?
>>> AddConstraint, and DropConstraint. AlterSpaceOp looks good
>>> because
>>> - it will allow you to not set on_commit/on_rollback triggers
>>> manually - it already provides these methods;
>>> - you will be able to move this huge pieces of code out of
>>> on_replace_dd_index and other on_replace_*;
>>> - it is consistent with MoveIndex/DropIndex/etc classes.
>>> 
>>> All you will need to do is to add to on_replace_*
>>> (void) new AddConstraint(), and (void) new DropConstraint.
>>> And you will keep (void) new MoveConstraints().
>>> 
>>> Also that should eliminate code duplication between different
>>> on_replace_* functions about adding/dropping constraints.
>> +/**
>> + * CreateConstraintDef - add a new constraint def to the space.
>> + */
>> +class CreateConstraintDef: public AlterSpaceOp
>> +{
>> +private:
>> +	struct constraint_def *new_constraint_def;
>> +public:
>> +	CreateConstraintDef(struct alter_space *alter,
>> +			    struct constraint_def *def)
>> +		:AlterSpaceOp(alter), new_constraint_def(def)
>> +	{}
>> +	virtual void alter(struct alter_space *alter);
>> +	virtual void rollback(struct alter_space *alter);
>> +};
>> +
>> +void
>> +CreateConstraintDef::alter(struct alter_space *alter)
>> +{
>> +	assert(new_constraint_def != NULL);
>> +	if (space_put_constraint(alter->old_space, new_constraint_def) != 0)
>> +		panic("Can't recover after constraint alter (out of memory)");
> 
> 1. Wow, why panic? Alter can safely fail. It it is not a commit or
> rollback trigger. Maybe it can't return an error, but it can throw
> it.
> 
> (Although I would like to be able to panic on malloc fails, but it
> does not seem to happen in the nearest future.)
+void
+CreateConstraintDef::alter(struct alter_space *alter)
+{
+	assert(new_constraint_def != NULL);
+	if (space_put_constraint(alter->old_space, new_constraint_def) != 0)
+		diag_raise();
+}

>> +}
>> +
>> +void
>> +CreateConstraintDef::rollback(struct alter_space *alter)
>> +{
>> +	assert(space_pop_constraint(alter->new_space,
>> +				    new_constraint_def->name) ==
>> +	       new_constraint_def);
> 
> 2. This will fail in Release build mode, because
> assert arguments are not calculated in Release. So you
> delete the object, but it is still in the hash table.
+void
+CreateConstraintDef::rollback(struct alter_space *alter)
+{
+	struct constraint_def *constraint_def =
+		space_pop_constraint(alter->new_space,
+				     new_constraint_def->name);
+	assert(constraint_def == new_constraint_def);
+	(void) alter;
+	constraint_def_delete(new_constraint_def);
+}

>> +	(void) alter;
>> +	constraint_def_delete(new_constraint_def);
>> +}
>> +
>> 
>> I know, that I can combine these ifs to the one, but I did so, because I believe that
>> it is more readable.
> 
> 3. Yes, here I agree.
> 
>> 
>> +		struct index_def *old_def = old_index->def;
>> +		/*
>> +		 * We put a new name either an index is becoming
>> +		 * unique (i.e. constraint), or when an unique
>> +		 * index's name is under change.
>> +		 */
>> +		if (!old_def->opts.is_unique && index_def->opts.is_unique &&
>> +		    create_index_as_constraint(alter, index_def) != 0)
>> +			return -1;
>> +		if (old_def->opts.is_unique && index_def->opts.is_unique &&
>> +		    strcmp(index_def->name, old_def->name) != 0 &&
>> +		    (create_index_as_constraint(alter, index_def) != 0 ||
>> +		     drop_index_as_constraint(alter, old_def) != 0))
>> +			return -1;
>> +		if (old_def->opts.is_unique && !index_def->opts.is_unique &&
>> +		    drop_index_as_constraint(alter, old_def) != 0)
>> +			return -1;
>> 
>> 
>> commit c54d2e89d660d2f0b07f1e6917327131362a6568
>> Author: Roman Khabibov <roman.habibov@tarantool.org>
>> Date:   Wed Oct 23 15:54:16 2019 +0300
>> 
>>    sql: make constraint operations transactional
>> 
>>    Put constraint names into the space's hash table and drop them on
>>    replace in corresponding system spaces (_index, _fk_constraint,
>>    _ck_constraint).
>> 
>>    Closes #3503
>> 
>>    @TarantoolBot document
>>    Title: Table constraints in SQL
>> 
>>    SQL:
>>    According to ANSI SQL, table constraint is one of the following
>>    entities: PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK. Every
>>    constraint have its own name passed by user or automatically
>>    generated. And these names must be unique within one table/space.
>>    Naming in SQL is case-insensitive, so "CONSTRAINT c" and
>>    "CONSTRAINT C" are the same. For example, you will get error, if
>>    you try to:
>> 
>>    CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
>>                     CONSTRAINT c PRIMARY KEY (i));
>> 
>>    or
>> 
>>    CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);
>>    ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
>> 
>>    The same is for any other constraint types.
>> 
>>    Lua/box:
>> 
>>    You also can create/drop table constraints from box. See
>>    space_object:create_check_constraint() and space_object:create_index()
>>    (if an index is unique). Naming in box is case-sensitive, so 'c' and
>>    'C' are not the same (see naming policy). For example, an unique
>>    index is a constraint, but a non-unique index is not. So, a non-unique
>>    index can have the same name with a check/foreign key constraint
>>    within one space:
>> 
>>    box.execute('CREATE TABLE t2 (i INT PRIMARY KEY);');
>>    box.execute('CREATE INDEX e ON t2(i);');
>>    box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
>> 
>>    But, if you try to:
>> 
>>    box.space.T2.index.E:alter({unique = true});
>> 
>>    You will get error, beacuse index 'e' is becoming unique.
> 
> 4. That is a good comment. (But typo: beacuse -> because.)
Done.
>> 
>> diff --git a/src/box/alter.cc b/src/box/alter.cc
>> index bef25b605..c03bad589 100644
>> --- a/src/box/alter.cc
>> +++ b/src/box/alter.cc> @@ -2510,18 +2649,23 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
>> 	if (old_index == NULL && new_tuple != NULL) {
>> 		if (alter_space_move_indexes(alter, 0, iid))
>> 			return -1;
>> +		struct index_def *def =
>> +			index_def_new_from_tuple(new_tuple, old_space);
>> +		if (def == NULL)
>> +			return -1;
>> +		if (def->opts.is_unique &&
>> +		    create_index_as_constraint(alter, def) != 0) {
>> +			index_def_delete(def);
>> +			return -1;
>> +		}
>> 		CreateIndex *create_index;
>> 		try {
>> 			create_index = new CreateIndex(alter);
>> 		} catch (Exception *e) {
>> 			return -1;
> 
> 5. 'def' leaks here.
@ -2510,18 +2669,24 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 	if (old_index == NULL && new_tuple != NULL) {
 		if (alter_space_move_indexes(alter, 0, iid))
 			return -1;
+		struct index_def *def =
+			index_def_new_from_tuple(new_tuple, old_space);
+		if (def == NULL)
+			return -1;
+		if (def->opts.is_unique &&
+		    create_index_as_constraint(alter, def) != 0) {
+			index_def_delete(def);
+			return -1;
+		}
 		CreateIndex *create_index;
 		try {
 			create_index = new CreateIndex(alter);
 		} catch (Exception *e) {
+			index_def_delete(def);
 			return -1;
 		}

>> 		}
>> -		create_index->new_index_def =
>> -			index_def_new_from_tuple(new_tuple, old_space);
>> -		if (create_index->new_index_def == NULL)
>> -			return -1;
>> -		index_def_update_optionality(create_index->new_index_def,
>> -					     alter->new_min_field_count);
>> +		create_index->new_index_def = def;
>> +		index_def_update_optionality(def, alter->new_min_field_count);
>> 	}
>> 	/* Case 3 and 4: check if we need to rebuild index data. */
>> 	if (old_index != NULL && new_tuple != NULL) {
>> @@ -2531,6 +2675,23 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
>> 			return -1;
>> 		auto index_def_guard =
>> 			make_scoped_guard([=] { index_def_delete(index_def); });
>> +		struct index_def *old_def = old_index->def;
>> +		/*
>> +		 * We put a new name either an index is becoming
> 
> 6. 'either' -> 'when either’.
+		/*
+		 * We put a new name when either an index is
+		 * becoming unique (i.e. constraint), or when an
+		 * unique index's name is under change.
+		 */

>> +		 * unique (i.e. constraint), or when an unique
>> +		 * index's name is under change.
>> +		 */
>> +		if (!old_def->opts.is_unique && index_def->opts.is_unique &&
>> +		    create_index_as_constraint(alter, index_def) != 0)
>> +			return -1;
>> +		if (old_def->opts.is_unique && index_def->opts.is_unique &&
>> +		    strcmp(index_def->name, old_def->name) != 0 &&
>> +		    (create_index_as_constraint(alter, index_def) != 0 ||
>> +		     drop_index_as_constraint(alter, old_def) != 0))
>> +			return -1;
>> +		if (old_def->opts.is_unique && !index_def->opts.is_unique &&
>> +		    drop_index_as_constraint(alter, old_def) != 0)
>> +			return -1;
>> 		/*
>> 		 * To detect which key parts are optional,
>> 		 * min_field_count is required. But> @@ -5219,6 +5407,16 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
>> 						      fk);
>> 			if (on_rollback == NULL)
>> 				return -1;
>> +			struct constraint_def *constr_def =
>> +				constraint_def_new(child_space->def->id,
>> +						   CONSTRAINT_TYPE_FK,
>> +						   fk_def->name);
>> +			if (constr_def == NULL)
>> +				return -1;
>> +			if (space_put_constraint(child_space, constr_def) != 0) {
>> +				constraint_def_delete(constr_def);
>> +				return -1;
>> +			}
> 
> 7. Sequence constraint_def_new -> space_put_constraint is repeated 4
> times. I think this is worth wrapping into a function. The same about
> space_constraint_def_by_name -> space_pop_constraint ->
> constraint_def_delete.
+/**
+ * Put the node of a ck/fk constraint to the constraint hash table
+ * of @a space.
+ *
+ * This function is needed to wrap the duplicated piece of code
+ * inside the trigger functions below.
+ */
+static int
+create_fk_ck_as_constraint(struct space *space, enum constraint_type type,
+			   const char *name)
+{
+	assert(type == CONSTRAINT_TYPE_CK || type == CONSTRAINT_TYPE_FK);
+	struct constraint_def *constraint_def =
+		constraint_def_new(space->def->id, type, name);
+	if (constraint_def == NULL)
+		return -1;
+	if (space_put_constraint(space, constraint_def) != 0) {
+		constraint_def_delete(constraint_def);
+		return -1;
+	}
+	return 0;
+}
+
+/**
+ * Drop the node of a ck/fk constraint from the constraint hash
+ * table of @a space.
+ *
+ * This function is needed to wrap the duplicated piece of code
+ * inside the trigger functions below.
+ */
+static void
+drop_fk_ck_as_constraint(struct space *space, const char *name)
+{
+	struct constraint_def *constraint_def =
+		space_constraint_def_by_name(space, name);
+	assert(constraint_def != NULL);
+	assert(constraint_def->type == CONSTRAINT_TYPE_CK ||
+	       constraint_def->type == CONSTRAINT_TYPE_FK);
+	space_pop_constraint(space, name);
+	constraint_def_delete(constraint_def);
+}
+

>> 			txn_stmt_on_rollback(stmt, on_rollback);
>> 			fk_constraint_set_mask(fk,
>> 					       &parent_space->fk_constraint_mask,
>> diff --git a/test/sql/constraint.result b/test/sql/constraint.result
>> new file mode 100644
>> index 000000000..ba262182b
>> --- /dev/null
>> +++ b/test/sql/constraint.result
>> @@ -0,0 +1,190 @@
>> +-- test-run result file version 2
>> +test_run = require('test_run').new()
>> + | ---
>> + | ...
>> +engine = test_run:get_cfg('engine')
>> + | ---
>> + | ...
>> +box.execute('pragma sql_default_engine=\''..engine..'\'')
>> + | ---
>> + | - row_count: 0
>> + | ...
>> +test_run:cmd("setopt delimiter ';'")
> 
> 8. Please, don't. There is only one small place where you need
> multiple lines. You can either use ';' just for it, or use /.
> 
>> diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
>> new file mode 100755
>> index 000000000..50162e47d
>> --- /dev/null
>> +++ b/test/sql/constraint.test.lua
> 
> 9. Your test leaves lots of not deleted data. Please, do
> cleanup.
Done.

commit c90d7935b56c4603303bac45a7ebeb9af03eef3d
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Wed Oct 23 15:54:16 2019 +0300

    box: make constraint operations transactional
    
    Put constraint names into the space's hash table and drop them on
    replace in corresponding system spaces (_index, _fk_constraint,
    _ck_constraint).
    
    Closes #3503
    
    @TarantoolBot document
    Title: Table constraints in SQL
    
    SQL:
    According to ANSI SQL, table constraint is one of the following
    entities: PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK. Every
    constraint have its own name passed by user or automatically
    generated. And these names must be unique within one table/space.
    Naming in SQL is case-insensitive, so "CONSTRAINT c" and
    "CONSTRAINT C" are the same. For example, you will get error, if
    you try to:
    
    CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
                     CONSTRAINT c PRIMARY KEY (i));
    
    or
    
    CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);
    ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
    
    The same is for any other constraint types.
    
    Lua/box:
    
    You also can create/drop table constraints from box. See
    space_object:create_check_constraint() and space_object:create_index()
    (if an index is unique). Naming in box is case-sensitive, so 'c' and
    'C' are not the same (see naming policy). For example, an unique
    index is a constraint, but a non-unique index is not. So, a non-unique
    index can have the same name with a check/foreign key constraint
    within one space:
    
    box.execute('CREATE TABLE t2 (i INT PRIMARY KEY);');
    box.execute('CREATE INDEX e ON t2(i);');
    box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
    
    But, if you try to:
    
    box.space.T2.index.E:alter({unique = true});
    
    You will get error, because index 'e' is becoming unique.

diff --git a/src/box/alter.cc b/src/box/alter.cc
index bef25b605..d5e836ae7 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -57,6 +57,7 @@
 #include "version.h"
 #include "sequence.h"
 #include "sql.h"
+#include "constraint_def.h"
 
 /* {{{ Auxiliary functions and methods. */
 
@@ -1033,10 +1034,13 @@ alter_space_rollback(struct trigger *trigger, void * /* event */)
 	space_fill_index_map(alter->old_space);
 	space_fill_index_map(alter->new_space);
 	/*
-	 * Don't forget about space triggers and foreign keys.
+	 * Don't forget about space triggers, foreign keys and
+	 * constraints.
 	 */
 	space_swap_triggers(alter->new_space, alter->old_space);
 	space_swap_fk_constraints(alter->new_space, alter->old_space);
+	SWAP(alter->new_space->constraints,
+	     alter->old_space->constraints);
 	space_cache_replace(alter->new_space, alter->old_space);
 	alter_space_delete(alter);
 	return 0;
@@ -1143,10 +1147,13 @@ alter_space_do(struct txn_stmt *stmt, struct alter_space *alter)
 	space_fill_index_map(alter->old_space);
 	space_fill_index_map(alter->new_space);
 	/*
-	 * Don't forget about space triggers and foreign keys.
+	 * Don't forget about space triggers, foreign keys and
+	 * constraints.
 	 */
 	space_swap_triggers(alter->new_space, alter->old_space);
 	space_swap_fk_constraints(alter->new_space, alter->old_space);
+	SWAP(alter->new_space->constraints,
+	     alter->old_space->constraints);
 	/*
 	 * The new space is ready. Time to update the space
 	 * cache with it.
@@ -1764,6 +1771,85 @@ MoveCkConstraints::rollback(struct alter_space *alter)
 	space_swap_ck_constraint(alter->new_space, alter->old_space);
 }
 
+/**
+ * CreateConstraintDef - add a new constraint def to the space.
+ */
+class CreateConstraintDef: public AlterSpaceOp
+{
+private:
+	struct constraint_def *new_constraint_def;
+public:
+	CreateConstraintDef(struct alter_space *alter,
+			    struct constraint_def *def)
+		:AlterSpaceOp(alter), new_constraint_def(def)
+	{}
+	virtual void alter(struct alter_space *alter);
+	virtual void rollback(struct alter_space *alter);
+};
+
+void
+CreateConstraintDef::alter(struct alter_space *alter)
+{
+	assert(new_constraint_def != NULL);
+	if (space_put_constraint(alter->old_space, new_constraint_def) != 0)
+		diag_raise();
+}
+
+void
+CreateConstraintDef::rollback(struct alter_space *alter)
+{
+	struct constraint_def *constraint_def =
+		space_pop_constraint(alter->new_space,
+				     new_constraint_def->name);
+	assert(constraint_def == new_constraint_def);
+	(void) alter;
+	constraint_def_delete(new_constraint_def);
+}
+
+/** DropConstraintDef - drop a constraint def from the space. */
+class DropConstraintDef: public AlterSpaceOp
+{
+private:
+	struct constraint_def *old_constraint_def;
+	const char *name;
+public:
+	DropConstraintDef(struct alter_space *alter, const char *name)
+		:AlterSpaceOp(alter), old_constraint_def(NULL), name(name)
+	{}
+	virtual void alter(struct alter_space *alter);
+	virtual void commit(struct alter_space *alter , int64_t signature);
+	virtual void rollback(struct alter_space *alter);
+};
+
+void
+DropConstraintDef::alter(struct alter_space *alter)
+{
+	assert(name != NULL);
+	old_constraint_def =
+		space_constraint_def_by_name(alter->old_space, name);
+	assert(old_constraint_def != NULL);
+	space_pop_constraint(alter->old_space, name);
+}
+
+void
+DropConstraintDef::commit(struct alter_space *alter, int64_t signature)
+{
+	(void) alter;
+	(void) signature;
+	assert(old_constraint_def != NULL);
+	constraint_def_delete(old_constraint_def);
+}
+
+void
+DropConstraintDef::rollback(struct alter_space *alter)
+{
+	assert(old_constraint_def != NULL);
+	if (space_put_constraint(alter->new_space, old_constraint_def) != 0) {
+		panic("Can't recover after constraint drop rollback (out of "
+		      "memory)");
+	}
+}
+
 /* }}} */
 
 /**
@@ -2363,6 +2449,76 @@ index_is_used_by_fk_constraint(struct rlist *fk_list, uint32_t iid)
 	return false;
 }
 
+/**
+ * Check if constraint with @a name exists within @a space. Call
+ * diag_set() if it is so.
+ *
+ * @param space Space to find constraint within.
+ * @param name  Constraint name.
+ *
+ * @retval -1 Constraint exists.
+ * @retval 0  Doesn't exist.
+ */
+static inline int
+check_existence(struct space *space, const char *name)
+{
+	struct constraint_def *def = space_constraint_def_by_name(space, name);
+	if (def != NULL) {
+		diag_set(ClientError, ER_CONSTRAINT_EXISTS, name);
+		return -1;
+	}
+	return 0;
+}
+
+/**
+ * Put the node of an unique index to the constraint hash table of
+ * @a space.
+ *
+ * This function is needed to wrap the duplicated piece of code
+ * inside on_replace_dd_index().
+ */
+static int
+create_index_as_constraint(struct alter_space *alter,
+			   const struct index_def *def)
+{
+	assert(def->opts.is_unique);
+	struct space *space = alter->old_space;
+	if (check_existence(space, def->name) != 0)
+		return -1;
+	struct constraint_def *constr_def =
+		constraint_def_new(space->def->id, def->iid == 0 ?
+				   CONSTRAINT_TYPE_PK : CONSTRAINT_TYPE_UNIQUE,
+				   def->name);
+	if (constr_def == NULL)
+		return -1;
+	try {
+		(void) new CreateConstraintDef(alter, constr_def);
+	} catch (Exception *e) {
+		constraint_def_delete(constr_def);
+		return -1;
+	}
+	return 0;
+}
+
+/**
+ * Drop the node of an unique index from the constraint hash table
+ * of @a space.
+ *
+ * This function is needed to wrap the duplicated piece of code
+ * inside on_replace_dd_index().
+ */
+static int
+drop_index_as_constraint(struct alter_space *alter, const struct index_def *def)
+{
+	assert(def->opts.is_unique);
+	try {
+		(void) new DropConstraintDef(alter, def->name);
+	} catch (Exception *e) {
+		return -1;
+	}
+	return 0;
+}
+
 /**
  * Just like with _space, 3 major cases:
  *
@@ -2500,6 +2656,9 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 		}
 		if (alter_space_move_indexes(alter, 0, iid) != 0)
 			return -1;
+		if (old_index->def->opts.is_unique &&
+		    drop_index_as_constraint(alter, old_index->def) != 0)
+			return -1;
 		try {
 			(void) new DropIndex(alter, old_index);
 		} catch (Exception *e) {
@@ -2510,18 +2669,24 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 	if (old_index == NULL && new_tuple != NULL) {
 		if (alter_space_move_indexes(alter, 0, iid))
 			return -1;
+		struct index_def *def =
+			index_def_new_from_tuple(new_tuple, old_space);
+		if (def == NULL)
+			return -1;
+		if (def->opts.is_unique &&
+		    create_index_as_constraint(alter, def) != 0) {
+			index_def_delete(def);
+			return -1;
+		}
 		CreateIndex *create_index;
 		try {
 			create_index = new CreateIndex(alter);
 		} catch (Exception *e) {
+			index_def_delete(def);
 			return -1;
 		}
-		create_index->new_index_def =
-			index_def_new_from_tuple(new_tuple, old_space);
-		if (create_index->new_index_def == NULL)
-			return -1;
-		index_def_update_optionality(create_index->new_index_def,
-					     alter->new_min_field_count);
+		create_index->new_index_def = def;
+		index_def_update_optionality(def, alter->new_min_field_count);
 	}
 	/* Case 3 and 4: check if we need to rebuild index data. */
 	if (old_index != NULL && new_tuple != NULL) {
@@ -2531,6 +2696,23 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 			return -1;
 		auto index_def_guard =
 			make_scoped_guard([=] { index_def_delete(index_def); });
+		struct index_def *old_def = old_index->def;
+		/*
+		 * We put a new name when either an index is
+		 * becoming unique (i.e. constraint), or when an
+		 * unique index's name is under change.
+		 */
+		if (!old_def->opts.is_unique && index_def->opts.is_unique &&
+		    create_index_as_constraint(alter, index_def) != 0)
+			return -1;
+		if (old_def->opts.is_unique && index_def->opts.is_unique &&
+		    strcmp(index_def->name, old_def->name) != 0 &&
+		    (create_index_as_constraint(alter, index_def) != 0 ||
+		     drop_index_as_constraint(alter, old_def) != 0))
+			return -1;
+		if (old_def->opts.is_unique && !index_def->opts.is_unique &&
+		    drop_index_as_constraint(alter, old_def) != 0)
+			return -1;
 		/*
 		 * To detect which key parts are optional,
 		 * min_field_count is required. But
@@ -4984,6 +5166,48 @@ space_reset_fk_constraint_mask(struct space *space)
 	}
 }
 
+/**
+ * Put the node of a ck/fk constraint to the constraint hash table
+ * of @a space.
+ *
+ * This function is needed to wrap the duplicated piece of code
+ * inside the trigger functions below.
+ */
+static int
+create_fk_ck_as_constraint(struct space *space, enum constraint_type type,
+			   const char *name)
+{
+	assert(type == CONSTRAINT_TYPE_CK || type == CONSTRAINT_TYPE_FK);
+	struct constraint_def *constraint_def =
+		constraint_def_new(space->def->id, type, name);
+	if (constraint_def == NULL)
+		return -1;
+	if (space_put_constraint(space, constraint_def) != 0) {
+		constraint_def_delete(constraint_def);
+		return -1;
+	}
+	return 0;
+}
+
+/**
+ * Drop the node of a ck/fk constraint from the constraint hash
+ * table of @a space.
+ *
+ * This function is needed to wrap the duplicated piece of code
+ * inside the trigger functions below.
+ */
+static void
+drop_fk_ck_as_constraint(struct space *space, const char *name)
+{
+	struct constraint_def *constraint_def =
+		space_constraint_def_by_name(space, name);
+	assert(constraint_def != NULL);
+	assert(constraint_def->type == CONSTRAINT_TYPE_CK ||
+	       constraint_def->type == CONSTRAINT_TYPE_FK);
+	space_pop_constraint(space, name);
+	constraint_def_delete(constraint_def);
+}
+
 /**
  * On rollback of creation we remove FK constraint from DD, i.e.
  * from parent's and child's lists of constraints and
@@ -4996,8 +5220,11 @@ on_create_fk_constraint_rollback(struct trigger *trigger, void *event)
 	struct fk_constraint *fk = (struct fk_constraint *)trigger->data;
 	rlist_del_entry(fk, in_parent_space);
 	rlist_del_entry(fk, in_child_space);
+	struct space *child = space_by_id(fk->def->child_id);
+	assert(child != NULL);
+	drop_fk_ck_as_constraint(child, fk->def->name);
 	space_reset_fk_constraint_mask(space_by_id(fk->def->parent_id));
-	space_reset_fk_constraint_mask(space_by_id(fk->def->child_id));
+	space_reset_fk_constraint_mask(child);
 	fk_constraint_delete(fk);
 	return 0;
 }
@@ -5029,8 +5256,14 @@ on_drop_fk_constraint_rollback(struct trigger *trigger, void *event)
 	struct fk_constraint *old_fk = (struct fk_constraint *)trigger->data;
 	struct space *parent = space_by_id(old_fk->def->parent_id);
 	struct space *child = space_by_id(old_fk->def->child_id);
+	assert(child != NULL && parent != NULL);
 	rlist_add_entry(&child->child_fk_constraint, old_fk, in_child_space);
 	rlist_add_entry(&parent->parent_fk_constraint, old_fk, in_parent_space);
+	if (create_fk_ck_as_constraint(child, CONSTRAINT_TYPE_FK,
+				       old_fk->def->name) != 0) {
+		panic("Can't recover after FK constraint drop rollback (out of "
+		      "memory)");
+	}
 	fk_constraint_set_mask(old_fk, &child->fk_constraint_mask,
 			       FIELD_LINK_CHILD);
 	fk_constraint_set_mask(old_fk, &parent->fk_constraint_mask,
@@ -5210,15 +5443,21 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 		fk->def = fk_def;
 		fk->index_id = fk_index->def->iid;
 		if (old_tuple == NULL) {
-			rlist_add_entry(&child_space->child_fk_constraint,
-					fk, in_child_space);
-			rlist_add_entry(&parent_space->parent_fk_constraint,
-					fk, in_parent_space);
+			if (check_existence(child_space, fk_def->name) != 0)
+				return -1;
 			struct trigger *on_rollback =
 				txn_alter_trigger_new(on_create_fk_constraint_rollback,
 						      fk);
 			if (on_rollback == NULL)
 				return -1;
+			if (create_fk_ck_as_constraint(child_space,
+						       CONSTRAINT_TYPE_FK,
+						       fk_def->name) != 0)
+				return -1;
+			rlist_add_entry(&child_space->child_fk_constraint,
+					fk, in_child_space);
+			rlist_add_entry(&parent_space->parent_fk_constraint,
+					fk, in_parent_space);
 			txn_stmt_on_rollback(stmt, on_rollback);
 			fk_constraint_set_mask(fk,
 					       &parent_space->fk_constraint_mask,
@@ -5279,6 +5518,7 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 					      old_fk);
 		if (on_rollback == NULL)
 			return -1;
+		drop_fk_ck_as_constraint(child_space, fk_def->name);
 		txn_stmt_on_rollback(stmt, on_rollback);
 		space_reset_fk_constraint_mask(child_space);
 		space_reset_fk_constraint_mask(parent_space);
@@ -5344,9 +5584,10 @@ on_create_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
 	assert(ck != NULL);
 	struct space *space = space_by_id(ck->def->space_id);
 	assert(space != NULL);
-	assert(space_ck_constraint_by_name(space, ck->def->name,
-					   strlen(ck->def->name)) != NULL);
+	const char *name = ck->def->name;
+	assert(space_ck_constraint_by_name(space, name, strlen(name)) != NULL);
 	space_remove_ck_constraint(space, ck);
+	drop_fk_ck_as_constraint(space, name);
 	ck_constraint_delete(ck);
 	if (trigger_run(&on_alter_space, space) != 0)
 		return -1;
@@ -5371,9 +5612,10 @@ on_drop_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
 	assert(ck != NULL);
 	struct space *space = space_by_id(ck->def->space_id);
 	assert(space != NULL);
-	assert(space_ck_constraint_by_name(space, ck->def->name,
-					   strlen(ck->def->name)) == NULL);
-	if (space_add_ck_constraint(space, ck) != 0)
+	const char *name = ck->def->name;
+	assert(space_ck_constraint_by_name(space, name, strlen(name)) == NULL);
+	if (space_add_ck_constraint(space, ck) != 0 ||
+	    create_fk_ck_as_constraint(space, CONSTRAINT_TYPE_CK, name) != 0)
 		panic("Can't recover after CK constraint drop rollback");
 	if (trigger_run(&on_alter_space, space) != 0)
 		return -1;
@@ -5459,7 +5701,8 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		const char *name = ck_def->name;
 		struct ck_constraint *old_ck_constraint =
 			space_ck_constraint_by_name(space, name, strlen(name));
-		if (old_ck_constraint != NULL) {
+		bool is_insert = old_ck_constraint == NULL;
+		if (!is_insert) {
 			struct ck_constraint_def *old_def =
 						old_ck_constraint->def;
 			assert(old_def->space_id == ck_def->space_id);
@@ -5491,10 +5734,15 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		auto ck_guard = make_scoped_guard([=] {
 			ck_constraint_delete(new_ck_constraint);
 		});
-		if (old_ck_constraint != NULL)
-			rlist_del_entry(old_ck_constraint, link);
-		if (space_add_ck_constraint(space, new_ck_constraint) != 0)
+		if (is_insert && check_existence(space, name) != 0)
+			return -1;
+		if (space_add_ck_constraint(space, new_ck_constraint) != 0 ||
+		    (is_insert &&
+		     create_fk_ck_as_constraint(space, CONSTRAINT_TYPE_CK,
+						name) != 0))
 			return -1;
+		if (!is_insert)
+			rlist_del_entry(old_ck_constraint, link);
 		ck_guard.is_active = false;
 		if (old_tuple != NULL) {
 			on_rollback->data = old_ck_constraint;
@@ -5516,6 +5764,7 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		struct ck_constraint *old_ck_constraint =
 			space_ck_constraint_by_name(space, name, name_len);
 		assert(old_ck_constraint != NULL);
+		drop_fk_ck_as_constraint(space, old_ck_constraint->def->name);
 		space_remove_ck_constraint(space, old_ck_constraint);
 		on_commit->data = old_ck_constraint;
 		on_commit->run = on_drop_ck_constraint_commit;
diff --git a/test/sql/constraint.result b/test/sql/constraint.result
new file mode 100644
index 000000000..a3dd7d531
--- /dev/null
+++ b/test/sql/constraint.result
@@ -0,0 +1,214 @@
+-- test-run result file version 2
+test_run = require('test_run').new()
+ | ---
+ | ...
+engine = test_run:get_cfg('engine')
+ | ---
+ | ...
+box.execute('pragma sql_default_engine=\''..engine..'\'')
+ | ---
+ | - row_count: 0
+ | ...
+
+--
+-- Check a constraint name for duplicate within a single
+-- <CREATE TABLE> statement.
+--
+box.execute('CREATE TABLE t1 (i INT PRIMARY KEY);')
+ | ---
+ | - row_count: 1
+ | ...
+test_run:cmd("setopt delimiter ';'");
+ | ---
+ | - true
+ | ...
+box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c UNIQUE (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c UNIQUE (i));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c CHECK (i < 0));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c CHECK (i > 0));]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+test_run:cmd("setopt delimiter ''");
+ | ---
+ | - true
+ | ...
+
+--
+-- Check a constraint name for duplicate using <ALTER TABLE>
+-- statement.
+--
+box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);')
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);')
+ | ---
+ | - null
+ | - Index 'C' already exists in space 'T2'
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);')
+ | ---
+ | - null
+ | - Constraint C already exists
+ | ...
+
+--
+-- Make sure that a constraint's name isn't kept after the
+-- constraint drop.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('DROP INDEX d ON t2;')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.ck_constraint.D:drop()
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- The same inside a transaction.
+--
+test_run:cmd("setopt delimiter ';'");
+ | ---
+ | - true
+ | ...
+box.begin()
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+box.space.T2.ck_constraint.D:drop()
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+box.commit();
+ | ---
+ | ...
+test_run:cmd("setopt delimiter ''");
+ | ---
+ | - true
+ | ...
+
+--
+-- Make sure, that altering of an index name affect to its record
+-- in the space's constraint hash table.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.D:alter({name = 'E'})
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+ | ---
+ | - null
+ | - Constraint E already exists
+ | ...
+
+--
+-- Make sure, that altering of an index uniqueness puts/drops
+-- its name to/from the space's constraint hash table.
+--
+box.space.T2.index.E:alter({unique = false})
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.E:alter({unique = true})
+ | ---
+ | - error: Constraint E already exists
+ | ...
+box.space.T2.ck_constraint.E:drop()
+ | ---
+ | ...
+box.space.T2.index.E:alter({unique = true})
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+ | ---
+ | - null
+ | - Constraint E already exists
+ | ...
+
+-- Alter name and uniqueness of an unique index simultaneously.
+box.space.T2.index.E:alter({name = 'D', unique = false})
+ | ---
+ | ...
+box.execute('CREATE UNIQUE INDEX e ON t2(i);')
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Cleanup.
+--
+box.execute('DROP TABLE t2;')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('DROP TABLE t1;')
+ | ---
+ | - row_count: 1
+ | ...
diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
new file mode 100755
index 000000000..e321b87aa
--- /dev/null
+++ b/test/sql/constraint.test.lua
@@ -0,0 +1,93 @@
+test_run = require('test_run').new()
+engine = test_run:get_cfg('engine')
+box.execute('pragma sql_default_engine=\''..engine..'\'')
+
+--
+-- Check a constraint name for duplicate within a single
+-- <CREATE TABLE> statement.
+--
+box.execute('CREATE TABLE t1 (i INT PRIMARY KEY);')
+test_run:cmd("setopt delimiter ';'");
+box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+box.execute([[CREATE TABLE t2 (i INT,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c UNIQUE (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c UNIQUE (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c CHECK (i < 0));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c CHECK (i > 0));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
+test_run:cmd("setopt delimiter ''");
+
+--
+-- Check a constraint name for duplicate using <ALTER TABLE>
+-- statement.
+--
+box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);')
+
+--
+-- Make sure that a constraint's name isn't kept after the
+-- constraint drop.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+box.space.T2.ck_constraint.D:drop()
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+
+--
+-- The same inside a transaction.
+--
+test_run:cmd("setopt delimiter ';'");
+box.begin()
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+box.space.T2.ck_constraint.D:drop()
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+box.commit();
+test_run:cmd("setopt delimiter ''");
+
+--
+-- Make sure, that altering of an index name affect to its record
+-- in the space's constraint hash table.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.space.T2.index.D:alter({name = 'E'})
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+
+--
+-- Make sure, that altering of an index uniqueness puts/drops
+-- its name to/from the space's constraint hash table.
+--
+box.space.T2.index.E:alter({unique = false})
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+box.space.T2.index.E:alter({unique = true})
+box.space.T2.ck_constraint.E:drop()
+box.space.T2.index.E:alter({unique = true})
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+
+-- Alter name and uniqueness of an unique index simultaneously.
+box.space.T2.index.E:alter({name = 'D', unique = false})
+box.execute('CREATE UNIQUE INDEX e ON t2(i);')
+
+--
+-- Cleanup.
+--
+box.execute('DROP TABLE t2;')
+box.execute('DROP TABLE t1;')

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

* Re: [Tarantool-patches] [PATCH v2 2/3] box: make constraint operations transactional
  2019-12-10 12:49         ` [Tarantool-patches] [PATCH v2 2/3] box: " Roman Khabibov
@ 2019-12-15 22:26           ` Vladislav Shpilevoy
  2019-12-17 15:03             ` Roman Khabibov
  2019-12-21 20:54           ` Sergey Ostanevich
  1 sibling, 1 reply; 29+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-15 22:26 UTC (permalink / raw)
  To: Roman Khabibov; +Cc: tarantool-patches

Hi! Thanks for the fixes!

>> 2. This will fail in Release build mode, because
>> assert arguments are not calculated in Release. So you
>> delete the object, but it is still in the hash table.
> +void
> +CreateConstraintDef::rollback(struct alter_space *alter)
> +{
> +	struct constraint_def *constraint_def =
> +		space_pop_constraint(alter->new_space,
> +				     new_constraint_def->name);
> +	assert(constraint_def == new_constraint_def);
> +	(void) alter;
> +	constraint_def_delete(new_constraint_def);
> +}
> 

And it still fails in the release mode, on build stage.
https://travis-ci.org/tarantool/tarantool/builds/623163899?utm_source=github_status&utm_medium=notification

>>> 			if (on_rollback == NULL)
>>> 				return -1;
>>> +			struct constraint_def *constr_def =
>>> +				constraint_def_new(child_space->def->id,
>>> +						   CONSTRAINT_TYPE_FK,
>>> +						   fk_def->name);
>>> +			if (constr_def == NULL)
>>> +				return -1;
>>> +			if (space_put_constraint(child_space, constr_def) != 0) {
>>> +				constraint_def_delete(constr_def);
>>> +				return -1;
>>> +			}>     box: make constraint operations transactional
>     
>     Put constraint names into the space's hash table and drop them on
>     replace in corresponding system spaces (_index, _fk_constraint,
>     _ck_constraint).
>     
>     Closes #3503
>     
>     @TarantoolBot document
>     Title: Table constraints in SQL
>     
>     SQL:
>     According to ANSI SQL, table constraint is one of the following
>     entities: PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK. Every
>     constraint have its own name passed by user or automatically
>     generated. And these names must be unique within one table/space.
>     Naming in SQL is case-insensitive, so "CONSTRAINT c" and
>     "CONSTRAINT C" are the same. For example, you will get error, if
>     you try to:
>     
>     CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
>                      CONSTRAINT c PRIMARY KEY (i));
>     
>     or
>     
>     CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);
>     ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
>     
>     The same is for any other constraint types.
>     
>     Lua/box:
>     
>     You also can create/drop table constraints from box. See
>     space_object:create_check_constraint() and space_object:create_index()
>     (if an index is unique). Naming in box is case-sensitive, so 'c' and
>     'C' are not the same (see naming policy). For example, an unique
>     index is a constraint, but a non-unique index is not. So, a non-unique
>     index can have the same name with a check/foreign key constraint
>     within one space:
>     
>     box.execute('CREATE TABLE t2 (i INT PRIMARY KEY);');
>     box.execute('CREATE INDEX e ON t2(i);');
>     box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
>     
>     But, if you try to:
>     
>     box.space.T2.index.E:alter({unique = true});
>     
>     You will get error, because index 'e' is becoming unique.
> 

The patch is getting better too slow, and 2.3.1 release is coming,
so I've finished it. I've pushed my review fixes on top of the
branch. Please, review my fixes. If you are ok with them, squash it
and all your 3 commits into 1 commit, and keep this commit message.
Then send to Nikita for a second review.

General changes I made:

- Renamed constraint_def -> constraint_id. Def is definition. Type
  and name is not a definition actually. It is identifier consisting
  of two parts. Definition includes CHECK expression, FOREIGN KEY
  column list, etc. This is not a definition. By definition you can
  create the object. By constraint_def it was not possible.

- Removed space_id from constraint_id, because it is the same in all
  constraint identifiers of a space. And it was not used anyway.

- I removed ER_CK_CONSTRAINT_EXISTS and ER_FK_CONSTRAINT_EXISTS,
  because appeared, that ER_CONSTRAINT_EXISTS works. Even with
  the new format. I don't know what was a problem with it.

- String names of constraint types are moved to constraint_id.c.
  Because this is where enum is defined. And it is logical to put
  the strings somewhere near. And it is consistent with all the
  other 'strs' arrays.

Other changes can be found in the 10 comments below.

My review fixes are appended to the end of the email.

> diff --git a/src/box/alter.cc b/src/box/alter.cc
> index bef25b605..d5e836ae7 100644
> --- a/src/box/alter.cc
> +++ b/src/box/alter.cc
> @@ -1033,10 +1034,13 @@ alter_space_rollback(struct trigger *trigger, void * /* event */)
>  	space_fill_index_map(alter->old_space);
>  	space_fill_index_map(alter->new_space);
>  	/*
> -	 * Don't forget about space triggers and foreign keys.
> +	 * Don't forget about space triggers, foreign keys and
> +	 * constraints.
>  	 */
>  	space_swap_triggers(alter->new_space, alter->old_space);
>  	space_swap_fk_constraints(alter->new_space, alter->old_space);
> +	SWAP(alter->new_space->constraints,
> +	     alter->old_space->constraints);

1. For the sake of similarity I wrapped it into a function
space_swap_constraint_ids().

>  	space_cache_replace(alter->new_space, alter->old_space);
>  	alter_space_delete(alter);
>  	return 0;
> @@ -1764,6 +1771,85 @@ MoveCkConstraints::rollback(struct alter_space *alter)
>  	space_swap_ck_constraint(alter->new_space, alter->old_space);
>  }
>  
> +/**
> + * CreateConstraintDef - add a new constraint def to the space.
> + */
> +class CreateConstraintDef: public AlterSpaceOp
> +{
> +private:
> +	struct constraint_def *new_constraint_def;
> +public:
> +	CreateConstraintDef(struct alter_space *alter,
> +			    struct constraint_def *def)
> +		:AlterSpaceOp(alter), new_constraint_def(def)
> +	{}
> +	virtual void alter(struct alter_space *alter);
> +	virtual void rollback(struct alter_space *alter);
> +};
> +

2. new_constraint_def leaks when CreateIndex::prepare() fails.
Because in that case rollback triggers are not called.
alter_space_do() sets rollback triggers only after all prepare()
and alter() have been finished successfully.

Fixed in my commit.

> +void
> +CreateConstraintDef::alter(struct alter_space *alter)
> +{
> +	assert(new_constraint_def != NULL);
> +	if (space_put_constraint(alter->old_space, new_constraint_def) != 0)
> +		diag_raise();

3. Well, I was wrong. alter_space_do() forbids exceptions after
prepare() calls have been finished. It is said explicitly in the
alter_space_do() comments. It means, that you can't raise here.
On the other hand you can't move it to prepare(), because if your
prepare() is finished, and then CreateIndex::prepare() fails,
then the new constraint stays in the space. Prepare() can't change
any state.

I changed it to panic().

> +}
> +
> +void
> +CreateConstraintDef::rollback(struct alter_space *alter)
> +{
> +	struct constraint_def *constraint_def =
> +		space_pop_constraint(alter->new_space,
> +				     new_constraint_def->name);
> +	assert(constraint_def == new_constraint_def);
> +	(void) alter;

4. Why (void) alter? It is used here. Even in the release mode.
I dropped it.

> +	constraint_def_delete(new_constraint_def);
> +}
> +
> +/** DropConstraintDef - drop a constraint def from the space. */
> +class DropConstraintDef: public AlterSpaceOp
> +{
> +private:
> +	struct constraint_def *old_constraint_def;
> +	const char *name;
> +public:
> +	DropConstraintDef(struct alter_space *alter, const char *name)
> +		:AlterSpaceOp(alter), old_constraint_def(NULL), name(name)
> +	{}
> +	virtual void alter(struct alter_space *alter);
> +	virtual void commit(struct alter_space *alter , int64_t signature);
> +	virtual void rollback(struct alter_space *alter);
> +};
> +
> +void
> +DropConstraintDef::alter(struct alter_space *alter)
> +{
> +	assert(name != NULL);
> +	old_constraint_def =
> +		space_constraint_def_by_name(alter->old_space, name);
> +	assert(old_constraint_def != NULL);
> +	space_pop_constraint(alter->old_space, name);

5. This is exactly why I asked you to add pop() - to avoid
space_constraint_def_by_name(). I removed the latter. In some
other places too.

> +}
> +
> +void
> +DropConstraintDef::commit(struct alter_space *alter, int64_t signature)
> +{
> +	(void) alter;
> +	(void) signature;
> +	assert(old_constraint_def != NULL);
> @@ -2363,6 +2449,76 @@ index_is_used_by_fk_constraint(struct rlist *fk_list, uint32_t iid)
>  	return false;
>  }
>  
> +/**
> + * Check if constraint with @a name exists within @a space. Call
> + * diag_set() if it is so.
> + *
> + * @param space Space to find constraint within.
> + * @param name  Constraint name.
> + *
> + * @retval -1 Constraint exists.
> + * @retval 0  Doesn't exist.
> + */
> +static inline int
> +check_existence(struct space *space, const char *name)
> +{

6. Check existence of what? You do realize that in the
code I don't see comments or function's body, and it looks
like 'check_existence(...)'? From that line it is totally
unclear existence of what you are trying to check.

Changed to space_check_constraint_existence(), and used in
CreateConstraintID::prepare().

> +	struct constraint_def *def = space_constraint_def_by_name(space, name);
> +	if (def != NULL) {
> +		diag_set(ClientError, ER_CONSTRAINT_EXISTS, name);
> +		return -1;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * Put the node of an unique index to the constraint hash table of
> + * @a space.
> + *
> + * This function is needed to wrap the duplicated piece of code
> + * inside on_replace_dd_index().
> + */
> +static int
> +create_index_as_constraint(struct alter_space *alter,
> +			   const struct index_def *def)
> +{
> +	assert(def->opts.is_unique);
> +	struct space *space = alter->old_space;
> +	if (check_existence(space, def->name) != 0)
> +		return -1;
> +	struct constraint_def *constr_def =
> +		constraint_def_new(space->def->id, def->iid == 0 ?
> +				   CONSTRAINT_TYPE_PK : CONSTRAINT_TYPE_UNIQUE,
> +				   def->name);
> +	if (constr_def == NULL)
> +		return -1;
> +	try {
> +		(void) new CreateConstraintDef(alter, constr_def);
> +	} catch (Exception *e) {
> +		constraint_def_delete(constr_def);
> +		return -1;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * Drop the node of an unique index from the constraint hash table
> + * of @a space.
> + *
> + * This function is needed to wrap the duplicated piece of code
> + * inside on_replace_dd_index().
> + */
> +static int
> +drop_index_as_constraint(struct alter_space *alter, const struct index_def *def)
> +{
> +	assert(def->opts.is_unique);
> +	try {
> +		(void) new DropConstraintDef(alter, def->name);
> +	} catch (Exception *e) {
> +		return -1;
> +	}
> +	return 0;
> +}

7. I moved construction of constraint_id to CreateConstraintID()
constructor, and removed these two functions.

> +
>  /**
>   * Just like with _space, 3 major cases:
>   *
> @@ -4984,6 +5166,48 @@ space_reset_fk_constraint_mask(struct space *space)
>  	}
>  }
>  
> +/**
> + * Put the node of a ck/fk constraint to the constraint hash table
> + * of @a space.
> + *
> + * This function is needed to wrap the duplicated piece of code
> + * inside the trigger functions below.
> + */
> +static int
> +create_fk_ck_as_constraint(struct space *space, enum constraint_type type,
> +			   const char *name)
> +{
> +	assert(type == CONSTRAINT_TYPE_CK || type == CONSTRAINT_TYPE_FK);
> +	struct constraint_def *constraint_def =
> +		constraint_def_new(space->def->id, type, name);
> +	if (constraint_def == NULL)
> +		return -1;
> +	if (space_put_constraint(space, constraint_def) != 0) {
> +		constraint_def_delete(constraint_def);
> +		return -1;
> +	}
> +	return 0;
> +}
> +
> +/**
> + * Drop the node of a ck/fk constraint from the constraint hash
> + * table of @a space.
> + *
> + * This function is needed to wrap the duplicated piece of code
> + * inside the trigger functions below.
> + */
> +static void
> +drop_fk_ck_as_constraint(struct space *space, const char *name)
> +{
> +	struct constraint_def *constraint_def =
> +		space_constraint_def_by_name(space, name);
> +	assert(constraint_def != NULL);
> +	assert(constraint_def->type == CONSTRAINT_TYPE_CK ||
> +	       constraint_def->type == CONSTRAINT_TYPE_FK);
> +	space_pop_constraint(space, name);
> +	constraint_def_delete(constraint_def);
> +}

8. FK and CK are constraints, so this is strange to
add 'as constraint' suffix. Also there is no reason
why they should be called 'fk_ck', because in fact
don't use anything specific to FK and CK.

I changed them to space_insert_constraint_id() and
space_delete_constraint_id().

> +
>  /**
>   * On rollback of creation we remove FK constraint from DD, i.e.
>   * from parent's and child's lists of constraints and
> @@ -5029,8 +5256,14 @@ on_drop_fk_constraint_rollback(struct trigger *trigger, void *event)
>  	struct fk_constraint *old_fk = (struct fk_constraint *)trigger->data;
>  	struct space *parent = space_by_id(old_fk->def->parent_id);
>  	struct space *child = space_by_id(old_fk->def->child_id);
> +	assert(child != NULL && parent != NULL);

9. Why did you add this assertion? You don't even need parent
space here (in your diff).

Dropped.

>  	rlist_add_entry(&child->child_fk_constraint, old_fk, in_child_space);
>  	rlist_add_entry(&parent->parent_fk_constraint, old_fk, in_parent_space);
> +	if (create_fk_ck_as_constraint(child, CONSTRAINT_TYPE_FK,
> +				       old_fk->def->name) != 0) {
> +		panic("Can't recover after FK constraint drop rollback (out of "
> +		      "memory)");
> +	}
>  	fk_constraint_set_mask(old_fk, &child->fk_constraint_mask,
>  			       FIELD_LINK_CHILD);
>  	fk_constraint_set_mask(old_fk, &parent->fk_constraint_mask,> @@ -5491,10 +5734,15 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
>  		auto ck_guard = make_scoped_guard([=] {
>  			ck_constraint_delete(new_ck_constraint);
>  		});
> -		if (old_ck_constraint != NULL)
> -			rlist_del_entry(old_ck_constraint, link);
> -		if (space_add_ck_constraint(space, new_ck_constraint) != 0)
> +		if (is_insert && check_existence(space, name) != 0)
> +			return -1;
> +		if (space_add_ck_constraint(space, new_ck_constraint) != 0 ||
> +		    (is_insert &&
> +		     create_fk_ck_as_constraint(space, CONSTRAINT_TYPE_CK,
> +						name) != 0))

10. Now if space_add_ck_constraint() succeeds and create_fk_ck_as_constraint()
does not, the added ck is not removed.

Fixed in my commit. See it and some explanations below.

================================================================================
commit f45592dd6994c03f6cc7f348ce4c524289c6a8e6
Author: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Date:   Sun Dec 15 20:07:29 2019 +0100

    Review fixes

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index bf3895262..19ce3d481 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -102,7 +102,7 @@ add_library(box STATIC
     sequence.c
     ck_constraint.c
     fk_constraint.c
-    constraint_def.c
+    constraint_id.c
     func.c
     func_def.c
     key_list.c
diff --git a/src/box/alter.cc b/src/box/alter.cc
index b1ef7ec4f..300b63712 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -57,7 +57,7 @@
 #include "version.h"
 #include "sequence.h"
 #include "sql.h"
-#include "constraint_def.h"
+#include "constraint_id.h"
 
 /* {{{ Auxiliary functions and methods. */
 
@@ -704,6 +704,12 @@ space_swap_fk_constraints(struct space *new_space, struct space *old_space)
 	SWAP(new_space->fk_constraint_mask, old_space->fk_constraint_mask);
 }
 
+static void
+space_swap_constraint_ids(struct space *new_space, struct space *old_space)
+{
+	SWAP(new_space->constraint_ids, old_space->constraint_ids);
+}
+
 /**
  * True if the space has records identified by key 'uid'.
  * Uses 'iid' index.
@@ -1039,8 +1045,7 @@ alter_space_rollback(struct trigger *trigger, void * /* event */)
 	 */
 	space_swap_triggers(alter->new_space, alter->old_space);
 	space_swap_fk_constraints(alter->new_space, alter->old_space);
-	SWAP(alter->new_space->constraints,
-	     alter->old_space->constraints);
+	space_swap_constraint_ids(alter->new_space, alter->old_space);
 	space_cache_replace(alter->new_space, alter->old_space);
 	alter_space_delete(alter);
 	return 0;
@@ -1152,8 +1157,7 @@ alter_space_do(struct txn_stmt *stmt, struct alter_space *alter)
 	 */
 	space_swap_triggers(alter->new_space, alter->old_space);
 	space_swap_fk_constraints(alter->new_space, alter->old_space);
-	SWAP(alter->new_space->constraints,
-	     alter->old_space->constraints);
+	space_swap_constraint_ids(alter->new_space, alter->old_space);
 	/*
 	 * The new space is ready. Time to update the space
 	 * cache with it.
@@ -1415,14 +1419,14 @@ ModifyIndex::~ModifyIndex()
 /** CreateIndex - add a new index to the space. */
 class CreateIndex: public AlterSpaceOp
 {
-public:
-	CreateIndex(struct alter_space *alter)
-		:AlterSpaceOp(alter), new_index(NULL), new_index_def(NULL)
-	{}
 	/** New index. */
 	struct index *new_index;
 	/** New index index_def. */
 	struct index_def *new_index_def;
+public:
+	CreateIndex(struct alter_space *alter, struct index_def *def)
+		:AlterSpaceOp(alter), new_index(NULL), new_index_def(def)
+	{}
 	virtual void alter_def(struct alter_space *alter);
 	virtual void prepare(struct alter_space *alter);
 	virtual void commit(struct alter_space *alter, int64_t lsn);
@@ -1772,49 +1776,115 @@ MoveCkConstraints::rollback(struct alter_space *alter)
 }
 
 /**
- * CreateConstraintDef - add a new constraint def to the space.
+ * Check if constraint with @a name exists within @a space. Call
+ * diag_set() if it is so.
  */
-class CreateConstraintDef: public AlterSpaceOp
+static inline int
+space_check_constraint_existence(struct space *space, const char *name)
 {
-private:
-	struct constraint_def *new_constraint_def;
+	struct constraint_id *id = space_find_constraint_id(space, name);
+	if (id == NULL)
+		return 0;
+	diag_set(ClientError, ER_CONSTRAINT_EXISTS,
+		 constraint_type_strs[id->type], name, space_name(space));
+	return -1;
+}
+
+/**
+ * Put a new constraint name into the space's namespace of
+ * constraints, with duplicate check.
+ */
+static int
+space_insert_constraint_id(struct space *space, enum constraint_type type,
+			   const char *name)
+{
+	if (space_check_constraint_existence(space, name) != 0)
+		return -1;
+	struct constraint_id *id = constraint_id_new(type, name);
+	if (id == NULL)
+		return -1;
+	if (space_add_constraint_id(space, id) != 0) {
+		constraint_id_delete(id);
+		return -1;
+	}
+	return 0;
+}
+
+static inline void
+space_delete_constraint_id(struct space *space, const char *name)
+{
+	constraint_id_delete(space_pop_constraint_id(space, name));
+}
+
+/** CreateConstraintID - add a new constraint id to a space. */
+class CreateConstraintID: public AlterSpaceOp
+{
+	struct constraint_id *new_id;
 public:
-	CreateConstraintDef(struct alter_space *alter,
-			    struct constraint_def *def)
-		:AlterSpaceOp(alter), new_constraint_def(def)
-	{}
+	CreateConstraintID(struct alter_space *alter, enum constraint_type type,
+			   const char *name)
+		:AlterSpaceOp(alter), new_id(NULL)
+	{
+		new_id = constraint_id_new(type, name);
+		if (new_id == NULL)
+			diag_raise();
+	}
+	virtual void prepare(struct alter_space *alter);
 	virtual void alter(struct alter_space *alter);
 	virtual void rollback(struct alter_space *alter);
+	virtual void commit(struct alter_space *alter, int64_t signature);
+	virtual ~CreateConstraintID();
 };
 
 void
-CreateConstraintDef::alter(struct alter_space *alter)
+CreateConstraintID::prepare(struct alter_space *alter)
 {
-	assert(new_constraint_def != NULL);
-	if (space_put_constraint(alter->old_space, new_constraint_def) != 0)
+	if (space_check_constraint_existence(alter->old_space,
+					     new_id->name) != 0)
 		diag_raise();
 }
 
 void
-CreateConstraintDef::rollback(struct alter_space *alter)
+CreateConstraintID::alter(struct alter_space *alter)
+{
+	/* Alter() can't fail, so can't just throw an error. */
+	if (space_add_constraint_id(alter->old_space, new_id) != 0)
+		panic("Can't add a new constraint id, out of memory");
+}
+
+void
+CreateConstraintID::rollback(struct alter_space *alter)
+{
+	space_delete_constraint_id(alter->new_space, new_id->name);
+	new_id = NULL;
+}
+
+void
+CreateConstraintID::commit(struct alter_space *alter, int64_t signature)
 {
-	struct constraint_def *constraint_def =
-		space_pop_constraint(alter->new_space,
-				     new_constraint_def->name);
-	assert(constraint_def == new_constraint_def);
 	(void) alter;
-	constraint_def_delete(new_constraint_def);
+	(void) signature;
+	/*
+	 * Constraint id is added to the space, and should not be
+	 * deleted from now on.
+	 */
+	new_id = NULL;
 }
 
-/** DropConstraintDef - drop a constraint def from the space. */
-class DropConstraintDef: public AlterSpaceOp
+CreateConstraintID::~CreateConstraintID()
 {
-private:
-	struct constraint_def *old_constraint_def;
+	if (new_id != NULL)
+		constraint_id_delete(new_id);
+}
+
+/** DropConstraintID - drop a constraint id from the space. */
+class DropConstraintID: public AlterSpaceOp
+{
+	struct constraint_id *old_id;
 	const char *name;
 public:
-	DropConstraintDef(struct alter_space *alter, const char *name)
-		:AlterSpaceOp(alter), old_constraint_def(NULL), name(name)
+	DropConstraintID(struct alter_space *alter, const char *name)
+		:AlterSpaceOp(alter), old_id(NULL), name(name)
 	{}
 	virtual void alter(struct alter_space *alter);
 	virtual void commit(struct alter_space *alter , int64_t signature);
@@ -1822,29 +1892,23 @@ public:
 };
 
 void
-DropConstraintDef::alter(struct alter_space *alter)
+DropConstraintID::alter(struct alter_space *alter)
 {
-	assert(name != NULL);
-	old_constraint_def =
-		space_constraint_def_by_name(alter->old_space, name);
-	assert(old_constraint_def != NULL);
-	space_pop_constraint(alter->old_space, name);
+	old_id = space_pop_constraint_id(alter->old_space, name);
 }
 
 void
-DropConstraintDef::commit(struct alter_space *alter, int64_t signature)
+DropConstraintID::commit(struct alter_space *alter, int64_t signature)
 {
 	(void) alter;
 	(void) signature;
-	assert(old_constraint_def != NULL);
-	constraint_def_delete(old_constraint_def);
+	constraint_id_delete(old_id);
 }
 
 void
-DropConstraintDef::rollback(struct alter_space *alter)
+DropConstraintID::rollback(struct alter_space *alter)
 {
-	assert(old_constraint_def != NULL);
-	if (space_put_constraint(alter->new_space, old_constraint_def) != 0) {
+	if (space_add_constraint_id(alter->new_space, old_id) != 0) {
 		panic("Can't recover after constraint drop rollback (out of "
 		      "memory)");
 	}
@@ -2449,95 +2513,6 @@ index_is_used_by_fk_constraint(struct rlist *fk_list, uint32_t iid)
 	return false;
 }
 
-/**
- * Just return string with constraint type to print it in an error
- * message.
- */
-static inline const char *
-constraint_type_str(struct constraint_def *def)
-{
-	static const char *type_str[CONSTRAINT_TYPE_MAX] = {
-		[CONSTRAINT_TYPE_PK]		= "PRIMARY KEY",
-		[CONSTRAINT_TYPE_UNIQUE]	= "UNIQUE",
-		[CONSTRAINT_TYPE_FK]		= "FOREIGN KEY",
-		[CONSTRAINT_TYPE_CK]		= "CHECK",
-	};
-
-	return def->type <= CONSTRAINT_TYPE_MAX ? type_str[def->type] :
-		"UNKNOWN";
-}
-
-/**
- * Check if constraint with @a name exists within @a space. Call
- * diag_set() if it is so.
- *
- * @param space Space to find constraint within.
- * @param name  Constraint name.
- *
- * @retval -1 Constraint exists.
- * @retval 0  Doesn't exist.
- */
-static inline int
-check_existence(struct space *space, const char *name)
-{
-	struct constraint_def *def = space_constraint_def_by_name(space, name);
-	if (def != NULL) {
-		diag_set(ClientError, ER_CONSTRAINT_EXISTS,
-			 constraint_type_str(def), name, space_name(space));
-		return -1;
-	}
-	return 0;
-}
-
-/**
- * Put the node of an unique index to the constraint hash table of
- * @a space.
- *
- * This function is needed to wrap the duplicated piece of code
- * inside on_replace_dd_index().
- */
-static int
-create_index_as_constraint(struct alter_space *alter,
-			   const struct index_def *def)
-{
-	assert(def->opts.is_unique);
-	struct space *space = alter->old_space;
-	if (check_existence(space, def->name) != 0)
-		return -1;
-	struct constraint_def *constr_def =
-		constraint_def_new(space->def->id, def->iid == 0 ?
-				   CONSTRAINT_TYPE_PK : CONSTRAINT_TYPE_UNIQUE,
-				   def->name);
-	if (constr_def == NULL)
-		return -1;
-	try {
-		(void) new CreateConstraintDef(alter, constr_def);
-	} catch (Exception *e) {
-		constraint_def_delete(constr_def);
-		return -1;
-	}
-	return 0;
-}
-
-/**
- * Drop the node of an unique index from the constraint hash table
- * of @a space.
- *
- * This function is needed to wrap the duplicated piece of code
- * inside on_replace_dd_index().
- */
-static int
-drop_index_as_constraint(struct alter_space *alter, const struct index_def *def)
-{
-	assert(def->opts.is_unique);
-	try {
-		(void) new DropConstraintDef(alter, def->name);
-	} catch (Exception *e) {
-		return -1;
-	}
-	return 0;
-}
-
 /**
  * Just like with _space, 3 major cases:
  *
@@ -2603,6 +2578,7 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 			 old_space->def->uid, SC_SPACE, priv_type) != 0)
 		return -1;
 	struct index *old_index = space_index(old_space, iid);
+	struct index_def *old_def = old_index != NULL ? old_index->def : NULL;
 
 	/*
 	 * Deal with various cases of dropping of the primary key.
@@ -2675,10 +2651,11 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 		}
 		if (alter_space_move_indexes(alter, 0, iid) != 0)
 			return -1;
-		if (old_index->def->opts.is_unique &&
-		    drop_index_as_constraint(alter, old_index->def) != 0)
-			return -1;
 		try {
+			if (old_index->def->opts.is_unique) {
+				(void) new DropConstraintID(alter,
+							    old_def->name);
+			}
 			(void) new DropIndex(alter, old_index);
 		} catch (Exception *e) {
 			return -1;
@@ -2692,20 +2669,18 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 			index_def_new_from_tuple(new_tuple, old_space);
 		if (def == NULL)
 			return -1;
-		if (def->opts.is_unique &&
-		    create_index_as_constraint(alter, def) != 0) {
-			index_def_delete(def);
-			return -1;
-		}
-		CreateIndex *create_index;
+		index_def_update_optionality(def, alter->new_min_field_count);
 		try {
-			create_index = new CreateIndex(alter);
+			if (def->opts.is_unique) {
+				(void) new CreateConstraintID(
+					alter, iid == 0 ? CONSTRAINT_TYPE_PK :
+					CONSTRAINT_TYPE_UNIQUE, def->name);
+			}
+			(void) new CreateIndex(alter, def);
 		} catch (Exception *e) {
 			index_def_delete(def);
 			return -1;
 		}
-		create_index->new_index_def = def;
-		index_def_update_optionality(def, alter->new_min_field_count);
 	}
 	/* Case 3 and 4: check if we need to rebuild index data. */
 	if (old_index != NULL && new_tuple != NULL) {
@@ -2715,23 +2690,34 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 			return -1;
 		auto index_def_guard =
 			make_scoped_guard([=] { index_def_delete(index_def); });
-		struct index_def *old_def = old_index->def;
 		/*
 		 * We put a new name when either an index is
-		 * becoming unique (i.e. constraint), or when an
+		 * becoming unique (i.e. constraint), or when a
================================================================================

'a unique', not 'an unique'. Because 'u' is ЙУ. So the first
sound is consonant.

================================================================================
 		 * unique index's name is under change.
 		 */
-		if (!old_def->opts.is_unique && index_def->opts.is_unique &&
-		    create_index_as_constraint(alter, index_def) != 0)
-			return -1;
+		bool do_new_constraint_id =
+			!old_def->opts.is_unique && index_def->opts.is_unique;
+		bool do_drop_constraint_id =
+			old_def->opts.is_unique && !index_def->opts.is_unique;
+
 		if (old_def->opts.is_unique && index_def->opts.is_unique &&
-		    strcmp(index_def->name, old_def->name) != 0 &&
-		    (create_index_as_constraint(alter, index_def) != 0 ||
-		     drop_index_as_constraint(alter, old_def) != 0))
-			return -1;
-		if (old_def->opts.is_unique && !index_def->opts.is_unique &&
-		    drop_index_as_constraint(alter, old_def) != 0)
+		    strcmp(index_def->name, old_def->name) != 0) {
+			do_new_constraint_id = true;
+			do_drop_constraint_id = true;
+		}
+		try {
+			if (do_new_constraint_id) {
+				(void) new CreateConstraintID(
+					alter, CONSTRAINT_TYPE_UNIQUE,
+					index_def->name);
+			}
+			if (do_drop_constraint_id) {
+				(void) new DropConstraintID(alter,
+							    old_def->name);
+			}
+		} catch (Exception *e) {
 			return -1;
+		}
 		/*
 		 * To detect which key parts are optional,
 		 * min_field_count is required. But
@@ -5185,48 +5171,6 @@ space_reset_fk_constraint_mask(struct space *space)
 	}
 }
 
-/**
- * Put the node of a ck/fk constraint to the constraint hash table
- * of @a space.
- *
- * This function is needed to wrap the duplicated piece of code
- * inside the trigger functions below.
- */
-static int
-create_fk_ck_as_constraint(struct space *space, enum constraint_type type,
-			   const char *name)
-{
-	assert(type == CONSTRAINT_TYPE_CK || type == CONSTRAINT_TYPE_FK);
-	struct constraint_def *constraint_def =
-		constraint_def_new(space->def->id, type, name);
-	if (constraint_def == NULL)
-		return -1;
-	if (space_put_constraint(space, constraint_def) != 0) {
-		constraint_def_delete(constraint_def);
-		return -1;
-	}
-	return 0;
-}
-
-/**
- * Drop the node of a ck/fk constraint from the constraint hash
- * table of @a space.
- *
- * This function is needed to wrap the duplicated piece of code
- * inside the trigger functions below.
- */
-static void
-drop_fk_ck_as_constraint(struct space *space, const char *name)
-{
-	struct constraint_def *constraint_def =
-		space_constraint_def_by_name(space, name);
-	assert(constraint_def != NULL);
-	assert(constraint_def->type == CONSTRAINT_TYPE_CK ||
-	       constraint_def->type == CONSTRAINT_TYPE_FK);
-	space_pop_constraint(space, name);
-	constraint_def_delete(constraint_def);
-}
-
 /**
  * On rollback of creation we remove FK constraint from DD, i.e.
  * from parent's and child's lists of constraints and
@@ -5241,7 +5185,7 @@ on_create_fk_constraint_rollback(struct trigger *trigger, void *event)
 	rlist_del_entry(fk, in_child_space);
 	struct space *child = space_by_id(fk->def->child_id);
 	assert(child != NULL);
-	drop_fk_ck_as_constraint(child, fk->def->name);
+	space_delete_constraint_id(child, fk->def->name);
 	space_reset_fk_constraint_mask(space_by_id(fk->def->parent_id));
 	space_reset_fk_constraint_mask(child);
 	fk_constraint_delete(fk);
@@ -5275,14 +5219,13 @@ on_drop_fk_constraint_rollback(struct trigger *trigger, void *event)
 	struct fk_constraint *old_fk = (struct fk_constraint *)trigger->data;
 	struct space *parent = space_by_id(old_fk->def->parent_id);
 	struct space *child = space_by_id(old_fk->def->child_id);
-	assert(child != NULL && parent != NULL);
-	rlist_add_entry(&child->child_fk_constraint, old_fk, in_child_space);
-	rlist_add_entry(&parent->parent_fk_constraint, old_fk, in_parent_space);
-	if (create_fk_ck_as_constraint(child, CONSTRAINT_TYPE_FK,
+	if (space_insert_constraint_id(child, CONSTRAINT_TYPE_FK,
 				       old_fk->def->name) != 0) {
 		panic("Can't recover after FK constraint drop rollback (out of "
 		      "memory)");
 	}
+	rlist_add_entry(&child->child_fk_constraint, old_fk, in_child_space);
+	rlist_add_entry(&parent->parent_fk_constraint, old_fk, in_parent_space);
================================================================================

I moved rlists down, because you added create_fk_ck_as_constraint() right in the
middle of FK creation. It is better not to scramble code.

================================================================================
 	fk_constraint_set_mask(old_fk, &child->fk_constraint_mask,
 			       FIELD_LINK_CHILD);
 	fk_constraint_set_mask(old_fk, &parent->fk_constraint_mask,
@@ -5462,14 +5405,12 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 		fk->def = fk_def;
 		fk->index_id = fk_index->def->iid;
 		if (old_tuple == NULL) {
-			if (check_existence(child_space, fk_def->name) != 0)
-				return -1;
================================================================================

I moved the check to space_insert_constraint_id(), because it was also
repeated.

================================================================================
 			struct trigger *on_rollback =
 				txn_alter_trigger_new(on_create_fk_constraint_rollback,
 						      fk);
 			if (on_rollback == NULL)
 				return -1;
-			if (create_fk_ck_as_constraint(child_space,
+			if (space_insert_constraint_id(child_space,
 						       CONSTRAINT_TYPE_FK,
 						       fk_def->name) != 0)
 				return -1;
@@ -5537,7 +5478,7 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 					      old_fk);
 		if (on_rollback == NULL)
 			return -1;
-		drop_fk_ck_as_constraint(child_space, fk_def->name);
+		space_delete_constraint_id(child_space, fk_def->name);
 		txn_stmt_on_rollback(stmt, on_rollback);
 		space_reset_fk_constraint_mask(child_space);
 		space_reset_fk_constraint_mask(parent_space);
@@ -5606,8 +5547,8 @@ on_create_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
 	const char *name = ck->def->name;
 	assert(space_ck_constraint_by_name(space, name, strlen(name)) != NULL);
 	space_remove_ck_constraint(space, ck);
-	drop_fk_ck_as_constraint(space, name);
 	ck_constraint_delete(ck);
+	space_delete_constraint_id(space, name);
 	if (trigger_run(&on_alter_space, space) != 0)
 		return -1;
 	return 0;
@@ -5634,7 +5575,7 @@ on_drop_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
 	const char *name = ck->def->name;
 	assert(space_ck_constraint_by_name(space, name, strlen(name)) == NULL);
 	if (space_add_ck_constraint(space, ck) != 0 ||
-	    create_fk_ck_as_constraint(space, CONSTRAINT_TYPE_CK, name) != 0)
+	    space_insert_constraint_id(space, CONSTRAINT_TYPE_CK, name) != 0)
 		panic("Can't recover after CK constraint drop rollback");
 	if (trigger_run(&on_alter_space, space) != 0)
 		return -1;
@@ -5753,23 +5694,24 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		auto ck_guard = make_scoped_guard([=] {
 			ck_constraint_delete(new_ck_constraint);
 		});
-		if (is_insert && check_existence(space, name) != 0)
-			return -1;
-		if (space_add_ck_constraint(space, new_ck_constraint) != 0 ||
-		    (is_insert &&
-		     create_fk_ck_as_constraint(space, CONSTRAINT_TYPE_CK,
-						name) != 0))
+		if (space_add_ck_constraint(space, new_ck_constraint) != 0)
 			return -1;
-		if (!is_insert)
+		if (!is_insert) {
 			rlist_del_entry(old_ck_constraint, link);
-		ck_guard.is_active = false;
-		if (old_tuple != NULL) {
 			on_rollback->data = old_ck_constraint;
 			on_rollback->run = on_replace_ck_constraint_rollback;
 		} else {
+			if (space_insert_constraint_id(space,
+						       CONSTRAINT_TYPE_CK,
+						       name) != 0) {
+				space_remove_ck_constraint(space,
+							   new_ck_constraint);
+				return -1;
+			}
 			on_rollback->data = new_ck_constraint;
 			on_rollback->run = on_create_ck_constraint_rollback;
 		}
+		ck_guard.is_active = false;
 		on_commit->data = old_ck_constraint;
 		on_commit->run = on_replace_ck_constraint_commit;
 	} else {
@@ -5783,7 +5725,7 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		struct ck_constraint *old_ck_constraint =
 			space_ck_constraint_by_name(space, name, name_len);
 		assert(old_ck_constraint != NULL);
-		drop_fk_ck_as_constraint(space, old_ck_constraint->def->name);
+		space_delete_constraint_id(space, old_ck_constraint->def->name);
 		space_remove_ck_constraint(space, old_ck_constraint);
 		on_commit->data = old_ck_constraint;
 		on_commit->run = on_drop_ck_constraint_commit;
diff --git a/src/box/constraint_def.c b/src/box/constraint_id.c
similarity index 77%
rename from src/box/constraint_def.c
rename to src/box/constraint_id.c
index 1d4367532..ba6ed859c 100755
--- a/src/box/constraint_def.c
+++ b/src/box/constraint_id.c
@@ -29,33 +29,35 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
-
-#include "constraint_def.h"
+#include "constraint_id.h"
 #include "assoc.h"
 #include "errcode.h"
 #include "diag.h"
-#include <string.h>
-#include <stdlib.h>
 
-struct constraint_def *
-constraint_def_new(uint32_t space_id, enum constraint_type type,
-		   const char *name)
+const char *constraint_type_strs[] = {
+	[CONSTRAINT_TYPE_PK]		= "PRIMARY KEY",
+	[CONSTRAINT_TYPE_UNIQUE]	= "UNIQUE",
+	[CONSTRAINT_TYPE_FK]		= "FOREIGN KEY",
+	[CONSTRAINT_TYPE_CK]		= "CHECK",
+};
+
+struct constraint_id *
+constraint_id_new(enum constraint_type type, const char *name)
 {
 	uint32_t len = strlen(name);
-	uint32_t size = sizeof(struct constraint_def) + len + 1;
-	struct constraint_def *ret = malloc(size);
+	uint32_t size = sizeof(struct constraint_id) + len + 1;
+	struct constraint_id *ret = malloc(size);
 	if (ret == NULL) {
 		diag_set(OutOfMemory, size, "malloc", "ret");
 		return NULL;
 	}
-	ret->space_id = space_id;
 	ret->type = type;
 	memcpy(ret->name, name, len + 1);
 	return ret;
 }
 
 void
-constraint_def_delete(struct constraint_def *def)
+constraint_id_delete(struct constraint_id *id)
 {
-	free(def);
+	free(id);
 }
diff --git a/src/box/constraint_def.h b/src/box/constraint_id.h
similarity index 74%
rename from src/box/constraint_def.h
rename to src/box/constraint_id.h
index d1b1a725a..21f067cdc 100755
--- a/src/box/constraint_def.h
+++ b/src/box/constraint_id.h
@@ -31,9 +31,6 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
-
-#include <stdint.h>
-
 #if defined(__cplusplus)
 extern "C" {
 #endif
@@ -43,40 +40,24 @@ enum constraint_type {
 	CONSTRAINT_TYPE_UNIQUE,
 	CONSTRAINT_TYPE_FK,
 	CONSTRAINT_TYPE_CK,
-
-	CONSTRAINT_TYPE_MAX,
 };
 
-struct constraint_def {
-	/** Space id. */
-	uint32_t space_id;
+extern const char *constraint_type_strs[];
+
+struct constraint_id {
 	/** Constraint type. */
 	enum constraint_type type;
 	/** Zero-terminated string with name. */
 	char name[0];
 };
 
-/**
- * Allocate memory and construct constraint def object.
- *
- * @param space_id Space id.
- * @param type     Constraint type.
- * @param name     Constraint name.
- *
- * @retval ptr  Constraint def object.
- * @retval NULL Memory allocation error.
- */
-struct constraint_def *
-constraint_def_new(uint32_t space_id, enum constraint_type type,
-		   const char *name);
+/** Allocate memory and construct constraint id. */
+struct constraint_id *
+constraint_id_new(enum constraint_type type, const char *name);
 
-/**
- * Free memory of constraint def object.
- *
- * @param def Constraint def.
- */
+/** Free memory of constraint id. */
 void
-constraint_def_delete(struct constraint_def *def);
+constraint_id_delete(struct constraint_id *id);
 
 #if defined(__cplusplus)
 }
diff --git a/src/box/errcode.h b/src/box/errcode.h
index 658f64e9b..094a63ee1 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -222,7 +222,7 @@ struct errcode_record {
 	/*167 */_(ER_CREATE_FK_CONSTRAINT,	"Failed to create foreign key constraint '%s': %s") \
 	/*168 */_(ER_DROP_FK_CONSTRAINT,	"Failed to drop foreign key constraint '%s': %s") \
 	/*169 */_(ER_NO_SUCH_CONSTRAINT,	"Constraint %s does not exist") \
-	/*170 */_(ER_CONSTRAINT_EXISTS,		"%s constraint '%s' already exists within space '%s'") \
+	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s '%s' already exists in space '%s'") \
================================================================================

Error message is changed to conform with other similar messages:

    <object type> <name> already exists in space <name>.

================================================================================
 	/*171 */_(ER_SQL_TYPE_MISMATCH,		"Type mismatch: can not convert %s to %s") \
 	/*172 */_(ER_ROWID_OVERFLOW,            "Rowid is overflowed: too many entries in ephemeral space") \
 	/*173 */_(ER_DROP_COLLATION,		"Can't drop collation %s : %s") \
@@ -258,8 +258,6 @@ struct errcode_record {
 	/*203 */_(ER_BOOTSTRAP_READONLY,	"Trying to bootstrap a local read-only instance as master") \
 	/*204 */_(ER_SQL_FUNC_WRONG_RET_COUNT,	"SQL expects exactly one argument returned from %s, got %d")\
 	/*205 */_(ER_FUNC_INVALID_RETURN_TYPE,	"Function '%s' returned value of invalid type: expected %s got %s") \
-	/*206 */_(ER_CK_CONSTRAINT_EXISTS,	"CHECK constraint '%s' already exists within space '%s'") \
-	/*207 */_(ER_FK_CONSTRAINT_EXISTS,	"FOREIGN KEY constraint '%s' already exists within space '%s'") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/space.c b/src/box/space.c
index cfae93cf1..1c19d099b 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -46,7 +46,7 @@
 #include "schema.h"
 #include "ck_constraint.h"
 #include "assoc.h"
-#include "constraint_def.h"
+#include "constraint_id.h"
 
 int
 access_check_space(struct space *space, user_access_t access)
@@ -204,11 +204,10 @@ space_create(struct space *space, struct engine *engine,
 			}
 		}
 	}
-
-	space->constraints = mh_strnptr_new();
-	if (space->constraints == NULL) {
-		diag_set(OutOfMemory, sizeof(*space->constraints), "malloc",
-			 "constraints");
+	space->constraint_ids = mh_strnptr_new();
+	if (space->constraint_ids == NULL) {
+		diag_set(OutOfMemory, sizeof(*space->constraint_ids), "malloc",
+			 "constraint_ids");
 		goto fail;
 	}
 	return 0;
@@ -271,8 +270,8 @@ space_delete(struct space *space)
 	 * on_replace_dd_ triggers on deletion from corresponding
 	 * system space.
 	 */
-	assert(mh_size(space->constraints) == 0);
-	mh_strnptr_delete(space->constraints);
+	assert(mh_size(space->constraint_ids) == 0);
+	mh_strnptr_delete(space->constraint_ids);
 	assert(space->sql_triggers == NULL);
 	assert(rlist_empty(&space->parent_fk_constraint));
 	assert(rlist_empty(&space->child_fk_constraint));
@@ -629,43 +628,43 @@ space_remove_ck_constraint(struct space *space, struct ck_constraint *ck)
 	}
 }
 
-struct constraint_def *
-space_constraint_def_by_name(struct space *space, const char *name)
+struct constraint_id *
+space_find_constraint_id(struct space *space, const char *name)
 {
+	struct mh_strnptr_t *ids = space->constraint_ids;
 	uint32_t len = strlen(name);
-	mh_int_t pos = mh_strnptr_find_inp(space->constraints, name, len);
-	if (pos == mh_end(space->constraints))
+	mh_int_t pos = mh_strnptr_find_inp(ids, name, len);
+	if (pos == mh_end(ids))
 		return NULL;
-	return (struct constraint_def *)
-		mh_strnptr_node(space->constraints, pos)->val;
+	return (struct constraint_id *) mh_strnptr_node(ids, pos)->val;
 }
 
 int
-space_put_constraint(struct space *space, struct constraint_def *def)
-{
-	uint32_t len = strlen(def->name);
-	uint32_t hash = mh_strn_hash(def->name, len);
-	const struct mh_strnptr_node_t name_node = { def->name, len, hash, def};
-	if (mh_strnptr_put(space->constraints, &name_node, NULL, NULL) ==
-	    mh_end(space->constraints)) {
-		diag_set(OutOfMemory, sizeof(name_node), "malloc",
-			 "constraints");
+space_add_constraint_id(struct space *space, struct constraint_id *id)
+{
+	assert(space_find_constraint_id(space, id->name) == NULL);
+	struct mh_strnptr_t *ids = space->constraint_ids;
+	uint32_t len = strlen(id->name);
+	uint32_t hash = mh_strn_hash(id->name, len);
+	const struct mh_strnptr_node_t name_node = {id->name, len, hash, id};
+	if (mh_strnptr_put(ids, &name_node, NULL, NULL) == mh_end(ids)) {
+		diag_set(OutOfMemory, sizeof(name_node), "malloc", "node");
 		return -1;
 	}
 	return 0;
 }
 
-struct constraint_def *
-space_pop_constraint(struct space *space, const char *name)
+struct constraint_id *
+space_pop_constraint_id(struct space *space, const char *name)
 {
+	struct mh_strnptr_t *ids = space->constraint_ids;
 	uint32_t len = strlen(name);
-	mh_int_t pos = mh_strnptr_find_inp(space->constraints, name, len);
-	if (pos == mh_end(space->constraints))
-		return NULL;
================================================================================

Pop() is never called not knowing in advance that the constraint exists.

================================================================================
-	struct constraint_def *def = (struct constraint_def *)
-		mh_strnptr_node(space->constraints, pos)->val;
-	mh_strnptr_del(space->constraints, pos, NULL);
-	return def;
+	mh_int_t pos = mh_strnptr_find_inp(ids, name, len);
+	assert(pos != mh_end(ids));
+	struct constraint_id *id = (struct constraint_id *)
+		mh_strnptr_node(ids, pos)->val;
+	mh_strnptr_del(ids, pos, NULL);
+	return id;
 }
 
 /* {{{ Virtual method stubs */
diff --git a/src/box/space.h b/src/box/space.h
index 08b5b4777..9aea4e5be 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -52,7 +52,7 @@ struct port;
 struct tuple;
 struct tuple_format;
 struct ck_constraint;
-struct constraint_def;
+struct constraint_id;
 
 struct space_vtab {
 	/** Free a space instance. */
@@ -235,8 +235,10 @@ struct space {
 	 * of parent constraints as well as child ones.
 	 */
 	uint64_t fk_constraint_mask;
-	/** Hash table with constraint def objects by name. */
-	struct mh_strnptr_t *constraints;
+	/**
+	 * Hash table with constraint identifiers hashed by name.
+	 */
+	struct mh_strnptr_t *constraint_ids;
================================================================================

This is not constraints themselves. The constraint objects are
stored in other members. This is why I renamed it to ids.

================================================================================
 };
 
 /** Initialize a base space instance. */
@@ -519,44 +521,24 @@ space_add_ck_constraint(struct space *space, struct ck_constraint *ck);
 void
 space_remove_ck_constraint(struct space *space, struct ck_constraint *ck);
 
-/**
- * Find node with @a name in the constraint hash table of @a
- * space.
- *
- * @param space Space.
- * @param name  Constraint name.
- *
- * @retval constraint_def object.
- * @retval NULL Constraint doesn't exist.
- */
-struct constraint_def *
-space_constraint_def_by_name(struct space *space, const char *name);
+/** Find a constraint identifier by name. */
+struct constraint_id *
+space_find_constraint_id(struct space *space, const char *name);
 
 /**
- * Put node with @a def to the constraint hash table of @a space.
- *
- * @param space Space.
- * @param def   Constraint def.
- *
- * @retval  0 Success.
- * @retval -1 Memory allocation error.
+ * Add a new constraint id to the space's hash table of all
+ * constraints. That is used to prevent existence of constraints
+ * with equal names.
  */
 int
-space_put_constraint(struct space *space, struct constraint_def *def);
+space_add_constraint_id(struct space *space, struct constraint_id *id);
 
 /**
- * Remove node with @a name from the constraint hash table of @a
- * space. But don't destroy the constraint def object binded to
- * this @a name.
- *
- * @param space Space.
- * @param name  Constraint name.
- *
- * @retval constraint_def object.
- * @retval NULL Constraint doesn't exist.
+ * Remove a given name from the hash of all constraint
+ * identifiers of the given space.
  */
-struct constraint_def *
-space_pop_constraint(struct space *space, const char *name);
+struct constraint_id *
+space_pop_constraint_id(struct space *space, const char *name);
 
 /*
  * Virtual method stubs.
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 556df7786..9470ef4c3 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -56,6 +56,7 @@
 #include "box/schema.h"
 #include "box/tuple_format.h"
 #include "box/coll_id_cache.h"
+#include "box/constraint_id.h"
 
 void
 sql_finish_coding(struct Parse *parse_context)
@@ -970,7 +971,9 @@ emitNewSysSpaceSequenceRecord(Parse *pParse, int reg_space_id, int reg_seq_id)
  * @param parser Parsing context.
  * @param ck_def Check constraint definition to be serialized.
  * @param reg_space_id The VDBE register containing space id.
-*/
+ * @param space_name Name of the space owning the CHECK. For error
+ *     message.
+ */
 static void
 vdbe_emit_ck_constraint_create(struct Parse *parser,
 			       const struct ck_constraint_def *ck_def,
@@ -996,13 +999,13 @@ vdbe_emit_ck_constraint_create(struct Parse *parser,
 	sqlVdbeAddOp3(v, OP_MakeRecord, ck_constraint_reg, 6,
 		      ck_constraint_reg + 6);
 	const char *error_msg =
-		tt_sprintf(tnt_errcode_desc(ER_CK_CONSTRAINT_EXISTS),
-					    ck_def->name, space_name);
+		tt_sprintf(tnt_errcode_desc(ER_CONSTRAINT_EXISTS),
+			   constraint_type_strs[CONSTRAINT_TYPE_CK],
+			   ck_def->name, space_name);
 	if (vdbe_emit_halt_with_presence_test(parser, BOX_CK_CONSTRAINT_ID, 0,
 					      ck_constraint_reg, 2,
-					      ER_CK_CONSTRAINT_EXISTS,
-					      error_msg, false,
-					      OP_NoConflict) != 0)
+					      ER_CONSTRAINT_EXISTS, error_msg,
+					      false, OP_NoConflict) != 0)
================================================================================

This being squashed with previous commits will result into 0 diff.

================================================================================
 		return;
 	sqlVdbeAddOp2(v, OP_SInsert, BOX_CK_CONSTRAINT_ID,
 		      ck_constraint_reg + 6);
@@ -1016,6 +1019,8 @@ vdbe_emit_ck_constraint_create(struct Parse *parser,
  *
  * @param parse_context Parsing context.
  * @param fk Foreign key to be created.
+ * @param space_name Name of the space owning the FOREIGN KEY. For
+ *     error message.
  */
 static void
 vdbe_emit_fk_constraint_create(struct Parse *parse_context,
@@ -1061,14 +1066,14 @@ vdbe_emit_fk_constraint_create(struct Parse *parse_context,
 	 * been created before.
 	 */
 	const char *error_msg =
-		tt_sprintf(tnt_errcode_desc(ER_FK_CONSTRAINT_EXISTS),
-			   name_copy, space_name);
+		tt_sprintf(tnt_errcode_desc(ER_CONSTRAINT_EXISTS),
+			   constraint_type_strs[CONSTRAINT_TYPE_FK], name_copy,
+			   space_name);
 	if (vdbe_emit_halt_with_presence_test(parse_context,
 					      BOX_FK_CONSTRAINT_ID, 0,
 					      constr_tuple_reg, 2,
-					      ER_FK_CONSTRAINT_EXISTS,
-					      error_msg, false,
-					      OP_NoConflict) != 0)
+					      ER_CONSTRAINT_EXISTS, error_msg,
+					      false, OP_NoConflict) != 0)
 		return;
 	sqlVdbeAddOp2(vdbe, OP_Bool, fk->is_deferred, constr_tuple_reg + 3);
 	sqlVdbeAddOp4(vdbe, OP_String8, 0, constr_tuple_reg + 4, 0,
diff --git a/test/box/misc.result b/test/box/misc.result
index c343726da..d2a20307a 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -554,8 +554,6 @@ t;
   203: box.error.BOOTSTRAP_READONLY
   204: box.error.SQL_FUNC_WRONG_RET_COUNT
   205: box.error.FUNC_INVALID_RETURN_TYPE
-  206: box.error.CK_CONSTRAINT_EXISTS
-  207: box.error.FK_CONSTRAINT_EXISTS
 ...
 test_run:cmd("setopt delimiter ''");
 ---
diff --git a/test/sql-tap/alter2.test.lua b/test/sql-tap/alter2.test.lua
index f1b4ff660..ac3ed000e 100755
--- a/test/sql-tap/alter2.test.lua
+++ b/test/sql-tap/alter2.test.lua
@@ -246,7 +246,7 @@ test:do_catchsql_test(
         ALTER TABLE child ADD CONSTRAINT fk FOREIGN KEY (a) REFERENCES child;
     ]], {
         -- <alter2-5.1>
-        1, "FOREIGN KEY constraint 'FK' already exists within space 'CHILD'"
+        1, "Constraint FOREIGN KEY 'FK' already exists in space 'CHILD'"
         -- </alter2-5.1>
     })
 
@@ -278,7 +278,7 @@ test:do_catchsql_test(
     "alter2-6.2",
     [[
         ALTER TABLE t1 ADD CONSTRAINT ck CHECK(id > 0);
-    ]], { 1, "CHECK constraint 'CK' already exists within space 'T1'" })
+    ]], { 1, "Constraint CHECK 'CK' already exists in space 'T1'" })
 
 -- Make sure that CHECK constraint can be created only on empty space.
 --
diff --git a/test/sql/checks.result b/test/sql/checks.result
index afd7c3d25..488659e84 100644
--- a/test/sql/checks.result
+++ b/test/sql/checks.result
@@ -260,7 +260,7 @@ s:drop()
 box.execute("CREATE TABLE T2(ID INT PRIMARY KEY, CONSTRAINT CK1 CHECK(ID > 0), CONSTRAINT CK1 CHECK(ID < 0))")
 ---
 - null
-- CHECK constraint 'CK1' already exists within space 'T2'
+- Constraint CHECK 'CK1' already exists in space 'T2'
 ...
 box.space.T2
 ---
diff --git a/test/sql/clear.result b/test/sql/clear.result
index 90070811a..988a972e7 100644
--- a/test/sql/clear.result
+++ b/test/sql/clear.result
@@ -177,7 +177,7 @@ box.execute("DROP TABLE zoobar")
 box.execute("CREATE TABLE t1(id INT PRIMARY KEY, CONSTRAINT ck1 CHECK(id > 0), CONSTRAINT ck1 CHECK(id < 0));")
 ---
 - null
-- CHECK constraint 'CK1' already exists within space 'T1'
+- Constraint CHECK 'CK1' already exists in space 'T1'
 ...
 box.space.t1
 ---
@@ -190,7 +190,7 @@ box.space._ck_constraint:select()
 box.execute("CREATE TABLE t2(id INT PRIMARY KEY, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t2, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t2);")
 ---
 - null
-- FOREIGN KEY constraint 'FK1' already exists within space 'T2'
+- Constraint FOREIGN KEY 'FK1' already exists in space 'T2'
 ...
 box.space.t2
 ---
@@ -207,7 +207,7 @@ box.space._fk_constraint:select()
 box.execute("CREATE TABLE t3(id INT PRIMARY KEY, CONSTRAINT ck1 CHECK(id > 0), CONSTRAINT ck1 FOREIGN KEY(id) REFERENCES t3, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t3, CONSTRAINT ck1 CHECK(id < 0));")
 ---
 - null
-- FOREIGN KEY constraint 'CK1' already exists within space 'T3'
+- Constraint FOREIGN KEY 'CK1' already exists in space 'T3'
 ...
 box.space.t1
 ---
diff --git a/test/sql/constraint.result b/test/sql/constraint.result
index 7383acdac..e452e5305 100644
--- a/test/sql/constraint.result
+++ b/test/sql/constraint.result
@@ -26,48 +26,48 @@ box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
                                CONSTRAINT c PRIMARY KEY (i));]]);
  | ---
  | - null
- | - PRIMARY KEY constraint 'C' already exists within space 'T2'
+ | - Constraint PRIMARY KEY 'C' already exists in space 'T2'
  | ...
 box.execute([[CREATE TABLE t2 (i INT,
                                CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
                                CONSTRAINT c PRIMARY KEY (i));]]);
  | ---
  | - null
- | - PRIMARY KEY constraint 'C' already exists within space 'T2'
+ | - Constraint PRIMARY KEY 'C' already exists in space 'T2'
  | ...
 box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
                                CONSTRAINT c CHECK (i > 0),
                                CONSTRAINT c UNIQUE (i));]]);
  | ---
  | - null
- | - UNIQUE constraint 'C' already exists within space 'T2'
+ | - Constraint UNIQUE 'C' already exists in space 'T2'
  | ...
 box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
                                CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
                                CONSTRAINT c UNIQUE (i));]]);
  | ---
  | - null
- | - UNIQUE constraint 'C' already exists within space 'T2'
+ | - Constraint UNIQUE 'C' already exists in space 'T2'
  | ...
 box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
                                CONSTRAINT c CHECK (i > 0),
                                CONSTRAINT c CHECK (i < 0));]]);
  | ---
  | - null
- | - CHECK constraint 'C' already exists within space 'T2'
+ | - Constraint CHECK 'C' already exists in space 'T2'
  | ...
 box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
                                CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
                                CONSTRAINT c CHECK (i > 0));]]);
  | ---
  | - null
- | - FOREIGN KEY constraint 'C' already exists within space 'T2'
+ | - Constraint FOREIGN KEY 'C' already exists in space 'T2'
  | ...
 box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
                                CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
  | ---
  | - null
- | - FOREIGN KEY constraint 'C' already exists within space 'T2'
+ | - Constraint FOREIGN KEY 'C' already exists in space 'T2'
  | ...
 test_run:cmd("setopt delimiter ''");
  | ---
@@ -85,7 +85,7 @@ box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);')
 box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);')
  | ---
  | - null
- | - PRIMARY KEY constraint 'C' already exists within space 'T2'
+ | - Constraint PRIMARY KEY 'C' already exists in space 'T2'
  | ...
 box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);')
  | ---
@@ -95,7 +95,7 @@ box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);')
 box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);')
  | ---
  | - null
- | - PRIMARY KEY constraint 'C' already exists within space 'T2'
+ | - Constraint PRIMARY KEY 'C' already exists in space 'T2'
  | ...
 
 --
@@ -149,7 +149,7 @@ test_run:cmd("setopt delimiter ''");
  | ...
 
 --
--- Make sure, that altering of an index name affect to its record
+-- Make sure, that altering of an index name affects its record
 -- in the space's constraint hash table.
 --
 box.execute('CREATE UNIQUE INDEX d ON t2(i);')
@@ -162,7 +162,7 @@ box.space.T2.index.D:alter({name = 'E'})
 box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
  | ---
  | - null
- | - UNIQUE constraint 'E' already exists within space 'T2'
+ | - Constraint UNIQUE 'E' already exists in space 'T2'
  | ...
 
 --
@@ -178,7 +178,7 @@ box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
  | ...
 box.space.T2.index.E:alter({unique = true})
  | ---
- | - error: CHECK constraint 'E' already exists within space 'T2'
+ | - error: Constraint CHECK 'E' already exists in space 'T2'
  | ...
 box.space.T2.ck_constraint.E:drop()
  | ---
@@ -189,7 +189,7 @@ box.space.T2.index.E:alter({unique = true})
 box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
  | ---
  | - null
- | - UNIQUE constraint 'E' already exists within space 'T2'
+ | - Constraint UNIQUE 'E' already exists in space 'T2'
  | ...
 
 -- Alter name and uniqueness of an unique index simultaneously.
diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
index e321b87aa..b41e16dc2 100755
--- a/test/sql/constraint.test.lua
+++ b/test/sql/constraint.test.lua
@@ -64,7 +64,7 @@ box.commit();
 test_run:cmd("setopt delimiter ''");
 
 --
--- Make sure, that altering of an index name affect to its record
+-- Make sure, that altering of an index name affects its record
 -- in the space's constraint hash table.
 --
 box.execute('CREATE UNIQUE INDEX d ON t2(i);')

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

* Re: [Tarantool-patches] [PATCH v2 2/3] box: make constraint operations transactional
  2019-12-15 22:26           ` Vladislav Shpilevoy
@ 2019-12-17 15:03             ` Roman Khabibov
  2019-12-28  0:18               ` Nikita Pettik
  0 siblings, 1 reply; 29+ messages in thread
From: Roman Khabibov @ 2019-12-17 15:03 UTC (permalink / raw)
  To: Sergey Ostanevich; +Cc: Vladislav Shpilevoy, tarantool-patches

Hi! Vlad, thanks for the review. Sergos or Nikita, can you, please, do a second one?

https://github.com/tarantool/tarantool/tree/romanhabibov/gh-3503-constr-names_v3

> On Dec 16, 2019, at 01:26, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Hi! Thanks for the fixes!
> 
>>> 2. This will fail in Release build mode, because
>>> assert arguments are not calculated in Release. So you
>>> delete the object, but it is still in the hash table.
>> +void
>> +CreateConstraintDef::rollback(struct alter_space *alter)
>> +{
>> +	struct constraint_def *constraint_def =
>> +		space_pop_constraint(alter->new_space,
>> +				     new_constraint_def->name);
>> +	assert(constraint_def == new_constraint_def);
>> +	(void) alter;
>> +	constraint_def_delete(new_constraint_def);
>> +}
>> 
> 
> And it still fails in the release mode, on build stage.
> https://travis-ci.org/tarantool/tarantool/builds/623163899?utm_source=github_status&utm_medium=notification
> 
>>>> 			if (on_rollback == NULL)
>>>> 				return -1;
>>>> +			struct constraint_def *constr_def =
>>>> +				constraint_def_new(child_space->def->id,
>>>> +						   CONSTRAINT_TYPE_FK,
>>>> +						   fk_def->name);
>>>> +			if (constr_def == NULL)
>>>> +				return -1;
>>>> +			if (space_put_constraint(child_space, constr_def) != 0) {
>>>> +				constraint_def_delete(constr_def);
>>>> +				return -1;
>>>> +			}>     box: make constraint operations transactional
>> 
>>    Put constraint names into the space's hash table and drop them on
>>    replace in corresponding system spaces (_index, _fk_constraint,
>>    _ck_constraint).
>> 
>>    Closes #3503
>> 
>>    @TarantoolBot document
>>    Title: Table constraints in SQL
>> 
>>    SQL:
>>    According to ANSI SQL, table constraint is one of the following
>>    entities: PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK. Every
>>    constraint have its own name passed by user or automatically
>>    generated. And these names must be unique within one table/space.
>>    Naming in SQL is case-insensitive, so "CONSTRAINT c" and
>>    "CONSTRAINT C" are the same. For example, you will get error, if
>>    you try to:
>> 
>>    CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
>>                     CONSTRAINT c PRIMARY KEY (i));
>> 
>>    or
>> 
>>    CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);
>>    ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
>> 
>>    The same is for any other constraint types.
>> 
>>    Lua/box:
>> 
>>    You also can create/drop table constraints from box. See
>>    space_object:create_check_constraint() and space_object:create_index()
>>    (if an index is unique). Naming in box is case-sensitive, so 'c' and
>>    'C' are not the same (see naming policy). For example, an unique
>>    index is a constraint, but a non-unique index is not. So, a non-unique
>>    index can have the same name with a check/foreign key constraint
>>    within one space:
>> 
>>    box.execute('CREATE TABLE t2 (i INT PRIMARY KEY);');
>>    box.execute('CREATE INDEX e ON t2(i);');
>>    box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
>> 
>>    But, if you try to:
>> 
>>    box.space.T2.index.E:alter({unique = true});
>> 
>>    You will get error, because index 'e' is becoming unique.
>> 
> 
> The patch is getting better too slow, and 2.3.1 release is coming,
> so I've finished it. I've pushed my review fixes on top of the
> branch. Please, review my fixes. If you are ok with them, squash it
> and all your 3 commits into 1 commit, and keep this commit message.
> Then send to Nikita for a second review.
> 
> General changes I made:
> 
> - Renamed constraint_def -> constraint_id. Def is definition. Type
>  and name is not a definition actually. It is identifier consisting
>  of two parts. Definition includes CHECK expression, FOREIGN KEY
>  column list, etc. This is not a definition. By definition you can
>  create the object. By constraint_def it was not possible.
> 
> - Removed space_id from constraint_id, because it is the same in all
>  constraint identifiers of a space. And it was not used anyway.
> 
> - I removed ER_CK_CONSTRAINT_EXISTS and ER_FK_CONSTRAINT_EXISTS,
>  because appeared, that ER_CONSTRAINT_EXISTS works. Even with
>  the new format. I don't know what was a problem with it.
> 
> - String names of constraint types are moved to constraint_id.c.
>  Because this is where enum is defined. And it is logical to put
>  the strings somewhere near. And it is consistent with all the
>  other 'strs' arrays.
> 
> Other changes can be found in the 10 comments below.
> 
> My review fixes are appended to the end of the email.

I’m ok with it. I also fixed one use-after-free bug appeared after me.

commit cb846d4ce625c2095f6607cfb126158e88ff7cf4
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Tue Nov 5 18:06:59 2019 +0300

    box: make constraint operations transactional
    
    Put constraint names into the space's hash table and drop them on
    replace in corresponding system spaces (_index, _fk_constraint,
    _ck_constraint).
    
    Closes #3503
    
    @TarantoolBot document
    Title: Table constraints in SQL
    
    SQL:
    According to ANSI SQL, table constraint is one of the following
    entities: PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK. Every
    constraint have its own name passed by user or automatically
    generated. And these names must be unique within one table/space.
    Naming in SQL is case-insensitive, so "CONSTRAINT c" and
    "CONSTRAINT C" are the same. For example, you will get error, if
    you try to:
    
    CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
                     CONSTRAINT c PRIMARY KEY (i));
    
    or
    
    CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);
    ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
    
    The same is for any other constraint types.
    
    Lua/box:
    
    You also can create/drop table constraints from box. See
    space_object:create_check_constraint() and space_object:create_index()
    (if an index is unique). Naming in box is case-sensitive, so 'c' and
    'C' are not the same (see naming policy). For example, an unique
    index is a constraint, but a non-unique index is not. So, a non-unique
    index can have the same name with a check/foreign key constraint
    within one space:
    
    box.execute('CREATE TABLE t2 (i INT PRIMARY KEY);');
    box.execute('CREATE INDEX e ON t2(i);');
    box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);');
    
    But, if you try to:
    
    box.space.T2.index.E:alter({unique = true});
    
    You will get error, because index 'e' is becoming unique.

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 5cd5cba81..19ce3d481 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -102,6 +102,7 @@ add_library(box STATIC
     sequence.c
     ck_constraint.c
     fk_constraint.c
+    constraint_id.c
     func.c
     func_def.c
     key_list.c
diff --git a/src/box/alter.cc b/src/box/alter.cc
index bef25b605..2e4fa3c41 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -57,6 +57,7 @@
 #include "version.h"
 #include "sequence.h"
 #include "sql.h"
+#include "constraint_id.h"
 
 /* {{{ Auxiliary functions and methods. */
 
@@ -703,6 +704,12 @@ space_swap_fk_constraints(struct space *new_space, struct space *old_space)
 	SWAP(new_space->fk_constraint_mask, old_space->fk_constraint_mask);
 }
 
+static void
+space_swap_constraint_ids(struct space *new_space, struct space *old_space)
+{
+	SWAP(new_space->constraint_ids, old_space->constraint_ids);
+}
+
 /**
  * True if the space has records identified by key 'uid'.
  * Uses 'iid' index.
@@ -1033,10 +1040,12 @@ alter_space_rollback(struct trigger *trigger, void * /* event */)
 	space_fill_index_map(alter->old_space);
 	space_fill_index_map(alter->new_space);
 	/*
-	 * Don't forget about space triggers and foreign keys.
+	 * Don't forget about space triggers, foreign keys and
+	 * constraints.
 	 */
 	space_swap_triggers(alter->new_space, alter->old_space);
 	space_swap_fk_constraints(alter->new_space, alter->old_space);
+	space_swap_constraint_ids(alter->new_space, alter->old_space);
 	space_cache_replace(alter->new_space, alter->old_space);
 	alter_space_delete(alter);
 	return 0;
@@ -1143,10 +1152,12 @@ alter_space_do(struct txn_stmt *stmt, struct alter_space *alter)
 	space_fill_index_map(alter->old_space);
 	space_fill_index_map(alter->new_space);
 	/*
-	 * Don't forget about space triggers and foreign keys.
+	 * Don't forget about space triggers, foreign keys and
+	 * constraints.
 	 */
 	space_swap_triggers(alter->new_space, alter->old_space);
 	space_swap_fk_constraints(alter->new_space, alter->old_space);
+	space_swap_constraint_ids(alter->new_space, alter->old_space);
 	/*
 	 * The new space is ready. Time to update the space
 	 * cache with it.
@@ -1408,14 +1419,14 @@ ModifyIndex::~ModifyIndex()
 /** CreateIndex - add a new index to the space. */
 class CreateIndex: public AlterSpaceOp
 {
-public:
-	CreateIndex(struct alter_space *alter)
-		:AlterSpaceOp(alter), new_index(NULL), new_index_def(NULL)
-	{}
 	/** New index. */
 	struct index *new_index;
 	/** New index index_def. */
 	struct index_def *new_index_def;
+public:
+	CreateIndex(struct alter_space *alter, struct index_def *def)
+		:AlterSpaceOp(alter), new_index(NULL), new_index_def(def)
+	{}
 	virtual void alter_def(struct alter_space *alter);
 	virtual void prepare(struct alter_space *alter);
 	virtual void commit(struct alter_space *alter, int64_t lsn);
@@ -1764,6 +1775,145 @@ MoveCkConstraints::rollback(struct alter_space *alter)
 	space_swap_ck_constraint(alter->new_space, alter->old_space);
 }
 
+/**
+ * Check if constraint with @a name exists within @a space. Call
+ * diag_set() if it is so.
+ */
+static inline int
+space_check_constraint_existence(struct space *space, const char *name)
+{
+	struct constraint_id *id = space_find_constraint_id(space, name);
+	if (id == NULL)
+		return 0;
+	diag_set(ClientError, ER_CONSTRAINT_EXISTS,
+		 constraint_type_strs[id->type], name, space_name(space));
+	return -1;
+}
+
+/**
+ * Put a new constraint name into the space's namespace of
+ * constraints, with duplicate check.
+ */
+static int
+space_insert_constraint_id(struct space *space, enum constraint_type type,
+			   const char *name)
+{
+	if (space_check_constraint_existence(space, name) != 0)
+		return -1;
+	struct constraint_id *id = constraint_id_new(type, name);
+	if (id == NULL)
+		return -1;
+	if (space_add_constraint_id(space, id) != 0) {
+		constraint_id_delete(id);
+		return -1;
+	}
+	return 0;
+}
+
+static inline void
+space_delete_constraint_id(struct space *space, const char *name)
+{
+	constraint_id_delete(space_pop_constraint_id(space, name));
+}
+
+/** CreateConstraintID - add a new constraint id to a space. */
+class CreateConstraintID: public AlterSpaceOp
+{
+	struct constraint_id *new_id;
+public:
+	CreateConstraintID(struct alter_space *alter, enum constraint_type type,
+			   const char *name)
+		:AlterSpaceOp(alter), new_id(NULL)
+	{
+		new_id = constraint_id_new(type, name);
+		if (new_id == NULL)
+			diag_raise();
+	}
+	virtual void prepare(struct alter_space *alter);
+	virtual void alter(struct alter_space *alter);
+	virtual void rollback(struct alter_space *alter);
+	virtual void commit(struct alter_space *alter, int64_t signature);
+	virtual ~CreateConstraintID();
+};
+
+void
+CreateConstraintID::prepare(struct alter_space *alter)
+{
+	if (space_check_constraint_existence(alter->old_space,
+					     new_id->name) != 0)
+		diag_raise();
+}
+
+void
+CreateConstraintID::alter(struct alter_space *alter)
+{
+	/* Alter() can't fail, so can't just throw an error. */
+	if (space_add_constraint_id(alter->old_space, new_id) != 0)
+		panic("Can't add a new constraint id, out of memory");
+}
+
+void
+CreateConstraintID::rollback(struct alter_space *alter)
+{
+	space_delete_constraint_id(alter->new_space, new_id->name);
+	new_id = NULL;
+}
+
+void
+CreateConstraintID::commit(struct alter_space *alter, int64_t signature)
+{
+	(void) alter;
+	(void) signature;
+	/*
+	 * Constraint id is added to the space, and should not be
+	 * deleted from now on.
+	 */
+	new_id = NULL;
+}
+
+CreateConstraintID::~CreateConstraintID()
+{
+	if (new_id != NULL)
+		constraint_id_delete(new_id);
+}
+
+/** DropConstraintID - drop a constraint id from the space. */
+class DropConstraintID: public AlterSpaceOp
+{
+	struct constraint_id *old_id;
+	const char *name;
+public:
+	DropConstraintID(struct alter_space *alter, const char *name)
+		:AlterSpaceOp(alter), old_id(NULL), name(name)
+	{}
+	virtual void alter(struct alter_space *alter);
+	virtual void commit(struct alter_space *alter , int64_t signature);
+	virtual void rollback(struct alter_space *alter);
+};
+
+void
+DropConstraintID::alter(struct alter_space *alter)
+{
+	old_id = space_pop_constraint_id(alter->old_space, name);
+}
+
+void
+DropConstraintID::commit(struct alter_space *alter, int64_t signature)
+{
+	(void) alter;
+	(void) signature;
+	constraint_id_delete(old_id);
+}
+
+void
+DropConstraintID::rollback(struct alter_space *alter)
+{
+	if (space_add_constraint_id(alter->new_space, old_id) != 0) {
+		panic("Can't recover after constraint drop rollback (out of "
+		      "memory)");
+	}
+}
+
 /* }}} */
 
 /**
@@ -2428,6 +2578,7 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 			 old_space->def->uid, SC_SPACE, priv_type) != 0)
 		return -1;
 	struct index *old_index = space_index(old_space, iid);
+	struct index_def *old_def = old_index != NULL ? old_index->def : NULL;
 
 	/*
 	 * Deal with various cases of dropping of the primary key.
@@ -2501,6 +2652,10 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 		if (alter_space_move_indexes(alter, 0, iid) != 0)
 			return -1;
 		try {
+			if (old_index->def->opts.is_unique) {
+				(void) new DropConstraintID(alter,
+							    old_def->name);
+			}
 			(void) new DropIndex(alter, old_index);
 		} catch (Exception *e) {
 			return -1;
@@ -2510,18 +2665,22 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 	if (old_index == NULL && new_tuple != NULL) {
 		if (alter_space_move_indexes(alter, 0, iid))
 			return -1;
-		CreateIndex *create_index;
+		struct index_def *def =
+			index_def_new_from_tuple(new_tuple, old_space);
+		if (def == NULL)
+			return -1;
+		index_def_update_optionality(def, alter->new_min_field_count);
 		try {
-			create_index = new CreateIndex(alter);
+			if (def->opts.is_unique) {
+				(void) new CreateConstraintID(
+					alter, iid == 0 ? CONSTRAINT_TYPE_PK :
+					CONSTRAINT_TYPE_UNIQUE, def->name);
+			}
+			(void) new CreateIndex(alter, def);
 		} catch (Exception *e) {
+			index_def_delete(def);
 			return -1;
 		}
-		create_index->new_index_def =
-			index_def_new_from_tuple(new_tuple, old_space);
-		if (create_index->new_index_def == NULL)
-			return -1;
-		index_def_update_optionality(create_index->new_index_def,
-					     alter->new_min_field_count);
 	}
 	/* Case 3 and 4: check if we need to rebuild index data. */
 	if (old_index != NULL && new_tuple != NULL) {
@@ -2531,6 +2690,34 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 			return -1;
 		auto index_def_guard =
 			make_scoped_guard([=] { index_def_delete(index_def); });
+		/*
+		 * We put a new name when either an index is
+		 * becoming unique (i.e. constraint), or when a
+		 * unique index's name is under change.
+		 */
+		bool do_new_constraint_id =
+			!old_def->opts.is_unique && index_def->opts.is_unique;
+		bool do_drop_constraint_id =
+			old_def->opts.is_unique && !index_def->opts.is_unique;
+
+		if (old_def->opts.is_unique && index_def->opts.is_unique &&
+		    strcmp(index_def->name, old_def->name) != 0) {
+			do_new_constraint_id = true;
+			do_drop_constraint_id = true;
+		}
+		try {
+			if (do_new_constraint_id) {
+				(void) new CreateConstraintID(
+					alter, CONSTRAINT_TYPE_UNIQUE,
+					index_def->name);
+			}
+			if (do_drop_constraint_id) {
+				(void) new DropConstraintID(alter,
+							    old_def->name);
+			}
+		} catch (Exception *e) {
+			return -1;
+		}
 		/*
 		 * To detect which key parts are optional,
 		 * min_field_count is required. But
@@ -4996,8 +5183,11 @@ on_create_fk_constraint_rollback(struct trigger *trigger, void *event)
 	struct fk_constraint *fk = (struct fk_constraint *)trigger->data;
 	rlist_del_entry(fk, in_parent_space);
 	rlist_del_entry(fk, in_child_space);
+	struct space *child = space_by_id(fk->def->child_id);
+	assert(child != NULL);
+	space_delete_constraint_id(child, fk->def->name);
 	space_reset_fk_constraint_mask(space_by_id(fk->def->parent_id));
-	space_reset_fk_constraint_mask(space_by_id(fk->def->child_id));
+	space_reset_fk_constraint_mask(child);
 	fk_constraint_delete(fk);
 	return 0;
 }
@@ -5029,6 +5219,11 @@ on_drop_fk_constraint_rollback(struct trigger *trigger, void *event)
 	struct fk_constraint *old_fk = (struct fk_constraint *)trigger->data;
 	struct space *parent = space_by_id(old_fk->def->parent_id);
 	struct space *child = space_by_id(old_fk->def->child_id);
+	if (space_insert_constraint_id(child, CONSTRAINT_TYPE_FK,
+				       old_fk->def->name) != 0) {
+		panic("Can't recover after FK constraint drop rollback (out of "
+		      "memory)");
+	}
 	rlist_add_entry(&child->child_fk_constraint, old_fk, in_child_space);
 	rlist_add_entry(&parent->parent_fk_constraint, old_fk, in_parent_space);
 	fk_constraint_set_mask(old_fk, &child->fk_constraint_mask,
@@ -5210,15 +5405,19 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 		fk->def = fk_def;
 		fk->index_id = fk_index->def->iid;
 		if (old_tuple == NULL) {
-			rlist_add_entry(&child_space->child_fk_constraint,
-					fk, in_child_space);
-			rlist_add_entry(&parent_space->parent_fk_constraint,
-					fk, in_parent_space);
 			struct trigger *on_rollback =
 				txn_alter_trigger_new(on_create_fk_constraint_rollback,
 						      fk);
 			if (on_rollback == NULL)
 				return -1;
+			if (space_insert_constraint_id(child_space,
+						       CONSTRAINT_TYPE_FK,
+						       fk_def->name) != 0)
+				return -1;
+			rlist_add_entry(&child_space->child_fk_constraint,
+					fk, in_child_space);
+			rlist_add_entry(&parent_space->parent_fk_constraint,
+					fk, in_parent_space);
 			txn_stmt_on_rollback(stmt, on_rollback);
 			fk_constraint_set_mask(fk,
 					       &parent_space->fk_constraint_mask,
@@ -5279,6 +5478,7 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 					      old_fk);
 		if (on_rollback == NULL)
 			return -1;
+		space_delete_constraint_id(child_space, fk_def->name);
 		txn_stmt_on_rollback(stmt, on_rollback);
 		space_reset_fk_constraint_mask(child_space);
 		space_reset_fk_constraint_mask(parent_space);
@@ -5344,9 +5544,10 @@ on_create_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
 	assert(ck != NULL);
 	struct space *space = space_by_id(ck->def->space_id);
 	assert(space != NULL);
-	assert(space_ck_constraint_by_name(space, ck->def->name,
-					   strlen(ck->def->name)) != NULL);
+	const char *name = ck->def->name;
+	assert(space_ck_constraint_by_name(space, name, strlen(name)) != NULL);
 	space_remove_ck_constraint(space, ck);
+	space_delete_constraint_id(space, name);
 	ck_constraint_delete(ck);
 	if (trigger_run(&on_alter_space, space) != 0)
 		return -1;
@@ -5371,9 +5572,10 @@ on_drop_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
 	assert(ck != NULL);
 	struct space *space = space_by_id(ck->def->space_id);
 	assert(space != NULL);
-	assert(space_ck_constraint_by_name(space, ck->def->name,
-					   strlen(ck->def->name)) == NULL);
-	if (space_add_ck_constraint(space, ck) != 0)
+	const char *name = ck->def->name;
+	assert(space_ck_constraint_by_name(space, name, strlen(name)) == NULL);
+	if (space_add_ck_constraint(space, ck) != 0 ||
+	    space_insert_constraint_id(space, CONSTRAINT_TYPE_CK, name) != 0)
 		panic("Can't recover after CK constraint drop rollback");
 	if (trigger_run(&on_alter_space, space) != 0)
 		return -1;
@@ -5459,7 +5661,8 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		const char *name = ck_def->name;
 		struct ck_constraint *old_ck_constraint =
 			space_ck_constraint_by_name(space, name, strlen(name));
-		if (old_ck_constraint != NULL) {
+		bool is_insert = old_ck_constraint == NULL;
+		if (!is_insert) {
 			struct ck_constraint_def *old_def =
 						old_ck_constraint->def;
 			assert(old_def->space_id == ck_def->space_id);
@@ -5491,18 +5694,24 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		auto ck_guard = make_scoped_guard([=] {
 			ck_constraint_delete(new_ck_constraint);
 		});
-		if (old_ck_constraint != NULL)
-			rlist_del_entry(old_ck_constraint, link);
 		if (space_add_ck_constraint(space, new_ck_constraint) != 0)
 			return -1;
-		ck_guard.is_active = false;
-		if (old_tuple != NULL) {
+		if (!is_insert) {
+			rlist_del_entry(old_ck_constraint, link);
 			on_rollback->data = old_ck_constraint;
 			on_rollback->run = on_replace_ck_constraint_rollback;
 		} else {
+			if (space_insert_constraint_id(space,
+						       CONSTRAINT_TYPE_CK,
+						       name) != 0) {
+				space_remove_ck_constraint(space,
+							   new_ck_constraint);
+				return -1;
+			}
 			on_rollback->data = new_ck_constraint;
 			on_rollback->run = on_create_ck_constraint_rollback;
 		}
+		ck_guard.is_active = false;
 		on_commit->data = old_ck_constraint;
 		on_commit->run = on_replace_ck_constraint_commit;
 	} else {
@@ -5516,6 +5725,7 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		struct ck_constraint *old_ck_constraint =
 			space_ck_constraint_by_name(space, name, name_len);
 		assert(old_ck_constraint != NULL);
+		space_delete_constraint_id(space, old_ck_constraint->def->name);
 		space_remove_ck_constraint(space, old_ck_constraint);
 		on_commit->data = old_ck_constraint;
 		on_commit->run = on_drop_ck_constraint_commit;
diff --git a/src/box/constraint_id.c b/src/box/constraint_id.c
new file mode 100755
index 000000000..ba6ed859c
--- /dev/null
+++ b/src/box/constraint_id.c
@@ -0,0 +1,63 @@
+/*
+ * 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 "constraint_id.h"
+#include "assoc.h"
+#include "errcode.h"
+#include "diag.h"
+
+const char *constraint_type_strs[] = {
+	[CONSTRAINT_TYPE_PK]		= "PRIMARY KEY",
+	[CONSTRAINT_TYPE_UNIQUE]	= "UNIQUE",
+	[CONSTRAINT_TYPE_FK]		= "FOREIGN KEY",
+	[CONSTRAINT_TYPE_CK]		= "CHECK",
+};
+
+struct constraint_id *
+constraint_id_new(enum constraint_type type, const char *name)
+{
+	uint32_t len = strlen(name);
+	uint32_t size = sizeof(struct constraint_id) + len + 1;
+	struct constraint_id *ret = malloc(size);
+	if (ret == NULL) {
+		diag_set(OutOfMemory, size, "malloc", "ret");
+		return NULL;
+	}
+	ret->type = type;
+	memcpy(ret->name, name, len + 1);
+	return ret;
+}
+
+void
+constraint_id_delete(struct constraint_id *id)
+{
+	free(id);
+}
diff --git a/src/box/constraint_id.h b/src/box/constraint_id.h
new file mode 100755
index 000000000..21f067cdc
--- /dev/null
+++ b/src/box/constraint_id.h
@@ -0,0 +1,64 @@
+#pragma once
+
+/*
+ * 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.
+ */
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+enum constraint_type {
+	CONSTRAINT_TYPE_PK = 0,
+	CONSTRAINT_TYPE_UNIQUE,
+	CONSTRAINT_TYPE_FK,
+	CONSTRAINT_TYPE_CK,
+};
+
+extern const char *constraint_type_strs[];
+
+struct constraint_id {
+	/** Constraint type. */
+	enum constraint_type type;
+	/** Zero-terminated string with name. */
+	char name[0];
+};
+
+/** Allocate memory and construct constraint id. */
+struct constraint_id *
+constraint_id_new(enum constraint_type type, const char *name);
+
+/** Free memory of constraint id. */
+void
+constraint_id_delete(struct constraint_id *id);
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/src/box/errcode.h b/src/box/errcode.h
index c660b1c70..094a63ee1 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -222,7 +222,7 @@ struct errcode_record {
 	/*167 */_(ER_CREATE_FK_CONSTRAINT,	"Failed to create foreign key constraint '%s': %s") \
 	/*168 */_(ER_DROP_FK_CONSTRAINT,	"Failed to drop foreign key constraint '%s': %s") \
 	/*169 */_(ER_NO_SUCH_CONSTRAINT,	"Constraint %s does not exist") \
-	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s already exists") \
+	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s '%s' already exists in space '%s'") \
 	/*171 */_(ER_SQL_TYPE_MISMATCH,		"Type mismatch: can not convert %s to %s") \
 	/*172 */_(ER_ROWID_OVERFLOW,            "Rowid is overflowed: too many entries in ephemeral space") \
 	/*173 */_(ER_DROP_COLLATION,		"Can't drop collation %s : %s") \
diff --git a/src/box/space.c b/src/box/space.c
index 94716a414..1c19d099b 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -45,6 +45,8 @@
 #include "iproto_constants.h"
 #include "schema.h"
 #include "ck_constraint.h"
+#include "assoc.h"
+#include "constraint_id.h"
 
 int
 access_check_space(struct space *space, user_access_t access)
@@ -202,6 +204,12 @@ space_create(struct space *space, struct engine *engine,
 			}
 		}
 	}
+	space->constraint_ids = mh_strnptr_new();
+	if (space->constraint_ids == NULL) {
+		diag_set(OutOfMemory, sizeof(*space->constraint_ids), "malloc",
+			 "constraint_ids");
+		goto fail;
+	}
 	return 0;
 
 fail_free_indexes:
@@ -258,9 +266,12 @@ space_delete(struct space *space)
 	trigger_destroy(&space->on_replace);
 	space_def_delete(space->def);
 	/*
-	 * SQL Triggers should be deleted with
-	 * on_replace_dd_trigger on deletion from _trigger.
+	 * SQL triggers and constraints should be deleted with
+	 * on_replace_dd_ triggers on deletion from corresponding
+	 * system space.
 	 */
+	assert(mh_size(space->constraint_ids) == 0);
+	mh_strnptr_delete(space->constraint_ids);
 	assert(space->sql_triggers == NULL);
 	assert(rlist_empty(&space->parent_fk_constraint));
 	assert(rlist_empty(&space->child_fk_constraint));
@@ -617,6 +628,45 @@ space_remove_ck_constraint(struct space *space, struct ck_constraint *ck)
 	}
 }
 
+struct constraint_id *
+space_find_constraint_id(struct space *space, const char *name)
+{
+	struct mh_strnptr_t *ids = space->constraint_ids;
+	uint32_t len = strlen(name);
+	mh_int_t pos = mh_strnptr_find_inp(ids, name, len);
+	if (pos == mh_end(ids))
+		return NULL;
+	return (struct constraint_id *) mh_strnptr_node(ids, pos)->val;
+}
+
+int
+space_add_constraint_id(struct space *space, struct constraint_id *id)
+{
+	assert(space_find_constraint_id(space, id->name) == NULL);
+	struct mh_strnptr_t *ids = space->constraint_ids;
+	uint32_t len = strlen(id->name);
+	uint32_t hash = mh_strn_hash(id->name, len);
+	const struct mh_strnptr_node_t name_node = {id->name, len, hash, id};
+	if (mh_strnptr_put(ids, &name_node, NULL, NULL) == mh_end(ids)) {
+		diag_set(OutOfMemory, sizeof(name_node), "malloc", "node");
+		return -1;
+	}
+	return 0;
+}
+
+struct constraint_id *
+space_pop_constraint_id(struct space *space, const char *name)
+{
+	struct mh_strnptr_t *ids = space->constraint_ids;
+	uint32_t len = strlen(name);
+	mh_int_t pos = mh_strnptr_find_inp(ids, name, len);
+	assert(pos != mh_end(ids));
+	struct constraint_id *id = (struct constraint_id *)
+		mh_strnptr_node(ids, pos)->val;
+	mh_strnptr_del(ids, pos, NULL);
+	return id;
+}
+
 /* {{{ Virtual method stubs */
 
 size_t
diff --git a/src/box/space.h b/src/box/space.h
index 7926aa65e..9aea4e5be 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -52,6 +52,7 @@ struct port;
 struct tuple;
 struct tuple_format;
 struct ck_constraint;
+struct constraint_id;
 
 struct space_vtab {
 	/** Free a space instance. */
@@ -234,6 +235,10 @@ struct space {
 	 * of parent constraints as well as child ones.
 	 */
 	uint64_t fk_constraint_mask;
+	/**
+	 * Hash table with constraint identifiers hashed by name.
+	 */
+	struct mh_strnptr_t *constraint_ids;
 };
 
 /** Initialize a base space instance. */
@@ -516,6 +521,25 @@ space_add_ck_constraint(struct space *space, struct ck_constraint *ck);
 void
 space_remove_ck_constraint(struct space *space, struct ck_constraint *ck);
 
+/** Find a constraint identifier by name. */
+struct constraint_id *
+space_find_constraint_id(struct space *space, const char *name);
+
+/**
+ * Add a new constraint id to the space's hash table of all
+ * constraints. That is used to prevent existence of constraints
+ * with equal names.
+ */
+int
+space_add_constraint_id(struct space *space, struct constraint_id *id);
+
+/**
+ * Remove a given name from the hash of all constraint
+ * identifiers of the given space.
+ */
+struct constraint_id *
+space_pop_constraint_id(struct space *space, const char *name);
+
 /*
  * Virtual method stubs.
  */
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 51cd7ce63..9470ef4c3 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -56,6 +56,7 @@
 #include "box/schema.h"
 #include "box/tuple_format.h"
 #include "box/coll_id_cache.h"
+#include "box/constraint_id.h"
 
 void
 sql_finish_coding(struct Parse *parse_context)
@@ -584,7 +585,7 @@ trim_space_snprintf(char *wptr, const char *str, uint32_t str_len)
 static void
 vdbe_emit_ck_constraint_create(struct Parse *parser,
 			       const struct ck_constraint_def *ck_def,
-			       uint32_t reg_space_id);
+			       uint32_t reg_space_id, const char *space_name);
 
 void
 sql_create_check_contraint(struct Parse *parser)
@@ -664,7 +665,8 @@ sql_create_check_contraint(struct Parse *parser)
 		struct Vdbe *v = sqlGetVdbe(parser);
 		sqlVdbeAddOp2(v, OP_Integer, space->def->id,
 			      space_id_reg);
-		vdbe_emit_ck_constraint_create(parser, ck_def, space_id_reg);
+		vdbe_emit_ck_constraint_create(parser, ck_def, space_id_reg,
+					       space->def->name);
 		assert(sqlVdbeGetOp(v, v->nOp - 1)->opcode == OP_SInsert);
 		sqlVdbeCountChanges(v);
 		sqlVdbeChangeP5(v, OPFLAG_NCHANGE);
@@ -969,11 +971,13 @@ emitNewSysSpaceSequenceRecord(Parse *pParse, int reg_space_id, int reg_seq_id)
  * @param parser Parsing context.
  * @param ck_def Check constraint definition to be serialized.
  * @param reg_space_id The VDBE register containing space id.
-*/
+ * @param space_name Name of the space owning the CHECK. For error
+ *     message.
+ */
 static void
 vdbe_emit_ck_constraint_create(struct Parse *parser,
 			       const struct ck_constraint_def *ck_def,
-			       uint32_t reg_space_id)
+			       uint32_t reg_space_id, const char *space_name)
 {
 	struct sql *db = parser->db;
 	struct Vdbe *v = sqlGetVdbe(parser);
@@ -996,7 +1000,8 @@ vdbe_emit_ck_constraint_create(struct Parse *parser,
 		      ck_constraint_reg + 6);
 	const char *error_msg =
 		tt_sprintf(tnt_errcode_desc(ER_CONSTRAINT_EXISTS),
-					    ck_def->name);
+			   constraint_type_strs[CONSTRAINT_TYPE_CK],
+			   ck_def->name, space_name);
 	if (vdbe_emit_halt_with_presence_test(parser, BOX_CK_CONSTRAINT_ID, 0,
 					      ck_constraint_reg, 2,
 					      ER_CONSTRAINT_EXISTS, error_msg,
@@ -1014,10 +1019,13 @@ vdbe_emit_ck_constraint_create(struct Parse *parser,
  *
  * @param parse_context Parsing context.
  * @param fk Foreign key to be created.
+ * @param space_name Name of the space owning the FOREIGN KEY. For
+ *     error message.
  */
 static void
 vdbe_emit_fk_constraint_create(struct Parse *parse_context,
-			       const struct fk_constraint_def *fk)
+			       const struct fk_constraint_def *fk,
+			       const char *space_name)
 {
 	assert(parse_context != NULL);
 	assert(fk != NULL);
@@ -1058,7 +1066,9 @@ vdbe_emit_fk_constraint_create(struct Parse *parse_context,
 	 * been created before.
 	 */
 	const char *error_msg =
-		tt_sprintf(tnt_errcode_desc(ER_CONSTRAINT_EXISTS), name_copy);
+		tt_sprintf(tnt_errcode_desc(ER_CONSTRAINT_EXISTS),
+			   constraint_type_strs[CONSTRAINT_TYPE_FK], name_copy,
+			   space_name);
 	if (vdbe_emit_halt_with_presence_test(parse_context,
 					      BOX_FK_CONSTRAINT_ID, 0,
 					      constr_tuple_reg, 2,
@@ -1272,13 +1282,13 @@ sqlEndTable(struct Parse *pParse)
 			fk_def->parent_id = reg_space_id;
 		}
 		fk_def->child_id = reg_space_id;
-		vdbe_emit_fk_constraint_create(pParse, fk_def);
+		vdbe_emit_fk_constraint_create(pParse, fk_def, space_name_copy);
 	}
 	struct ck_constraint_parse *ck_parse;
 	rlist_foreach_entry(ck_parse, &pParse->create_table_def.new_check,
 			    link) {
 		vdbe_emit_ck_constraint_create(pParse, ck_parse->ck_def,
-					       reg_space_id);
+					       reg_space_id, space_name_copy);
 	}
 }
 
@@ -1959,7 +1969,8 @@ sql_create_foreign_key(struct Parse *parse_context)
 					  struct fk_constraint_parse, link);
 		fk_parse->fk_def = fk_def;
 	} else {
-		vdbe_emit_fk_constraint_create(parse_context, fk_def);
+		vdbe_emit_fk_constraint_create(parse_context, fk_def,
+					       child_space->def->name);
 	}
 
 exit_create_fk:
diff --git a/test/sql-tap/alter2.test.lua b/test/sql-tap/alter2.test.lua
index e0bd60727..ac3ed000e 100755
--- a/test/sql-tap/alter2.test.lua
+++ b/test/sql-tap/alter2.test.lua
@@ -246,7 +246,7 @@ test:do_catchsql_test(
         ALTER TABLE child ADD CONSTRAINT fk FOREIGN KEY (a) REFERENCES child;
     ]], {
         -- <alter2-5.1>
-        1, "Constraint FK already exists"
+        1, "Constraint FOREIGN KEY 'FK' already exists in space 'CHILD'"
         -- </alter2-5.1>
     })
 
@@ -278,7 +278,7 @@ test:do_catchsql_test(
     "alter2-6.2",
     [[
         ALTER TABLE t1 ADD CONSTRAINT ck CHECK(id > 0);
-    ]], { 1, "Constraint CK already exists" })
+    ]], { 1, "Constraint CHECK 'CK' already exists in space 'T1'" })
 
 -- Make sure that CHECK constraint can be created only on empty space.
 --
diff --git a/test/sql/checks.result b/test/sql/checks.result
index a952b2b70..488659e84 100644
--- a/test/sql/checks.result
+++ b/test/sql/checks.result
@@ -260,7 +260,7 @@ s:drop()
 box.execute("CREATE TABLE T2(ID INT PRIMARY KEY, CONSTRAINT CK1 CHECK(ID > 0), CONSTRAINT CK1 CHECK(ID < 0))")
 ---
 - null
-- Constraint CK1 already exists
+- Constraint CHECK 'CK1' already exists in space 'T2'
 ...
 box.space.T2
 ---
diff --git a/test/sql/clear.result b/test/sql/clear.result
index afa6520e8..988a972e7 100644
--- a/test/sql/clear.result
+++ b/test/sql/clear.result
@@ -177,7 +177,7 @@ box.execute("DROP TABLE zoobar")
 box.execute("CREATE TABLE t1(id INT PRIMARY KEY, CONSTRAINT ck1 CHECK(id > 0), CONSTRAINT ck1 CHECK(id < 0));")
 ---
 - null
-- Constraint CK1 already exists
+- Constraint CHECK 'CK1' already exists in space 'T1'
 ...
 box.space.t1
 ---
@@ -190,7 +190,7 @@ box.space._ck_constraint:select()
 box.execute("CREATE TABLE t2(id INT PRIMARY KEY, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t2, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t2);")
 ---
 - null
-- Constraint FK1 already exists
+- Constraint FOREIGN KEY 'FK1' already exists in space 'T2'
 ...
 box.space.t2
 ---
@@ -207,7 +207,7 @@ box.space._fk_constraint:select()
 box.execute("CREATE TABLE t3(id INT PRIMARY KEY, CONSTRAINT ck1 CHECK(id > 0), CONSTRAINT ck1 FOREIGN KEY(id) REFERENCES t3, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t3, CONSTRAINT ck1 CHECK(id < 0));")
 ---
 - null
-- Constraint CK1 already exists
+- Constraint FOREIGN KEY 'CK1' already exists in space 'T3'
 ...
 box.space.t1
 ---
diff --git a/test/sql/constraint.result b/test/sql/constraint.result
new file mode 100644
index 000000000..e452e5305
--- /dev/null
+++ b/test/sql/constraint.result
@@ -0,0 +1,214 @@
+-- test-run result file version 2
+test_run = require('test_run').new()
+ | ---
+ | ...
+engine = test_run:get_cfg('engine')
+ | ---
+ | ...
+box.execute('pragma sql_default_engine=\''..engine..'\'')
+ | ---
+ | - row_count: 0
+ | ...
+
+--
+-- Check a constraint name for duplicate within a single
+-- <CREATE TABLE> statement.
+--
+box.execute('CREATE TABLE t1 (i INT PRIMARY KEY);')
+ | ---
+ | - row_count: 1
+ | ...
+test_run:cmd("setopt delimiter ';'");
+ | ---
+ | - true
+ | ...
+box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+ | ---
+ | - null
+ | - Constraint PRIMARY KEY 'C' already exists in space 'T2'
+ | ...
+box.execute([[CREATE TABLE t2 (i INT,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+ | ---
+ | - null
+ | - Constraint PRIMARY KEY 'C' already exists in space 'T2'
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c UNIQUE (i));]]);
+ | ---
+ | - null
+ | - Constraint UNIQUE 'C' already exists in space 'T2'
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c UNIQUE (i));]]);
+ | ---
+ | - null
+ | - Constraint UNIQUE 'C' already exists in space 'T2'
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c CHECK (i < 0));]]);
+ | ---
+ | - null
+ | - Constraint CHECK 'C' already exists in space 'T2'
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c CHECK (i > 0));]]);
+ | ---
+ | - null
+ | - Constraint FOREIGN KEY 'C' already exists in space 'T2'
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
+ | ---
+ | - null
+ | - Constraint FOREIGN KEY 'C' already exists in space 'T2'
+ | ...
+test_run:cmd("setopt delimiter ''");
+ | ---
+ | - true
+ | ...
+
+--
+-- Check a constraint name for duplicate using <ALTER TABLE>
+-- statement.
+--
+box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);')
+ | ---
+ | - null
+ | - Constraint PRIMARY KEY 'C' already exists in space 'T2'
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);')
+ | ---
+ | - null
+ | - Index 'C' already exists in space 'T2'
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);')
+ | ---
+ | - null
+ | - Constraint PRIMARY KEY 'C' already exists in space 'T2'
+ | ...
+
+--
+-- Make sure that a constraint's name isn't kept after the
+-- constraint drop.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('DROP INDEX d ON t2;')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.ck_constraint.D:drop()
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- The same inside a transaction.
+--
+test_run:cmd("setopt delimiter ';'");
+ | ---
+ | - true
+ | ...
+box.begin()
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+box.space.T2.ck_constraint.D:drop()
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+box.commit();
+ | ---
+ | ...
+test_run:cmd("setopt delimiter ''");
+ | ---
+ | - true
+ | ...
+
+--
+-- Make sure, that altering of an index name affects its record
+-- in the space's constraint hash table.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.D:alter({name = 'E'})
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+ | ---
+ | - null
+ | - Constraint UNIQUE 'E' already exists in space 'T2'
+ | ...
+
+--
+-- Make sure, that altering of an index uniqueness puts/drops
+-- its name to/from the space's constraint hash table.
+--
+box.space.T2.index.E:alter({unique = false})
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.E:alter({unique = true})
+ | ---
+ | - error: Constraint CHECK 'E' already exists in space 'T2'
+ | ...
+box.space.T2.ck_constraint.E:drop()
+ | ---
+ | ...
+box.space.T2.index.E:alter({unique = true})
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+ | ---
+ | - null
+ | - Constraint UNIQUE 'E' already exists in space 'T2'
+ | ...
+
+-- Alter name and uniqueness of an unique index simultaneously.
+box.space.T2.index.E:alter({name = 'D', unique = false})
+ | ---
+ | ...
+box.execute('CREATE UNIQUE INDEX e ON t2(i);')
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Cleanup.
+--
+box.execute('DROP TABLE t2;')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('DROP TABLE t1;')
+ | ---
+ | - row_count: 1
+ | ...
diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
new file mode 100755
index 000000000..b41e16dc2
--- /dev/null
+++ b/test/sql/constraint.test.lua
@@ -0,0 +1,93 @@
+test_run = require('test_run').new()
+engine = test_run:get_cfg('engine')
+box.execute('pragma sql_default_engine=\''..engine..'\'')
+
+--
+-- Check a constraint name for duplicate within a single
+-- <CREATE TABLE> statement.
+--
+box.execute('CREATE TABLE t1 (i INT PRIMARY KEY);')
+test_run:cmd("setopt delimiter ';'");
+box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+box.execute([[CREATE TABLE t2 (i INT,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c UNIQUE (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c UNIQUE (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c CHECK (i < 0));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c CHECK (i > 0));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
+test_run:cmd("setopt delimiter ''");
+
+--
+-- Check a constraint name for duplicate using <ALTER TABLE>
+-- statement.
+--
+box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);')
+
+--
+-- Make sure that a constraint's name isn't kept after the
+-- constraint drop.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+box.space.T2.ck_constraint.D:drop()
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+
+--
+-- The same inside a transaction.
+--
+test_run:cmd("setopt delimiter ';'");
+box.begin()
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+box.space.T2.ck_constraint.D:drop()
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+box.commit();
+test_run:cmd("setopt delimiter ''");
+
+--
+-- Make sure, that altering of an index name affects its record
+-- in the space's constraint hash table.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.space.T2.index.D:alter({name = 'E'})
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+
+--
+-- Make sure, that altering of an index uniqueness puts/drops
+-- its name to/from the space's constraint hash table.
+--
+box.space.T2.index.E:alter({unique = false})
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+box.space.T2.index.E:alter({unique = true})
+box.space.T2.ck_constraint.E:drop()
+box.space.T2.index.E:alter({unique = true})
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+
+-- Alter name and uniqueness of an unique index simultaneously.
+box.space.T2.index.E:alter({name = 'D', unique = false})
+box.execute('CREATE UNIQUE INDEX e ON t2(i);')
+
+--
+-- Cleanup.
+--
+box.execute('DROP TABLE t2;')
+box.execute('DROP TABLE t1;')

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

* Re: [Tarantool-patches] [PATCH v2 2/3] box: make constraint operations transactional
  2019-12-10 12:49         ` [Tarantool-patches] [PATCH v2 2/3] box: " Roman Khabibov
  2019-12-15 22:26           ` Vladislav Shpilevoy
@ 2019-12-21 20:54           ` Sergey Ostanevich
  2019-12-22 14:59             ` Vladislav Shpilevoy
  2019-12-24 12:06             ` Roman Khabibov
  1 sibling, 2 replies; 29+ messages in thread
From: Sergey Ostanevich @ 2019-12-21 20:54 UTC (permalink / raw)
  To: Roman Khabibov; +Cc: tarantool-patches, Vladislav Shpilevoy

Hi!

Im looking to the latest branch romanhabibov/gh-3503-constr-names_v3, 
apparently with changes from Vlad?

Although the change is simple, it is not relevant to the patchset?

@@ -1408,14 +1419,14 @@ ModifyIndex::~ModifyIndex()
 /** CreateIndex - add a new index to the space. */
 class CreateIndex: public AlterSpaceOp
 {
-public:
-       CreateIndex(struct alter_space *alter)
-               :AlterSpaceOp(alter), new_index(NULL), new_index_def(NULL)
-       {}
        /** New index. */
        struct index *new_index;
        /** New index index_def. */
        struct index_def *new_index_def;
+public:
+       CreateIndex(struct alter_space *alter, struct index_def *def)
+               :AlterSpaceOp(alter), new_index(NULL), new_index_def(def)
+       {}
        virtual void alter_def(struct alter_space *alter);
        virtual void prepare(struct alter_space *alter);
        virtual void commit(struct alter_space *alter, int64_t lsn);

And I'm still not sure about memory allocation/deallocation is correct during
constraint id creation/drop. 

Regards,
Sergos

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

* Re: [Tarantool-patches] [PATCH v2 2/3] box: make constraint operations transactional
  2019-12-21 20:54           ` Sergey Ostanevich
@ 2019-12-22 14:59             ` Vladislav Shpilevoy
  2019-12-24 12:06             ` Roman Khabibov
  1 sibling, 0 replies; 29+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-22 14:59 UTC (permalink / raw)
  To: Sergey Ostanevich, Roman Khabibov; +Cc: tarantool-patches

Hi! Thanks for the review!

On 21/12/2019 21:54, Sergey Ostanevich wrote:
> Hi!
> 
> Im looking to the latest branch romanhabibov/gh-3503-constr-names_v3, 
> apparently with changes from Vlad?
> 
> Although the change is simple, it is not relevant to the patchset?

The change is relevant, because allows to simplify CreateConstraintID
object creation.

> 
> @@ -1408,14 +1419,14 @@ ModifyIndex::~ModifyIndex()
>  /** CreateIndex - add a new index to the space. */
>  class CreateIndex: public AlterSpaceOp
>  {
> -public:
> -       CreateIndex(struct alter_space *alter)
> -               :AlterSpaceOp(alter), new_index(NULL), new_index_def(NULL)
> -       {}
>         /** New index. */
>         struct index *new_index;
>         /** New index index_def. */
>         struct index_def *new_index_def;
> +public:
> +       CreateIndex(struct alter_space *alter, struct index_def *def)
> +               :AlterSpaceOp(alter), new_index(NULL), new_index_def(def)
> +       {}
>         virtual void alter_def(struct alter_space *alter);
>         virtual void prepare(struct alter_space *alter);
>         virtual void commit(struct alter_space *alter, int64_t lsn);
> 
> And I'm still not sure about memory allocation/deallocation is correct during
> constraint id creation/drop.

If you've found an error, then please, share. I fixed all the
problems I found.

> 
> Regards,
> Sergos
> 

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

* Re: [Tarantool-patches] [PATCH v2 2/3] box: make constraint operations transactional
  2019-12-21 20:54           ` Sergey Ostanevich
  2019-12-22 14:59             ` Vladislav Shpilevoy
@ 2019-12-24 12:06             ` Roman Khabibov
  1 sibling, 0 replies; 29+ messages in thread
From: Roman Khabibov @ 2019-12-24 12:06 UTC (permalink / raw)
  To: Sergey Ostanevich; +Cc: tarantool-patches, Vladislav Shpilevoy

Hi!

> And I'm still not sure about memory allocation/deallocation is correct during
> constraint id creation/drop. 

If you mean: “Why do you allocate memory for the name string and copy it, if
the same name is already stored in corresponding index_def/ck_def/fk_def
structure”, I’ll answer you the following. At first, I didn’t use the constraint
name copy and did’t allocate memory for it. I simply put pointer to the original
name stored in corresponding index_def/ck_def/fk_def structure. As a result, I had
broken nodes with thrash instead of payload (name) in the constraint hash table.
It was occurred, because this name was free somewhere before. To avoid such situations,
I decided to copy the constraint name.

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

* Re: [Tarantool-patches] [PATCH v2 2/3] box: make constraint operations transactional
  2019-12-17 15:03             ` Roman Khabibov
@ 2019-12-28  0:18               ` Nikita Pettik
  2019-12-28 11:07                 ` Vladislav Shpilevoy
  0 siblings, 1 reply; 29+ messages in thread
From: Nikita Pettik @ 2019-12-28  0:18 UTC (permalink / raw)
  To: Roman Khabibov; +Cc: tarantool-patches, Vladislav Shpilevoy

On 17 Dec 18:03, Roman Khabibov wrote:
> >> 
> >>    @TarantoolBot document
> >>    Title: Table constraints in SQL
> >> 
> >>    SQL:
> >>    According to ANSI SQL, table constraint is one of the following
> >>    entities: PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK. Every
> >>    constraint have its own name passed by user or automatically

have -> has

> >>    generated. And these names must be unique within one table/space.
> >>    Naming in SQL is case-insensitive, so "CONSTRAINT c" and

False. Naming in SQL is case-sensitive. It's all about uppercasing
unquoted identifiers:

CREATE TABLE t2 (i INT, CONSTRAINT "c" CHECK (i > 0), CONSTRAINT c PRIMARY KEY (i)); 
---
- row_count: 1
...

> >>    "CONSTRAINT C" are the same. For example, you will get error, if
> >>    you try to:
> >> 
> >>    CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
> >>                     CONSTRAINT c PRIMARY KEY (i));
> >> 
> >>    or
> >> 
> >>    CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);
> >>    ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
> >>    The same is for any other constraint types.
> >> 
>  
> +/**
> + * Check if constraint with @a name exists within @a space. Call
> + * diag_set() if it is so.
> + */
> +static inline int
> +space_check_constraint_existence(struct space *space, const char *name)

IMHO this function looks a bit strange (or I'd say - contradictory).
Let's simply inline it, only 2 usages tho.

> +{
> +	struct constraint_id *id = space_find_constraint_id(space, name);
> +	if (id == NULL)
> +		return 0;
> +	diag_set(ClientError, ER_CONSTRAINT_EXISTS,
> +		 constraint_type_strs[id->type], name, space_name(space));
> +	return -1;
> +}
> +
> +
> +static inline void
> +space_delete_constraint_id(struct space *space, const char *name)

Why do we need this wrapper?

> +{
> +	constraint_id_delete(space_pop_constraint_id(space, name));
> +}
> +
> diff --git a/src/box/errcode.h b/src/box/errcode.h
> index c660b1c70..094a63ee1 100644
> --- a/src/box/errcode.h
> +++ b/src/box/errcode.h
> @@ -222,7 +222,7 @@ struct errcode_record {
>  	/*167 */_(ER_CREATE_FK_CONSTRAINT,	"Failed to create foreign key constraint '%s': %s") \
>  	/*168 */_(ER_DROP_FK_CONSTRAINT,	"Failed to drop foreign key constraint '%s': %s") \
>  	/*169 */_(ER_NO_SUCH_CONSTRAINT,	"Constraint %s does not exist") \
> -	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s already exists") \
> +	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s '%s' already exists in space '%s'") \

I'd better place type of constraint at the beggining of sentence:

Check constraint xxx already exists
Foreign key constraint ...
Unique constraint ...

Sounds way much better, doesn't it?

> new file mode 100755
> index 000000000..b41e16dc2
> --- /dev/null
> +++ b/test/sql/constraint.test.lua
> @@ -0,0 +1,93 @@
> +test_run = require('test_run').new()
> +engine = test_run:get_cfg('engine')
> +box.execute('pragma sql_default_engine=\''..engine..'\'')
> +
> +-- Make sure, that altering of an index name affects its record
> +-- in the space's constraint hash table.
> +--
> +box.execute('CREATE UNIQUE INDEX d ON t2(i);')
> +box.space.T2.index.D:alter({name = 'E'})
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')

As far as I see, you missed case of altering index (or other constraint)
name to already occupied by another constraint.

> +--
> +-- Make sure, that altering of an index uniqueness puts/drops
> +-- its name to/from the space's constraint hash table.
> +--
> +box.space.T2.index.E:alter({unique = false})
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
> +box.space.T2.index.E:alter({unique = true})
> +box.space.T2.ck_constraint.E:drop()
> +box.space.T2.index.E:alter({unique = true})
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
> +
> +-- Alter name and uniqueness of an unique index simultaneously.
> +box.space.T2.index.E:alter({name = 'D', unique = false})
> +box.execute('CREATE UNIQUE INDEX e ON t2(i);')

And make sure that CREATE UNIQUE INDEX d ON t2(i); fails

What about circle renaming of consraint inside transaction which
is rolled back later?

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

* Re: [Tarantool-patches] [PATCH v2 2/3] box: make constraint operations transactional
  2019-12-28  0:18               ` Nikita Pettik
@ 2019-12-28 11:07                 ` Vladislav Shpilevoy
  2019-12-29  0:07                   ` Nikita Pettik
  0 siblings, 1 reply; 29+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-28 11:07 UTC (permalink / raw)
  To: Nikita Pettik, Roman Khabibov; +Cc: tarantool-patches

Thanks for the review!

On 28/12/2019 03:18, Nikita Pettik wrote:
> On 17 Dec 18:03, Roman Khabibov wrote:
>>>>
>>>>    @TarantoolBot document
>>>>    Title: Table constraints in SQL
>>>>
>>>>    SQL:
>>>>    According to ANSI SQL, table constraint is one of the following
>>>>    entities: PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK. Every
>>>>    constraint have its own name passed by user or automatically
> 
> have -> has
> 
>>>>    generated. And these names must be unique within one table/space.
>>>>    Naming in SQL is case-insensitive, so "CONSTRAINT c" and
> 
> False. Naming in SQL is case-sensitive. It's all about uppercasing
> unquoted identifiers:
> 
> CREATE TABLE t2 (i INT, CONSTRAINT "c" CHECK (i > 0), CONSTRAINT c PRIMARY KEY (i)); 
> ---
> - row_count: 1
> ...

I rewrote the commit message, check this:

================================================================================
    sql: make constraint names unique in scope of table
    
    Put constraint names into the space's hash table and drop them on
    insert/delete in corresponding system spaces (_index,
    _fk_constraint, _ck_constraint).
    
    Closes #3503
    
    @TarantoolBot document
    Title: Constraint names are unique in scope of table
    
    SQL:
    According to ANSI SQL, table constraint is one of the following
    entities: PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK. (Also there
    is NOT NULL, but we don't consider it a constraint.) Every
    constraint has its own name passed by user or automatically
    generated. And these names must be unique within one table/space.
    
    For example:
    
        tarantool> box.execute([[CREATE TABLE test (
                                     a INTEGER PRIMARY KEY,
                                     b INTEGER,
                                     CONSTRAINT cnstr CHECK (a >= 0)
                                 );]])
        ---
        - row_count: 1
        ...
    
        tarantool> box.execute('CREATE UNIQUE INDEX cnstr ON test(b);')
        ---
        - null
        - Constraint CHECK 'CNSTR' already exists in space 'TEST'
        ...
    
    Unique index and CHECK are different constraint types, but they
    share namespace, and can't have clashing names. The same for all
    the other constraints.
================================================================================

>>>>    "CONSTRAINT C" are the same. For example, you will get error, if
>>>>    you try to:
>>>>
>>>>    CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
>>>>                     CONSTRAINT c PRIMARY KEY (i));
>>>>
>>>>    or
>>>>
>>>>    CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);
>>>>    ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);');
>>>>    The same is for any other constraint types.
>>>>
>>  
>> +/**
>> + * Check if constraint with @a name exists within @a space. Call
>> + * diag_set() if it is so.
>> + */
>> +static inline int
>> +space_check_constraint_existence(struct space *space, const char *name)
> 
> IMHO this function looks a bit strange (or I'd say - contradictory)

Yes, maybe the name is bad. Check this:

================================================================================
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 2e4fa3c41..1ce1c3858 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -1776,11 +1776,11 @@ MoveCkConstraints::rollback(struct alter_space *alter)
 }
 
 /**
- * Check if constraint with @a name exists within @a space. Call
- * diag_set() if it is so.
+ * Check if a constraint name is not occupied in @a space. Treat
+ * existence as an error.
  */
 static inline int
-space_check_constraint_existence(struct space *space, const char *name)
+space_check_constraint_name_is_free(struct space *space, const char *name)
 {
 	struct constraint_id *id = space_find_constraint_id(space, name);
 	if (id == NULL)
@@ -1798,7 +1798,7 @@ static int
 space_insert_constraint_id(struct space *space, enum constraint_type type,
 			   const char *name)
 {
-	if (space_check_constraint_existence(space, name) != 0)
+	if (space_check_constraint_name_is_free(space, name) != 0)
 		return -1;
 	struct constraint_id *id = constraint_id_new(type, name);
 	if (id == NULL)
@@ -1839,8 +1839,8 @@ public:
 void
 CreateConstraintID::prepare(struct alter_space *alter)
 {
-	if (space_check_constraint_existence(alter->old_space,
-					     new_id->name) != 0)
+	if (space_check_constraint_name_is_free(alter->old_space,
+						new_id->name) != 0)
 		diag_raise();
 }
================================================================================

> Let's simply inline it, only 2 usages tho.

I would agree if not the long diag_set. Purpose of this function is not to
write it more than once.

>> +{
>> +	struct constraint_id *id = space_find_constraint_id(space, name);
>> +	if (id == NULL)
>> +		return 0;
>> +	diag_set(ClientError, ER_CONSTRAINT_EXISTS,
>> +		 constraint_type_strs[id->type], name, space_name(space));
>> +	return -1;
>> +}
>> +
>> +
>> +static inline void
>> +space_delete_constraint_id(struct space *space, const char *name)
> 
> Why do we need this wrapper?

Multiple reasons: 1) it appears 5 times, so I decided to wrap it;
2) it is shorter, 3) it is consistent with other
space_<action>_constraint_id() functions. But is not worth moving
to space.h/.c IMO.

>> +{
>> +	constraint_id_delete(space_pop_constraint_id(space, name));
>> +}
>> +
>> diff --git a/src/box/errcode.h b/src/box/errcode.h
>> index c660b1c70..094a63ee1 100644
>> --- a/src/box/errcode.h
>> +++ b/src/box/errcode.h
>> @@ -222,7 +222,7 @@ struct errcode_record {
>>  	/*167 */_(ER_CREATE_FK_CONSTRAINT,	"Failed to create foreign key constraint '%s': %s") \
>>  	/*168 */_(ER_DROP_FK_CONSTRAINT,	"Failed to drop foreign key constraint '%s': %s") \
>>  	/*169 */_(ER_NO_SUCH_CONSTRAINT,	"Constraint %s does not exist") \
>> -	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s already exists") \
>> +	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s '%s' already exists in space '%s'") \
> 
> I'd better place type of constraint at the beggining of sentence:
> 
> Check constraint xxx already exists
> Foreign key constraint ...
> Unique constraint ...
> 
> Sounds way much better, doesn't it?

It is subjective. I changed it to this order on purpose.
1) Because I wanted to go from more common term to a more
concrete: 'Constraint' - common, <constraint type> - more
concrete, <name> - even more concrete.
2) Because when I see '<type> constraint <name> already exists ...'
for me it looks like someone tried to create constraint of
type <type>. But it is not always so. Type is not the most
important part of the message.

But I don't mind to change it back. Since I have no a
proof that my point is correct, except personal taste.

================================================================================
diff --git a/src/box/errcode.h b/src/box/errcode.h
index 094a63ee1..c940299b1 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -222,7 +222,7 @@ struct errcode_record {
 	/*167 */_(ER_CREATE_FK_CONSTRAINT,	"Failed to create foreign key constraint '%s': %s") \
 	/*168 */_(ER_DROP_FK_CONSTRAINT,	"Failed to drop foreign key constraint '%s': %s") \
 	/*169 */_(ER_NO_SUCH_CONSTRAINT,	"Constraint %s does not exist") \
-	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s '%s' already exists in space '%s'") \
+	/*170 */_(ER_CONSTRAINT_EXISTS,		"%s constraint '%s' already exists in space '%s'") \
 	/*171 */_(ER_SQL_TYPE_MISMATCH,		"Type mismatch: can not convert %s to %s") \
 	/*172 */_(ER_ROWID_OVERFLOW,            "Rowid is overflowed: too many entries in ephemeral space") \
 	/*173 */_(ER_DROP_COLLATION,		"Can't drop collation %s : %s") \
diff --git a/test/sql-tap/alter2.test.lua b/test/sql-tap/alter2.test.lua
index ac3ed000e..f6d421424 100755
--- a/test/sql-tap/alter2.test.lua
+++ b/test/sql-tap/alter2.test.lua
@@ -246,7 +246,7 @@ test:do_catchsql_test(
         ALTER TABLE child ADD CONSTRAINT fk FOREIGN KEY (a) REFERENCES child;
     ]], {
         -- <alter2-5.1>
-        1, "Constraint FOREIGN KEY 'FK' already exists in space 'CHILD'"
+        1, "FOREIGN KEY constraint 'FK' already exists in space 'CHILD'"
         -- </alter2-5.1>
     })
 
@@ -278,7 +278,7 @@ test:do_catchsql_test(
     "alter2-6.2",
     [[
         ALTER TABLE t1 ADD CONSTRAINT ck CHECK(id > 0);
-    ]], { 1, "Constraint CHECK 'CK' already exists in space 'T1'" })
+    ]], { 1, "CHECK constraint 'CK' already exists in space 'T1'" })
 
 -- Make sure that CHECK constraint can be created only on empty space.
 --
diff --git a/test/sql/checks.result b/test/sql/checks.result
index 488659e84..ebdf8803a 100644
--- a/test/sql/checks.result
+++ b/test/sql/checks.result
@@ -260,7 +260,7 @@ s:drop()
 box.execute("CREATE TABLE T2(ID INT PRIMARY KEY, CONSTRAINT CK1 CHECK(ID > 0), CONSTRAINT CK1 CHECK(ID < 0))")
 ---
 - null
-- Constraint CHECK 'CK1' already exists in space 'T2'
+- CHECK constraint 'CK1' already exists in space 'T2'
 ...
 box.space.T2
 ---
diff --git a/test/sql/clear.result b/test/sql/clear.result
index 988a972e7..e7c498595 100644
--- a/test/sql/clear.result
+++ b/test/sql/clear.result
@@ -177,7 +177,7 @@ box.execute("DROP TABLE zoobar")
 box.execute("CREATE TABLE t1(id INT PRIMARY KEY, CONSTRAINT ck1 CHECK(id > 0), CONSTRAINT ck1 CHECK(id < 0));")
 ---
 - null
-- Constraint CHECK 'CK1' already exists in space 'T1'
+- CHECK constraint 'CK1' already exists in space 'T1'
 ...
 box.space.t1
 ---
@@ -190,7 +190,7 @@ box.space._ck_constraint:select()
 box.execute("CREATE TABLE t2(id INT PRIMARY KEY, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t2, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t2);")
 ---
 - null
-- Constraint FOREIGN KEY 'FK1' already exists in space 'T2'
+- FOREIGN KEY constraint 'FK1' already exists in space 'T2'
 ...
 box.space.t2
 ---
@@ -207,7 +207,7 @@ box.space._fk_constraint:select()
 box.execute("CREATE TABLE t3(id INT PRIMARY KEY, CONSTRAINT ck1 CHECK(id > 0), CONSTRAINT ck1 FOREIGN KEY(id) REFERENCES t3, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t3, CONSTRAINT ck1 CHECK(id < 0));")
 ---
 - null
-- Constraint FOREIGN KEY 'CK1' already exists in space 'T3'
+- FOREIGN KEY constraint 'CK1' already exists in space 'T3'
 ...
 box.space.t1
 ---
diff --git a/test/sql/constraint.result b/test/sql/constraint.result
index e452e5305..fcb8c8d65 100644
--- a/test/sql/constraint.result
+++ b/test/sql/constraint.result
@@ -26,48 +26,48 @@ box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
                                CONSTRAINT c PRIMARY KEY (i));]]);
  | ---
  | - null
- | - Constraint PRIMARY KEY 'C' already exists in space 'T2'
+ | - PRIMARY KEY constraint 'C' already exists in space 'T2'
  | ...
 box.execute([[CREATE TABLE t2 (i INT,
                                CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
                                CONSTRAINT c PRIMARY KEY (i));]]);
  | ---
  | - null
- | - Constraint PRIMARY KEY 'C' already exists in space 'T2'
+ | - PRIMARY KEY constraint 'C' already exists in space 'T2'
  | ...
 box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
                                CONSTRAINT c CHECK (i > 0),
                                CONSTRAINT c UNIQUE (i));]]);
  | ---
  | - null
- | - Constraint UNIQUE 'C' already exists in space 'T2'
+ | - UNIQUE constraint 'C' already exists in space 'T2'
  | ...
 box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
                                CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
                                CONSTRAINT c UNIQUE (i));]]);
  | ---
  | - null
- | - Constraint UNIQUE 'C' already exists in space 'T2'
+ | - UNIQUE constraint 'C' already exists in space 'T2'
  | ...
 box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
                                CONSTRAINT c CHECK (i > 0),
                                CONSTRAINT c CHECK (i < 0));]]);
  | ---
  | - null
- | - Constraint CHECK 'C' already exists in space 'T2'
+ | - CHECK constraint 'C' already exists in space 'T2'
  | ...
 box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
                                CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
                                CONSTRAINT c CHECK (i > 0));]]);
  | ---
  | - null
- | - Constraint FOREIGN KEY 'C' already exists in space 'T2'
+ | - FOREIGN KEY constraint 'C' already exists in space 'T2'
  | ...
 box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
                                CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
  | ---
  | - null
- | - Constraint FOREIGN KEY 'C' already exists in space 'T2'
+ | - FOREIGN KEY constraint 'C' already exists in space 'T2'
  | ...
 test_run:cmd("setopt delimiter ''");
  | ---
@@ -85,7 +85,7 @@ box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);')
 box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);')
  | ---
  | - null
- | - Constraint PRIMARY KEY 'C' already exists in space 'T2'
+ | - PRIMARY KEY constraint 'C' already exists in space 'T2'
  | ...
 box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);')
  | ---
@@ -95,7 +95,7 @@ box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);')
 box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);')
  | ---
  | - null
- | - Constraint PRIMARY KEY 'C' already exists in space 'T2'
+ | - PRIMARY KEY constraint 'C' already exists in space 'T2'
  | ...
 
 --
@@ -162,7 +162,7 @@ box.space.T2.index.D:alter({name = 'E'})
 box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
  | ---
  | - null
- | - Constraint UNIQUE 'E' already exists in space 'T2'
+ | - UNIQUE constraint 'E' already exists in space 'T2'
  | ...
 
 --
@@ -178,7 +178,7 @@ box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
  | ...
 box.space.T2.index.E:alter({unique = true})
  | ---
- | - error: Constraint CHECK 'E' already exists in space 'T2'
+ | - error: CHECK constraint 'E' already exists in space 'T2'
  | ...
 box.space.T2.ck_constraint.E:drop()
  | ---
@@ -189,7 +189,7 @@ box.space.T2.index.E:alter({unique = true})
 box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
  | ---
  | - null
- | - Constraint UNIQUE 'E' already exists in space 'T2'
+ | - UNIQUE constraint 'E' already exists in space 'T2'
  | ...
 
 -- Alter name and uniqueness of an unique index simultaneously.
================================================================================

>> new file mode 100755
>> index 000000000..b41e16dc2
>> --- /dev/null
>> +++ b/test/sql/constraint.test.lua
>> @@ -0,0 +1,93 @@
>> +test_run = require('test_run').new()
>> +engine = test_run:get_cfg('engine')
>> +box.execute('pragma sql_default_engine=\''..engine..'\'')
>> +
>> +-- Make sure, that altering of an index name affects its record
>> +-- in the space's constraint hash table.
>> +--
>> +box.execute('CREATE UNIQUE INDEX d ON t2(i);')
>> +box.space.T2.index.D:alter({name = 'E'})
>> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
> 
> As far as I see, you missed case of altering index (or other constraint)
> name to already occupied by another constraint.

True.

================================================================================
diff --git a/test/sql/constraint.result b/test/sql/constraint.result
index fcb8c8d65..306eb7184 100644
--- a/test/sql/constraint.result
+++ b/test/sql/constraint.result
@@ -165,6 +165,29 @@ box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
  | - UNIQUE constraint 'E' already exists in space 'T2'
  | ...
 
+--
+-- Try rename to an already occupied name.
+--
+box.execute('ALTER TABLE t2 ADD CONSTRAINT f CHECK(i > 0);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('CREATE UNIQUE INDEX g ON t2(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.G:alter({name = 'F'})
+ | ---
+ | - error: CHECK constraint 'F' already exists in space 'T2'
+ | ...
+box.execute('DROP INDEX g ON t2;')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.ck_constraint.F:drop()
+ | ---
+ | ...
+
 --
 -- Make sure, that altering of an index uniqueness puts/drops
 -- its name to/from the space's constraint hash table.
diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
index b41e16dc2..627421f77 100755
--- a/test/sql/constraint.test.lua
+++ b/test/sql/constraint.test.lua
@@ -71,6 +71,15 @@ box.execute('CREATE UNIQUE INDEX d ON t2(i);')
 box.space.T2.index.D:alter({name = 'E'})
 box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
 
+--
+-- Try rename to an already occupied name.
+--
+box.execute('ALTER TABLE t2 ADD CONSTRAINT f CHECK(i > 0);')
+box.execute('CREATE UNIQUE INDEX g ON t2(i);')
+box.space.T2.index.G:alter({name = 'F'})
+box.execute('DROP INDEX g ON t2;')
+box.space.T2.ck_constraint.F:drop()
+
 --
 -- Make sure, that altering of an index uniqueness puts/drops
 -- its name to/from the space's constraint hash table.
================================================================================

>> +--
>> +-- Make sure, that altering of an index uniqueness puts/drops
>> +-- its name to/from the space's constraint hash table.
>> +--
>> +box.space.T2.index.E:alter({unique = false})
>> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
>> +box.space.T2.index.E:alter({unique = true})
>> +box.space.T2.ck_constraint.E:drop()
>> +box.space.T2.index.E:alter({unique = true})
>> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
>> +
>> +-- Alter name and uniqueness of an unique index simultaneously.
>> +box.space.T2.index.E:alter({name = 'D', unique = false})
>> +box.execute('CREATE UNIQUE INDEX e ON t2(i);')
> 
> And make sure that CREATE UNIQUE INDEX d ON t2(i); fails

I am not sure how that test is related to the patch,
because uniqueness of index names is a different task,
and it has many tests in other places. But ok.

================================================================================
diff --git a/test/sql/constraint.result b/test/sql/constraint.result
index 306eb7184..65ec61e66 100644
--- a/test/sql/constraint.result
+++ b/test/sql/constraint.result
@@ -223,6 +223,13 @@ box.execute('CREATE UNIQUE INDEX e ON t2(i);')
  | ---
  | - row_count: 1
  | ...
+-- The existing D index is not unique, but regardless of
+-- uniqueness, index names should be unique in a space.
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+ | ---
+ | - null
+ | - Index 'D' already exists in space 'T2'
+ | ...
 
 --
 -- Cleanup.
diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
index 627421f77..7510a69d6 100755
--- a/test/sql/constraint.test.lua
+++ b/test/sql/constraint.test.lua
@@ -94,6 +94,9 @@ box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
 -- Alter name and uniqueness of an unique index simultaneously.
 box.space.T2.index.E:alter({name = 'D', unique = false})
 box.execute('CREATE UNIQUE INDEX e ON t2(i);')
+-- The existing D index is not unique, but regardless of
+-- uniqueness, index names should be unique in a space.
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
 
 --
 -- Cleanup.
================================================================================

> What about circle renaming of consraint inside transaction which
> is rolled back later?

I wasn't sure what do you mean as a circle renaming. I tried this:

================================================================================
diff --git a/test/sql/constraint.result b/test/sql/constraint.result
index 65ec61e66..32c42897a 100644
--- a/test/sql/constraint.result
+++ b/test/sql/constraint.result
@@ -148,6 +148,48 @@ test_run:cmd("setopt delimiter ''");
  | - true
  | ...
 
+--
+-- Circle renaming in a transaction.
+--
+box.begin()                                                                 \
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')                              \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')               \
+-- D and E constraints exchange their names. Note, CHECK can't be           \
+-- renamed, because CHECK name is a primary key of _ck_constraint.          \
+-- It can be only dropped and created again with a new name.                \
+box.space.T2.index.D:rename('F')                                            \
+box.space.T2.ck_constraint.E:drop()                                         \
+box.space.T2.index.F:rename('E')                                            \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT D CHECK (i > 0);')               \
+-- Now an attempt to occupy the names will report, that the names           \
+-- are really swapped.                                                      \
+_, err1 = box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')     \
+_, err2 = box.execute('CREATE UNIQUE INDEX d ON t2(i);')                    \
+box.rollback()
+ | ---
+ | ...
+err1, err2
+ | ---
+ | - UNIQUE constraint 'E' already exists in space 'T2'
+ | - CHECK constraint 'D' already exists in space 'T2'
+ | ...
+-- After rollback ensure, that the names are free again.
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('DROP INDEX d ON t2;')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.ck_constraint.E:drop()
+ | ---
+ | ...
+
 --
 -- Make sure, that altering of an index name affects its record
 -- in the space's constraint hash table.
diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
index 7510a69d6..1e679b734 100755
--- a/test/sql/constraint.test.lua
+++ b/test/sql/constraint.test.lua
@@ -63,6 +63,31 @@ box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
 box.commit();
 test_run:cmd("setopt delimiter ''");
 
+--
+-- Circle renaming in a transaction.
+--
+box.begin()                                                                 \
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')                              \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')               \
+-- D and E constraints exchange their names. Note, CHECK can't be           \
+-- renamed, because CHECK name is a primary key of _ck_constraint.          \
+-- It can be only dropped and created again with a new name.                \
+box.space.T2.index.D:rename('F')                                            \
+box.space.T2.ck_constraint.E:drop()                                         \
+box.space.T2.index.F:rename('E')                                            \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT D CHECK (i > 0);')               \
+-- Now an attempt to occupy the names will report, that the names           \
+-- are really swapped.                                                      \
+_, err1 = box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')     \
+_, err2 = box.execute('CREATE UNIQUE INDEX d ON t2(i);')                    \
+box.rollback()
+err1, err2
+-- After rollback ensure, that the names are free again.
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.space.T2.ck_constraint.E:drop()
+
 --
 -- Make sure, that altering of an index name affects its record
 -- in the space's constraint hash table.
================================================================================

Also I did some minor refactoring to the test. See the whole patch below.

================================================================================

    sql: make constraint names unique in scope of table
    
    Put constraint names into the space's hash table and drop them on
    insert/delete in corresponding system spaces (_index,
    _fk_constraint, _ck_constraint).
    
    Closes #3503
    
    @TarantoolBot document
    Title: Constraint names are unique in scope of table
    
    SQL:
    According to ANSI SQL, table constraint is one of the following
    entities: PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK. (Also there
    is NOT NULL, but we don't consider it a constraint.) Every
    constraint has its own name passed by user or automatically
    generated. And these names must be unique within one table/space.
    
    For example:
    
        tarantool> box.execute([[CREATE TABLE test (
                                     a INTEGER PRIMARY KEY,
                                     b INTEGER,
                                     CONSTRAINT cnstr CHECK (a >= 0)
                                 );]])
        ---
        - row_count: 1
        ...
    
        tarantool> box.execute('CREATE UNIQUE INDEX cnstr ON test(b);')
        ---
        - null
        - Constraint CHECK 'CNSTR' already exists in space 'TEST'
        ...
    
    Unique index and CHECK are different constraint types, but they
    share namespace, and can't have clashing names. The same for all
    the other constraints.

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 5cd5cba81..19ce3d481 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -102,6 +102,7 @@ add_library(box STATIC
     sequence.c
     ck_constraint.c
     fk_constraint.c
+    constraint_id.c
     func.c
     func_def.c
     key_list.c
diff --git a/src/box/alter.cc b/src/box/alter.cc
index bef25b605..1ce1c3858 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -57,6 +57,7 @@
 #include "version.h"
 #include "sequence.h"
 #include "sql.h"
+#include "constraint_id.h"
 
 /* {{{ Auxiliary functions and methods. */
 
@@ -703,6 +704,12 @@ space_swap_fk_constraints(struct space *new_space, struct space *old_space)
 	SWAP(new_space->fk_constraint_mask, old_space->fk_constraint_mask);
 }
 
+static void
+space_swap_constraint_ids(struct space *new_space, struct space *old_space)
+{
+	SWAP(new_space->constraint_ids, old_space->constraint_ids);
+}
+
 /**
  * True if the space has records identified by key 'uid'.
  * Uses 'iid' index.
@@ -1033,10 +1040,12 @@ alter_space_rollback(struct trigger *trigger, void * /* event */)
 	space_fill_index_map(alter->old_space);
 	space_fill_index_map(alter->new_space);
 	/*
-	 * Don't forget about space triggers and foreign keys.
+	 * Don't forget about space triggers, foreign keys and
+	 * constraints.
 	 */
 	space_swap_triggers(alter->new_space, alter->old_space);
 	space_swap_fk_constraints(alter->new_space, alter->old_space);
+	space_swap_constraint_ids(alter->new_space, alter->old_space);
 	space_cache_replace(alter->new_space, alter->old_space);
 	alter_space_delete(alter);
 	return 0;
@@ -1143,10 +1152,12 @@ alter_space_do(struct txn_stmt *stmt, struct alter_space *alter)
 	space_fill_index_map(alter->old_space);
 	space_fill_index_map(alter->new_space);
 	/*
-	 * Don't forget about space triggers and foreign keys.
+	 * Don't forget about space triggers, foreign keys and
+	 * constraints.
 	 */
 	space_swap_triggers(alter->new_space, alter->old_space);
 	space_swap_fk_constraints(alter->new_space, alter->old_space);
+	space_swap_constraint_ids(alter->new_space, alter->old_space);
 	/*
 	 * The new space is ready. Time to update the space
 	 * cache with it.
@@ -1408,14 +1419,14 @@ ModifyIndex::~ModifyIndex()
 /** CreateIndex - add a new index to the space. */
 class CreateIndex: public AlterSpaceOp
 {
-public:
-	CreateIndex(struct alter_space *alter)
-		:AlterSpaceOp(alter), new_index(NULL), new_index_def(NULL)
-	{}
 	/** New index. */
 	struct index *new_index;
 	/** New index index_def. */
 	struct index_def *new_index_def;
+public:
+	CreateIndex(struct alter_space *alter, struct index_def *def)
+		:AlterSpaceOp(alter), new_index(NULL), new_index_def(def)
+	{}
 	virtual void alter_def(struct alter_space *alter);
 	virtual void prepare(struct alter_space *alter);
 	virtual void commit(struct alter_space *alter, int64_t lsn);
@@ -1764,6 +1775,145 @@ MoveCkConstraints::rollback(struct alter_space *alter)
 	space_swap_ck_constraint(alter->new_space, alter->old_space);
 }
 
+/**
+ * Check if a constraint name is not occupied in @a space. Treat
+ * existence as an error.
+ */
+static inline int
+space_check_constraint_name_is_free(struct space *space, const char *name)
+{
+	struct constraint_id *id = space_find_constraint_id(space, name);
+	if (id == NULL)
+		return 0;
+	diag_set(ClientError, ER_CONSTRAINT_EXISTS,
+		 constraint_type_strs[id->type], name, space_name(space));
+	return -1;
+}
+
+/**
+ * Put a new constraint name into the space's namespace of
+ * constraints, with duplicate check.
+ */
+static int
+space_insert_constraint_id(struct space *space, enum constraint_type type,
+			   const char *name)
+{
+	if (space_check_constraint_name_is_free(space, name) != 0)
+		return -1;
+	struct constraint_id *id = constraint_id_new(type, name);
+	if (id == NULL)
+		return -1;
+	if (space_add_constraint_id(space, id) != 0) {
+		constraint_id_delete(id);
+		return -1;
+	}
+	return 0;
+}
+
+static inline void
+space_delete_constraint_id(struct space *space, const char *name)
+{
+	constraint_id_delete(space_pop_constraint_id(space, name));
+}
+
+/** CreateConstraintID - add a new constraint id to a space. */
+class CreateConstraintID: public AlterSpaceOp
+{
+	struct constraint_id *new_id;
+public:
+	CreateConstraintID(struct alter_space *alter, enum constraint_type type,
+			   const char *name)
+		:AlterSpaceOp(alter), new_id(NULL)
+	{
+		new_id = constraint_id_new(type, name);
+		if (new_id == NULL)
+			diag_raise();
+	}
+	virtual void prepare(struct alter_space *alter);
+	virtual void alter(struct alter_space *alter);
+	virtual void rollback(struct alter_space *alter);
+	virtual void commit(struct alter_space *alter, int64_t signature);
+	virtual ~CreateConstraintID();
+};
+
+void
+CreateConstraintID::prepare(struct alter_space *alter)
+{
+	if (space_check_constraint_name_is_free(alter->old_space,
+						new_id->name) != 0)
+		diag_raise();
+}
+
+void
+CreateConstraintID::alter(struct alter_space *alter)
+{
+	/* Alter() can't fail, so can't just throw an error. */
+	if (space_add_constraint_id(alter->old_space, new_id) != 0)
+		panic("Can't add a new constraint id, out of memory");
+}
+
+void
+CreateConstraintID::rollback(struct alter_space *alter)
+{
+	space_delete_constraint_id(alter->new_space, new_id->name);
+	new_id = NULL;
+}
+
+void
+CreateConstraintID::commit(struct alter_space *alter, int64_t signature)
+{
+	(void) alter;
+	(void) signature;
+	/*
+	 * Constraint id is added to the space, and should not be
+	 * deleted from now on.
+	 */
+	new_id = NULL;
+}
+
+CreateConstraintID::~CreateConstraintID()
+{
+	if (new_id != NULL)
+		constraint_id_delete(new_id);
+}
+
+/** DropConstraintID - drop a constraint id from the space. */
+class DropConstraintID: public AlterSpaceOp
+{
+	struct constraint_id *old_id;
+	const char *name;
+public:
+	DropConstraintID(struct alter_space *alter, const char *name)
+		:AlterSpaceOp(alter), old_id(NULL), name(name)
+	{}
+	virtual void alter(struct alter_space *alter);
+	virtual void commit(struct alter_space *alter , int64_t signature);
+	virtual void rollback(struct alter_space *alter);
+};
+
+void
+DropConstraintID::alter(struct alter_space *alter)
+{
+	old_id = space_pop_constraint_id(alter->old_space, name);
+}
+
+void
+DropConstraintID::commit(struct alter_space *alter, int64_t signature)
+{
+	(void) alter;
+	(void) signature;
+	constraint_id_delete(old_id);
+}
+
+void
+DropConstraintID::rollback(struct alter_space *alter)
+{
+	if (space_add_constraint_id(alter->new_space, old_id) != 0) {
+		panic("Can't recover after constraint drop rollback (out of "
+		      "memory)");
+	}
+}
+
 /* }}} */
 
 /**
@@ -2428,6 +2578,7 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 			 old_space->def->uid, SC_SPACE, priv_type) != 0)
 		return -1;
 	struct index *old_index = space_index(old_space, iid);
+	struct index_def *old_def = old_index != NULL ? old_index->def : NULL;
 
 	/*
 	 * Deal with various cases of dropping of the primary key.
@@ -2501,6 +2652,10 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 		if (alter_space_move_indexes(alter, 0, iid) != 0)
 			return -1;
 		try {
+			if (old_index->def->opts.is_unique) {
+				(void) new DropConstraintID(alter,
+							    old_def->name);
+			}
 			(void) new DropIndex(alter, old_index);
 		} catch (Exception *e) {
 			return -1;
@@ -2510,18 +2665,22 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 	if (old_index == NULL && new_tuple != NULL) {
 		if (alter_space_move_indexes(alter, 0, iid))
 			return -1;
-		CreateIndex *create_index;
+		struct index_def *def =
+			index_def_new_from_tuple(new_tuple, old_space);
+		if (def == NULL)
+			return -1;
+		index_def_update_optionality(def, alter->new_min_field_count);
 		try {
-			create_index = new CreateIndex(alter);
+			if (def->opts.is_unique) {
+				(void) new CreateConstraintID(
+					alter, iid == 0 ? CONSTRAINT_TYPE_PK :
+					CONSTRAINT_TYPE_UNIQUE, def->name);
+			}
+			(void) new CreateIndex(alter, def);
 		} catch (Exception *e) {
+			index_def_delete(def);
 			return -1;
 		}
-		create_index->new_index_def =
-			index_def_new_from_tuple(new_tuple, old_space);
-		if (create_index->new_index_def == NULL)
-			return -1;
-		index_def_update_optionality(create_index->new_index_def,
-					     alter->new_min_field_count);
 	}
 	/* Case 3 and 4: check if we need to rebuild index data. */
 	if (old_index != NULL && new_tuple != NULL) {
@@ -2531,6 +2690,34 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 			return -1;
 		auto index_def_guard =
 			make_scoped_guard([=] { index_def_delete(index_def); });
+		/*
+		 * We put a new name when either an index is
+		 * becoming unique (i.e. constraint), or when a
+		 * unique index's name is under change.
+		 */
+		bool do_new_constraint_id =
+			!old_def->opts.is_unique && index_def->opts.is_unique;
+		bool do_drop_constraint_id =
+			old_def->opts.is_unique && !index_def->opts.is_unique;
+
+		if (old_def->opts.is_unique && index_def->opts.is_unique &&
+		    strcmp(index_def->name, old_def->name) != 0) {
+			do_new_constraint_id = true;
+			do_drop_constraint_id = true;
+		}
+		try {
+			if (do_new_constraint_id) {
+				(void) new CreateConstraintID(
+					alter, CONSTRAINT_TYPE_UNIQUE,
+					index_def->name);
+			}
+			if (do_drop_constraint_id) {
+				(void) new DropConstraintID(alter,
+							    old_def->name);
+			}
+		} catch (Exception *e) {
+			return -1;
+		}
 		/*
 		 * To detect which key parts are optional,
 		 * min_field_count is required. But
@@ -4996,8 +5183,11 @@ on_create_fk_constraint_rollback(struct trigger *trigger, void *event)
 	struct fk_constraint *fk = (struct fk_constraint *)trigger->data;
 	rlist_del_entry(fk, in_parent_space);
 	rlist_del_entry(fk, in_child_space);
+	struct space *child = space_by_id(fk->def->child_id);
+	assert(child != NULL);
+	space_delete_constraint_id(child, fk->def->name);
 	space_reset_fk_constraint_mask(space_by_id(fk->def->parent_id));
-	space_reset_fk_constraint_mask(space_by_id(fk->def->child_id));
+	space_reset_fk_constraint_mask(child);
 	fk_constraint_delete(fk);
 	return 0;
 }
@@ -5029,6 +5219,11 @@ on_drop_fk_constraint_rollback(struct trigger *trigger, void *event)
 	struct fk_constraint *old_fk = (struct fk_constraint *)trigger->data;
 	struct space *parent = space_by_id(old_fk->def->parent_id);
 	struct space *child = space_by_id(old_fk->def->child_id);
+	if (space_insert_constraint_id(child, CONSTRAINT_TYPE_FK,
+				       old_fk->def->name) != 0) {
+		panic("Can't recover after FK constraint drop rollback (out of "
+		      "memory)");
+	}
 	rlist_add_entry(&child->child_fk_constraint, old_fk, in_child_space);
 	rlist_add_entry(&parent->parent_fk_constraint, old_fk, in_parent_space);
 	fk_constraint_set_mask(old_fk, &child->fk_constraint_mask,
@@ -5210,15 +5405,19 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 		fk->def = fk_def;
 		fk->index_id = fk_index->def->iid;
 		if (old_tuple == NULL) {
-			rlist_add_entry(&child_space->child_fk_constraint,
-					fk, in_child_space);
-			rlist_add_entry(&parent_space->parent_fk_constraint,
-					fk, in_parent_space);
 			struct trigger *on_rollback =
 				txn_alter_trigger_new(on_create_fk_constraint_rollback,
 						      fk);
 			if (on_rollback == NULL)
 				return -1;
+			if (space_insert_constraint_id(child_space,
+						       CONSTRAINT_TYPE_FK,
+						       fk_def->name) != 0)
+				return -1;
+			rlist_add_entry(&child_space->child_fk_constraint,
+					fk, in_child_space);
+			rlist_add_entry(&parent_space->parent_fk_constraint,
+					fk, in_parent_space);
 			txn_stmt_on_rollback(stmt, on_rollback);
 			fk_constraint_set_mask(fk,
 					       &parent_space->fk_constraint_mask,
@@ -5279,6 +5478,7 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
 					      old_fk);
 		if (on_rollback == NULL)
 			return -1;
+		space_delete_constraint_id(child_space, fk_def->name);
 		txn_stmt_on_rollback(stmt, on_rollback);
 		space_reset_fk_constraint_mask(child_space);
 		space_reset_fk_constraint_mask(parent_space);
@@ -5344,9 +5544,10 @@ on_create_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
 	assert(ck != NULL);
 	struct space *space = space_by_id(ck->def->space_id);
 	assert(space != NULL);
-	assert(space_ck_constraint_by_name(space, ck->def->name,
-					   strlen(ck->def->name)) != NULL);
+	const char *name = ck->def->name;
+	assert(space_ck_constraint_by_name(space, name, strlen(name)) != NULL);
 	space_remove_ck_constraint(space, ck);
+	space_delete_constraint_id(space, name);
 	ck_constraint_delete(ck);
 	if (trigger_run(&on_alter_space, space) != 0)
 		return -1;
@@ -5371,9 +5572,10 @@ on_drop_ck_constraint_rollback(struct trigger *trigger, void * /* event */)
 	assert(ck != NULL);
 	struct space *space = space_by_id(ck->def->space_id);
 	assert(space != NULL);
-	assert(space_ck_constraint_by_name(space, ck->def->name,
-					   strlen(ck->def->name)) == NULL);
-	if (space_add_ck_constraint(space, ck) != 0)
+	const char *name = ck->def->name;
+	assert(space_ck_constraint_by_name(space, name, strlen(name)) == NULL);
+	if (space_add_ck_constraint(space, ck) != 0 ||
+	    space_insert_constraint_id(space, CONSTRAINT_TYPE_CK, name) != 0)
 		panic("Can't recover after CK constraint drop rollback");
 	if (trigger_run(&on_alter_space, space) != 0)
 		return -1;
@@ -5459,7 +5661,8 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		const char *name = ck_def->name;
 		struct ck_constraint *old_ck_constraint =
 			space_ck_constraint_by_name(space, name, strlen(name));
-		if (old_ck_constraint != NULL) {
+		bool is_insert = old_ck_constraint == NULL;
+		if (!is_insert) {
 			struct ck_constraint_def *old_def =
 						old_ck_constraint->def;
 			assert(old_def->space_id == ck_def->space_id);
@@ -5491,18 +5694,24 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		auto ck_guard = make_scoped_guard([=] {
 			ck_constraint_delete(new_ck_constraint);
 		});
-		if (old_ck_constraint != NULL)
-			rlist_del_entry(old_ck_constraint, link);
 		if (space_add_ck_constraint(space, new_ck_constraint) != 0)
 			return -1;
-		ck_guard.is_active = false;
-		if (old_tuple != NULL) {
+		if (!is_insert) {
+			rlist_del_entry(old_ck_constraint, link);
 			on_rollback->data = old_ck_constraint;
 			on_rollback->run = on_replace_ck_constraint_rollback;
 		} else {
+			if (space_insert_constraint_id(space,
+						       CONSTRAINT_TYPE_CK,
+						       name) != 0) {
+				space_remove_ck_constraint(space,
+							   new_ck_constraint);
+				return -1;
+			}
 			on_rollback->data = new_ck_constraint;
 			on_rollback->run = on_create_ck_constraint_rollback;
 		}
+		ck_guard.is_active = false;
 		on_commit->data = old_ck_constraint;
 		on_commit->run = on_replace_ck_constraint_commit;
 	} else {
@@ -5516,6 +5725,7 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		struct ck_constraint *old_ck_constraint =
 			space_ck_constraint_by_name(space, name, name_len);
 		assert(old_ck_constraint != NULL);
+		space_delete_constraint_id(space, old_ck_constraint->def->name);
 		space_remove_ck_constraint(space, old_ck_constraint);
 		on_commit->data = old_ck_constraint;
 		on_commit->run = on_drop_ck_constraint_commit;
diff --git a/src/box/constraint_id.c b/src/box/constraint_id.c
new file mode 100755
index 000000000..ba6ed859c
--- /dev/null
+++ b/src/box/constraint_id.c
@@ -0,0 +1,63 @@
+/*
+ * 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 "constraint_id.h"
+#include "assoc.h"
+#include "errcode.h"
+#include "diag.h"
+
+const char *constraint_type_strs[] = {
+	[CONSTRAINT_TYPE_PK]		= "PRIMARY KEY",
+	[CONSTRAINT_TYPE_UNIQUE]	= "UNIQUE",
+	[CONSTRAINT_TYPE_FK]		= "FOREIGN KEY",
+	[CONSTRAINT_TYPE_CK]		= "CHECK",
+};
+
+struct constraint_id *
+constraint_id_new(enum constraint_type type, const char *name)
+{
+	uint32_t len = strlen(name);
+	uint32_t size = sizeof(struct constraint_id) + len + 1;
+	struct constraint_id *ret = malloc(size);
+	if (ret == NULL) {
+		diag_set(OutOfMemory, size, "malloc", "ret");
+		return NULL;
+	}
+	ret->type = type;
+	memcpy(ret->name, name, len + 1);
+	return ret;
+}
+
+void
+constraint_id_delete(struct constraint_id *id)
+{
+	free(id);
+}
diff --git a/src/box/constraint_id.h b/src/box/constraint_id.h
new file mode 100755
index 000000000..21f067cdc
--- /dev/null
+++ b/src/box/constraint_id.h
@@ -0,0 +1,64 @@
+#pragma once
+
+/*
+ * 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.
+ */
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+enum constraint_type {
+	CONSTRAINT_TYPE_PK = 0,
+	CONSTRAINT_TYPE_UNIQUE,
+	CONSTRAINT_TYPE_FK,
+	CONSTRAINT_TYPE_CK,
+};
+
+extern const char *constraint_type_strs[];
+
+struct constraint_id {
+	/** Constraint type. */
+	enum constraint_type type;
+	/** Zero-terminated string with name. */
+	char name[0];
+};
+
+/** Allocate memory and construct constraint id. */
+struct constraint_id *
+constraint_id_new(enum constraint_type type, const char *name);
+
+/** Free memory of constraint id. */
+void
+constraint_id_delete(struct constraint_id *id);
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/src/box/errcode.h b/src/box/errcode.h
index c660b1c70..c940299b1 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -222,7 +222,7 @@ struct errcode_record {
 	/*167 */_(ER_CREATE_FK_CONSTRAINT,	"Failed to create foreign key constraint '%s': %s") \
 	/*168 */_(ER_DROP_FK_CONSTRAINT,	"Failed to drop foreign key constraint '%s': %s") \
 	/*169 */_(ER_NO_SUCH_CONSTRAINT,	"Constraint %s does not exist") \
-	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s already exists") \
+	/*170 */_(ER_CONSTRAINT_EXISTS,		"%s constraint '%s' already exists in space '%s'") \
 	/*171 */_(ER_SQL_TYPE_MISMATCH,		"Type mismatch: can not convert %s to %s") \
 	/*172 */_(ER_ROWID_OVERFLOW,            "Rowid is overflowed: too many entries in ephemeral space") \
 	/*173 */_(ER_DROP_COLLATION,		"Can't drop collation %s : %s") \
diff --git a/src/box/space.c b/src/box/space.c
index 94716a414..1c19d099b 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -45,6 +45,8 @@
 #include "iproto_constants.h"
 #include "schema.h"
 #include "ck_constraint.h"
+#include "assoc.h"
+#include "constraint_id.h"
 
 int
 access_check_space(struct space *space, user_access_t access)
@@ -202,6 +204,12 @@ space_create(struct space *space, struct engine *engine,
 			}
 		}
 	}
+	space->constraint_ids = mh_strnptr_new();
+	if (space->constraint_ids == NULL) {
+		diag_set(OutOfMemory, sizeof(*space->constraint_ids), "malloc",
+			 "constraint_ids");
+		goto fail;
+	}
 	return 0;
 
 fail_free_indexes:
@@ -258,9 +266,12 @@ space_delete(struct space *space)
 	trigger_destroy(&space->on_replace);
 	space_def_delete(space->def);
 	/*
-	 * SQL Triggers should be deleted with
-	 * on_replace_dd_trigger on deletion from _trigger.
+	 * SQL triggers and constraints should be deleted with
+	 * on_replace_dd_ triggers on deletion from corresponding
+	 * system space.
 	 */
+	assert(mh_size(space->constraint_ids) == 0);
+	mh_strnptr_delete(space->constraint_ids);
 	assert(space->sql_triggers == NULL);
 	assert(rlist_empty(&space->parent_fk_constraint));
 	assert(rlist_empty(&space->child_fk_constraint));
@@ -617,6 +628,45 @@ space_remove_ck_constraint(struct space *space, struct ck_constraint *ck)
 	}
 }
 
+struct constraint_id *
+space_find_constraint_id(struct space *space, const char *name)
+{
+	struct mh_strnptr_t *ids = space->constraint_ids;
+	uint32_t len = strlen(name);
+	mh_int_t pos = mh_strnptr_find_inp(ids, name, len);
+	if (pos == mh_end(ids))
+		return NULL;
+	return (struct constraint_id *) mh_strnptr_node(ids, pos)->val;
+}
+
+int
+space_add_constraint_id(struct space *space, struct constraint_id *id)
+{
+	assert(space_find_constraint_id(space, id->name) == NULL);
+	struct mh_strnptr_t *ids = space->constraint_ids;
+	uint32_t len = strlen(id->name);
+	uint32_t hash = mh_strn_hash(id->name, len);
+	const struct mh_strnptr_node_t name_node = {id->name, len, hash, id};
+	if (mh_strnptr_put(ids, &name_node, NULL, NULL) == mh_end(ids)) {
+		diag_set(OutOfMemory, sizeof(name_node), "malloc", "node");
+		return -1;
+	}
+	return 0;
+}
+
+struct constraint_id *
+space_pop_constraint_id(struct space *space, const char *name)
+{
+	struct mh_strnptr_t *ids = space->constraint_ids;
+	uint32_t len = strlen(name);
+	mh_int_t pos = mh_strnptr_find_inp(ids, name, len);
+	assert(pos != mh_end(ids));
+	struct constraint_id *id = (struct constraint_id *)
+		mh_strnptr_node(ids, pos)->val;
+	mh_strnptr_del(ids, pos, NULL);
+	return id;
+}
+
 /* {{{ Virtual method stubs */
 
 size_t
diff --git a/src/box/space.h b/src/box/space.h
index 7926aa65e..9aea4e5be 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -52,6 +52,7 @@ struct port;
 struct tuple;
 struct tuple_format;
 struct ck_constraint;
+struct constraint_id;
 
 struct space_vtab {
 	/** Free a space instance. */
@@ -234,6 +235,10 @@ struct space {
 	 * of parent constraints as well as child ones.
 	 */
 	uint64_t fk_constraint_mask;
+	/**
+	 * Hash table with constraint identifiers hashed by name.
+	 */
+	struct mh_strnptr_t *constraint_ids;
 };
 
 /** Initialize a base space instance. */
@@ -516,6 +521,25 @@ space_add_ck_constraint(struct space *space, struct ck_constraint *ck);
 void
 space_remove_ck_constraint(struct space *space, struct ck_constraint *ck);
 
+/** Find a constraint identifier by name. */
+struct constraint_id *
+space_find_constraint_id(struct space *space, const char *name);
+
+/**
+ * Add a new constraint id to the space's hash table of all
+ * constraints. That is used to prevent existence of constraints
+ * with equal names.
+ */
+int
+space_add_constraint_id(struct space *space, struct constraint_id *id);
+
+/**
+ * Remove a given name from the hash of all constraint
+ * identifiers of the given space.
+ */
+struct constraint_id *
+space_pop_constraint_id(struct space *space, const char *name);
+
 /*
  * Virtual method stubs.
  */
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 51cd7ce63..9470ef4c3 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -56,6 +56,7 @@
 #include "box/schema.h"
 #include "box/tuple_format.h"
 #include "box/coll_id_cache.h"
+#include "box/constraint_id.h"
 
 void
 sql_finish_coding(struct Parse *parse_context)
@@ -584,7 +585,7 @@ trim_space_snprintf(char *wptr, const char *str, uint32_t str_len)
 static void
 vdbe_emit_ck_constraint_create(struct Parse *parser,
 			       const struct ck_constraint_def *ck_def,
-			       uint32_t reg_space_id);
+			       uint32_t reg_space_id, const char *space_name);
 
 void
 sql_create_check_contraint(struct Parse *parser)
@@ -664,7 +665,8 @@ sql_create_check_contraint(struct Parse *parser)
 		struct Vdbe *v = sqlGetVdbe(parser);
 		sqlVdbeAddOp2(v, OP_Integer, space->def->id,
 			      space_id_reg);
-		vdbe_emit_ck_constraint_create(parser, ck_def, space_id_reg);
+		vdbe_emit_ck_constraint_create(parser, ck_def, space_id_reg,
+					       space->def->name);
 		assert(sqlVdbeGetOp(v, v->nOp - 1)->opcode == OP_SInsert);
 		sqlVdbeCountChanges(v);
 		sqlVdbeChangeP5(v, OPFLAG_NCHANGE);
@@ -969,11 +971,13 @@ emitNewSysSpaceSequenceRecord(Parse *pParse, int reg_space_id, int reg_seq_id)
  * @param parser Parsing context.
  * @param ck_def Check constraint definition to be serialized.
  * @param reg_space_id The VDBE register containing space id.
-*/
+ * @param space_name Name of the space owning the CHECK. For error
+ *     message.
+ */
 static void
 vdbe_emit_ck_constraint_create(struct Parse *parser,
 			       const struct ck_constraint_def *ck_def,
-			       uint32_t reg_space_id)
+			       uint32_t reg_space_id, const char *space_name)
 {
 	struct sql *db = parser->db;
 	struct Vdbe *v = sqlGetVdbe(parser);
@@ -996,7 +1000,8 @@ vdbe_emit_ck_constraint_create(struct Parse *parser,
 		      ck_constraint_reg + 6);
 	const char *error_msg =
 		tt_sprintf(tnt_errcode_desc(ER_CONSTRAINT_EXISTS),
-					    ck_def->name);
+			   constraint_type_strs[CONSTRAINT_TYPE_CK],
+			   ck_def->name, space_name);
 	if (vdbe_emit_halt_with_presence_test(parser, BOX_CK_CONSTRAINT_ID, 0,
 					      ck_constraint_reg, 2,
 					      ER_CONSTRAINT_EXISTS, error_msg,
@@ -1014,10 +1019,13 @@ vdbe_emit_ck_constraint_create(struct Parse *parser,
  *
  * @param parse_context Parsing context.
  * @param fk Foreign key to be created.
+ * @param space_name Name of the space owning the FOREIGN KEY. For
+ *     error message.
  */
 static void
 vdbe_emit_fk_constraint_create(struct Parse *parse_context,
-			       const struct fk_constraint_def *fk)
+			       const struct fk_constraint_def *fk,
+			       const char *space_name)
 {
 	assert(parse_context != NULL);
 	assert(fk != NULL);
@@ -1058,7 +1066,9 @@ vdbe_emit_fk_constraint_create(struct Parse *parse_context,
 	 * been created before.
 	 */
 	const char *error_msg =
-		tt_sprintf(tnt_errcode_desc(ER_CONSTRAINT_EXISTS), name_copy);
+		tt_sprintf(tnt_errcode_desc(ER_CONSTRAINT_EXISTS),
+			   constraint_type_strs[CONSTRAINT_TYPE_FK], name_copy,
+			   space_name);
 	if (vdbe_emit_halt_with_presence_test(parse_context,
 					      BOX_FK_CONSTRAINT_ID, 0,
 					      constr_tuple_reg, 2,
@@ -1272,13 +1282,13 @@ sqlEndTable(struct Parse *pParse)
 			fk_def->parent_id = reg_space_id;
 		}
 		fk_def->child_id = reg_space_id;
-		vdbe_emit_fk_constraint_create(pParse, fk_def);
+		vdbe_emit_fk_constraint_create(pParse, fk_def, space_name_copy);
 	}
 	struct ck_constraint_parse *ck_parse;
 	rlist_foreach_entry(ck_parse, &pParse->create_table_def.new_check,
 			    link) {
 		vdbe_emit_ck_constraint_create(pParse, ck_parse->ck_def,
-					       reg_space_id);
+					       reg_space_id, space_name_copy);
 	}
 }
 
@@ -1959,7 +1969,8 @@ sql_create_foreign_key(struct Parse *parse_context)
 					  struct fk_constraint_parse, link);
 		fk_parse->fk_def = fk_def;
 	} else {
-		vdbe_emit_fk_constraint_create(parse_context, fk_def);
+		vdbe_emit_fk_constraint_create(parse_context, fk_def,
+					       child_space->def->name);
 	}
 
 exit_create_fk:
diff --git a/test/sql-tap/alter2.test.lua b/test/sql-tap/alter2.test.lua
index e0bd60727..f6d421424 100755
--- a/test/sql-tap/alter2.test.lua
+++ b/test/sql-tap/alter2.test.lua
@@ -246,7 +246,7 @@ test:do_catchsql_test(
         ALTER TABLE child ADD CONSTRAINT fk FOREIGN KEY (a) REFERENCES child;
     ]], {
         -- <alter2-5.1>
-        1, "Constraint FK already exists"
+        1, "FOREIGN KEY constraint 'FK' already exists in space 'CHILD'"
         -- </alter2-5.1>
     })
 
@@ -278,7 +278,7 @@ test:do_catchsql_test(
     "alter2-6.2",
     [[
         ALTER TABLE t1 ADD CONSTRAINT ck CHECK(id > 0);
-    ]], { 1, "Constraint CK already exists" })
+    ]], { 1, "CHECK constraint 'CK' already exists in space 'T1'" })
 
 -- Make sure that CHECK constraint can be created only on empty space.
 --
diff --git a/test/sql/checks.result b/test/sql/checks.result
index a952b2b70..ebdf8803a 100644
--- a/test/sql/checks.result
+++ b/test/sql/checks.result
@@ -260,7 +260,7 @@ s:drop()
 box.execute("CREATE TABLE T2(ID INT PRIMARY KEY, CONSTRAINT CK1 CHECK(ID > 0), CONSTRAINT CK1 CHECK(ID < 0))")
 ---
 - null
-- Constraint CK1 already exists
+- CHECK constraint 'CK1' already exists in space 'T2'
 ...
 box.space.T2
 ---
diff --git a/test/sql/clear.result b/test/sql/clear.result
index afa6520e8..e7c498595 100644
--- a/test/sql/clear.result
+++ b/test/sql/clear.result
@@ -177,7 +177,7 @@ box.execute("DROP TABLE zoobar")
 box.execute("CREATE TABLE t1(id INT PRIMARY KEY, CONSTRAINT ck1 CHECK(id > 0), CONSTRAINT ck1 CHECK(id < 0));")
 ---
 - null
-- Constraint CK1 already exists
+- CHECK constraint 'CK1' already exists in space 'T1'
 ...
 box.space.t1
 ---
@@ -190,7 +190,7 @@ box.space._ck_constraint:select()
 box.execute("CREATE TABLE t2(id INT PRIMARY KEY, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t2, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t2);")
 ---
 - null
-- Constraint FK1 already exists
+- FOREIGN KEY constraint 'FK1' already exists in space 'T2'
 ...
 box.space.t2
 ---
@@ -207,7 +207,7 @@ box.space._fk_constraint:select()
 box.execute("CREATE TABLE t3(id INT PRIMARY KEY, CONSTRAINT ck1 CHECK(id > 0), CONSTRAINT ck1 FOREIGN KEY(id) REFERENCES t3, CONSTRAINT fk1 FOREIGN KEY(id) REFERENCES t3, CONSTRAINT ck1 CHECK(id < 0));")
 ---
 - null
-- Constraint CK1 already exists
+- FOREIGN KEY constraint 'CK1' already exists in space 'T3'
 ...
 box.space.t1
 ---
diff --git a/test/sql/constraint.result b/test/sql/constraint.result
new file mode 100644
index 000000000..1063d6d9b
--- /dev/null
+++ b/test/sql/constraint.result
@@ -0,0 +1,278 @@
+-- test-run result file version 2
+test_run = require('test_run').new()
+ | ---
+ | ...
+engine = test_run:get_cfg('engine')
+ | ---
+ | ...
+box.execute('pragma sql_default_engine=\''..engine..'\'')
+ | ---
+ | - row_count: 0
+ | ...
+
+--
+-- Check a constraint name for duplicate within a single
+-- <CREATE TABLE> statement.
+--
+box.execute('CREATE TABLE t1 (i INT PRIMARY KEY);')
+ | ---
+ | - row_count: 1
+ | ...
+test_run:cmd("setopt delimiter ';'");
+ | ---
+ | - true
+ | ...
+box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+ | ---
+ | - null
+ | - PRIMARY KEY constraint 'C' already exists in space 'T2'
+ | ...
+box.execute([[CREATE TABLE t2 (i INT,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+ | ---
+ | - null
+ | - PRIMARY KEY constraint 'C' already exists in space 'T2'
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c UNIQUE (i));]]);
+ | ---
+ | - null
+ | - UNIQUE constraint 'C' already exists in space 'T2'
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c UNIQUE (i));]]);
+ | ---
+ | - null
+ | - UNIQUE constraint 'C' already exists in space 'T2'
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c CHECK (i < 0));]]);
+ | ---
+ | - null
+ | - CHECK constraint 'C' already exists in space 'T2'
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c CHECK (i > 0));]]);
+ | ---
+ | - null
+ | - FOREIGN KEY constraint 'C' already exists in space 'T2'
+ | ...
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
+ | ---
+ | - null
+ | - FOREIGN KEY constraint 'C' already exists in space 'T2'
+ | ...
+test_run:cmd("setopt delimiter ''");
+ | ---
+ | - true
+ | ...
+
+--
+-- Check a constraint name for duplicate using <ALTER TABLE>
+-- statement.
+--
+box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);')
+ | ---
+ | - null
+ | - PRIMARY KEY constraint 'C' already exists in space 'T2'
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);')
+ | ---
+ | - null
+ | - Index 'C' already exists in space 'T2'
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);')
+ | ---
+ | - null
+ | - PRIMARY KEY constraint 'C' already exists in space 'T2'
+ | ...
+
+--
+-- Make sure that a constraint's name isn't kept after the
+-- constraint drop.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('DROP INDEX d ON t2;')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.ck_constraint.D:drop()
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- The same inside a transaction.
+--
+box.begin()                                                                     \
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')                                  \
+box.execute('DROP INDEX d ON t2;')                                              \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')                    \
+box.space.T2.ck_constraint.D:drop()                                             \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);') \
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')                                \
+box.commit()
+ | ---
+ | ...
+
+--
+-- Circle renaming in a transaction.
+--
+box.begin()                                                                 \
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')                              \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')               \
+-- D and E constraints exchange their names. Note, CHECK can't be           \
+-- renamed, because CHECK name is a primary key of _ck_constraint.          \
+-- It can be only dropped and created again with a new name.                \
+box.space.T2.index.D:rename('F')                                            \
+box.space.T2.ck_constraint.E:drop()                                         \
+box.space.T2.index.F:rename('E')                                            \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT D CHECK (i > 0);')               \
+-- Now an attempt to occupy the names will report, that the names           \
+-- are really swapped.                                                      \
+_, err1 = box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')     \
+_, err2 = box.execute('CREATE UNIQUE INDEX d ON t2(i);')                    \
+box.rollback()
+ | ---
+ | ...
+err1, err2
+ | ---
+ | - UNIQUE constraint 'E' already exists in space 'T2'
+ | - CHECK constraint 'D' already exists in space 'T2'
+ | ...
+-- After rollback ensure, that the names are free again.
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('DROP INDEX d ON t2;')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.ck_constraint.E:drop()
+ | ---
+ | ...
+
+--
+-- Make sure, that altering of an index name affects its record
+-- in the space's constraint hash table.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.D:rename('E')
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+ | ---
+ | - null
+ | - UNIQUE constraint 'E' already exists in space 'T2'
+ | ...
+
+--
+-- Try rename to an already occupied name.
+--
+box.execute('ALTER TABLE t2 ADD CONSTRAINT f CHECK(i > 0);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('CREATE UNIQUE INDEX g ON t2(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.G:rename('F')
+ | ---
+ | - error: CHECK constraint 'F' already exists in space 'T2'
+ | ...
+box.execute('DROP INDEX g ON t2;')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.ck_constraint.F:drop()
+ | ---
+ | ...
+
+--
+-- Make sure, that altering of an index uniqueness puts/drops
+-- its name to/from the space's constraint hash table.
+--
+box.space.T2.index.E:alter({unique = false})
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.E:alter({unique = true})
+ | ---
+ | - error: CHECK constraint 'E' already exists in space 'T2'
+ | ...
+box.space.T2.ck_constraint.E:drop()
+ | ---
+ | ...
+box.space.T2.index.E:alter({unique = true})
+ | ---
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+ | ---
+ | - null
+ | - UNIQUE constraint 'E' already exists in space 'T2'
+ | ...
+
+-- Alter name and uniqueness of an unique index simultaneously.
+box.space.T2.index.E:alter({name = 'D', unique = false})
+ | ---
+ | ...
+box.execute('CREATE UNIQUE INDEX e ON t2(i);')
+ | ---
+ | - row_count: 1
+ | ...
+-- The existing D index is not unique, but regardless of
+-- uniqueness, index names should be unique in a space.
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+ | ---
+ | - null
+ | - Index 'D' already exists in space 'T2'
+ | ...
+
+--
+-- Cleanup.
+--
+box.execute('DROP TABLE t2;')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('DROP TABLE t1;')
+ | ---
+ | - row_count: 1
+ | ...
diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
new file mode 100755
index 000000000..ddaf09795
--- /dev/null
+++ b/test/sql/constraint.test.lua
@@ -0,0 +1,128 @@
+test_run = require('test_run').new()
+engine = test_run:get_cfg('engine')
+box.execute('pragma sql_default_engine=\''..engine..'\'')
+
+--
+-- Check a constraint name for duplicate within a single
+-- <CREATE TABLE> statement.
+--
+box.execute('CREATE TABLE t1 (i INT PRIMARY KEY);')
+test_run:cmd("setopt delimiter ';'");
+box.execute([[CREATE TABLE t2 (i INT, CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+box.execute([[CREATE TABLE t2 (i INT,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c PRIMARY KEY (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c UNIQUE (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c UNIQUE (i));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c CHECK (i > 0),
+                               CONSTRAINT c CHECK (i < 0));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY,
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i),
+                               CONSTRAINT c CHECK (i > 0));]]);
+box.execute([[CREATE TABLE t2 (i INT PRIMARY KEY CONSTRAINT c REFERENCES t1(i),
+                               CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i))]]);
+test_run:cmd("setopt delimiter ''");
+
+--
+-- Check a constraint name for duplicate using <ALTER TABLE>
+-- statement.
+--
+box.execute('CREATE TABLE t2 (i INT CONSTRAINT c PRIMARY KEY);')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c CHECK(i > 0);')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c UNIQUE(i);')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT c FOREIGN KEY(i) REFERENCES t1(i);')
+
+--
+-- Make sure that a constraint's name isn't kept after the
+-- constraint drop.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')
+box.space.T2.ck_constraint.D:drop()
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')
+
+--
+-- The same inside a transaction.
+--
+box.begin()                                                                     \
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')                                  \
+box.execute('DROP INDEX d ON t2;')                                              \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK(i > 0);')                    \
+box.space.T2.ck_constraint.D:drop()                                             \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d FOREIGN KEY(i) REFERENCES t1(i);') \
+box.execute('ALTER TABLE t2 DROP CONSTRAINT d;')                                \
+box.commit()
+
+--
+-- Circle renaming in a transaction.
+--
+box.begin()                                                                 \
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')                              \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')               \
+-- D and E constraints exchange their names. Note, CHECK can't be           \
+-- renamed, because CHECK name is a primary key of _ck_constraint.          \
+-- It can be only dropped and created again with a new name.                \
+box.space.T2.index.D:rename('F')                                            \
+box.space.T2.ck_constraint.E:drop()                                         \
+box.space.T2.index.F:rename('E')                                            \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT D CHECK (i > 0);')               \
+-- Now an attempt to occupy the names will report, that the names           \
+-- are really swapped.                                                      \
+_, err1 = box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')     \
+_, err2 = box.execute('CREATE UNIQUE INDEX d ON t2(i);')                    \
+box.rollback()
+err1, err2
+-- After rollback ensure, that the names are free again.
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('DROP INDEX d ON t2;')
+box.space.T2.ck_constraint.E:drop()
+
+--
+-- Make sure, that altering of an index name affects its record
+-- in the space's constraint hash table.
+--
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.space.T2.index.D:rename('E')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+
+--
+-- Try rename to an already occupied name.
+--
+box.execute('ALTER TABLE t2 ADD CONSTRAINT f CHECK(i > 0);')
+box.execute('CREATE UNIQUE INDEX g ON t2(i);')
+box.space.T2.index.G:rename('F')
+box.execute('DROP INDEX g ON t2;')
+box.space.T2.ck_constraint.F:drop()
+
+--
+-- Make sure, that altering of an index uniqueness puts/drops
+-- its name to/from the space's constraint hash table.
+--
+box.space.T2.index.E:alter({unique = false})
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+box.space.T2.index.E:alter({unique = true})
+box.space.T2.ck_constraint.E:drop()
+box.space.T2.index.E:alter({unique = true})
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+
+-- Alter name and uniqueness of an unique index simultaneously.
+box.space.T2.index.E:alter({name = 'D', unique = false})
+box.execute('CREATE UNIQUE INDEX e ON t2(i);')
+-- The existing D index is not unique, but regardless of
+-- uniqueness, index names should be unique in a space.
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+
+--
+-- Cleanup.
+--
+box.execute('DROP TABLE t2;')
+box.execute('DROP TABLE t1;')

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

* Re: [Tarantool-patches] [PATCH v2 2/3] box: make constraint operations transactional
  2019-12-28 11:07                 ` Vladislav Shpilevoy
@ 2019-12-29  0:07                   ` Nikita Pettik
  2019-12-29 15:51                     ` Vladislav Shpilevoy
  0 siblings, 1 reply; 29+ messages in thread
From: Nikita Pettik @ 2019-12-29  0:07 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

On 28 Dec 14:07, Vladislav Shpilevoy wrote:
> 
> I rewrote the commit message, check this:
> 
> ================================================================================
>     sql: make constraint names unique in scope of table
>     
>     Put constraint names into the space's hash table and drop them on
>     insert/delete in corresponding system spaces (_index,
>     _fk_constraint, _ck_constraint).
>     
>     Closes #3503
>     
>     @TarantoolBot document
>     Title: Constraint names are unique in scope of table
>     
>     SQL:
>     According to ANSI SQL, table constraint is one of the following
>     entities: PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK. (Also there
>     is NOT NULL, but we don't consider it a constraint.) Every
>     constraint has its own name passed by user or automatically
>     generated. And these names must be unique within one table/space.
>     
>     For example:
>     
>         tarantool> box.execute([[CREATE TABLE test (
>                                      a INTEGER PRIMARY KEY,
>                                      b INTEGER,
>                                      CONSTRAINT cnstr CHECK (a >= 0)
>                                  );]])
>         ---
>         - row_count: 1
>         ...
>     
>         tarantool> box.execute('CREATE UNIQUE INDEX cnstr ON test(b);')
>         ---
>         - null
>         - Constraint CHECK 'CNSTR' already exists in space 'TEST'
>         ...
>     
>     Unique index and CHECK are different constraint types, but they
>     share namespace, and can't have clashing names. The same for all
>     the other constraints.

It's OK, thanks.

> >> +/**
> >> + * Check if constraint with @a name exists within @a space. Call
> >> + * diag_set() if it is so.
> >> + */
> >> +static inline int
> >> +space_check_constraint_existence(struct space *space, const char *name)
> > 
> > IMHO this function looks a bit strange (or I'd say - contradictory)
> 
> Yes, maybe the name is bad. Check this:
> 
> ================================================================================
> diff --git a/src/box/alter.cc b/src/box/alter.cc
> index 2e4fa3c41..1ce1c3858 100644
> --- a/src/box/alter.cc
> +++ b/src/box/alter.cc
> @@ -1776,11 +1776,11 @@ MoveCkConstraints::rollback(struct alter_space *alter)
>  }
>  
>  /**
> - * Check if constraint with @a name exists within @a space. Call
> - * diag_set() if it is so.
> + * Check if a constraint name is not occupied in @a space. Treat
> + * existence as an error.
>   */
>  static inline int
> -space_check_constraint_existence(struct space *space, const char *name)
> +space_check_constraint_name_is_free(struct space *space, const char *name)

Now I notice that space_check_constraint_... implies referring to
CHECK constraint (if fact we use _ck_constraint prefix, but anyway
it looks a bit messy). Personally I would better call it:

space_constraint_name_is_available(...)

> > Let's simply inline it, only 2 usages tho.
> 
> I would agree if not the long diag_set. Purpose of this function is not to
> write it more than once.

Ok, it is non-functional nit, so let's keep it if you want.
 
> >> +static inline void
> >> +space_delete_constraint_id(struct space *space, const char *name)
> > 
> > Why do we need this wrapper?
> 
> Multiple reasons: 1) it appears 5 times, so I decided to wrap it;
> 2) it is shorter, 3) it is consistent with other
> space_<action>_constraint_id() functions. But is not worth moving
> to space.h/.c IMO.

Nevermind, I missed space_pop_constraint_id() call.
 
> >> +{
> >> +	constraint_id_delete(space_pop_constraint_id(space, name));
> >> +}
> >> +
> >> diff --git a/src/box/errcode.h b/src/box/errcode.h
> >> index c660b1c70..094a63ee1 100644
> >> --- a/src/box/errcode.h
> >> +++ b/src/box/errcode.h
> >> @@ -222,7 +222,7 @@ struct errcode_record {
> >>  	/*167 */_(ER_CREATE_FK_CONSTRAINT,	"Failed to create foreign key constraint '%s': %s") \
> >>  	/*168 */_(ER_DROP_FK_CONSTRAINT,	"Failed to drop foreign key constraint '%s': %s") \
> >>  	/*169 */_(ER_NO_SUCH_CONSTRAINT,	"Constraint %s does not exist") \
> >> -	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s already exists") \
> >> +	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s '%s' already exists in space '%s'") \
> > 
> > I'd better place type of constraint at the beggining of sentence:
> > 
> > Check constraint xxx already exists
> > Foreign key constraint ...
> > Unique constraint ...
> > 
> > Sounds way much better, doesn't it?
> 
> It is subjective. I changed it to this order on purpose.
> 1) Because I wanted to go from more common term to a more
> concrete: 'Constraint' - common, <constraint type> - more
> concrete, <name> - even more concrete.
> 2) Because when I see '<type> constraint <name> already exists ...'
> for me it looks like someone tried to create constraint of
> type <type>. But it is not always so. Type is not the most
> important part of the message.

Well, I think it may turn out to be good argument. Why do
you add type of constraint to message at all? My guess
is to simplify user's life and make finding constraint
with given name easy. If so, does it make sense?
 
> But I don't mind to change it back. Since I have no a
> proof that my point is correct, except personal taste.
> 
> 
> >> new file mode 100755
> >> index 000000000..b41e16dc2
> >> --- /dev/null
> >> +++ b/test/sql/constraint.test.lua
> >> @@ -0,0 +1,93 @@
> 
> >> +--
> >> +-- Make sure, that altering of an index uniqueness puts/drops
> >> +-- its name to/from the space's constraint hash table.
> >> +--
> >> +box.space.T2.index.E:alter({unique = false})
> >> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
> >> +box.space.T2.index.E:alter({unique = true})
> >> +box.space.T2.ck_constraint.E:drop()
> >> +box.space.T2.index.E:alter({unique = true})
> >> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
> >> +
> >> +-- Alter name and uniqueness of an unique index simultaneously.
> >> +box.space.T2.index.E:alter({name = 'D', unique = false})
> >> +box.execute('CREATE UNIQUE INDEX e ON t2(i);')
> > 
> > And make sure that CREATE UNIQUE INDEX d ON t2(i); fails
> 
> I am not sure how that test is related to the patch,
> because uniqueness of index names is a different task,
> and it has many tests in other places. But ok.

Ok, sorry, here you absolutely right. It's very optional test.
Maybe with other constraint type it will be a bit more meaningful.
 
> ================================================================================
> 
> > What about circle renaming of consraint inside transaction which
> > is rolled back later?
> 
> I wasn't sure what do you mean as a circle renaming. I tried this:
> 
> ================================================================================
> diff --git a/test/sql/constraint.result b/test/sql/constraint.result
> index 65ec61e66..32c42897a 100644
> --- a/test/sql/constraint.result
> +++ b/test/sql/constraint.result
> @@ -148,6 +148,48 @@ test_run:cmd("setopt delimiter ''");
>   | - true
>   | ...
>  
> +--
> +-- Circle renaming in a transaction.
> +--
> +box.begin()                                                                 \
> +box.execute('CREATE UNIQUE INDEX d ON t2(i);')                              \
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')  

I'd move constraints creation out of transaction, rename E
constraint to F, D to E, and after transaction is rollbacked
check that F is free and E and D are not.
  
> +-- D and E constraints exchange their names. Note, CHECK can't be           \
> +-- renamed, because CHECK name is a primary key of _ck_constraint.          \
> +-- It can be only dropped and created again with a new name.                \
> +box.space.T2.index.D:rename('F')                                            \
> +box.space.T2.ck_constraint.E:drop()                                         \
> +box.space.T2.index.F:rename('E')                                            \
> +box.execute('ALTER TABLE t2 ADD CONSTRAINT D CHECK (i > 0);')               \
> +-- Now an attempt to occupy the names will report, that the names           \
> +-- are really swapped.                                                      \
> +_, err1 = box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')     \
> +_, err2 = box.execute('CREATE UNIQUE INDEX d ON t2(i);')                    \
> +box.rollback()
> + | ---
> + | ...
> Also I did some minor refactoring to the test. See the whole patch below.

It's ok.
 

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

* Re: [Tarantool-patches] [PATCH v2 2/3] box: make constraint operations transactional
  2019-12-29  0:07                   ` Nikita Pettik
@ 2019-12-29 15:51                     ` Vladislav Shpilevoy
  2019-12-29 22:28                       ` Nikita Pettik
  0 siblings, 1 reply; 29+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-29 15:51 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches

>>>> +/**
>>>> + * Check if constraint with @a name exists within @a space. Call
>>>> + * diag_set() if it is so.
>>>> + */
>>>> +static inline int
>>>> +space_check_constraint_existence(struct space *space, const char *name)
>>>
>>> IMHO this function looks a bit strange (or I'd say - contradictory)
>>
>> Yes, maybe the name is bad. Check this:
>>
>> ================================================================================
>> diff --git a/src/box/alter.cc b/src/box/alter.cc
>> index 2e4fa3c41..1ce1c3858 100644
>> --- a/src/box/alter.cc
>> +++ b/src/box/alter.cc
>> @@ -1776,11 +1776,11 @@ MoveCkConstraints::rollback(struct alter_space *alter)
>>  }
>>  
>>  /**
>> - * Check if constraint with @a name exists within @a space. Call
>> - * diag_set() if it is so.
>> + * Check if a constraint name is not occupied in @a space. Treat
>> + * existence as an error.
>>   */
>>  static inline int
>> -space_check_constraint_existence(struct space *space, const char *name)
>> +space_check_constraint_name_is_free(struct space *space, const char *name)
> 
> Now I notice that space_check_constraint_... implies referring to
> CHECK constraint (if fact we use _ck_constraint prefix, but anyway
> it looks a bit messy). Personally I would better call it:
> 
> space_constraint_name_is_available(...)

Agree. But we usually use 'is' without other verb, when it
is a simple getter method. Without any side effects such as
diag_set. I added 'ensure' verb:

    space_ensure_constraint_name_is_available()

>>>> +{
>>>> +	constraint_id_delete(space_pop_constraint_id(space, name));
>>>> +}
>>>> +
>>>> diff --git a/src/box/errcode.h b/src/box/errcode.h
>>>> index c660b1c70..094a63ee1 100644
>>>> --- a/src/box/errcode.h
>>>> +++ b/src/box/errcode.h
>>>> @@ -222,7 +222,7 @@ struct errcode_record {
>>>>  	/*167 */_(ER_CREATE_FK_CONSTRAINT,	"Failed to create foreign key constraint '%s': %s") \
>>>>  	/*168 */_(ER_DROP_FK_CONSTRAINT,	"Failed to drop foreign key constraint '%s': %s") \
>>>>  	/*169 */_(ER_NO_SUCH_CONSTRAINT,	"Constraint %s does not exist") \
>>>> -	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s already exists") \
>>>> +	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s '%s' already exists in space '%s'") \
>>>
>>> I'd better place type of constraint at the beggining of sentence:
>>>
>>> Check constraint xxx already exists
>>> Foreign key constraint ...
>>> Unique constraint ...
>>>
>>> Sounds way much better, doesn't it?
>>
>> It is subjective. I changed it to this order on purpose.
>> 1) Because I wanted to go from more common term to a more
>> concrete: 'Constraint' - common, <constraint type> - more
>> concrete, <name> - even more concrete.
>> 2) Because when I see '<type> constraint <name> already exists ...'
>> for me it looks like someone tried to create constraint of
>> type <type>. But it is not always so. Type is not the most
>> important part of the message.
> 
> Well, I think it may turn out to be good argument. Why do
> you add type of constraint to message at all? My guess
> is to simplify user's life and make finding constraint
> with given name easy. If so, does it make sense?

Your guess is right. I wanted to make it simpler to determine
with what a constraint there is a conflict. So as a user could
do rename of index/fk/ck/whatever-else which occupies the needed
name.

>> ================================================================================
>>
>>> What about circle renaming of consraint inside transaction which
>>> is rolled back later?
>>
>> I wasn't sure what do you mean as a circle renaming. I tried this:
>>
>> ================================================================================
>> diff --git a/test/sql/constraint.result b/test/sql/constraint.result
>> index 65ec61e66..32c42897a 100644
>> --- a/test/sql/constraint.result
>> +++ b/test/sql/constraint.result
>> @@ -148,6 +148,48 @@ test_run:cmd("setopt delimiter ''");
>>   | - true
>>   | ...
>>  
>> +--
>> +-- Circle renaming in a transaction.
>> +--
>> +box.begin()                                                                 \
>> +box.execute('CREATE UNIQUE INDEX d ON t2(i);')                              \
>> +box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')  
> 
> I'd move constraints creation out of transaction, rename E
> constraint to F, D to E, and after transaction is rollbacked
> check that F is free and E and D are not.

Ok, done.

================================================================================
diff --git a/test/sql/constraint.result b/test/sql/constraint.result
index 8ca63fd7a..3000a9244 100644
--- a/test/sql/constraint.result
+++ b/test/sql/constraint.result
@@ -143,34 +143,61 @@ box.commit()
 --
 -- Circle renaming in a transaction.
 --
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')
+ | ---
+ | - row_count: 1
+ | ...
 box.begin()                                                                     \
-box.execute('CREATE UNIQUE INDEX d ON t2(i);')                                  \
-box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')                   \
--- D and E constraints exchange their names. Note, CHECK can't be               \
--- renamed, because CHECK name is a primary key of _ck_constraint.              \
+-- Rename CHECK E to F. Note, CHECK can't be properly renamed,                  \
+-- because CHECK name is a primary key of _ck_constraint.                       \
 -- It can be only dropped and created again with a new name.                    \
-box.space.T2.index.D:rename('F')                                                \
 box.space.T2.ck_constraint.E:drop()                                             \
-box.space.T2.index.F:rename('E')                                                \
-box.execute('ALTER TABLE t2 ADD CONSTRAINT D CHECK (i > 0);')                   \
--- Now an attempt to occupy the names will report, that the names               \
--- are really swapped.                                                          \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT f CHECK (i > 0);')                   \
+-- Rename UNIQUE D to E.                                                        \
+box.space.T2.index.D:rename('E')                                                \
+-- Now E and F are occupied, and D is free. Check this.                         \
 _, err1 = box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')         \
-_, err2 = box.execute('CREATE UNIQUE INDEX d ON t2(i);')                        \
+_, err2 = box.execute('CREATE UNIQUE INDEX f ON t2(i);')                        \
+_, err3 = box.execute('CREATE UNIQUE INDEX d ON t2(i);')                        \
 box.rollback()
  | ---
  | ...
+-- Fail. E and F were occupied in the end of the transaction. The
+-- error messages say that E was an index, not a CHECK, like it
+-- was before the transaction.
 err1, err2
  | ---
  | - UNIQUE constraint 'E' already exists in space 'T2'
- | - CHECK constraint 'D' already exists in space 'T2'
+ | - CHECK constraint 'F' already exists in space 'T2'
  | ...
--- After rollback ensure, that the names are free again.
-box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')
+-- Success, D was free in the end.
+err3
+ | ---
+ | - null
+ | ...
+-- After rollback ensure, that the names E and D are occupied both
+-- again.
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK (i > 0);')
+ | ---
+ | - null
+ | - UNIQUE constraint 'D' already exists in space 'T2'
+ | ...
+box.execute('CREATE UNIQUE INDEX e ON t2(i);')
+ | ---
+ | - null
+ | - CHECK constraint 'E' already exists in space 'T2'
+ | ...
+-- F is free after rollback.
+box.execute('CREATE UNIQUE INDEX f ON t2(i);')
  | ---
  | - row_count: 1
  | ...
-box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+-- Cleanup after the test case.
+box.execute('DROP INDEX f ON t2;')
  | ---
  | - row_count: 1
  | ...
diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
index 8439bebc1..2f367eafe 100755
--- a/test/sql/constraint.test.lua
+++ b/test/sql/constraint.test.lua
@@ -64,25 +64,35 @@ box.commit()
 --
 -- Circle renaming in a transaction.
 --
+box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')
 box.begin()                                                                     \
-box.execute('CREATE UNIQUE INDEX d ON t2(i);')                                  \
-box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')                   \
--- D and E constraints exchange their names. Note, CHECK can't be               \
--- renamed, because CHECK name is a primary key of _ck_constraint.              \
+-- Rename CHECK E to F. Note, CHECK can't be properly renamed,                  \
+-- because CHECK name is a primary key of _ck_constraint.                       \
 -- It can be only dropped and created again with a new name.                    \
-box.space.T2.index.D:rename('F')                                                \
 box.space.T2.ck_constraint.E:drop()                                             \
-box.space.T2.index.F:rename('E')                                                \
-box.execute('ALTER TABLE t2 ADD CONSTRAINT D CHECK (i > 0);')                   \
--- Now an attempt to occupy the names will report, that the names               \
--- are really swapped.                                                          \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT f CHECK (i > 0);')                   \
+-- Rename UNIQUE D to E.                                                        \
+box.space.T2.index.D:rename('E')                                                \
+-- Now E and F are occupied, and D is free. Check this.                         \
 _, err1 = box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')         \
-_, err2 = box.execute('CREATE UNIQUE INDEX d ON t2(i);')                        \
+_, err2 = box.execute('CREATE UNIQUE INDEX f ON t2(i);')                        \
+_, err3 = box.execute('CREATE UNIQUE INDEX d ON t2(i);')                        \
 box.rollback()
+-- Fail. E and F were occupied in the end of the transaction. The
+-- error messages say that E was an index, not a CHECK, like it
+-- was before the transaction.
 err1, err2
--- After rollback ensure, that the names are free again.
-box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK (i > 0);')
-box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+-- Success, D was free in the end.
+err3
+-- After rollback ensure, that the names E and D are occupied both
+-- again.
+box.execute('ALTER TABLE t2 ADD CONSTRAINT d CHECK (i > 0);')
+box.execute('CREATE UNIQUE INDEX e ON t2(i);')
+-- F is free after rollback.
+box.execute('CREATE UNIQUE INDEX f ON t2(i);')
+-- Cleanup after the test case.
+box.execute('DROP INDEX f ON t2;')
 box.execute('DROP INDEX d ON t2;')
 box.space.T2.ck_constraint.E:drop()
================================================================================

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

* Re: [Tarantool-patches] [PATCH v2 2/3] box: make constraint operations transactional
  2019-12-29 15:51                     ` Vladislav Shpilevoy
@ 2019-12-29 22:28                       ` Nikita Pettik
  2019-12-29 22:35                         ` Vladislav Shpilevoy
  2019-12-30 11:12                         ` Sergey Ostanevich
  0 siblings, 2 replies; 29+ messages in thread
From: Nikita Pettik @ 2019-12-29 22:28 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

On 29 Dec 18:51, Vladislav Shpilevoy wrote:
> >>>> index c660b1c70..094a63ee1 100644
> >>>> --- a/src/box/errcode.h
> >>>> +++ b/src/box/errcode.h
> >>>> @@ -222,7 +222,7 @@ struct errcode_record {
> >>>>  	/*167 */_(ER_CREATE_FK_CONSTRAINT,	"Failed to create foreign key constraint '%s': %s") \
> >>>>  	/*168 */_(ER_DROP_FK_CONSTRAINT,	"Failed to drop foreign key constraint '%s': %s") \
> >>>>  	/*169 */_(ER_NO_SUCH_CONSTRAINT,	"Constraint %s does not exist") \
> >>>> -	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s already exists") \
> >>>> +	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s '%s' already exists in space '%s'") \
> >>>
> >>> I'd better place type of constraint at the beggining of sentence:
> >>>
> >>> Check constraint xxx already exists
> >>> Foreign key constraint ...
> >>> Unique constraint ...
> >>>
> >>> Sounds way much better, doesn't it?
> >>
> >> It is subjective. I changed it to this order on purpose.
> >> 1) Because I wanted to go from more common term to a more
> >> concrete: 'Constraint' - common, <constraint type> - more
> >> concrete, <name> - even more concrete.
> >> 2) Because when I see '<type> constraint <name> already exists ...'
> >> for me it looks like someone tried to create constraint of
> >> type <type>. But it is not always so. Type is not the most
> >> important part of the message.
> > 
> > Well, I think it may turn out to be good argument. Why do
> > you add type of constraint to message at all? My guess
> > is to simplify user's life and make finding constraint
> > with given name easy. If so, does it make sense?
> 
> Your guess is right. I wanted to make it simpler to determine
> with what a constraint there is a conflict. So as a user could
> do rename of index/fk/ck/whatever-else which occupies the needed
> name.

I mean if user finds out that name is already occupied,
one is likely to use another name rather than drop/rename
existing constraint. In this case type of constraint is not
vital.

Anyway, patch LGTM. This is second one approve so it can be pushed,
but Sergos wrote that he is concerned about memory management
during constraint id creation/drop. Sergos, did you find any
issues, or we can push this patch?

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

* Re: [Tarantool-patches] [PATCH v2 2/3] box: make constraint operations transactional
  2019-12-29 22:28                       ` Nikita Pettik
@ 2019-12-29 22:35                         ` Vladislav Shpilevoy
  2019-12-30 11:12                         ` Sergey Ostanevich
  1 sibling, 0 replies; 29+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-29 22:35 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches

> Anyway, patch LGTM. This is second one approve so it can be pushed,
> but Sergos wrote that he is concerned about memory management
> during constraint id creation/drop. Sergos, did you find any
> issues, or we can push this patch?

I see sometimes, that random box tests hang. In logs I see
nothing, or mp_tuple_assert fail even before a test is started.
I tried to run with ASAN - found nothing. Did one another
self-review - nothing. Probably these were problems with luajit,
don't know. Can't reproduce anywhere.

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

* Re: [Tarantool-patches] [PATCH v2 2/3] box: make constraint operations transactional
  2019-12-29 22:28                       ` Nikita Pettik
  2019-12-29 22:35                         ` Vladislav Shpilevoy
@ 2019-12-30 11:12                         ` Sergey Ostanevich
  2019-12-30 12:05                           ` Nikita Pettik
  1 sibling, 1 reply; 29+ messages in thread
From: Sergey Ostanevich @ 2019-12-30 11:12 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches, Vladislav Shpilevoy

Hi!

I didn't find any issues, let's push it.

Regards,
Sergos

On 30 Dec 00:28, Nikita Pettik wrote:
> On 29 Dec 18:51, Vladislav Shpilevoy wrote:
> > >>>> index c660b1c70..094a63ee1 100644
> > >>>> --- a/src/box/errcode.h
> > >>>> +++ b/src/box/errcode.h
> > >>>> @@ -222,7 +222,7 @@ struct errcode_record {
> > >>>>  	/*167 */_(ER_CREATE_FK_CONSTRAINT,	"Failed to create foreign key constraint '%s': %s") \
> > >>>>  	/*168 */_(ER_DROP_FK_CONSTRAINT,	"Failed to drop foreign key constraint '%s': %s") \
> > >>>>  	/*169 */_(ER_NO_SUCH_CONSTRAINT,	"Constraint %s does not exist") \
> > >>>> -	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s already exists") \
> > >>>> +	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s '%s' already exists in space '%s'") \
> > >>>
> > >>> I'd better place type of constraint at the beggining of sentence:
> > >>>
> > >>> Check constraint xxx already exists
> > >>> Foreign key constraint ...
> > >>> Unique constraint ...
> > >>>
> > >>> Sounds way much better, doesn't it?
> > >>
> > >> It is subjective. I changed it to this order on purpose.
> > >> 1) Because I wanted to go from more common term to a more
> > >> concrete: 'Constraint' - common, <constraint type> - more
> > >> concrete, <name> - even more concrete.
> > >> 2) Because when I see '<type> constraint <name> already exists ...'
> > >> for me it looks like someone tried to create constraint of
> > >> type <type>. But it is not always so. Type is not the most
> > >> important part of the message.
> > > 
> > > Well, I think it may turn out to be good argument. Why do
> > > you add type of constraint to message at all? My guess
> > > is to simplify user's life and make finding constraint
> > > with given name easy. If so, does it make sense?
> > 
> > Your guess is right. I wanted to make it simpler to determine
> > with what a constraint there is a conflict. So as a user could
> > do rename of index/fk/ck/whatever-else which occupies the needed
> > name.
> 
> I mean if user finds out that name is already occupied,
> one is likely to use another name rather than drop/rename
> existing constraint. In this case type of constraint is not
> vital.
> 
> Anyway, patch LGTM. This is second one approve so it can be pushed,
> but Sergos wrote that he is concerned about memory management
> during constraint id creation/drop. Sergos, did you find any
> issues, or we can push this patch?
> 

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

* Re: [Tarantool-patches] [PATCH v2 2/3] box: make constraint operations transactional
  2019-12-30 11:12                         ` Sergey Ostanevich
@ 2019-12-30 12:05                           ` Nikita Pettik
  0 siblings, 0 replies; 29+ messages in thread
From: Nikita Pettik @ 2019-12-30 12:05 UTC (permalink / raw)
  To: Sergey Ostanevich; +Cc: tarantool-patches, Vladislav Shpilevoy

On 30 Dec 14:12, Sergey Ostanevich wrote:
> Hi!
> 
> I didn't find any issues, let's push it.

Pushed to master.
 
> Regards,
> Sergos
> 
> On 30 Dec 00:28, Nikita Pettik wrote:
> > On 29 Dec 18:51, Vladislav Shpilevoy wrote:
> > > >>>> index c660b1c70..094a63ee1 100644
> > > >>>> --- a/src/box/errcode.h
> > > >>>> +++ b/src/box/errcode.h
> > > >>>> @@ -222,7 +222,7 @@ struct errcode_record {
> > > >>>>  	/*167 */_(ER_CREATE_FK_CONSTRAINT,	"Failed to create foreign key constraint '%s': %s") \
> > > >>>>  	/*168 */_(ER_DROP_FK_CONSTRAINT,	"Failed to drop foreign key constraint '%s': %s") \
> > > >>>>  	/*169 */_(ER_NO_SUCH_CONSTRAINT,	"Constraint %s does not exist") \
> > > >>>> -	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s already exists") \
> > > >>>> +	/*170 */_(ER_CONSTRAINT_EXISTS,		"Constraint %s '%s' already exists in space '%s'") \
> > > >>>
> > > >>> I'd better place type of constraint at the beggining of sentence:
> > > >>>
> > > >>> Check constraint xxx already exists
> > > >>> Foreign key constraint ...
> > > >>> Unique constraint ...
> > > >>>
> > > >>> Sounds way much better, doesn't it?
> > > >>
> > > >> It is subjective. I changed it to this order on purpose.
> > > >> 1) Because I wanted to go from more common term to a more
> > > >> concrete: 'Constraint' - common, <constraint type> - more
> > > >> concrete, <name> - even more concrete.
> > > >> 2) Because when I see '<type> constraint <name> already exists ...'
> > > >> for me it looks like someone tried to create constraint of
> > > >> type <type>. But it is not always so. Type is not the most
> > > >> important part of the message.
> > > > 
> > > > Well, I think it may turn out to be good argument. Why do
> > > > you add type of constraint to message at all? My guess
> > > > is to simplify user's life and make finding constraint
> > > > with given name easy. If so, does it make sense?
> > > 
> > > Your guess is right. I wanted to make it simpler to determine
> > > with what a constraint there is a conflict. So as a user could
> > > do rename of index/fk/ck/whatever-else which occupies the needed
> > > name.
> > 
> > I mean if user finds out that name is already occupied,
> > one is likely to use another name rather than drop/rename
> > existing constraint. In this case type of constraint is not
> > vital.
> > 
> > Anyway, patch LGTM. This is second one approve so it can be pushed,
> > but Sergos wrote that he is concerned about memory management
> > during constraint id creation/drop. Sergos, did you find any
> > issues, or we can push this patch?
> > 

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

end of thread, other threads:[~2019-12-30 12:05 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-11-28 18:34 [Tarantool-patches] [PATCH v2 0/2] Add constraint names hash table to space Roman Khabibov
2019-11-28 18:34 ` [Tarantool-patches] [PATCH v2 1/2] box: introduce constraint names hash table Roman Khabibov
2019-11-30  1:03   ` Vladislav Shpilevoy
2019-12-04 16:23     ` [Tarantool-patches] [PATCH v2 1/3] " Roman Khabibov
2019-12-07 16:34       ` Vladislav Shpilevoy
2019-12-10 12:48         ` Roman Khabibov
2019-11-28 18:34 ` [Tarantool-patches] [PATCH v2 2/2] sql: make constraint operations transactional Roman Khabibov
2019-11-29  7:38   ` Roman Khabibov
2019-11-30  1:03   ` Vladislav Shpilevoy
2019-12-04 16:23     ` [Tarantool-patches] [PATCH v2 2/3] " Roman Khabibov
2019-12-05 18:43     ` Roman Khabibov
2019-12-07 16:35       ` Vladislav Shpilevoy
2019-12-10 12:49         ` [Tarantool-patches] [PATCH v2 2/3] box: " Roman Khabibov
2019-12-15 22:26           ` Vladislav Shpilevoy
2019-12-17 15:03             ` Roman Khabibov
2019-12-28  0:18               ` Nikita Pettik
2019-12-28 11:07                 ` Vladislav Shpilevoy
2019-12-29  0:07                   ` Nikita Pettik
2019-12-29 15:51                     ` Vladislav Shpilevoy
2019-12-29 22:28                       ` Nikita Pettik
2019-12-29 22:35                         ` Vladislav Shpilevoy
2019-12-30 11:12                         ` Sergey Ostanevich
2019-12-30 12:05                           ` Nikita Pettik
2019-12-21 20:54           ` Sergey Ostanevich
2019-12-22 14:59             ` Vladislav Shpilevoy
2019-12-24 12:06             ` Roman Khabibov
2019-11-30  1:03 ` [Tarantool-patches] [PATCH v2 0/2] Add constraint names hash table to space Vladislav Shpilevoy
2019-12-04 16:23   ` [Tarantool-patches] [PATCH v2 0/3] " Roman Khabibov
2019-12-07 16:34     ` Vladislav Shpilevoy

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