Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH v2 0/5] Introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PK
@ 2019-01-23 17:56 Nikita Pettik
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing Nikita Pettik
                   ` (4 more replies)
  0 siblings, 5 replies; 34+ messages in thread
From: Nikita Pettik @ 2019-01-23 17:56 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

Branch: https://github.com/tarantool/tarantool/tree/np/gh-3914-fix-create-index
Issues:
https://github.com/tarantool/tarantool/issues/3097
https://github.com/tarantool/tarantool/issues/3914

Link to previous thread:
https://www.freelists.org/post/tarantool-patches/PATCH-06-Introduce-ALTER-TABLE-ADD-CONSTRAINT-UNIQUEPK

Changelog:
 - Completely reworked first patch in series: now it introduces
   hierarchical format for structures which assemble arguments during
   DDL parsing.
 - Removed third patch (sql: remove start token from sql_create_index args)
   since it was merged to the first one.

Nikita Pettik (5):
  sql: introduce structs assembling DDL arguments during parsing
  sql: rework ALTER TABLE grammar
  sql: refactor getNewIid() function
  sql: fix error message for improperly created index
  sql: introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PRIMARY KEY

 src/box/sql/CMakeLists.txt   |   1 +
 src/box/sql/alter.c          |  10 +-
 src/box/sql/build.c          | 240 ++++++++++++++++++++++++---------------
 src/box/sql/parse.y          | 262 ++++++++++++++++++++++++++++++++++++-------
 src/box/sql/parse_def.c      | 158 ++++++++++++++++++++++++++
 src/box/sql/parse_def.h      | 170 ++++++++++++++++++++++++++++
 src/box/sql/sqliteInt.h      |  70 +++---------
 src/box/sql/trigger.c        |  57 +++++-----
 test/sql-tap/alter.test.lua  |  57 +++++++++-
 test/sql-tap/index1.test.lua |  28 ++++-
 test/sql-tap/index7.test.lua |   2 +-
 11 files changed, 830 insertions(+), 225 deletions(-)
 create mode 100644 src/box/sql/parse_def.c
 create mode 100644 src/box/sql/parse_def.h

-- 
2.15.1

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

* [tarantool-patches] [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-01-23 17:56 [tarantool-patches] [PATCH v2 0/5] Introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PK Nikita Pettik
@ 2019-01-23 17:56 ` Nikita Pettik
  2019-01-24  8:36   ` [tarantool-patches] " Konstantin Osipov
  2019-01-29 19:29   ` Vladislav Shpilevoy
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 2/5] sql: rework ALTER TABLE grammar Nikita Pettik
                   ` (3 subsequent siblings)
  4 siblings, 2 replies; 34+ messages in thread
From: Nikita Pettik @ 2019-01-23 17:56 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

Parser's rules implementing DDL have a lot in common. For instance,
to drop any entity it is enough to know its name and name of table it is
related to.
Thus, it was suggested to arrange arguments of DDL rules into
hierarchical structure. The root of chain always includes name of table
to be altered: all existing entities are related to some table.  Then
comes one of drop/create/rename rules. Indeed, each DDL operation can be
classified in these terms, at least until we introduce ALTER TABLE ALTER
CONSTRAINT statement. Drop is represented by single structure (as it was
mentioned); rename can be applied only to table; create can be applied
to CONSTRAINT (indexes are considered as constraints) and TRIGGER, which
in turn are different in arguments required to create those objects. And
so forth.

What is more, we are going to introduce ALTER TABLE ADD CONSTRAINT
UNIQUE With new hierarchy we can extend ALTER TABLE statement with ease:
basic structures (alter -> create entity -> create constraint) are the
same for .. FOREIGN KEY/UNIQUE, but the last one will be different.

Note that grammar for CREATE TABLE statement is not trivial and consists
of wide range of sub-clauses (e.g. to parse foreign key or check
constraints). Therefore, parts of space definition are assembled
as soon as parser processes sub-rules. For this reason, current patch
doesn't affect CREATE TABLE handling.

Patch itself is made up of refactoring; no functional changes are
provided.

Needed for #3097
---
 src/box/sql/CMakeLists.txt   |   1 +
 src/box/sql/alter.c          |  10 ++-
 src/box/sql/build.c          | 146 ++++++++++++++++++------------
 src/box/sql/parse.y          | 209 ++++++++++++++++++++++++++++++++++++-------
 src/box/sql/parse_def.c      | 158 ++++++++++++++++++++++++++++++++
 src/box/sql/parse_def.h      | 170 +++++++++++++++++++++++++++++++++++
 src/box/sql/sqliteInt.h      |  70 +++------------
 src/box/sql/trigger.c        |  57 ++++++------
 test/sql-tap/index7.test.lua |   2 +-
 9 files changed, 647 insertions(+), 176 deletions(-)
 create mode 100644 src/box/sql/parse_def.c
 create mode 100644 src/box/sql/parse_def.h

diff --git a/src/box/sql/CMakeLists.txt b/src/box/sql/CMakeLists.txt
index 7f7b60e22..0ff311173 100644
--- a/src/box/sql/CMakeLists.txt
+++ b/src/box/sql/CMakeLists.txt
@@ -45,6 +45,7 @@ add_library(sql STATIC
     malloc.c
     os.c
     os_unix.c
+    parse_def.c
     pragma.c
     prepare.c
     printf.c
diff --git a/src/box/sql/alter.c b/src/box/sql/alter.c
index d0ce9d893..cf1164ab9 100644
--- a/src/box/sql/alter.c
+++ b/src/box/sql/alter.c
@@ -33,17 +33,19 @@
  * This file contains C code routines that used to generate VDBE code
  * that implements the ALTER TABLE command.
  */
-#include "sqliteInt.h"
+#include "parse_def.h"
 #include "box/box.h"
 #include "box/schema.h"
 
 void
-sql_alter_table_rename(struct Parse *parse, struct SrcList *src_tab,
-		       struct Token *new_name_tk)
+sql_alter_table_rename(struct Parse *parse)
 {
+	struct rename_entity_def *rename_def =
+		(struct rename_entity_def *) parse->alter_entity_def;
+	struct SrcList *src_tab = rename_def->base->entity_name;
 	assert(src_tab->nSrc == 1);
 	struct sqlite3 *db = parse->db;
-	char *new_name = sqlite3NameFromToken(db, new_name_tk);
+	char *new_name = sqlite3NameFromToken(db, &rename_def->new_name);
 	if (new_name == NULL)
 		goto exit_rename_table;
 	/* Check that new name isn't occupied by another table. */
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 49b90b5d0..d0e19407a 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -46,6 +46,7 @@
 #include "sqliteInt.h"
 #include "vdbeInt.h"
 #include "tarantoolInt.h"
+#include "parse_def.h"
 #include "box/box.h"
 #include "box/fkey.h"
 #include "box/sequence.h"
@@ -566,7 +567,6 @@ sqlite3AddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
 	column_def->affinity = type_def->type;
 	column_def->type = sql_affinity_to_field_type(column_def->affinity);
 	p->def->field_count++;
-	pParse->constraintName.n = 0;
 }
 
 void
@@ -713,6 +713,16 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 			}
 		}
 	}
+	struct alter_entity_def alter_def = {};
+	struct create_entity_def create_def = {};
+	struct create_constraint_def constr_def = {};
+	struct create_index_def idx_def = {};
+	create_def.base = &alter_def;
+	constr_def.base = &create_def;
+	idx_def.base = &constr_def;
+	idx_def.idx_type = SQL_INDEX_TYPE_CONSTRAINT_PK;
+	idx_def.sort_order = sortOrder;
+	pParse->alter_entity_def = (void *) &idx_def;
 	if (nTerm == 1 && iCol != -1 &&
 	    pTab->def->fields[iCol].type == FIELD_TYPE_INTEGER &&
 	    sortOrder != SORT_ORDER_DESC) {
@@ -727,8 +737,8 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 							     &token, 0));
 		if (list == NULL)
 			goto primary_key_exit;
-		sql_create_index(pParse, 0, 0, list, 0, SORT_ORDER_ASC,
-				 false, SQL_INDEX_TYPE_CONSTRAINT_PK);
+		idx_def.cols = list;
+		sql_create_index(pParse);
 		if (db->mallocFailed)
 			goto primary_key_exit;
 	} else if (autoInc) {
@@ -736,8 +746,8 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 				"INTEGER PRIMARY KEY or INT PRIMARY KEY");
 		goto primary_key_exit;
 	} else {
-		sql_create_index(pParse, 0, 0, pList, 0, sortOrder, false,
-				 SQL_INDEX_TYPE_CONSTRAINT_PK);
+		idx_def.cols = pList;
+		sql_create_index(pParse);
 		pList = 0;
 		if (pParse->nErr > 0)
 			goto primary_key_exit;
@@ -774,9 +784,12 @@ sql_add_check_constraint(struct Parse *parser, struct ExprSpan *span)
 			sqlite3DbFree(parser->db, expr->u.zToken);
 			goto release_expr;
 		}
-		if (parser->constraintName.n) {
+		struct create_constraint_def *constr_def =
+			(struct create_constraint_def *) parser->alter_entity_def;
+		struct create_entity_def *entity_def = constr_def->base;
+		if (entity_def->name.n > 0) {
 			sqlite3ExprListSetName(parser, table->def->opts.checks,
-					       &parser->constraintName, 1);
+					       &entity_def->name, 1);
 		}
 	} else {
 release_expr:
@@ -1695,14 +1708,14 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space,
  * This routine is called to do the work of a DROP TABLE statement.
  *
  * @param parse_context Current parsing context.
- * @param table_name_list List containing table name.
  * @param is_view True, if statement is really 'DROP VIEW'.
- * @param if_exists True, if statement contains 'IF EXISTS' clause.
  */
 void
-sql_drop_table(struct Parse *parse_context, struct SrcList *table_name_list,
-	       bool is_view, bool if_exists)
+sql_drop_table(struct Parse *parse_context, bool is_view)
 {
+	struct drop_entity_def *drop_def = parse_context->alter_entity_def;
+	struct alter_entity_def *alter_def = drop_def->base;
+	struct SrcList *table_name_list = alter_def->entity_name;
 	struct Vdbe *v = sqlite3GetVdbe(parse_context);
 	struct sqlite3 *db = parse_context->db;
 	if (v == NULL || db->mallocFailed) {
@@ -1714,10 +1727,10 @@ sql_drop_table(struct Parse *parse_context, struct SrcList *table_name_list,
 	const char *space_name = table_name_list->a[0].zName;
 	struct space *space = space_by_name(space_name);
 	if (space == NULL) {
-		if (!is_view && !if_exists)
+		if (!is_view && !drop_def->if_exist)
 			sqlite3ErrorMsg(parse_context, "no such table: %s",
 					space_name);
-		if (is_view && !if_exists)
+		if (is_view && !drop_def->if_exist)
 			sqlite3ErrorMsg(parse_context, "no such view: %s",
 					space_name);
 		goto exit_drop_table;
@@ -1800,12 +1813,14 @@ columnno_by_name(struct Parse *parse_context, const struct space *space,
 }
 
 void
-sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
-		       struct Token *constraint, struct ExprList *child_cols,
-		       struct Token *parent, struct ExprList *parent_cols,
-		       bool is_deferred, int actions)
+sql_create_foreign_key(struct Parse *parse_context)
 {
 	struct sqlite3 *db = parse_context->db;
+	struct create_fk_def *create_fk_def =
+		(struct create_fk_def *) parse_context->alter_entity_def;
+	struct create_constraint_def *create_constr_def = create_fk_def->base;
+	struct create_entity_def *create_def = create_constr_def->base;
+	struct alter_entity_def *alter_def = create_def->base;
 	/*
 	 * When this function is called second time during
 	 * <CREATE TABLE ...> statement (i.e. at VDBE runtime),
@@ -1828,16 +1843,17 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 	/* Whether we are processing ALTER TABLE or CREATE TABLE. */
 	bool is_alter = new_tab == NULL;
 	uint32_t child_cols_count;
+	struct ExprList *child_cols = create_fk_def->child_cols;
 	if (child_cols == NULL) {
 		assert(!is_alter);
 		child_cols_count = 1;
 	} else {
 		child_cols_count = child_cols->nExpr;
 	}
-	assert(!is_alter || (child != NULL && child->nSrc == 1));
 	struct space *child_space = NULL;
 	if (is_alter) {
-		const char *child_name = child->a[0].zName;
+		const char *child_name =
+			alter_def->entity_name->a[0].zName;
 		child_space = space_by_name(child_name);
 		if (child_space == NULL) {
 			diag_set(ClientError, ER_NO_SUCH_SPACE, child_name);
@@ -1854,6 +1870,7 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 		memset(fk, 0, sizeof(*fk));
 		rlist_add_entry(&parse_context->new_fkey, fk, link);
 	}
+	struct Token *parent = create_fk_def->parent_name;
 	assert(parent != NULL);
 	parent_name = sqlite3NameFromToken(db, parent);
 	if (parent_name == NULL)
@@ -1865,6 +1882,7 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 	 */
 	is_self_referenced = !is_alter &&
 			     strcmp(parent_name, new_tab->def->name) == 0;
+	struct ExprList *parent_cols = create_fk_def->parent_cols;
 	struct space *parent_space = space_by_name(parent_name);
 	if (parent_space == NULL) {
 		if (is_self_referenced) {
@@ -1884,18 +1902,19 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 			goto exit_create_fk;
 		}
 	}
-	if (constraint == NULL && !is_alter) {
-		if (parse_context->constraintName.n == 0) {
+	if (!is_alter) {
+		if (create_def->name.n == 0) {
 			constraint_name =
 				sqlite3MPrintf(db, "FK_CONSTRAINT_%d_%s",
 					       ++parse_context->fkey_count,
 					       new_tab->def->name);
 		} else {
-			struct Token *cnstr_nm = &parse_context->constraintName;
-			constraint_name = sqlite3NameFromToken(db, cnstr_nm);
+			constraint_name =
+				sqlite3NameFromToken(db, &create_def->name);
 		}
 	} else {
-		constraint_name = sqlite3NameFromToken(db, constraint);
+		constraint_name =
+			sqlite3NameFromToken(db, &create_def->name);
 	}
 	if (constraint_name == NULL)
 		goto exit_create_fk;
@@ -1928,10 +1947,11 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 		diag_set(OutOfMemory, fk_size, "region", "struct fkey");
 		goto tnt_error;
 	}
+	int actions = create_fk_def->actions;
 	fk->field_count = child_cols_count;
 	fk->child_id = child_space != NULL ? child_space->def->id : 0;
 	fk->parent_id = parent_space != NULL ? parent_space->def->id : 0;
-	fk->is_deferred = is_deferred;
+	fk->is_deferred = create_constr_def->is_deferred;
 	fk->match = (enum fkey_match) ((actions >> 16) & 0xff);
 	fk->on_update = (enum fkey_action) ((actions >> 8) & 0xff);
 	fk->on_delete = (enum fkey_action) (actions & 0xff);
@@ -2016,11 +2036,13 @@ fkey_change_defer_mode(struct Parse *parse_context, bool is_deferred)
 }
 
 void
-sql_drop_foreign_key(struct Parse *parse_context, struct SrcList *table,
-		     struct Token *constraint)
+sql_drop_foreign_key(struct Parse *parse_context)
 {
-	assert(table != NULL && table->nSrc == 1);
-	const char *table_name = table->a[0].zName;
+	struct drop_entity_def *drop_def =
+		(struct drop_entity_def *) parse_context->alter_entity_def;
+	struct alter_entity_def *alter_def = drop_def->base;
+	const char *table_name = alter_def->entity_name->a[0].zName;
+	assert(table_name != NULL);
 	struct space *child = space_by_name(table_name);
 	if (child == NULL) {
 		diag_set(ClientError, ER_NO_SUCH_SPACE, table_name);
@@ -2029,7 +2051,7 @@ sql_drop_foreign_key(struct Parse *parse_context, struct SrcList *table,
 		return;
 	}
 	char *constraint_name = sqlite3NameFromToken(parse_context->db,
-						     constraint);
+						     &drop_def->name);
 	if (constraint_name != NULL)
 		vdbe_emit_fkey_drop(parse_context, constraint_name,
 				    child->def->id);
@@ -2223,19 +2245,29 @@ constraint_is_named(const char *name)
 }
 
 void
-sql_create_index(struct Parse *parse, struct Token *token,
-		 struct SrcList *tbl_name, struct ExprList *col_list,
-		 MAYBE_UNUSED struct Token *start, enum sort_order sort_order,
-		 bool if_not_exist, enum sql_index_type idx_type) {
+sql_create_index(struct Parse *parse) {
 	/* The index to be created. */
 	struct index *index = NULL;
 	/* Name of the index. */
 	char *name = NULL;
 	struct sqlite3 *db = parse->db;
 	assert(!db->init.busy);
+	struct create_index_def *create_idx_def =
+		(struct create_index_def *) parse->alter_entity_def;
+	struct create_constraint_def *create_constr_def = create_idx_def->base;
+	struct create_entity_def *create_entity_def = create_constr_def->base;
+	struct alter_entity_def *alter_entity_def = create_entity_def->base;
+	/*
+	 * Get list of columns to be indexed. It will be NULL if
+	 * this is a primary key or unique-constraint on the most
+	 * recent column added to the table under construction.
+	 */
+	struct ExprList *col_list = create_idx_def->cols;
+	struct SrcList *tbl_name = alter_entity_def->entity_name;
 
 	if (db->mallocFailed || parse->nErr > 0)
 		goto exit_create_index;
+	enum sql_index_type idx_type = create_idx_def->idx_type;
 	if (idx_type == SQL_INDEX_TYPE_UNIQUE ||
 	    idx_type == SQL_INDEX_TYPE_NON_UNIQUE) {
 		Vdbe *v = sqlite3GetVdbe(parse);
@@ -2250,12 +2282,13 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 */
 	struct space *space = NULL;
 	struct space_def *def = NULL;
+	struct Token token = create_entity_def->name;
 	if (tbl_name != NULL) {
-		assert(token != NULL && token->z != NULL);
+		assert(token.n > 0 && token.z != NULL);
 		const char *name = tbl_name->a[0].zName;
 		space = space_by_name(name);
 		if (space == NULL) {
-			if (! if_not_exist) {
+			if (! create_entity_def->if_not_exist) {
 				diag_set(ClientError, ER_NO_SUCH_SPACE, name);
 				parse->rc = SQL_TARANTOOL_ERROR;
 				parse->nErr++;
@@ -2266,8 +2299,6 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	} else {
 		if (parse->pNewTable == NULL)
 			goto exit_create_index;
-		assert(token == NULL);
-		assert(start == NULL);
 		space = parse->pNewTable->space;
 		def = parse->pNewTable->def;
 	}
@@ -2296,13 +2327,13 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 * 2) UNIQUE constraint is non-named and standard
 	 *    auto-index name will be generated.
 	 */
-	if (token != NULL) {
-		assert(token->z != NULL);
-		name = sqlite3NameFromToken(db, token);
+	if (parse->pNewTable == NULL) {
+		assert(token.z != NULL);
+		name = sqlite3NameFromToken(db, &token);
 		if (name == NULL)
 			goto exit_create_index;
 		if (sql_space_index_by_name(space, name) != NULL) {
-			if (!if_not_exist) {
+			if (! create_entity_def->if_not_exist) {
 				sqlite3ErrorMsg(parse,
 						"index %s.%s already exists",
 						def->name, name);
@@ -2311,11 +2342,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		}
 	} else {
 		char *constraint_name = NULL;
-		if (parse->constraintName.z != NULL)
+		if (create_entity_def->name.n > 0)
 			constraint_name =
 				sqlite3NameFromToken(db,
-						     &parse->constraintName);
-
+						     &create_entity_def->name);
 	       /*
 		* This naming is temporary. Now it's not
 		* possible (since we implement UNIQUE
@@ -2373,7 +2403,8 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		if (col_list == NULL)
 			goto exit_create_index;
 		assert(col_list->nExpr == 1);
-		sqlite3ExprListSetSortOrder(col_list, sort_order);
+		sqlite3ExprListSetSortOrder(col_list,
+					    create_idx_def->sort_order);
 	} else {
 		sqlite3ExprListCheckLength(parse, col_list, "index");
 	}
@@ -2521,8 +2552,6 @@ sql_create_index(struct Parse *parse, struct Token *token,
 				  (void *)space_by_id(BOX_INDEX_ID),
 				  P4_SPACEPTR);
 		sqlite3VdbeChangeP5(vdbe, OPFLAG_SEEKEQ);
-
-		assert(start != NULL);
 		int index_id = getNewIid(parse, def->id, cursor);
 		sqlite3VdbeAddOp1(vdbe, OP_Close, cursor);
 		vdbe_emit_create_index(parse, def, index->def,
@@ -2546,30 +2575,33 @@ sql_create_index(struct Parse *parse, struct Token *token,
 }
 
 void
-sql_drop_index(struct Parse *parse_context, struct SrcList *index_name_list,
-	       struct Token *table_token, bool if_exists)
+sql_drop_index(struct Parse *parse_context)
 {
+	struct drop_entity_def *drop_def =
+		(struct drop_entity_def *) parse_context->alter_entity_def;
+	struct alter_entity_def *alter_def = drop_def->base;
 	struct Vdbe *v = sqlite3GetVdbe(parse_context);
 	assert(v != NULL);
 	struct sqlite3 *db = parse_context->db;
 	/* Never called with prior errors. */
 	assert(parse_context->nErr == 0);
-	assert(table_token != NULL);
-	const char *table_name = sqlite3NameFromToken(db, table_token);
+	struct SrcList *table_list = alter_def->entity_name;
+	assert(table_list->nSrc == 1);
+	char *table_name = table_list->a[0].zName;
+	const char *index_name = NULL;
 	if (db->mallocFailed) {
 		goto exit_drop_index;
 	}
 	sqlite3VdbeCountChanges(v);
-	assert(index_name_list->nSrc == 1);
-	assert(table_token->n > 0);
 	struct space *space = space_by_name(table_name);
+	bool if_exists = drop_def->if_exist;
 	if (space == NULL) {
 		if (!if_exists)
 			sqlite3ErrorMsg(parse_context, "no such space: %s",
 					table_name);
 		goto exit_drop_index;
 	}
-	const char *index_name = index_name_list->a[0].zName;
+	index_name = sqlite3NameFromToken(db, &drop_def->name);
 	uint32_t index_id = box_index_id_by_name(space->def->id, index_name,
 						 strlen(index_name));
 	if (index_id == BOX_ID_NIL) {
@@ -2595,8 +2627,8 @@ sql_drop_index(struct Parse *parse_context, struct SrcList *index_name_list,
 	sqlite3VdbeAddOp2(v, OP_SDelete, BOX_INDEX_ID, record_reg);
 	sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
  exit_drop_index:
-	sqlite3SrcListDelete(db, index_name_list);
-	sqlite3DbFree(db, (void *) table_name);
+	sqlite3SrcListDelete(db, table_list);
+	sqlite3DbFree(db, (void *) index_name);
 }
 
 /*
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 50bb2ba01..b0327c27a 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -52,6 +52,7 @@
 %include {
 #include "sqliteInt.h"
 #include "box/fkey.h"
+#include "parse_def.h"
 
 /*
 ** Disable all error recovery processing in the parser push-down
@@ -237,8 +238,36 @@ nm(A) ::= id(A). {
 carglist ::= carglist cconsdef.
 carglist ::= .
 cconsdef ::= cconsname ccons.
-cconsname ::= CONSTRAINT nm(X).           {pParse->constraintName = X;}
-cconsname ::= .                           {pParse->constraintName.n = 0;}
+cconsname ::= cconsname_start cconsname_parse .
+cconsname_start ::= . {
+  struct alter_entity_def *alter_def =
+    alter_entity_def_new(pParse, NULL);
+  if (alter_def == NULL)
+    break;
+  struct create_entity_def *create_def =
+    create_entity_def_new(pParse, alter_def, (struct Token){}, false);
+  if (create_def == NULL)
+    break;
+  pParse->alter_entity_def = (void *) create_def;
+}
+cconsname_parse ::= CONSTRAINT nm(X). {
+   struct create_entity_def *create_def = pParse->alter_entity_def;
+   create_def->name = X;
+   struct create_constraint_def *constr_def =
+     create_constraint_def_new(pParse, create_def, false);
+   if (constr_def == NULL)
+     break;
+   constr_def->base = create_def;
+   pParse->alter_entity_def = constr_def;
+}
+cconsname_parse ::= . {
+  struct create_entity_def *create_def = pParse->alter_entity_def;
+  struct create_constraint_def *constr_def =
+    create_constraint_def_new(pParse, create_def, false);
+  if (constr_def == NULL)
+    break;
+  pParse->alter_entity_def = constr_def;
+}
 ccons ::= DEFAULT term(X).            {sqlite3AddDefaultValue(pParse,&X);}
 ccons ::= DEFAULT LP expr(X) RP.      {sqlite3AddDefaultValue(pParse,&X);}
 ccons ::= DEFAULT PLUS term(X).       {sqlite3AddDefaultValue(pParse,&X);}
@@ -262,12 +291,26 @@ ccons ::= NULL onconf(R).        {
 ccons ::= NOT NULL onconf(R).    {sql_column_add_nullable_action(pParse, R);}
 ccons ::= PRIMARY KEY sortorder(Z) autoinc(I).
                                  {sqlite3AddPrimaryKey(pParse,0,I,Z);}
-ccons ::= UNIQUE.                {sql_create_index(pParse,0,0,0,0,
-                                                   SORT_ORDER_ASC, false,
-                                                   SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
+ccons ::= UNIQUE. {
+  struct create_constraint_def *constr_def = pParse->alter_entity_def;
+  struct create_index_def *idx_def =
+    create_index_def_new(pParse, constr_def, NULL,
+                         SQL_INDEX_TYPE_CONSTRAINT_UNIQUE, SORT_ORDER_ASC);
+  if (idx_def == NULL)
+    break;
+  pParse->alter_entity_def = (void *) idx_def;
+  sql_create_index(pParse);
+}
 ccons ::= CHECK LP expr(X) RP.   {sql_add_check_constraint(pParse,&X);}
-ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R).
-                                 {sql_create_foreign_key(pParse, NULL, NULL, NULL, &T, TA, false, R);}
+ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R). {
+ struct create_constraint_def *constr_def = pParse->alter_entity_def;
+  struct create_fk_def *fk_def =
+    create_fk_def_new(pParse, constr_def, NULL, &T, TA, R);
+  if (fk_def == NULL)
+    break;
+  pParse->alter_entity_def = (void *) fk_def;
+  sql_create_foreign_key(pParse);
+}
 ccons ::= defer_subclause(D).    {fkey_change_defer_mode(pParse, D);}
 ccons ::= COLLATE id(C).        {sqlite3AddCollateType(pParse, &C);}
 
@@ -307,20 +350,31 @@ init_deferred_pred_opt(A) ::= .                       {A = 0;}
 init_deferred_pred_opt(A) ::= INITIALLY DEFERRED.     {A = 1;}
 init_deferred_pred_opt(A) ::= INITIALLY IMMEDIATE.    {A = 0;}
 
-tconsdef ::= tconsname tcons.
-tconsname ::= CONSTRAINT nm(X).      {pParse->constraintName = X;}
-tconsname ::= .                      {pParse->constraintName.n = 0;}
+tconsdef ::= cconsname tcons.
 tcons ::= PRIMARY KEY LP sortlist(X) autoinc(I) RP.
                                  {sqlite3AddPrimaryKey(pParse,X,I,0);}
-tcons ::= UNIQUE LP sortlist(X) RP.
-                                 {sql_create_index(pParse,0,0,X,0,
-                                                   SORT_ORDER_ASC,false,
-                                                   SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
+tcons ::= UNIQUE LP sortlist(X) RP. {
+  struct create_constraint_def *constr_def = pParse->alter_entity_def;
+  struct create_index_def *idx_def =
+    create_index_def_new(pParse, constr_def, X,
+                         SQL_INDEX_TYPE_CONSTRAINT_UNIQUE, SORT_ORDER_ASC);
+  if (idx_def == NULL)
+    break;
+  pParse->alter_entity_def = (void *) idx_def;
+  sql_create_index(pParse);
+}
 tcons ::= CHECK LP expr(E) RP onconf.
                                  {sql_add_check_constraint(pParse,&E);}
 tcons ::= FOREIGN KEY LP eidlist(FA) RP
           REFERENCES nm(T) eidlist_opt(TA) refargs(R) defer_subclause_opt(D). {
-    sql_create_foreign_key(pParse, NULL, NULL, FA, &T, TA, D, R);
+  struct create_constraint_def *constr_def = pParse->alter_entity_def;
+  constr_def->is_deferred = D;
+  struct create_fk_def *fk_def =
+    create_fk_def_new(pParse, constr_def, FA, &T, TA, R);
+  if (fk_def == NULL)
+    break;
+  pParse->alter_entity_def = (void *) fk_def;
+  sql_create_foreign_key(pParse);
 }
 %type defer_subclause_opt {int}
 defer_subclause_opt(A) ::= .                    {A = 0;}
@@ -343,9 +397,27 @@ resolvetype(A) ::= REPLACE.                  {A = ON_CONFLICT_ACTION_REPLACE;}
 
 ////////////////////////// The DROP TABLE /////////////////////////////////////
 //
-cmd ::= DROP TABLE ifexists(E) fullname(X). {
-  sql_drop_table(pParse, X, 0, E);
+
+cmd ::= drop_start(X) drop_table . {
+  sql_drop_table(pParse, X);
 }
+
+drop_table ::= ifexists(E) fullname(X) . {
+  struct alter_entity_def *alter_def =
+    alter_entity_def_new(pParse, X);
+  if (alter_def == NULL)
+    break;
+  struct drop_entity_def *drop_def =
+    drop_entity_def_new(pParse, alter_def, (struct Token){}, E);
+  if (drop_def == NULL)
+    break;
+  pParse->alter_entity_def = (void *) drop_def;
+}
+
+%type drop_start {bool}
+drop_start(X) ::= DROP VIEW . { X = true; }
+drop_start(X) ::= DROP TABLE . { X = false; }
+
 %type ifexists {int}
 ifexists(A) ::= IF EXISTS.   {A = 1;}
 ifexists(A) ::= .            {A = 0;}
@@ -359,9 +431,6 @@ cmd ::= createkw(X) VIEW ifnotexists(E) nm(Y) eidlist_opt(C)
   else
     sql_store_select(pParse, S);
 }
-cmd ::= DROP VIEW ifexists(E) fullname(X). {
-  sql_drop_table(pParse, X, 1, E);
-}
 
 //////////////////////// The SELECT statement /////////////////////////////////
 //
@@ -1209,10 +1278,22 @@ paren_exprlist(A) ::= LP exprlist(X) RP.  {A = X;}
 
 ///////////////////////////// The CREATE INDEX command ///////////////////////
 //
-cmd ::= createkw(S) uniqueflag(U) INDEX ifnotexists(NE) nm(X)
+cmd ::= createkw uniqueflag(U) INDEX ifnotexists(NE) nm(X)
         ON nm(Y) LP sortlist(Z) RP. {
-  sql_create_index(pParse, &X, sqlite3SrcListAppend(pParse->db,0,&Y), Z, &S,
-                   SORT_ORDER_ASC, NE, U);
+  struct alter_entity_def alter_def = {};
+  struct create_entity_def create_def = {};
+  struct create_constraint_def constr_def = {};
+  struct create_index_def idx_def = {};
+  alter_def.entity_name = sqlite3SrcListAppend(pParse->db,0,&Y);
+  create_def.base = &alter_def;
+  create_def.name = X;
+  create_def.if_not_exist = NE;
+  constr_def.base = &create_def;
+  idx_def.base = &constr_def;
+  idx_def.cols = Z;
+  idx_def.idx_type = U;
+  pParse->alter_entity_def = (void *) &idx_def;
+  sql_create_index(pParse);
 }
 
 %type uniqueflag {int}
@@ -1274,8 +1355,15 @@ collate(C) ::= COLLATE id.   {C = 1;}
 
 ///////////////////////////// The DROP INDEX command /////////////////////////
 //
-cmd ::= DROP INDEX ifexists(E) fullname(X) ON nm(Y).   {
-    sql_drop_index(pParse, X, &Y, E);
+cmd ::= DROP INDEX ifexists(E) nm(X) ON fullname(Y).   {
+  struct alter_entity_def alter_def = {};
+  struct drop_entity_def drop_def = {};
+  alter_def.entity_name = Y;
+  drop_def.base = &alter_def;
+  drop_def.if_exist = E;
+  drop_def.name = X;
+  pParse->alter_entity_def = (void *) &drop_def;
+  sql_drop_index(pParse);
 }
 
 ///////////////////////////// The PRAGMA command /////////////////////////////
@@ -1326,7 +1414,20 @@ cmd ::= createkw trigger_decl(A) BEGIN trigger_cmd_list(S) END(Z). {
 trigger_decl(A) ::= TRIGGER ifnotexists(NOERR) nm(B)
                     trigger_time(C) trigger_event(D)
                     ON fullname(E) foreach_clause when_clause(G). {
-  sql_trigger_begin(pParse, &B, C, D.a, D.b, E, G, NOERR);
+  struct alter_entity_def alter_def = {};
+  struct create_entity_def create_def = {};
+  struct create_trigger_def trigger_def = {};
+  alter_def.entity_name = E;
+  create_def.base = &alter_def;
+  create_def.name = B;
+  create_def.if_not_exist = NOERR;
+  trigger_def.base = &create_def;
+  trigger_def.tr_tm = C;
+  trigger_def.op = D.a;
+  trigger_def.cols = D.b;
+  trigger_def.when = G;
+  pParse->alter_entity_def = (void *) &trigger_def;
+  sql_trigger_begin(pParse);
   A = B; /*A-overwrites-T*/
 }
 
@@ -1436,7 +1537,13 @@ raisetype(A) ::= FAIL.      {A = ON_CONFLICT_ACTION_FAIL;}
 
 ////////////////////////  DROP TRIGGER statement //////////////////////////////
 cmd ::= DROP TRIGGER ifexists(NOERR) fullname(X). {
-  sql_drop_trigger(pParse,X,NOERR);
+  struct alter_entity_def alter_def = {};
+  struct drop_entity_def drop_def = {};
+  alter_def.entity_name = X;
+  drop_def.base = &alter_def;
+  drop_def.if_exist = NOERR;
+  pParse->alter_entity_def = (void *) &drop_def;
+  sql_drop_trigger(pParse);
 }
 
 /////////////////////////////////// ANALYZE ///////////////////////////////////
@@ -1445,17 +1552,59 @@ cmd ::= ANALYZE nm(X).          {sqlite3Analyze(pParse, &X);}
 
 //////////////////////// ALTER TABLE table ... ////////////////////////////////
 cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). {
-  sql_alter_table_rename(pParse,X,&Z);
+  struct alter_entity_def *alter_def =
+    alter_entity_def_new(pParse, X);
+  /*
+   * After code preprocessing, this code snippet will get to
+   * one of "switch" cases. Hence, break is enough to gently
+   * terminate it. No clean-ups are required since all structs
+   * are allocated on region. OOM error is set inside def
+   * constructors.
+   */
+  if (alter_def == NULL)
+    break;
+  struct rename_entity_def *rename_def =
+    rename_entity_def_new(pParse, alter_def, Z);
+  if (rename_def == NULL)
+    break;
+  pParse->alter_entity_def = (void *) rename_def;
+  sql_alter_table_rename(pParse);
 }
 
 cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT nm(Z) FOREIGN KEY
         LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) refargs(R)
         defer_subclause_opt(D). {
-    sql_create_foreign_key(pParse, X, &Z, FA, &T, TA, D, R);
+  struct alter_entity_def *alter_def =
+    alter_entity_def_new(pParse, X);
+  if (alter_def == NULL)
+    break;
+  struct create_entity_def *create_def =
+    create_entity_def_new(pParse, alter_def, Z, false);
+  if (create_def == NULL)
+    break;
+  struct create_constraint_def *constraint_def =
+    create_constraint_def_new(pParse, create_def, D);
+  if (constraint_def == NULL)
+    break;
+  struct create_fk_def *fk_def =
+    create_fk_def_new(pParse, constraint_def, FA, &T, TA, R);
+  if (fk_def == NULL)
+    break;
+  pParse->alter_entity_def = (void *) fk_def;
+  sql_create_foreign_key(pParse);
 }
 
 cmd ::= ALTER TABLE fullname(X) DROP CONSTRAINT nm(Z). {
-    sql_drop_foreign_key(pParse, X, &Z);
+  struct alter_entity_def *alter_def =
+    alter_entity_def_new(pParse, X);
+  if (alter_def == NULL)
+    break;
+  struct drop_entity_def *drop_def =
+    drop_entity_def_new(pParse, alter_def, Z, false);
+  if (drop_def == NULL)
+    break;
+  pParse->alter_entity_def = (void *) drop_def;
+  sql_drop_foreign_key(pParse);
 }
 
 //////////////////////// COMMON TABLE EXPRESSIONS ////////////////////////////
diff --git a/src/box/sql/parse_def.c b/src/box/sql/parse_def.c
new file mode 100644
index 000000000..7f241636a
--- /dev/null
+++ b/src/box/sql/parse_def.c
@@ -0,0 +1,158 @@
+/*
+ * 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 "parse_def.h"
+
+struct alter_entity_def *
+alter_entity_def_new(struct Parse *parse, struct SrcList *name)
+{
+	size_t sz = sizeof(struct alter_entity_def);
+	struct alter_entity_def *alter_def = region_alloc(&parse->region, sz);
+	if (alter_def == NULL) {
+		diag_set(OutOfMemory, sz, "region", "alter_def");
+		parse->rc = SQL_TARANTOOL_ERROR;
+		parse->nErr++;
+		return NULL;
+	}
+	alter_def->entity_name = name;
+	return alter_def;
+}
+
+struct rename_entity_def *
+rename_entity_def_new(struct Parse *parse, struct alter_entity_def *base,
+		      struct Token new_name)
+{
+	size_t sz = sizeof(struct rename_entity_def);
+	struct rename_entity_def *rename_def = region_alloc(&parse->region, sz);
+	if (rename_def == NULL) {
+		diag_set(OutOfMemory, sz, "region", "rename_def");
+		parse->rc = SQL_TARANTOOL_ERROR;
+		parse->nErr++;
+		return NULL;
+	}
+	rename_def->base = base;
+	rename_def->new_name = new_name;
+	return rename_def;
+}
+
+struct create_entity_def *
+create_entity_def_new(struct Parse *parse, struct alter_entity_def *base,
+		      struct Token name, bool if_not_exists)
+{
+	size_t sz = sizeof(struct create_entity_def);
+	struct create_entity_def *create_def = region_alloc(&parse->region, sz);
+	if (create_def == NULL) {
+		diag_set(OutOfMemory, sz, "region", "create_def");
+		parse->rc = SQL_TARANTOOL_ERROR;
+		parse->nErr++;
+		return NULL;
+	}
+	create_def->base = base;
+	create_def->name = name;
+	create_def->if_not_exist = if_not_exists;
+	return create_def;
+}
+
+struct drop_entity_def *
+drop_entity_def_new(struct Parse *parse, struct alter_entity_def *base,
+		    struct Token entity_name, bool if_exist)
+{
+	size_t sz = sizeof(struct drop_entity_def);
+	struct drop_entity_def *drop_def = region_alloc(&parse->region, sz);
+	if (drop_def == NULL) {
+		diag_set(OutOfMemory, sz, "region", "drop_def");
+		parse->rc = SQL_TARANTOOL_ERROR;
+		parse->nErr++;
+		return NULL;
+	}
+	drop_def->base = base;
+	drop_def->name = entity_name;
+	drop_def->if_exist = if_exist;
+	return drop_def;
+}
+
+struct create_constraint_def *
+create_constraint_def_new(struct Parse *parse, struct create_entity_def *base,
+			  bool is_deferred)
+{
+	size_t sz = sizeof(struct create_constraint_def);
+	struct create_constraint_def *constr_def =
+		region_alloc(&parse->region, sz);
+	if (constr_def == NULL) {
+		diag_set(OutOfMemory, sz, "region", "constr_def");
+		parse->rc = SQL_TARANTOOL_ERROR;
+		parse->nErr++;
+		return NULL;
+	}
+	constr_def->base = base;
+	constr_def->is_deferred = is_deferred;
+	return constr_def;
+}
+
+struct create_fk_def *
+create_fk_def_new(struct Parse *parse, struct create_constraint_def *base,
+		  struct ExprList *child_cols, struct Token *parent_name,
+		  struct ExprList *parent_cols, int actions)
+{
+	size_t sz = sizeof(struct create_fk_def);
+	struct create_fk_def *fk_def = region_alloc(&parse->region, sz);
+	if (fk_def == NULL) {
+		diag_set(OutOfMemory, sz, "region", "fk_def");
+		parse->rc = SQL_TARANTOOL_ERROR;
+		parse->nErr++;
+		return NULL;
+	}
+	fk_def->base = base;
+	fk_def->child_cols = child_cols;
+	fk_def->parent_name = parent_name;
+	fk_def->parent_cols = parent_cols;
+	fk_def->actions = actions;
+	return fk_def;
+}
+
+struct create_index_def *
+create_index_def_new(struct Parse *parse, struct create_constraint_def *base,
+		     struct ExprList *cols, enum sql_index_type idx_type,
+		     enum sort_order sort_order)
+{
+	size_t sz = sizeof(struct create_index_def);
+	struct create_index_def *idx_def = region_alloc(&parse->region, sz);
+	if (idx_def == NULL) {
+		diag_set(OutOfMemory, sz, "region", "idx_def");
+		parse->rc = SQL_TARANTOOL_ERROR;
+		parse->nErr++;
+		return NULL;
+	}
+	idx_def->base = base;
+	idx_def->cols = cols;
+	idx_def->idx_type = idx_type;
+	idx_def->sort_order = sort_order;
+	return idx_def;
+}
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
new file mode 100644
index 000000000..2f6d3d047
--- /dev/null
+++ b/src/box/sql/parse_def.h
@@ -0,0 +1,170 @@
+/*
+ * 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 "sqliteInt.h"
+#include "box/fkey.h"
+
+/**
+ * This file contains auxiliary structures and functions which
+ * are used only during parsing routine (see parse.y).
+ * Their main purpose is to assemble common parts of altered
+ * entities (such as name, or IF EXISTS clause) and pass them
+ * as a one object to further functions.
+ *
+ * Hierarchy is following:
+ *
+ * Base structure is ALTER.
+ * ALTER is omitted only for CREATE TABLE since table is filled
+ * with meta-information just-in-time of parsing:
+ * for instance, as soon as field's name and type are recognized
+ * they are added to space definition.
+ *
+ * DROP is general for all existing objects and includes
+ * name of object itself, name of parent object (table),
+ * IF EXISTS clause and may contain on-drop behaviour
+ * (CASCADE/RESTRICT, but now it is always RESTRICT).
+ * Hence, it terms of grammar - it is a terminal symbol.
+ *
+ * RENAME can be applied only to table (at least now, since it is
+ * ANSI extension), so it is also terminal symbol.
+ *
+ * CREATE in turn can be expanded to nonterminal symbol
+ * CREATE CONSTRAINT or to terminal CREATE TABLE/INDEX/TRIGGER.
+ * CREATE CONSTRAINT unfolds to FOREIGN KEY or UNIQUE/PRIMARY KEY.
+ *
+ * For instance:
+ * ALTER TABLE t ADD CONSTRAINT c FOREIGN KEY REFERENCES t2(id);
+ * ALTER *TABLE* -> CREATE ENTITY -> CREATE CONSTRAINT -> CREATE FK
+ *
+ * CREATE TRIGGER tr1 ...
+ * ALTER *TABLE* -> CREATE ENTITY -> CREATE TRIGGER
+ */
+struct alter_entity_def {
+	/** As a rule it is a name of table to be altered. */
+	struct SrcList *entity_name;
+};
+
+struct rename_entity_def {
+	struct alter_entity_def *base;
+	struct Token new_name;
+};
+
+struct create_entity_def {
+	struct alter_entity_def *base;
+	struct Token name;
+	/** Statement comes with IF NOT EXISTS clause. */
+	bool if_not_exist;
+};
+
+struct drop_entity_def {
+	struct alter_entity_def *base;
+	/** Name of index/trigger/constraint to be dropped. */
+	struct Token name;
+	/** Statement comes with IF EXISTS clause. */
+	bool if_exist;
+};
+
+struct create_trigger_def {
+	struct create_entity_def *base;
+	/** One of TK_BEFORE, TK_AFTER, TK_INSTEAD. */
+	int tr_tm;
+	/** One of TK_INSERT, TK_UPDATE, TK_DELETE. */
+	int op;
+	/** Column list if this is an UPDATE trigger. */
+	struct IdList *cols;
+	/** When clause. */
+	struct Expr *when;
+};
+
+struct create_constraint_def {
+	struct create_entity_def *base;
+	/** One of DEFERRED, IMMEDIATE. */
+	bool is_deferred;
+};
+
+struct create_fk_def {
+	struct create_constraint_def *base;
+	struct ExprList *child_cols;
+	struct Token *parent_name;
+	struct ExprList *parent_cols;
+	/**
+	 * Encoded actions for MATCH, ON DELETE and
+	 * ON UPDATE clauses.
+	 */
+	int actions;
+};
+
+struct create_index_def {
+	struct create_constraint_def *base;
+	/** List of indexed columns. */
+	struct ExprList *cols;
+	/** One of _PRIMARY_KEY, _UNIQUE, _NON_UNIQUE. */
+	enum sql_index_type idx_type;
+	enum sort_order sort_order;
+};
+
+
+/**
+ * Below is a list of *_def constructors. All of them allocate
+ * memory for new object using parser's region: it simplifies
+ * things since their lifetime is restricted by parser.
+ *
+ * In case of OOM, they return NULL and set appropriate
+ * error code in parser's structure and re-raise error
+ * via diag_set().
+ */
+struct alter_entity_def *
+alter_entity_def_new(struct Parse *parse, struct SrcList *name);
+
+struct rename_entity_def *
+rename_entity_def_new(struct Parse *parse, struct alter_entity_def *base,
+		      struct Token new_name);
+
+struct create_entity_def *
+create_entity_def_new(struct Parse *parse, struct alter_entity_def *base,
+		      struct Token name, bool if_not_exists);
+
+struct drop_entity_def *
+drop_entity_def_new(struct Parse *parse, struct alter_entity_def *base,
+		    struct Token name, bool if_exist);
+
+struct create_constraint_def *
+create_constraint_def_new(struct Parse *parse, struct create_entity_def *base,
+			  bool is_deferred);
+
+struct create_fk_def *
+create_fk_def_new(struct Parse *parse, struct create_constraint_def *base,
+		  struct ExprList *child_cols, struct Token *parent_name,
+		  struct ExprList *parent_cols, int actions);
+
+struct create_index_def *
+create_index_def_new(struct Parse *parse, struct create_constraint_def *base,
+		     struct ExprList *cols, enum sql_index_type idx_type,
+		     enum sort_order sort_order);
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 4110a5991..73d8a6a7c 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2728,7 +2728,6 @@ struct Parse {
 	int nLabel;		/* Number of labels used */
 	int *aLabel;		/* Space to hold the labels */
 	ExprList *pConstExpr;	/* Constant expressions */
-	Token constraintName;	/* Name of the constraint currently being parsed */
 	int nMaxArg;		/* Max args passed to user function by sub-program */
 	int nSelect;		/* Number of SELECT statements seen */
 	int nSelectIndent;	/* How far to indent SELECTTRACE() output */
@@ -2781,6 +2780,12 @@ struct Parse {
 	TriggerPrg *pTriggerPrg;	/* Linked list of coded triggers */
 	With *pWith;		/* Current WITH clause, or NULL */
 	With *pWithToFree;	/* Free this WITH object at the end of the parse */
+	/**
+	 * One of parse_def structures which are used to
+	 * assemble and carry arguments of DDL routines
+	 * from parse.y
+	 */
+	void *alter_entity_def;
 	/**
 	 * Number of FK constraints declared within
 	 * CREATE TABLE statement.
@@ -3459,7 +3464,7 @@ void
 sql_store_select(struct Parse *parse_context, struct Select *select);
 
 void
-sql_drop_table(struct Parse *, struct SrcList *, bool, bool);
+sql_drop_table(struct Parse *, bool);
 void sqlite3DeleteTable(sqlite3 *, Table *);
 void sqlite3Insert(Parse *, SrcList *, Select *, IdList *,
 		   enum on_conflict_action);
@@ -3488,36 +3493,19 @@ void sqlite3IdListDelete(sqlite3 *, IdList *);
  * parse->pNewTable is a table that is currently being
  * constructed by a CREATE TABLE statement.
  *
- * col_list is a list of columns to be indexed.  col_list will be
- * NULL if this is a primary key or unique-constraint on the most
- * recent column added to the table currently under construction.
- *
  * @param parse All information about this parse.
- * @param token Index name. May be NULL.
- * @param tbl_name Table to index. Use pParse->pNewTable ifNULL.
- * @param col_list A list of columns to be indexed.
- * @param start The CREATE token that begins this statement.
- * @param sort_order Sort order of primary key when pList==NULL.
- * @param if_not_exist Omit error if index already exists.
- * @param idx_type The index type.
  */
 void
-sql_create_index(struct Parse *parse, struct Token *token,
-		 struct SrcList *tbl_name, struct ExprList *col_list,
-		 struct Token *start, enum sort_order sort_order,
-		 bool if_not_exist, enum sql_index_type idx_type);
+sql_create_index(struct Parse *parse);
 
 /**
  * This routine will drop an existing named index.  This routine
  * implements the DROP INDEX statement.
  *
  * @param parse_context Current parsing context.
- * @param index_name_list List containing index name.
- * @param table_token Token representing table name.
- * @param if_exists True, if statement contains 'IF EXISTS' clause.
  */
 void
-sql_drop_index(struct Parse *, struct SrcList *, struct Token *, bool);
+sql_drop_index(struct Parse *parse_context);
 
 int sqlite3Select(Parse *, Select *, SelectDest *);
 Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
@@ -3919,20 +3907,9 @@ sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
  * After the trigger actions have been parsed, the
  * sql_trigger_finish() function is called to complete the trigger
  * construction process.
- *
- * @param parse The parse context of the CREATE TRIGGER statement.
- * @param name The name of the trigger.
- * @param tr_tm One of TK_BEFORE, TK_AFTER, TK_INSTEAD.
- * @param op One of TK_INSERT, TK_UPDATE, TK_DELETE.
- * @param columns column list if this is an UPDATE OF trigger.
- * @param table The name of the table/view the trigger applies to.
- * @param when  WHEN clause.
- * @param no_err Suppress errors if the trigger already exists.
  */
 void
-sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
-		  int op, struct IdList *columns, struct SrcList *table,
-		  struct Expr *when, int no_err);
+sql_trigger_begin(struct Parse *parse);
 
 /**
  * This routine is called after all of the trigger actions have
@@ -3952,11 +3929,9 @@ sql_trigger_finish(struct Parse *parse, struct TriggerStep *step_list,
  * VDBE code.
  *
  * @param parser Parser context.
- * @param name The name of trigger to drop.
- * @param no_err Suppress errors if the trigger already exists.
  */
 void
-sql_drop_trigger(struct Parse *parser, struct SrcList *name, bool no_err);
+sql_drop_trigger(struct Parse *parser);
 
 /**
  * Drop a trigger given a pointer to that trigger.
@@ -4136,14 +4111,6 @@ fkey_change_defer_mode(struct Parse *parse_context, bool is_deferred);
  * OR to handle <CREATE TABLE ...>
  *
  * @param parse_context Parsing context.
- * @param child Name of table to be altered. NULL on CREATE TABLE
- *              statement processing.
- * @param constraint Name of the constraint to be created. May be
- *                   NULL on CREATE TABLE statement processing.
- *                   Then, auto-generated name is used.
- * @param child_cols Columns of child table involved in FK.
- *                   May be NULL on CREATE TABLE statement processing.
- *                   If so, the last column added is used.
  * @param parent Name of referenced table.
  * @param parent_cols List of referenced columns. If NULL, columns
  *                    which make up PK of referenced table are used.
@@ -4152,22 +4119,16 @@ fkey_change_defer_mode(struct Parse *parse_context, bool is_deferred);
  *                algorithms (e.g. CASCADE, RESTRICT etc).
  */
 void
-sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
-		       struct Token *constraint, struct ExprList *child_cols,
-		       struct Token *parent, struct ExprList *parent_cols,
-		       bool is_deferred, int actions);
+sql_create_foreign_key(struct Parse *parse_context);
 
 /**
  * Function called from parser to handle
  * <ALTER TABLE table DROP CONSTRAINT constraint> SQL statement.
  *
  * @param parse_context Parsing context.
- * @param table Table to be altered.
- * @param constraint Name of constraint to be dropped.
  */
 void
-sql_drop_foreign_key(struct Parse *parse_context, struct SrcList *table,
-		     struct Token *constraint);
+sql_drop_foreign_key(struct Parse *parse_context);
 
 void sqlite3Detach(Parse *, Expr *);
 int sqlite3AtoF(const char *z, double *, int);
@@ -4385,12 +4346,9 @@ extern int sqlite3PendingByte;
  * command.
  *
  * @param parse Current parsing context.
- * @param src_tab The table to rename.
- * @param new_name_tk Token containing new name of the table.
  */
 void
-sql_alter_table_rename(struct Parse *parse, struct SrcList *src_tab,
-		       struct Token *new_name_tk);
+sql_alter_table_rename(struct Parse *parse);
 
 /**
  * Return the length (in bytes) of the token that begins at z[0].
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index 7d5dc9e23..685343ce9 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -34,7 +34,7 @@
  */
 
 #include "box/schema.h"
-#include "sqliteInt.h"
+#include "parse_def.h"
 #include "tarantoolInt.h"
 #include "vdbeInt.h"
 #include "box/session.h"
@@ -62,34 +62,29 @@ sqlite3DeleteTriggerStep(sqlite3 * db, TriggerStep * pTriggerStep)
 }
 
 void
-sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
-		  int op, struct IdList *columns, struct SrcList *table,
-		  struct Expr *when, int no_err)
+sql_trigger_begin(struct Parse *parse)
 {
 	/* The new trigger. */
 	struct sql_trigger *trigger = NULL;
 	/* The database connection. */
 	struct sqlite3 *db = parse->db;
-	/* The name of the Trigger. */
-	char *trigger_name = NULL;
-
-	/* pName->z might be NULL, but not pName itself. */
-	assert(name != NULL);
-	assert(op == TK_INSERT || op == TK_UPDATE || op == TK_DELETE);
-	assert(op > 0 && op < 0xff);
+	struct create_trigger_def *trigger_def = parse->alter_entity_def;
+	struct create_entity_def *create_def = trigger_def->base;
+	struct alter_entity_def *alter_def = create_def->base;
 
-	if (table == NULL || db->mallocFailed)
+	char *trigger_name = NULL;
+	if (alter_def->entity_name == NULL || db->mallocFailed)
 		goto trigger_cleanup;
-	assert(table->nSrc == 1);
-
-	trigger_name = sqlite3NameFromToken(db, name);
+	assert(alter_def->entity_name->nSrc == 1);
+	assert(create_def->name.n > 0);
+	trigger_name = sqlite3NameFromToken(db, &create_def->name);
 	if (trigger_name == NULL)
 		goto trigger_cleanup;
 
 	if (sqlite3CheckIdentifierName(parse, trigger_name) != SQLITE_OK)
 		goto trigger_cleanup;
 
-	const char *table_name = table->a[0].zName;
+	const char *table_name = alter_def->entity_name->a[0].zName;
 	uint32_t space_id;
 	if (schema_find_id(BOX_SPACE_ID, 2, table_name, strlen(table_name),
 			   &space_id) != 0)
@@ -112,6 +107,7 @@ sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
 		int name_reg = ++parse->nMem;
 		sqlite3VdbeAddOp4(parse->pVdbe, OP_String8, 0, name_reg, 0,
 				  name_copy, P4_DYNAMIC);
+		bool no_err = create_def->if_not_exist;
 		if (vdbe_emit_halt_with_presence_test(parse, BOX_TRIGGER_ID, 0,
 						      name_reg, 1,
 						      ER_TRIGGER_EXISTS,
@@ -129,13 +125,14 @@ sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
 	trigger->space_id = space_id;
 	trigger->zName = trigger_name;
 	trigger_name = NULL;
-
-	trigger->op = (u8) op;
-	trigger->tr_tm = tr_tm;
-	trigger->pWhen = sqlite3ExprDup(db, when, EXPRDUP_REDUCE);
-	trigger->pColumns = sqlite3IdListDup(db, columns);
-	if ((when != NULL && trigger->pWhen == NULL) ||
-	    (columns != NULL && trigger->pColumns == NULL))
+	assert(trigger_def->op == TK_INSERT || trigger_def->op == TK_UPDATE ||
+	       trigger_def->op== TK_DELETE);
+	trigger->op = (u8) trigger_def->op;
+	trigger->tr_tm = trigger_def->tr_tm;
+	trigger->pWhen = sqlite3ExprDup(db, trigger_def->when, EXPRDUP_REDUCE);
+	trigger->pColumns = sqlite3IdListDup(db, trigger_def->cols);
+	if ((trigger->pWhen != NULL && trigger->pWhen == NULL) ||
+	    (trigger->pColumns != NULL && trigger->pColumns == NULL))
 		goto trigger_cleanup;
 	assert(parse->parsed_ast.trigger == NULL);
 	parse->parsed_ast.trigger = trigger;
@@ -143,9 +140,9 @@ sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
 
  trigger_cleanup:
 	sqlite3DbFree(db, trigger_name);
-	sqlite3SrcListDelete(db, table);
-	sqlite3IdListDelete(db, columns);
-	sql_expr_delete(db, when, false);
+	sqlite3SrcListDelete(db, alter_def->entity_name);
+	sqlite3IdListDelete(db, trigger_def->cols);
+	sql_expr_delete(db, trigger_def->when, false);
 	if (parse->parsed_ast.trigger == NULL)
 		sql_trigger_delete(db, trigger);
 	else
@@ -424,9 +421,13 @@ vdbe_code_drop_trigger(struct Parse *parser, const char *trigger_name,
 }
 
 void
-sql_drop_trigger(struct Parse *parser, struct SrcList *name, bool no_err)
+sql_drop_trigger(struct Parse *parser)
 {
-
+	struct drop_entity_def *drop_def =
+		(struct drop_entity_def *) parser->alter_entity_def;
+	struct alter_entity_def *alter_def = drop_def->base;
+	struct SrcList *name = alter_def->entity_name;
+	bool no_err = drop_def->if_exist;
 	sqlite3 *db = parser->db;
 	if (db->mallocFailed)
 		goto drop_trigger_cleanup;
diff --git a/test/sql-tap/index7.test.lua b/test/sql-tap/index7.test.lua
index a8ce37f4b..ffb42403c 100755
--- a/test/sql-tap/index7.test.lua
+++ b/test/sql-tap/index7.test.lua
@@ -397,6 +397,6 @@ test:do_catchsql_test(
                 "_index"."id" = "_space"."id" AND
                 "_space"."name"='TEST8';
         ]],
-        {0, {"pk_TEST8_2",0,"unique_C1_1",1}})
+        {0, {"pk_unnamed_TEST8_2",0,"unique_C1_1",1}})
 
 test:finish_test()
-- 
2.15.1

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

* [tarantool-patches] [PATCH v2 2/5] sql: rework ALTER TABLE grammar
  2019-01-23 17:56 [tarantool-patches] [PATCH v2 0/5] Introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PK Nikita Pettik
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing Nikita Pettik
@ 2019-01-23 17:56 ` Nikita Pettik
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 3/5] sql: refactor getNewIid() function Nikita Pettik
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 34+ messages in thread
From: Nikita Pettik @ 2019-01-23 17:56 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

Since ALTER TABLE ADD CONSTRAINT is going to be used to add various
constraint types (foreign key, unique etc), we should rework its
grammar.  As a reference for it lets use one from ANSI.

Needed for #3097
---
 src/box/sql/parse.y | 77 ++++++++++++++++++++++++++++++-----------------------
 1 file changed, 43 insertions(+), 34 deletions(-)

diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index b0327c27a..7044921c7 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -365,17 +365,8 @@ tcons ::= UNIQUE LP sortlist(X) RP. {
 }
 tcons ::= CHECK LP expr(E) RP onconf.
                                  {sql_add_check_constraint(pParse,&E);}
-tcons ::= FOREIGN KEY LP eidlist(FA) RP
-          REFERENCES nm(T) eidlist_opt(TA) refargs(R) defer_subclause_opt(D). {
-  struct create_constraint_def *constr_def = pParse->alter_entity_def;
-  constr_def->is_deferred = D;
-  struct create_fk_def *fk_def =
-    create_fk_def_new(pParse, constr_def, FA, &T, TA, R);
-  if (fk_def == NULL)
-    break;
-  pParse->alter_entity_def = (void *) fk_def;
-  sql_create_foreign_key(pParse);
-}
+tcons ::= foreign_key_def.
+
 %type defer_subclause_opt {int}
 defer_subclause_opt(A) ::= .                    {A = 0;}
 defer_subclause_opt(A) ::= defer_subclause(A).
@@ -1551,18 +1542,33 @@ cmd ::= ANALYZE.                {sqlite3Analyze(pParse, 0);}
 cmd ::= ANALYZE nm(X).          {sqlite3Analyze(pParse, &X);}
 
 //////////////////////// ALTER TABLE table ... ////////////////////////////////
-cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). {
-  struct alter_entity_def *alter_def =
-    alter_entity_def_new(pParse, X);
-  /*
-   * After code preprocessing, this code snippet will get to
-   * one of "switch" cases. Hence, break is enough to gently
-   * terminate it. No clean-ups are required since all structs
-   * are allocated on region. OOM error is set inside def
-   * constructors.
-   */
+cmd ::= alter_table_start alter_table_action .
+
+/**
+ * We should get name of the table before processing
+ * any other rules. So, we've put this routine at
+ * the separate rule.
+ */
+alter_table_start ::= ALTER TABLE fullname(Z) . {
+  struct alter_entity_def *alter_def = alter_entity_def_new(pParse, Z);
+   /*
+    * After code preprocessing, this code snippet will get to
+    * one of "switch" cases. Hence, break is enough to gently
+    * terminate it. No clean-ups are required since all structs
+    * are allocated on region. OOM error is set inside def
+    * constructors.
+    */
   if (alter_def == NULL)
     break;
+  pParse->alter_entity_def = (void *) alter_def;
+}
+
+alter_table_action ::= add_constraint_def.
+alter_table_action ::= drop_constraint_def.
+alter_table_action ::= rename.
+
+rename ::= RENAME TO nm(Z). {
+  struct alter_entity_def *alter_def = pParse->alter_entity_def;
   struct rename_entity_def *rename_def =
     rename_entity_def_new(pParse, alter_def, Z);
   if (rename_def == NULL)
@@ -1571,21 +1577,27 @@ cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). {
   sql_alter_table_rename(pParse);
 }
 
-cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT nm(Z) FOREIGN KEY
-        LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) refargs(R)
-        defer_subclause_opt(D). {
-  struct alter_entity_def *alter_def =
-    alter_entity_def_new(pParse, X);
-  if (alter_def == NULL)
-    break;
+add_constraint_def ::= add_constraint_start constraint_def.
+
+add_constraint_start ::= ADD CONSTRAINT nm(Z). {
+  struct alter_entity_def *alter_def = pParse->alter_entity_def;
   struct create_entity_def *create_def =
     create_entity_def_new(pParse, alter_def, Z, false);
   if (create_def == NULL)
     break;
   struct create_constraint_def *constraint_def =
-    create_constraint_def_new(pParse, create_def, D);
+    create_constraint_def_new(pParse, create_def, false);
   if (constraint_def == NULL)
     break;
+  pParse->alter_entity_def = (void *) constraint_def;
+}
+
+constraint_def ::= foreign_key_def.
+
+foreign_key_def ::= FOREIGN KEY LP eidlist(FA) RP REFERENCES nm(T)
+                    eidlist_opt(TA) refargs(R) defer_subclause_opt(D). {
+  struct create_constraint_def *constraint_def = pParse->alter_entity_def;
+  constraint_def->is_deferred = D;
   struct create_fk_def *fk_def =
     create_fk_def_new(pParse, constraint_def, FA, &T, TA, R);
   if (fk_def == NULL)
@@ -1594,11 +1606,8 @@ cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT nm(Z) FOREIGN KEY
   sql_create_foreign_key(pParse);
 }
 
-cmd ::= ALTER TABLE fullname(X) DROP CONSTRAINT nm(Z). {
-  struct alter_entity_def *alter_def =
-    alter_entity_def_new(pParse, X);
-  if (alter_def == NULL)
-    break;
+drop_constraint_def ::= DROP CONSTRAINT nm(Z). {
+  struct alter_entity_def *alter_def = pParse->alter_entity_def;
   struct drop_entity_def *drop_def =
     drop_entity_def_new(pParse, alter_def, Z, false);
   if (drop_def == NULL)
-- 
2.15.1

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

* [tarantool-patches] [PATCH v2 3/5] sql: refactor getNewIid() function
  2019-01-23 17:56 [tarantool-patches] [PATCH v2 0/5] Introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PK Nikita Pettik
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing Nikita Pettik
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 2/5] sql: rework ALTER TABLE grammar Nikita Pettik
@ 2019-01-23 17:56 ` Nikita Pettik
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 4/5] sql: fix error message for improperly created index Nikita Pettik
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 5/5] sql: introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PRIMARY KEY Nikita Pettik
  4 siblings, 0 replies; 34+ messages in thread
From: Nikita Pettik @ 2019-01-23 17:56 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

This commit includes no functional changes. Lets simply rewrite
getNewIid() function according to Tarantool codestyle.

Part of #3914
---
 src/box/sql/build.c | 73 ++++++++++++++++++++++++++---------------------------
 1 file changed, 36 insertions(+), 37 deletions(-)

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index d0e19407a..d7dcc5cb4 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -2064,45 +2064,43 @@ sql_drop_foreign_key(struct Parse *parse_context)
 	sqlite3VdbeChangeP5(sqlite3GetVdbe(parse_context), OPFLAG_NCHANGE);
 }
 
-/*
- * Generate code to determine next free Iid in the space identified by
- * the iSpaceId. Return register number holding the result.
+/**
+ * Generate code to determine next free index id in
+ * the space identified by the @space_id.
+ * Return register holding the result.
+ *
+ * Overall VDBE program logic is following:
+ *
+ * 1 Seek for space id in _index, goto l1 if seeks fails.
+ * 2 Goto l2.
+ * 3 l1: Halt.
+ * 4 l2: Fetch index id from _index record.
  */
 static int
-getNewIid(Parse * pParse, int iSpaceId, int iCursor)
+generate_index_id(struct Parse *parse, uint32_t space_id, int cursor)
 {
-	Vdbe *v = sqlite3GetVdbe(pParse);
-	int iRes = ++pParse->nMem;
-	int iKey = ++pParse->nMem;
-	int iSeekInst, iGotoInst;
-
-	sqlite3VdbeAddOp2(v, OP_Integer, iSpaceId, iKey);
-	iSeekInst = sqlite3VdbeAddOp4Int(v, OP_SeekLE, iCursor, 0, iKey, 1);
-	sqlite3VdbeAddOp4Int(v, OP_IdxLT, iCursor, 0, iKey, 1);
-
-	/*
-	 * If SeekLE succeeds, the control falls through here, skipping
-	 * IdxLt.
-	 *
-	 * If it fails (no entry with the given key prefix: invalid spaceId)
-	 * VDBE jumps to the next code block (jump target is IMM, fixed up
-	 * later with sqlite3VdbeJumpHere()).
-	 */
-	iGotoInst = sqlite3VdbeAddOp0(v, OP_Goto);	/* Jump over Halt */
-
-	/* Invalid spaceId detected. Halt now. */
-	sqlite3VdbeJumpHere(v, iSeekInst);
-	sqlite3VdbeJumpHere(v, iSeekInst + 1);
-	sqlite3VdbeAddOp4(v,
-			  OP_Halt, SQLITE_ERROR, ON_CONFLICT_ACTION_FAIL, 0,
-			  sqlite3MPrintf(pParse->db, "Invalid space id: %d",
-					 iSpaceId), P4_DYNAMIC);
-
-	/* Fetch iid from the row and ++it. */
-	sqlite3VdbeJumpHere(v, iGotoInst);
-	sqlite3VdbeAddOp3(v, OP_Column, iCursor, 1, iRes);
-	sqlite3VdbeAddOp2(v, OP_AddImm, iRes, 1);
-	return iRes;
+	struct Vdbe *v = sqlite3GetVdbe(parse);
+	int key_reg = ++parse->nMem;
+
+	sqlite3VdbeAddOp2(v, OP_Integer, space_id, key_reg);
+	int seek_adr = sqlite3VdbeAddOp4Int(v, OP_SeekLE, cursor, 0,
+					    key_reg, 1);
+	sqlite3VdbeAddOp4Int(v, OP_IdxLT, cursor, 0, key_reg, 1);
+	/* Jump over Halt block. */
+	int goto_succ_addr = sqlite3VdbeAddOp0(v, OP_Goto);
+	/* Invalid space id handling block starts here. */
+	sqlite3VdbeJumpHere(v, seek_adr);
+	sqlite3VdbeJumpHere(v, seek_adr + 1);
+	sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_ERROR, ON_CONFLICT_ACTION_FAIL, 0,
+			  sqlite3MPrintf(parse->db, "Invalid space id: %d",
+					 space_id), P4_DYNAMIC);
+
+	sqlite3VdbeJumpHere(v, goto_succ_addr);
+	/* Fetch iid from the row and increment it. */
+	int iid_reg = ++parse->nMem;
+	sqlite3VdbeAddOp3(v, OP_Column, cursor, BOX_INDEX_FIELD_ID, iid_reg);
+	sqlite3VdbeAddOp2(v, OP_AddImm, iid_reg, 1);
+	return iid_reg;
 }
 
 /**
@@ -2552,7 +2550,8 @@ sql_create_index(struct Parse *parse) {
 				  (void *)space_by_id(BOX_INDEX_ID),
 				  P4_SPACEPTR);
 		sqlite3VdbeChangeP5(vdbe, OPFLAG_SEEKEQ);
-		int index_id = getNewIid(parse, def->id, cursor);
+
+		int index_id = generate_index_id(parse, def->id, cursor);
 		sqlite3VdbeAddOp1(vdbe, OP_Close, cursor);
 		vdbe_emit_create_index(parse, def, index->def,
 				       def->id, index_id);
-- 
2.15.1

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

* [tarantool-patches] [PATCH v2 4/5] sql: fix error message for improperly created index
  2019-01-23 17:56 [tarantool-patches] [PATCH v2 0/5] Introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PK Nikita Pettik
                   ` (2 preceding siblings ...)
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 3/5] sql: refactor getNewIid() function Nikita Pettik
@ 2019-01-23 17:56 ` Nikita Pettik
  2019-02-08 17:14   ` [tarantool-patches] " Konstantin Osipov
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 5/5] sql: introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PRIMARY KEY Nikita Pettik
  4 siblings, 1 reply; 34+ messages in thread
From: Nikita Pettik @ 2019-01-23 17:56 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

Table can be created without any indexes (for instance, from Lua-land).
On the other hand, bytecode generated for CREATE INDEX statement
attempts at finding entry in _index space with given space id.
In case it is not found error "wrong space id" is raised. On the other
hand, it is obvious that such message is appeared when table doesn't
have any created indexes yet. Hence, lets fix this message to indicate
that primary key should be created before any secondary indexes.

Closes #3914
---
 src/box/sql/build.c          |  4 ++--
 test/sql-tap/index1.test.lua | 19 ++++++++++++++++++-
 2 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index d7dcc5cb4..660d5acfc 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -2092,8 +2092,8 @@ generate_index_id(struct Parse *parse, uint32_t space_id, int cursor)
 	sqlite3VdbeJumpHere(v, seek_adr);
 	sqlite3VdbeJumpHere(v, seek_adr + 1);
 	sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_ERROR, ON_CONFLICT_ACTION_FAIL, 0,
-			  sqlite3MPrintf(parse->db, "Invalid space id: %d",
-					 space_id), P4_DYNAMIC);
+			  "can not add a secondary key before primary",
+			  P4_STATIC);
 
 	sqlite3VdbeJumpHere(v, goto_succ_addr);
 	/* Fetch iid from the row and increment it. */
diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
index 49f61a52a..121381747 100755
--- a/test/sql-tap/index1.test.lua
+++ b/test/sql-tap/index1.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(70)
+test:plan(72)
 
 --!./tcltestrunner.lua
 -- 2001 September 15
@@ -1016,5 +1016,22 @@ if (0 > 0)
 
 end
 
+test:do_test(
+    "index-22.1.0",
+    function()
+        format = {}
+        format[1] = { name = 'id', type = 'scalar'}
+        format[2] = { name = 'f2', type = 'scalar'}
+        s = box.schema.create_space('T', {format = format})
+    end,
+    {})
+
+test:do_catchsql_test(
+    "alter-8.1.1",
+    [[
+        CREATE UNIQUE INDEX pk ON t("id");
+    ]], {
+        1, "can not add a secondary key before primary"
+    })
 
 test:finish_test()
-- 
2.15.1

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

* [tarantool-patches] [PATCH v2 5/5] sql: introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PRIMARY KEY
  2019-01-23 17:56 [tarantool-patches] [PATCH v2 0/5] Introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PK Nikita Pettik
                   ` (3 preceding siblings ...)
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 4/5] sql: fix error message for improperly created index Nikita Pettik
@ 2019-01-23 17:56 ` Nikita Pettik
  2019-01-24  8:31   ` [tarantool-patches] " Konstantin Osipov
                     ` (2 more replies)
  4 siblings, 3 replies; 34+ messages in thread
From: Nikita Pettik @ 2019-01-23 17:56 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, kostja, Nikita Pettik

Table (aka space) can be created without indexes at least from Lua-land
(note that according ANSI SQL table may lack PK). Since there were no
facilities to create primary key constraint on already existing table,
lets extend ADD CONSTRAINT statement with UNIQUE and PRIMARY KEY
clauses. In this case, UNIQUE works exactly in the same way as CREATE
UNIQUE INDEX ... statement does.  In Tarantool primary index is an index
with id == 0, so during execution of ADD CONSTRAINT PRIMARY KEY we check
that there is no any entries in _index space and create that one.
Otherwise, error is raised.

Part of #3097
Follow-up #3914
---
 src/box/sql/build.c          | 29 ++++++++++++++++++++--
 src/box/sql/parse.y          | 20 ++++++++++++++++
 test/sql-tap/alter.test.lua  | 57 +++++++++++++++++++++++++++++++++++++++++++-
 test/sql-tap/index1.test.lua | 11 ++++++++-
 4 files changed, 113 insertions(+), 4 deletions(-)

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 660d5acfc..6eb9718be 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -2103,6 +2103,19 @@ generate_index_id(struct Parse *parse, uint32_t space_id, int cursor)
 	return iid_reg;
 }
 
+static void
+pk_check_existence(struct Parse *parse, uint32_t space_id, int _index_cursor)
+{
+	struct Vdbe *v = sqlite3GetVdbe(parse);
+	int tmp_reg = ++parse->nMem;
+	sqlite3VdbeAddOp2(v, OP_Integer, space_id, tmp_reg);
+	int found_addr = sqlite3VdbeAddOp4Int(v, OP_NotFound, _index_cursor, 0,
+					     tmp_reg, 1);
+	sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_ERROR, ON_CONFLICT_ACTION_FAIL, 0,
+			  "multiple primary keys are not allowed", P4_STATIC);
+	sqlite3VdbeJumpHere(v, found_addr);
+}
+
 /**
  * Add new index to table's indexes list.
  * We follow convention that PK comes first in list.
@@ -2550,8 +2563,20 @@ sql_create_index(struct Parse *parse) {
 				  (void *)space_by_id(BOX_INDEX_ID),
 				  P4_SPACEPTR);
 		sqlite3VdbeChangeP5(vdbe, OPFLAG_SEEKEQ);
-
-		int index_id = generate_index_id(parse, def->id, cursor);
+		int index_id;
+		/*
+		 * In case we are creating PRIMARY KEY constraint
+		 * (via ALTER TABLE) we must ensure that table
+		 * doesn't feature any indexes. Otherwise,
+		 * we can immediately halt execution of VDBE.
+		 */
+		if (idx_type == SQL_INDEX_TYPE_CONSTRAINT_PK) {
+			pk_check_existence(parse, def->id, cursor);
+			index_id = parse->nMem++;
+			sqlite3VdbeAddOp2(vdbe, OP_Integer, 0, index_id);
+		} else {
+			index_id = generate_index_id(parse, def->id, cursor);
+		}
 		sqlite3VdbeAddOp1(vdbe, OP_Close, cursor);
 		vdbe_emit_create_index(parse, def, index->def,
 				       def->id, index_id);
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 7044921c7..56485528d 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -1593,6 +1593,7 @@ add_constraint_start ::= ADD CONSTRAINT nm(Z). {
 }
 
 constraint_def ::= foreign_key_def.
+constraint_def ::= unique_def.
 
 foreign_key_def ::= FOREIGN KEY LP eidlist(FA) RP REFERENCES nm(T)
                     eidlist_opt(TA) refargs(R) defer_subclause_opt(D). {
@@ -1606,6 +1607,25 @@ foreign_key_def ::= FOREIGN KEY LP eidlist(FA) RP REFERENCES nm(T)
   sql_create_foreign_key(pParse);
 }
 
+unique_def ::= unique_spec(U) LP sortlist(X) RP. {
+  struct create_constraint_def *constr_def = pParse->alter_entity_def;
+  struct create_index_def *idx_def =
+    create_index_def_new(pParse, constr_def, X, U, SORT_ORDER_ASC);
+  if (idx_def == NULL)
+    break;
+  pParse->alter_entity_def = (void *) idx_def;
+  sql_create_index(pParse);
+ }
+
+%type unique_spec {int}
+unique_spec(U) ::= UNIQUE.      { U = SQL_INDEX_TYPE_CONSTRAINT_UNIQUE; }
+unique_spec(U) ::= PRIMARY KEY. { U = SQL_INDEX_TYPE_CONSTRAINT_PK; }
+
+/**
+ * Currently, to drop unique constraint it is required
+ * to use drop index (since fk and unique constraints don't
+ * share name space).
+ */
 drop_constraint_def ::= DROP CONSTRAINT nm(Z). {
   struct alter_entity_def *alter_def = pParse->alter_entity_def;
   struct drop_entity_def *drop_def =
diff --git a/test/sql-tap/alter.test.lua b/test/sql-tap/alter.test.lua
index 1aad555c0..318b0f68d 100755
--- a/test/sql-tap/alter.test.lua
+++ b/test/sql-tap/alter.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(43)
+test:plan(50)
 
 test:do_execsql_test(
     "alter-1.1",
@@ -517,6 +517,61 @@ test:do_catchsql_test(
         -- </alter-7.11>
     })
 
+test:do_test(
+    "alter-8.1.0",
+    function()
+        format = {}
+        format[1] = { name = 'id', type = 'scalar'}
+        format[2] = { name = 'f2', type = 'scalar'}
+        s = box.schema.create_space('T', {format = format})
+    end,
+    {})
+
+test:do_catchsql_test(
+    "alter-8.1.1",
+    [[
+        ALTER TABLE t ADD CONSTRAINT pk PRIMARY KEY("id");
+    ]], {
+        0
+    })
+
+test:do_test(
+    "alter-8.1.2",
+    function()
+        return box.space.T.index[0].id
+    end, 0)
+
+test:do_catchsql_test(
+    "alter-8.2",
+    [[
+        ALTER TABLE t ADD CONSTRAINT pk1 PRIMARY KEY("f2");
+    ]], {
+        1, "multiple primary keys are not allowed"
+    })
+
+test:do_catchsql_test(
+    "alter-8.3.1",
+    [[
+        ALTER TABLE t ADD CONSTRAINT i1 UNIQUE("f2");
+    ]], {
+        0
+    })
+
+test:do_test(
+    "alter-8.3.2",
+    function()
+        i = box.space.T.index[1]
+        return i.id
+    end, 1)
+
+test:do_catchsql_test(
+    "alter-8.4",
+    [[
+        DROP INDEX i1 ON t;
+        DROP INDEX pk ON t;
+    ]], {
+    0
+})
 
 -- Commented due to #2953
 --
diff --git a/test/sql-tap/index1.test.lua b/test/sql-tap/index1.test.lua
index 121381747..3edf863a9 100755
--- a/test/sql-tap/index1.test.lua
+++ b/test/sql-tap/index1.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
-test:plan(72)
+test:plan(73)
 
 --!./tcltestrunner.lua
 -- 2001 September 15
@@ -1034,4 +1034,13 @@ test:do_catchsql_test(
         1, "can not add a secondary key before primary"
     })
 
+test:do_catchsql_test(
+    "alter-8.2",
+    [[
+        ALTER TABLE t ADD CONSTRAINT pk PRIMARY KEY("id");
+        CREATE UNIQUE INDEX i ON t("id");
+    ]], {
+    0
+})
+
 test:finish_test()
-- 
2.15.1

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

* [tarantool-patches] Re: [PATCH v2 5/5] sql: introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PRIMARY KEY
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 5/5] sql: introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PRIMARY KEY Nikita Pettik
@ 2019-01-24  8:31   ` Konstantin Osipov
  2019-01-29 19:29   ` Vladislav Shpilevoy
  2019-02-08 17:16   ` Konstantin Osipov
  2 siblings, 0 replies; 34+ messages in thread
From: Konstantin Osipov @ 2019-01-24  8:31 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches, v.shpilevoy

* Nikita Pettik <korablev@tarantool.org> [19/01/23 23:01]:
> Table (aka space) can be created without indexes at least from Lua-land
> (note that according ANSI SQL table may lack PK). Since there were no
> facilities to create primary key constraint on already existing table,
> lets extend ADD CONSTRAINT statement with UNIQUE and PRIMARY KEY
> clauses. In this case, UNIQUE works exactly in the same way as CREATE
> UNIQUE INDEX ... statement does.  In Tarantool primary index is an index
> with id == 0, so during execution of ADD CONSTRAINT PRIMARY KEY we check
> that there is no any entries in _index space and create that one.
> Otherwise, error is raised.
> 
> Part of #3097
> Follow-up #3914


>  
> +static void
> +pk_check_existence(struct Parse *parse, uint32_t space_id, int _index_cursor)

I think the name is confusing. You can't check if an index exists
at code generation phase.  Things may change between code
generation and execution. So you actually generate code for
checking if an index exists.

This is why every function must have a comment. Even a static one.
A comment is a checksum that the function does what the writer
meant it to do.

> +{
> +	struct Vdbe *v = sqlite3GetVdbe(parse);
> +	int tmp_reg = ++parse->nMem;
> +	sqlite3VdbeAddOp2(v, OP_Integer, space_id, tmp_reg);
> +	int found_addr = sqlite3VdbeAddOp4Int(v, OP_NotFound, _index_cursor, 0,
> +					     tmp_reg, 1);
> +	sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_ERROR, ON_CONFLICT_ACTION_FAIL, 0,
> +			  "multiple primary keys are not allowed", P4_STATIC);
> +	sqlite3VdbeJumpHere(v, found_addr);
> +}

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing Nikita Pettik
@ 2019-01-24  8:36   ` Konstantin Osipov
  2019-01-24 10:47     ` n.pettik
  2019-01-29 19:29   ` Vladislav Shpilevoy
  1 sibling, 1 reply; 34+ messages in thread
From: Konstantin Osipov @ 2019-01-24  8:36 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches, v.shpilevoy

* Nikita Pettik <korablev@tarantool.org> [19/01/23 23:01]:
> +	/**
> +	 * One of parse_def structures which are used to
> +	 * assemble and carry arguments of DDL routines
> +	 * from parse.y
> +	 */
> +	void *alter_entity_def;

Please consider adding a type code to the base entity def, so that
you can use a base struct reference rather than void *. 


-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-01-24  8:36   ` [tarantool-patches] " Konstantin Osipov
@ 2019-01-24 10:47     ` n.pettik
  2019-01-24 12:30       ` Konstantin Osipov
  0 siblings, 1 reply; 34+ messages in thread
From: n.pettik @ 2019-01-24 10:47 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Konstantin Osipov, Vladislav Shpilevoy



> On 24 Jan 2019, at 11:36, Konstantin Osipov <kostja@tarantool.org> wrote:
> 
> * Nikita Pettik <korablev@tarantool.org> [19/01/23 23:01]:
>> +	/**
>> +	 * One of parse_def structures which are used to
>> +	 * assemble and carry arguments of DDL routines
>> +	 * from parse.y
>> +	 */
>> +	void *alter_entity_def;
> 
> Please consider adding a type code to the base entity def, so that
> you can use a base struct reference rather than void *. 

Please, look at code and explain why do we need type
code? At first I added it, but it turned out that such code
was completely useless. The only real usage was to
assert that type of structure matches with its code.

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-01-24 10:47     ` n.pettik
@ 2019-01-24 12:30       ` Konstantin Osipov
  2019-01-29 19:03         ` n.pettik
  0 siblings, 1 reply; 34+ messages in thread
From: Konstantin Osipov @ 2019-01-24 12:30 UTC (permalink / raw)
  To: n.pettik; +Cc: tarantool-patches, Vladislav Shpilevoy

* n.pettik <korablev@tarantool.org> [19/01/24 13:51]:
> >> +	 * One of parse_def structures which are used to
> >> +	 * assemble and carry arguments of DDL routines
> >> +	 * from parse.y
> >> +	 */
> >> +	void *alter_entity_def;
> > 
> > Please consider adding a type code to the base entity def, so that
> > you can use a base struct reference rather than void *. 
> 
> Please, look at code and explain why do we need type
> code? At first I added it, but it turned out that such code
> was completely useless. The only real usage was to
> assert that type of structure matches with its code.

To assert the structure matches with its code.

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-01-24 12:30       ` Konstantin Osipov
@ 2019-01-29 19:03         ` n.pettik
  0 siblings, 0 replies; 34+ messages in thread
From: n.pettik @ 2019-01-29 19:03 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Konstantin Osipov, Vladislav Shpilevoy



> On 24 Jan 2019, at 15:30, Konstantin Osipov <kostja@tarantool.org> wrote:
> 
> * n.pettik <korablev@tarantool.org> [19/01/24 13:51]:
>>>> +	 * One of parse_def structures which are used to
>>>> +	 * assemble and carry arguments of DDL routines
>>>> +	 * from parse.y
>>>> +	 */
>>>> +	void *alter_entity_def;
>>> 
>>> Please consider adding a type code to the base entity def, so that
>>> you can use a base struct reference rather than void *. 
>> 
>> Please, look at code and explain why do we need type
>> code? At first I added it, but it turned out that such code
>> was completely useless. The only real usage was to
>> assert that type of structure matches with its code.
> 
> To assert the structure matches with its code.
> 
> -- 
> Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
> http://tarantool.io - www.twitter.com/kostja_osipov

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index d0e19407a..dfaa6b505 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -717,6 +717,7 @@ sqlite3AddPrimaryKey(Parse * pParse,        /* Parsing context */
        struct create_entity_def create_def = {};
        struct create_constraint_def constr_def = {};
        struct create_index_def idx_def = {};
+       alter_def.entity_type = ENTITY_TYPE_INDEX;
        create_def.base = &alter_def;
        constr_def.base = &create_def;
        idx_def.base = &constr_def;
@@ -1715,6 +1716,7 @@ sql_drop_table(struct Parse *parse_context, bool is_view)
 {
        struct drop_entity_def *drop_def = parse_context->alter_entity_def;
        struct alter_entity_def *alter_def = drop_def->base;
+       assert(alter_def->entity_type == ENTITY_TYPE_TABLE);
        struct SrcList *table_name_list = alter_def->entity_name;
        struct Vdbe *v = sqlite3GetVdbe(parse_context);
        struct sqlite3 *db = parse_context->db;
@@ -1821,6 +1823,7 @@ sql_create_foreign_key(struct Parse *parse_context)
        struct create_constraint_def *create_constr_def = create_fk_def->base;
        struct create_entity_def *create_def = create_constr_def->base;
        struct alter_entity_def *alter_def = create_def->base;
+       assert(alter_def->entity_type == ENTITY_TYPE_FK);
        /*
         * When this function is called second time during
         * <CREATE TABLE ...> statement (i.e. at VDBE runtime),
@@ -2041,6 +2044,7 @@ sql_drop_foreign_key(struct Parse *parse_context)
        struct drop_entity_def *drop_def =
                (struct drop_entity_def *) parse_context->alter_entity_def;
        struct alter_entity_def *alter_def = drop_def->base;
+       assert(alter_def->entity_type == ENTITY_TYPE_FK);
        const char *table_name = alter_def->entity_name->a[0].zName;
        assert(table_name != NULL);
        struct space *child = space_by_name(table_name);
@@ -2257,6 +2261,7 @@ sql_create_index(struct Parse *parse) {
        struct create_constraint_def *create_constr_def = create_idx_def->base;
        struct create_entity_def *create_entity_def = create_constr_def->base;
        struct alter_entity_def *alter_entity_def = create_entity_def->base;
+       assert(alter_entity_def->entity_type == ENTITY_TYPE_INDEX);
        /*
         * Get list of columns to be indexed. It will be NULL if
         * this is a primary key or unique-constraint on the most
@@ -2580,6 +2585,7 @@ sql_drop_index(struct Parse *parse_context)
        struct drop_entity_def *drop_def =
                (struct drop_entity_def *) parse_context->alter_entity_def;
        struct alter_entity_def *alter_def = drop_def->base;
+       assert(alter_def->entity_type == ENTITY_TYPE_INDEX);
        struct Vdbe *v = sqlite3GetVdbe(parse_context);
        assert(v != NULL);
        struct sqlite3 *db = parse_context->db;
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index b0327c27a..1d7f13122 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -408,7 +408,8 @@ drop_table ::= ifexists(E) fullname(X) . {
   if (alter_def == NULL)
     break;
   struct drop_entity_def *drop_def =
-    drop_entity_def_new(pParse, alter_def, (struct Token){}, E);
+    drop_entity_def_new(pParse, alter_def, ENTITY_TYPE_TABLE,
+                        (struct Token){}, E);
   if (drop_def == NULL)
     break;
   pParse->alter_entity_def = (void *) drop_def;
@@ -1284,6 +1285,7 @@ cmd ::= createkw uniqueflag(U) INDEX ifnotexists(NE) nm(X)
   struct create_entity_def create_def = {};
   struct create_constraint_def constr_def = {};
   struct create_index_def idx_def = {};
+  alter_def.entity_type = ENTITY_TYPE_INDEX;
   alter_def.entity_name = sqlite3SrcListAppend(pParse->db,0,&Y);
   create_def.base = &alter_def;
   create_def.name = X;
@@ -1358,6 +1360,7 @@ collate(C) ::= COLLATE id.   {C = 1;}
 cmd ::= DROP INDEX ifexists(E) nm(X) ON fullname(Y).   {
   struct alter_entity_def alter_def = {};
   struct drop_entity_def drop_def = {};
+  alter_def.entity_type = ENTITY_TYPE_INDEX;
   alter_def.entity_name = Y;
   drop_def.base = &alter_def;
   drop_def.if_exist = E;
@@ -1417,6 +1420,7 @@ trigger_decl(A) ::= TRIGGER ifnotexists(NOERR) nm(B)
   struct alter_entity_def alter_def = {};
   struct create_entity_def create_def = {};
   struct create_trigger_def trigger_def = {};
+  alter_def.entity_type = ENTITY_TYPE_TRIGGER;
   alter_def.entity_name = E;
   create_def.base = &alter_def;
   create_def.name = B;
@@ -1539,6 +1543,7 @@ raisetype(A) ::= FAIL.      {A = ON_CONFLICT_ACTION_FAIL;}
 cmd ::= DROP TRIGGER ifexists(NOERR) fullname(X). {
   struct alter_entity_def alter_def = {};
   struct drop_entity_def drop_def = {};
+  alter_def.entity_type = ENTITY_TYPE_TRIGGER;
   alter_def.entity_name = X;
   drop_def.base = &alter_def;
   drop_def.if_exist = NOERR;
@@ -1600,7 +1605,7 @@ cmd ::= ALTER TABLE fullname(X) DROP CONSTRAINT nm(Z). {
   if (alter_def == NULL)
     break;
   struct drop_entity_def *drop_def =
-    drop_entity_def_new(pParse, alter_def, Z, false);
+    drop_entity_def_new(pParse, alter_def, ENTITY_TYPE_FK, Z, false);
   if (drop_def == NULL)
     break;
   pParse->alter_entity_def = (void *) drop_def;
diff --git a/src/box/sql/parse_def.c b/src/box/sql/parse_def.c
index 7f241636a..d3581e4bb 100644
--- a/src/box/sql/parse_def.c
+++ b/src/box/sql/parse_def.c
@@ -41,6 +41,7 @@ alter_entity_def_new(struct Parse *parse, struct SrcList *name)
                parse->nErr++;
                return NULL;
        }
+       alter_def->entity_type = ENTITY_TYPE_TABLE;
        alter_def->entity_name = name;
        return alter_def;
 }
@@ -82,7 +83,8 @@ create_entity_def_new(struct Parse *parse, struct alter_entity_def *base,
 
 struct drop_entity_def *
 drop_entity_def_new(struct Parse *parse, struct alter_entity_def *base,
-                   struct Token entity_name, bool if_exist)
+                   enum entity_type type, struct Token entity_name,
+                   bool if_exist)
 {
        size_t sz = sizeof(struct drop_entity_def);
        struct drop_entity_def *drop_def = region_alloc(&parse->region, sz);
@@ -92,6 +94,7 @@ drop_entity_def_new(struct Parse *parse, struct alter_entity_def *base,
                parse->nErr++;
                return NULL;
        }
+       base->entity_type = type;
        drop_def->base = base;
        drop_def->name = entity_name;
        drop_def->if_exist = if_exist;
@@ -129,6 +132,7 @@ create_fk_def_new(struct Parse *parse, struct create_constraint_def *base,
                parse->nErr++;
                return NULL;
        }
+       base->base->base->entity_type = ENTITY_TYPE_FK;
        fk_def->base = base;
        fk_def->child_cols = child_cols;
        fk_def->parent_name = parent_name;
@@ -150,6 +154,7 @@ create_index_def_new(struct Parse *parse, struct create_constraint_def *base,
                parse->nErr++;
                return NULL;
        }
+       base->base->base->entity_type = ENTITY_TYPE_INDEX;
        idx_def->base = base;
        idx_def->cols = cols;
        idx_def->idx_type = idx_type;
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index 2f6d3d047..4ed7c977a 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -66,7 +66,18 @@
  * CREATE TRIGGER tr1 ...
  * ALTER *TABLE* -> CREATE ENTITY -> CREATE TRIGGER
  */
+
+enum entity_type {
+       ENTITY_TYPE_TABLE = 0,
+       ENTITY_TYPE_INDEX,
+       ENTITY_TYPE_TRIGGER,
+       ENTITY_TYPE_FK,
+       entity_type_MAX
+};
+
 struct alter_entity_def {
+       /** Type of topmost entity. */
+       enum entity_type entity_type;
        /** As a rule it is a name of table to be altered. */
        struct SrcList *entity_name;
 };
@@ -153,7 +164,7 @@ create_entity_def_new(struct Parse *parse, struct alter_entity_def *base,
 
 struct drop_entity_def *
 drop_entity_def_new(struct Parse *parse, struct alter_entity_def *base,
-                   struct Token name, bool if_exist);
+                   enum entity_type type, struct Token name, bool if_exist);
 
 struct create_constraint_def *
 create_constraint_def_new(struct Parse *parse, struct create_entity_def *base,
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index 685343ce9..c6c9c0393 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -71,6 +71,7 @@ sql_trigger_begin(struct Parse *parse)
        struct create_trigger_def *trigger_def = parse->alter_entity_def;
        struct create_entity_def *create_def = trigger_def->base;
        struct alter_entity_def *alter_def = create_def->base;
+       assert(alter_def->entity_type == ENTITY_TYPE_TRIGGER);
 
        char *trigger_name = NULL;
        if (alter_def->entity_name == NULL || db->mallocFailed)
@@ -426,6 +427,7 @@ sql_drop_trigger(struct Parse *parser)
        struct drop_entity_def *drop_def =
                (struct drop_entity_def *) parser->alter_entity_def;
        struct alter_entity_def *alter_def = drop_def->base;
+       assert(alter_def->entity_type == ENTITY_TYPE_TRIGGER);
        struct SrcList *name = alter_def->entity_name;
        bool no_err = drop_def->if_exist;
        sqlite3 *db = parser->db;

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing Nikita Pettik
  2019-01-24  8:36   ` [tarantool-patches] " Konstantin Osipov
@ 2019-01-29 19:29   ` Vladislav Shpilevoy
  2019-01-29 20:04     ` n.pettik
  2019-01-31 19:32     ` n.pettik
  1 sibling, 2 replies; 34+ messages in thread
From: Vladislav Shpilevoy @ 2019-01-29 19:29 UTC (permalink / raw)
  To: tarantool-patches, Nikita Pettik; +Cc: kostja

Hi! Thanks for the patchset! See 5 comments below.

> What is more, we are going to introduce ALTER TABLE ADD CONSTRAINT
> UNIQUE With new hierarchy we can extend ALTER TABLE statement with ease:
> basic structures (alter -> create entity -> create constraint) are the
> same for .. FOREIGN KEY/UNIQUE, but the last one will be different.
> 
> Note that grammar for CREATE TABLE statement is not trivial and consists
> of wide range of sub-clauses (e.g. to parse foreign key or check
> constraints). Therefore, parts of space definition are assembled
> as soon as parser processes sub-rules. For this reason, current patch
> doesn't affect CREATE TABLE handling.

1. Why? I do not think, that "create table" port into parse_def.h is
impossible and follows from the facts above. 'Not trivial' does
not mean impossible.

As I see, you can create struct create_table_def, which will contain
Table *pNewTable, struct rlist new_fkey, uint32_t fkey_count,
bool is_new_table_autoinc. Just move some attributes from struct Parse
to this new structure. Is it possible? I think, it is worth doing,
because the code will look more consistent - any object change will go
through parse_def.h structures.

> diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
> new file mode 100644
> index 000000000..2f6d3d047
> --- /dev/null
> +++ b/src/box/sql/parse_def.h
> @@ -0,0 +1,170 @@
> +/*
> + * 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 "sqliteInt.h"
> +#include "box/fkey.h"
> +
> +/**
> + * This file contains auxiliary structures and functions which
> + * are used only during parsing routine (see parse.y).
> + * Their main purpose is to assemble common parts of altered
> + * entities (such as name, or IF EXISTS clause) and pass them
> + * as a one object to further functions.
> + *
> + * Hierarchy is following:
> + *
> + * Base structure is ALTER.
> + * ALTER is omitted only for CREATE TABLE since table is filled
> + * with meta-information just-in-time of parsing:
> + * for instance, as soon as field's name and type are recognized
> + * they are added to space definition.
> + *
> + * DROP is general for all existing objects and includes
> + * name of object itself, name of parent object (table),
> + * IF EXISTS clause and may contain on-drop behaviour
> + * (CASCADE/RESTRICT, but now it is always RESTRICT).
> + * Hence, it terms of grammar - it is a terminal symbol.
> + *
> + * RENAME can be applied only to table (at least now, since it is
> + * ANSI extension), so it is also terminal symbol.
> + *
> + * CREATE in turn can be expanded to nonterminal symbol
> + * CREATE CONSTRAINT or to terminal CREATE TABLE/INDEX/TRIGGER.
> + * CREATE CONSTRAINT unfolds to FOREIGN KEY or UNIQUE/PRIMARY KEY.
> + *
> + * For instance:
> + * ALTER TABLE t ADD CONSTRAINT c FOREIGN KEY REFERENCES t2(id);
> + * ALTER *TABLE* -> CREATE ENTITY -> CREATE CONSTRAINT -> CREATE FK
> + *
> + * CREATE TRIGGER tr1 ...
> + * ALTER *TABLE* -> CREATE ENTITY -> CREATE TRIGGER
> + */
> +struct alter_entity_def {
> +	/** As a rule it is a name of table to be altered. */
> +	struct SrcList *entity_name;
> +};
> +
> +struct rename_entity_def {
> +	struct alter_entity_def *base;

2. Please, look at how we usually do inheritance in C.
We include base structure into a child as an attribute,
not as a pointer. So you can cast the child to the parent
even without touching 'base' field. Also, this change
together with comment 4 allows to remove parse_def.c
completely.

> +	struct Token new_name;
> +};
> +
> +struct create_entity_def {
> +	struct alter_entity_def *base;
> +	struct Token name;
> +	/** Statement comes with IF NOT EXISTS clause. */
> +	bool if_not_exist;
> +};
> +
> +struct drop_entity_def {
> +	struct alter_entity_def *base;
> +	/** Name of index/trigger/constraint to be dropped. */
> +	struct Token name;
> +	/** Statement comes with IF EXISTS clause. */
> +	bool if_exist;
> +};
> +
> +struct create_trigger_def {
> +	struct create_entity_def *base;
> +	/** One of TK_BEFORE, TK_AFTER, TK_INSTEAD. */
> +	int tr_tm;
> +	/** One of TK_INSERT, TK_UPDATE, TK_DELETE. */
> +	int op;
> +	/** Column list if this is an UPDATE trigger. */
> +	struct IdList *cols;
> +	/** When clause. */
> +	struct Expr *when;
> +};
> +
> +struct create_constraint_def {
> +	struct create_entity_def *base;
> +	/** One of DEFERRED, IMMEDIATE. */
> +	bool is_deferred;
> +};
> +
> +struct create_fk_def {
> +	struct create_constraint_def *base;
> +	struct ExprList *child_cols;
> +	struct Token *parent_name;
> +	struct ExprList *parent_cols;
> +	/**
> +	 * Encoded actions for MATCH, ON DELETE and
> +	 * ON UPDATE clauses.
> +	 */
> +	int actions;
> +};
> +
> +struct create_index_def {
> +	struct create_constraint_def *base;
> +	/** List of indexed columns. */
> +	struct ExprList *cols;
> +	/** One of _PRIMARY_KEY, _UNIQUE, _NON_UNIQUE. */
> +	enum sql_index_type idx_type;
> +	enum sort_order sort_order;
> +};
> +
> +
> +/**
> + * Below is a list of *_def constructors. All of them allocate
> + * memory for new object using parser's region: it simplifies
> + * things since their lifetime is restricted by parser.
> + *
> + * In case of OOM, they return NULL and set appropriate
> + * error code in parser's structure and re-raise error
> + * via diag_set().
> + */
> +struct alter_entity_def *
> +alter_entity_def_new(struct Parse *parse, struct SrcList *name);
> +
> +struct rename_entity_def *
> +rename_entity_def_new(struct Parse *parse, struct alter_entity_def *base,
> +		      struct Token new_name);
> +
> +struct create_entity_def *
> +create_entity_def_new(struct Parse *parse, struct alter_entity_def *base,
> +		      struct Token name, bool if_not_exists);
> +
> +struct drop_entity_def *
> +drop_entity_def_new(struct Parse *parse, struct alter_entity_def *base,
> +		    struct Token name, bool if_exist);
> +
> +struct create_constraint_def *
> +create_constraint_def_new(struct Parse *parse, struct create_entity_def *base,
> +			  bool is_deferred);
> +
> +struct create_fk_def *
> +create_fk_def_new(struct Parse *parse, struct create_constraint_def *base,
> +		  struct ExprList *child_cols, struct Token *parent_name,
> +		  struct ExprList *parent_cols, int actions);
> +
> +struct create_index_def *
> +create_index_def_new(struct Parse *parse, struct create_constraint_def *base,
> +		     struct ExprList *cols, enum sql_index_type idx_type,
> +		     enum sort_order sort_order);

3. Once you applied my comments 2 and 4, you can rename these functions to
*_create(), remove struct Parse * argument, and inline them into the header
as trivial ones.

> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> index 4110a5991..73d8a6a7c 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -2781,6 +2780,12 @@ struct Parse {
>   	TriggerPrg *pTriggerPrg;	/* Linked list of coded triggers */
>   	With *pWith;		/* Current WITH clause, or NULL */
>   	With *pWithToFree;	/* Free this WITH object at the end of the parse */
> +	/**
> +	 * One of parse_def structures which are used to
> +	 * assemble and carry arguments of DDL routines
> +	 * from parse.y
> +	 */
> +	void *alter_entity_def;

4. Please, use union of all terminal defs from parse_def.h. It will
simplify many things. I see only one problem with it - cyclic dependency
sqliteInt.h <-> parse_def.h, which exists even now, but is just hidden
under void *. You can come up with your own solution or use one of my
owns:

     * there is only one structure, which makes impossible to remove
       include sqliteInt.h from parse_def.h - Token, stored by value in
       rename_entity_def, create_entity_def, drop_entity_def. All other
       values and other Tokens are stored by pointer, what makes it
       possible to announce them at the beginning of the file. So we can
       store it by pointer in these places too. Somehow;

     * replace these Token values with strict const char *str + int len.
       Honestly, I think, that it makes no sense to carry isReserved
       field inside SQL parser's logic. IsReserved is used by tokenizer
       and lemon only (only 3 usages in total over the whole source base).
       But it would be too big diff, not related to the patch.

     * move struct Token definition to parse_def.h;

     * finally move struct Parse into a separate file parser.h. Now you
       can include sqlitInt.h into parse_def.h and in parser.h without
       a cycle.

>   	/**
>   	 * Number of FK constraints declared within
>   	 * CREATE TABLE statement.
> diff --git a/test/sql-tap/index7.test.lua b/test/sql-tap/index7.test.lua
> index a8ce37f4b..ffb42403c 100755
> --- a/test/sql-tap/index7.test.lua
> +++ b/test/sql-tap/index7.test.lua
> @@ -397,6 +397,6 @@ test:do_catchsql_test(
>                   "_index"."id" = "_space"."id" AND
>                   "_space"."name"='TEST8';
>           ]],
> -        {0, {"pk_TEST8_2",0,"unique_C1_1",1}})
> +        {0, {"pk_unnamed_TEST8_2",0,"unique_C1_1",1}})

5. But you said "no functional changes" - what happened here?

>   
>   test:finish_test()
> -- 
> 2.15.1
> 
> 

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

* [tarantool-patches] Re: [PATCH v2 5/5] sql: introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PRIMARY KEY
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 5/5] sql: introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PRIMARY KEY Nikita Pettik
  2019-01-24  8:31   ` [tarantool-patches] " Konstantin Osipov
@ 2019-01-29 19:29   ` Vladislav Shpilevoy
  2019-02-08 17:16   ` Konstantin Osipov
  2 siblings, 0 replies; 34+ messages in thread
From: Vladislav Shpilevoy @ 2019-01-29 19:29 UTC (permalink / raw)
  To: Nikita Pettik, tarantool-patches; +Cc: kostja

Thanks for the patch! See 1 comment below.

On 23/01/2019 20:56, Nikita Pettik wrote:
> Table (aka space) can be created without indexes at least from Lua-land
> (note that according ANSI SQL table may lack PK). Since there were no
> facilities to create primary key constraint on already existing table,
> lets extend ADD CONSTRAINT statement with UNIQUE and PRIMARY KEY
> clauses. In this case, UNIQUE works exactly in the same way as CREATE
> UNIQUE INDEX ... statement does.  In Tarantool primary index is an index
> with id == 0, so during execution of ADD CONSTRAINT PRIMARY KEY we check
> that there is no any entries in _index space and create that one.
> Otherwise, error is raised.
> 
> Part of #3097
> Follow-up #3914
> ---
>   src/box/sql/build.c          | 29 ++++++++++++++++++++--
>   src/box/sql/parse.y          | 20 ++++++++++++++++
>   test/sql-tap/alter.test.lua  | 57 +++++++++++++++++++++++++++++++++++++++++++-
>   test/sql-tap/index1.test.lua | 11 ++++++++-
>   4 files changed, 113 insertions(+), 4 deletions(-)
> 
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index 660d5acfc..6eb9718be 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -2103,6 +2103,19 @@ generate_index_id(struct Parse *parse, uint32_t space_id, int cursor)
>   	return iid_reg;
>   }
>   
> +static void
> +pk_check_existence(struct Parse *parse, uint32_t space_id, int _index_cursor)

1. Maybe Kostja is right. Could you rename it please to vdbe_emit_pk_existence_check?
I see, that it does not use parser for anything but to just get Vdbe, so I think
we can just pass Vdbe.

I do not think, that such a simple function is worth a comment, but if Kostja
asked, then lets add a one.

> +{
> +	struct Vdbe *v = sqlite3GetVdbe(parse);
> +	int tmp_reg = ++parse->nMem;
> +	sqlite3VdbeAddOp2(v, OP_Integer, space_id, tmp_reg);
> +	int found_addr = sqlite3VdbeAddOp4Int(v, OP_NotFound, _index_cursor, 0,
> +					     tmp_reg, 1);
> +	sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_ERROR, ON_CONFLICT_ACTION_FAIL, 0,
> +			  "multiple primary keys are not allowed", P4_STATIC);
> +	sqlite3VdbeJumpHere(v, found_addr);
> +}

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-01-29 19:29   ` Vladislav Shpilevoy
@ 2019-01-29 20:04     ` n.pettik
  2019-01-29 20:20       ` Vladislav Shpilevoy
  2019-01-31 19:32     ` n.pettik
  1 sibling, 1 reply; 34+ messages in thread
From: n.pettik @ 2019-01-29 20:04 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy



> On 29 Jan 2019, at 22:29, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Hi! Thanks for the patchset! See 5 comments below.
> 
>> What is more, we are going to introduce ALTER TABLE ADD CONSTRAINT
>> UNIQUE With new hierarchy we can extend ALTER TABLE statement with ease:
>> basic structures (alter -> create entity -> create constraint) are the
>> same for .. FOREIGN KEY/UNIQUE, but the last one will be different.
>> Note that grammar for CREATE TABLE statement is not trivial and consists
>> of wide range of sub-clauses (e.g. to parse foreign key or check
>> constraints). Therefore, parts of space definition are assembled
>> as soon as parser processes sub-rules. For this reason, current patch
>> doesn't affect CREATE TABLE handling.
> 
> 1. Why? I do not think, that "create table" port into parse_def.h is
> impossible and follows from the facts above. 'Not trivial' does
> not mean impossible.
> 
> As I see, you can create struct create_table_def, which will contain
> Table *pNewTable, struct rlist new_fkey, uint32_t fkey_count,
> bool is_new_table_autoinc. Just move some attributes from struct Parse
> to this new structure. Is it possible? I think, it is worth doing,
> because the code will look more consistent - any object change will go
> through parse_def.h structures.

Ok, it is possible.

>> +struct alter_entity_def {
>> +	/** As a rule it is a name of table to be altered. */
>> +	struct SrcList *entity_name;
>> +};
>> +
>> +struct rename_entity_def {
>> +	struct alter_entity_def *base;
> 
> 2. Please, look at how we usually do inheritance in C.
> We include base structure into a child as an attribute,
> not as a pointer. So you can cast the child to the parent
> even without touching 'base' field. Also, this change
> together with comment 4 allows to remove parse_def.c
> completely.

Surely, before implementing this patch I checked out
inheritance examples in our code base. But here we
have slightly different case (or I misunderstood concepts).

For instance, when we create memtx space, we allocate
enough space for base space and for memtx parent at
once. When we are parsing ALTER TABLE, we are able
to allocate memory only for base structure (alter_def),
since we don’t know where we will get - whether to
drop constraint or create constraint or rename.
If we used similar inheritance model, we would
have to realloc enough memory (for base + parent)
each time we get to the next rule.

What I mean:

alter_table_start ::= …
  struct alter_entity_def *alter_def = malloc(sizeof(alter_def));
….

Then we can get to “rename” rule or to “add_constraint_start”.

rename ::=
…
struct rename_entity_def *rename_def = malloc (sizeof(alter_def));
memcpy(rename_def, alter_def, sizeof(alter_def));
free(alter_def);
…

The same allocation and free procedures would be called
inside add_constraint_start.

Or, as an option, we can intensely use region allocation.
But then we are going to waste a lot of memory.

Is it what you talked about? Or I am wrong and you meant
easier way?

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-01-29 20:04     ` n.pettik
@ 2019-01-29 20:20       ` Vladislav Shpilevoy
  2019-01-29 21:25         ` n.pettik
  0 siblings, 1 reply; 34+ messages in thread
From: Vladislav Shpilevoy @ 2019-01-29 20:20 UTC (permalink / raw)
  To: n.pettik, tarantool-patches



On 29/01/2019 23:04, n.pettik wrote:
> 
> 
>> On 29 Jan 2019, at 22:29, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
>>
>> Hi! Thanks for the patchset! See 5 comments below.
>>
>>> What is more, we are going to introduce ALTER TABLE ADD CONSTRAINT
>>> UNIQUE With new hierarchy we can extend ALTER TABLE statement with ease:
>>> basic structures (alter -> create entity -> create constraint) are the
>>> same for .. FOREIGN KEY/UNIQUE, but the last one will be different.
>>> Note that grammar for CREATE TABLE statement is not trivial and consists
>>> of wide range of sub-clauses (e.g. to parse foreign key or check
>>> constraints). Therefore, parts of space definition are assembled
>>> as soon as parser processes sub-rules. For this reason, current patch
>>> doesn't affect CREATE TABLE handling.
>>
>> 1. Why? I do not think, that "create table" port into parse_def.h is
>> impossible and follows from the facts above. 'Not trivial' does
>> not mean impossible.
>>
>> As I see, you can create struct create_table_def, which will contain
>> Table *pNewTable, struct rlist new_fkey, uint32_t fkey_count,
>> bool is_new_table_autoinc. Just move some attributes from struct Parse
>> to this new structure. Is it possible? I think, it is worth doing,
>> because the code will look more consistent - any object change will go
>> through parse_def.h structures.
> 
> Ok, it is possible.
> 
>>> +struct alter_entity_def {
>>> +	/** As a rule it is a name of table to be altered. */
>>> +	struct SrcList *entity_name;
>>> +};
>>> +
>>> +struct rename_entity_def {
>>> +	struct alter_entity_def *base;
>>
>> 2. Please, look at how we usually do inheritance in C.
>> We include base structure into a child as an attribute,
>> not as a pointer. So you can cast the child to the parent
>> even without touching 'base' field. Also, this change
>> together with comment 4 allows to remove parse_def.c
>> completely.
> 
> Surely, before implementing this patch I checked out
> inheritance examples in our code base. But here we
> have slightly different case (or I misunderstood concepts).
> 
> For instance, when we create memtx space, we allocate
> enough space for base space and for memtx parent at
> once. When we are parsing ALTER TABLE, we are able
> to allocate memory only for base structure (alter_def),
> since we don’t know where we will get - whether to
> drop constraint or create constraint or rename.
> If we used similar inheritance model, we would
> have to realloc enough memory (for base + parent)
> each time we get to the next rule.

You haven't, if struct Parse stores a union of all
terminal defs instead of a pointer. Then this field
will have enough memory to fit any def, with all its
base attributes. See the comment 4 from the original
email. And the comment below.

> 
> What I mean:
> 
> alter_table_start ::= …
>    struct alter_entity_def *alter_def = malloc(sizeof(alter_def));
> ….
> 
> Then we can get to “rename” rule or to “add_constraint_start”.
> 
> rename ::=
> …
> struct rename_entity_def *rename_def = malloc (sizeof(alter_def));
> memcpy(rename_def, alter_def, sizeof(alter_def));
> free(alter_def);
> …
> 
> The same allocation and free procedures would be called
> inside add_constraint_start.
> 
> Or, as an option, we can intensely use region allocation.
> But then we are going to waste a lot of memory.
> 
> Is it what you talked about? Or I am wrong and you meant
> easier way?
> 

No, you have no realloc anything, if you just remove '*' from
all 'base' members and use union of all terminal symbols
in struct Parse as I described. Then this union member in struct
Parse will have enough memory to fit any terminal symbol, with
all its inheritance levels (because of 'base' attributes, stored
by value).

It slightly similar to struct port. Base struct port has
padding, which is able to fit any child attributes, so we
can create base struct port, and then 'develop' it to a more
concrete port without reallocs. Here you will have a similar
situation, if fix my comment 4.

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-01-29 20:20       ` Vladislav Shpilevoy
@ 2019-01-29 21:25         ` n.pettik
  0 siblings, 0 replies; 34+ messages in thread
From: n.pettik @ 2019-01-29 21:25 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

[-- Attachment #1: Type: text/plain, Size: 4377 bytes --]



> On 29 Jan 2019, at 23:20, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> On 29/01/2019 23:04, n.pettik wrote:
>>> On 29 Jan 2019, at 22:29, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
>>> 
>>> Hi! Thanks for the patchset! See 5 comments below.
>>> 
>>>> What is more, we are going to introduce ALTER TABLE ADD CONSTRAINT
>>>> UNIQUE With new hierarchy we can extend ALTER TABLE statement with ease:
>>>> basic structures (alter -> create entity -> create constraint) are the
>>>> same for .. FOREIGN KEY/UNIQUE, but the last one will be different.
>>>> Note that grammar for CREATE TABLE statement is not trivial and consists
>>>> of wide range of sub-clauses (e.g. to parse foreign key or check
>>>> constraints). Therefore, parts of space definition are assembled
>>>> as soon as parser processes sub-rules. For this reason, current patch
>>>> doesn't affect CREATE TABLE handling.
>>> 
>>> 1. Why? I do not think, that "create table" port into parse_def.h is
>>> impossible and follows from the facts above. 'Not trivial' does
>>> not mean impossible.
>>> 
>>> As I see, you can create struct create_table_def, which will contain
>>> Table *pNewTable, struct rlist new_fkey, uint32_t fkey_count,
>>> bool is_new_table_autoinc. Just move some attributes from struct Parse
>>> to this new structure. Is it possible? I think, it is worth doing,
>>> because the code will look more consistent - any object change will go
>>> through parse_def.h structures.
>> Ok, it is possible.
>>>> +struct alter_entity_def {
>>>> +	/** As a rule it is a name of table to be altered. */
>>>> +	struct SrcList *entity_name;
>>>> +};
>>>> +
>>>> +struct rename_entity_def {
>>>> +	struct alter_entity_def *base;
>>> 
>>> 2. Please, look at how we usually do inheritance in C.
>>> We include base structure into a child as an attribute,
>>> not as a pointer. So you can cast the child to the parent
>>> even without touching 'base' field. Also, this change
>>> together with comment 4 allows to remove parse_def.c
>>> completely.
>> Surely, before implementing this patch I checked out
>> inheritance examples in our code base. But here we
>> have slightly different case (or I misunderstood concepts).
>> For instance, when we create memtx space, we allocate
>> enough space for base space and for memtx parent at
>> once. When we are parsing ALTER TABLE, we are able
>> to allocate memory only for base structure (alter_def),
>> since we don’t know where we will get - whether to
>> drop constraint or create constraint or rename.
>> If we used similar inheritance model, we would
>> have to realloc enough memory (for base + parent)
>> each time we get to the next rule.
> 
> You haven't, if struct Parse stores a union of all
> terminal defs instead of a pointer. Then this field
> will have enough memory to fit any def, with all its
> base attributes. See the comment 4 from the original
> email. And the comment below.
> 
>> What I mean:
>> alter_table_start ::= …
>>   struct alter_entity_def *alter_def = malloc(sizeof(alter_def));
>> ….
>> Then we can get to “rename” rule or to “add_constraint_start”.
>> rename ::=
>> …
>> struct rename_entity_def *rename_def = malloc (sizeof(alter_def));
>> memcpy(rename_def, alter_def, sizeof(alter_def));
>> free(alter_def);
>> …
>> The same allocation and free procedures would be called
>> inside add_constraint_start.
>> Or, as an option, we can intensely use region allocation.
>> But then we are going to waste a lot of memory.
>> Is it what you talked about? Or I am wrong and you meant
>> easier way?
> 
> No, you have no realloc anything, if you just remove '*' from
> all 'base' members and use union of all terminal symbols
> in struct Parse as I described. Then this union member in struct
> Parse will have enough memory to fit any terminal symbol, with
> all its inheritance levels (because of 'base' attributes, stored
> by value).
> 
> It slightly similar to struct port. Base struct port has
> padding, which is able to fit any child attributes, so we
> can create base struct port, and then 'develop' it to a more
> concrete port without reallocs. Here you will have a similar
> situation, if fix my comment 4.

Thx for explanation, I’ve got it.
I forgot about union, now it’s clear.


[-- Attachment #2: Type: text/html, Size: 19700 bytes --]

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-01-29 19:29   ` Vladislav Shpilevoy
  2019-01-29 20:04     ` n.pettik
@ 2019-01-31 19:32     ` n.pettik
  2019-02-04 15:25       ` Vladislav Shpilevoy
  1 sibling, 1 reply; 34+ messages in thread
From: n.pettik @ 2019-01-31 19:32 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

I’ve took into consideration your comments and pushed
new version of this patch:

Branch: np/gh-3914-fix-create-index-v2

If it is OK (except for minor things mb) I will add it to
the main patch-set and rebase the rest.

Author: Nikita Pettik <korablev@tarantool.org>
Date:   Wed Jan 9 12:28:09 2019 +0200

    sql: introduce structs assembling DDL arguments during parsing
    
    Parser's rules implementing DDL have a lot in common. For instance,
    to drop any entity it is enough to know its name and name of table it is
    related to.
    Thus, it was suggested to arrange arguments of DDL rules into
    hierarchical structure. The root of chain always includes name of table
    to be altered: all existing entities are related to some table.  Then
    comes one of drop/create/rename rules. Indeed, each DDL operation can be
    classified in these terms, at least until we introduce ALTER TABLE ALTER
    CONSTRAINT statement. Drop is represented by single structure (as it was
    mentioned); rename can be applied only to table; create can be applied
    to CONSTRAINT (indexes are considered as constraints) and TRIGGER, which
    in turn are different in arguments required to create those objects. And
    so forth.
    
    What is more, we are going to introduce ALTER TABLE ADD CONSTRAINT
    UNIQUE With new hierarchy we can extend ALTER TABLE statement with ease:
    basic structures (alter -> create entity -> create constraint) are the
    same for .. FOREIGN KEY/UNIQUE, but the last one will be different.
    
    Patch itself is made up of refactoring; no functional changes are
    provided.
    
    Needed for #3097

diff --git a/src/box/sql/alter.c b/src/box/sql/alter.c
index d0ce9d893..e88b57b59 100644
--- a/src/box/sql/alter.c
+++ b/src/box/sql/alter.c
@@ -38,12 +38,14 @@
 #include "box/schema.h"
 
 void
-sql_alter_table_rename(struct Parse *parse, struct SrcList *src_tab,
-		       struct Token *new_name_tk)
+sql_alter_table_rename(struct Parse *parse)
 {
+	struct rename_entity_def rename_def = parse->rename_entity_def;
+	struct SrcList *src_tab = rename_def.base.entity_name;
+	assert(rename_def.base.entity_type == ENTITY_TYPE_TABLE);
 	assert(src_tab->nSrc == 1);
 	struct sqlite3 *db = parse->db;
-	char *new_name = sqlite3NameFromToken(db, new_name_tk);
+	char *new_name = sqlite3NameFromToken(db, &rename_def.new_name);
 	if (new_name == NULL)
 		goto exit_rename_table;
 	/* Check that new name isn't occupied by another table. */
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index f92f39d8e..b6f099cf5 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -383,18 +383,16 @@ sql_table_new(Parse *parser, char *name)
  * when the "TEMP" or "TEMPORARY" keyword occurs in between
  * CREATE and TABLE.
  *
- * The new table record is initialized and put in pParse->pNewTable.
+ * The new table record is initialized and put in pParse->new_table.
  * As more of the CREATE TABLE statement is parsed, additional action
  * routines will be called to add more information to this record.
  * At the end of the CREATE TABLE statement, the sqlite3EndTable() routine
  * is called to complete the construction of the new table record.
  *
  * @param pParse Parser context.
- * @param pName1 First part of the name of the table or view.
- * @param noErr Do nothing if table already exists.
  */
 void
-sqlite3StartTable(Parse *pParse, Token *pName, int noErr)
+sqlite3StartTable(struct Parse *pParse)
 {
 	Table *pTable;
 	char *zName = 0;	/* The name of the new table */
@@ -403,8 +401,9 @@ sqlite3StartTable(Parse *pParse, Token *pName, int noErr)
 	if (v == NULL)
 		goto cleanup;
 	sqlite3VdbeCountChanges(v);
-
-	zName = sqlite3NameFromToken(db, pName);
+	struct Token name =
+		((struct create_entity_def *) &pParse->create_table_def)->name;
+	zName = sqlite3NameFromToken(db, &name);
 
 	if (zName == 0)
 		return;
@@ -413,7 +412,7 @@ sqlite3StartTable(Parse *pParse, Token *pName, int noErr)
 
 	struct space *space = space_by_name(zName);
 	if (space != NULL) {
-		if (!noErr) {
+		if (!pParse->create_table_def.base.if_not_exist) {
 			sqlite3ErrorMsg(pParse, "table %s already exists",
 					zName);
 		} else {
@@ -426,8 +425,7 @@ sqlite3StartTable(Parse *pParse, Token *pName, int noErr)
 	if (pTable == NULL)
 		goto cleanup;
 
-	assert(pParse->pNewTable == 0);
-	pParse->pNewTable = pTable;
+	pParse->create_table_def.new_table = pTable;
 
 	if (!db->init.busy && (v = sqlite3GetVdbe(pParse)) != 0)
 		sql_set_multi_write(pParse, true);
@@ -516,7 +514,7 @@ sqlite3AddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
 	int i;
 	char *z;
 	sqlite3 *db = pParse->db;
-	if ((p = pParse->pNewTable) == 0)
+	if ((p = pParse->create_table_def.new_table) == NULL)
 		return;
 #if SQLITE_MAX_COLUMN
 	if ((int)p->def->field_count + 1 > db->aLimit[SQLITE_LIMIT_COLUMN]) {
@@ -565,14 +563,13 @@ sqlite3AddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
 	column_def->affinity = type_def->type;
 	column_def->type = sql_affinity_to_field_type(column_def->affinity);
 	p->def->field_count++;
-	pParse->constraintName.n = 0;
 }
 
 void
 sql_column_add_nullable_action(struct Parse *parser,
 			       enum on_conflict_action nullable_action)
 {
-	struct Table *p = parser->pNewTable;
+	struct Table *p = parser->create_table_def.new_table;
 	if (p == NULL || NEVER(p->def->field_count < 1))
 		return;
 	struct field_def *field = &p->def->fields[p->def->field_count - 1];
@@ -609,7 +606,7 @@ sqlite3AddDefaultValue(Parse * pParse, ExprSpan * pSpan)
 {
 	Table *p;
 	sqlite3 *db = pParse->db;
-	p = pParse->pNewTable;
+	p = pParse->create_table_def.new_table;
 	assert(p->def->opts.is_temporary);
 	if (p != 0) {
 		if (!sqlite3ExprIsConstantOrFunction
@@ -673,11 +670,10 @@ field_def_create_for_pk(struct Parse *parser, struct field_def *field,
 void
 sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 		     ExprList * pList,	/* List of field names to be indexed */
-		     int autoInc,	/* True if the AUTOINCREMENT keyword is present */
 		     enum sort_order sortOrder
     )
 {
-	Table *pTab = pParse->pNewTable;
+	Table *pTab = pParse->create_table_def.new_table;
 	int iCol = -1, i;
 	int nTerm;
 	if (pTab == 0)
@@ -712,11 +708,12 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 			}
 		}
 	}
+	pParse->create_index_def.idx_type = SQL_INDEX_TYPE_CONSTRAINT_PK;
+	pParse->create_index_def.sort_order = sortOrder;
+	entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);
 	if (nTerm == 1 && iCol != -1 &&
 	    pTab->def->fields[iCol].type == FIELD_TYPE_INTEGER &&
 	    sortOrder != SORT_ORDER_DESC) {
-		assert(autoInc == 0 || autoInc == 1);
-		pParse->is_new_table_autoinc = autoInc;
 		struct sqlite3 *db = pParse->db;
 		struct ExprList *list;
 		struct Token token;
@@ -726,17 +723,17 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 							     &token, 0));
 		if (list == NULL)
 			goto primary_key_exit;
-		sql_create_index(pParse, 0, 0, list, 0, SORT_ORDER_ASC,
-				 false, SQL_INDEX_TYPE_CONSTRAINT_PK);
+		pParse->create_index_def.cols = list;
+		sql_create_index(pParse);
 		if (db->mallocFailed)
 			goto primary_key_exit;
-	} else if (autoInc) {
+	} else if (pParse->create_table_def.has_autoinc) {
 		sqlite3ErrorMsg(pParse, "AUTOINCREMENT is only allowed on an "
 				"INTEGER PRIMARY KEY or INT PRIMARY KEY");
 		goto primary_key_exit;
 	} else {
-		sql_create_index(pParse, 0, 0, pList, 0, sortOrder, false,
-				 SQL_INDEX_TYPE_CONSTRAINT_PK);
+		pParse->create_index_def.cols = pList;
+		sql_create_index(pParse);
 		pList = 0;
 		if (pParse->nErr > 0)
 			goto primary_key_exit;
@@ -756,14 +753,21 @@ primary_key_exit:
 }
 
 void
-sql_add_check_constraint(struct Parse *parser, struct ExprSpan *span)
-{
-	struct Expr *expr = span->pExpr;
-	struct Table *table = parser->pNewTable;
+sql_add_check_constraint(struct Parse *parser)
+{
+	struct create_ck_def *ck_def = &parser->create_ck_def;
+	struct alter_entity_def *alter_def =
+		(struct alter_entity_def *) &parser->create_ck_def;
+	assert(alter_def->entity_type == ENTITY_TYPE_CK);
+	(void) alter_def;
+	struct Expr *expr = ck_def->expr->pExpr;
+	struct Table *table = parser->create_table_def.new_table;
 	if (table != NULL) {
 		expr->u.zToken =
-			sqlite3DbStrNDup(parser->db, (char *)span->zStart,
-					 (int)(span->zEnd - span->zStart));
+			sqlite3DbStrNDup(parser->db,
+					 (char *) ck_def->expr->zStart,
+					 (int) (ck_def->expr->zEnd -
+					        ck_def->expr->zStart));
 		if (expr->u.zToken == NULL)
 			goto release_expr;
 		table->def->opts.checks =
@@ -773,9 +777,11 @@ sql_add_check_constraint(struct Parse *parser, struct ExprSpan *span)
 			sqlite3DbFree(parser->db, expr->u.zToken);
 			goto release_expr;
 		}
-		if (parser->constraintName.n) {
+		struct create_entity_def *entity_def =
+			(struct create_entity_def *) ck_def;
+		if (entity_def->name.n > 0) {
 			sqlite3ExprListSetName(parser, table->def->opts.checks,
-					       &parser->constraintName, 1);
+					       &entity_def->name, 1);
 		}
 	} else {
 release_expr:
@@ -790,7 +796,7 @@ release_expr:
 void
 sqlite3AddCollateType(Parse * pParse, Token * pToken)
 {
-	Table *p = pParse->pNewTable;
+	Table *p = pParse->create_table_def.new_table;
 	if (p == NULL)
 		return;
 	uint32_t i = p->def->field_count - 1;
@@ -923,7 +929,7 @@ vdbe_emit_create_index(struct Parse *parse, struct space_def *def,
 	memcpy(raw, index_parts, index_parts_sz);
 	index_parts = raw;
 
-	if (parse->pNewTable != NULL) {
+	if (parse->create_table_def.new_table != NULL) {
 		sqlite3VdbeAddOp2(v, OP_SCopy, space_id_reg, entry_reg);
 		sqlite3VdbeAddOp2(v, OP_Integer, idx_def->iid, entry_reg + 1);
 	} else {
@@ -963,7 +969,7 @@ error:
 static void
 createSpace(Parse * pParse, int iSpaceId)
 {
-	struct Table *table = pParse->pNewTable;
+	struct Table *table = pParse->create_table_def.new_table;
 	Vdbe *v = sqlite3GetVdbe(pParse);
 	int iFirstCol = ++pParse->nMem;
 	int iRecord = (pParse->nMem += 7);
@@ -1113,14 +1119,15 @@ vdbe_emit_fkey_create(struct Parse *parse_context, const struct fkey_def *fk)
 	 * of <CREATE TABLE ...> statement, we don't have child
 	 * id, but we know register where it will be stored.
 	 */
-	if (parse_context->pNewTable != NULL) {
+	if (parse_context->create_table_def.new_table != NULL) {
 		sqlite3VdbeAddOp2(vdbe, OP_SCopy, fk->child_id,
 				  constr_tuple_reg + 1);
 	} else {
 		sqlite3VdbeAddOp2(vdbe, OP_Integer, fk->child_id,
 				  constr_tuple_reg + 1);
 	}
-	if (parse_context->pNewTable != NULL && fkey_is_self_referenced(fk)) {
+	if (parse_context->create_table_def.new_table != NULL &&
+	    fkey_is_self_referenced(fk)) {
 		sqlite3VdbeAddOp2(vdbe, OP_SCopy, fk->parent_id,
 				  constr_tuple_reg + 2);
 	} else {
@@ -1183,7 +1190,7 @@ vdbe_emit_fkey_create(struct Parse *parse_context, const struct fkey_def *fk)
 			  constr_tuple_reg + 9);
 	sqlite3VdbeAddOp3(vdbe, OP_SInsert, BOX_FK_CONSTRAINT_ID, 0,
 			  constr_tuple_reg + 9);
-	if (parse_context->pNewTable == NULL)
+	if (parse_context->create_table_def.new_table == NULL)
 		sqlite3VdbeChangeP5(vdbe, OPFLAG_NCHANGE);
 	save_record(parse_context, BOX_FK_CONSTRAINT_ID, constr_tuple_reg, 2,
 		    vdbe->nOp - 1);
@@ -1257,7 +1264,7 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 		return;
 	}
 	assert(!db->mallocFailed);
-	p = pParse->pNewTable;
+	p = pParse->create_table_def.new_table;
 	if (p == 0)
 		return;
 
@@ -1306,7 +1313,7 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 	 * Check to see if we need to create an _sequence table
 	 * for keeping track of autoincrement keys.
 	 */
-	if (pParse->is_new_table_autoinc) {
+	if (pParse->create_table_def.has_autoinc) {
 		assert(reg_space_id != 0);
 		/* Do an insertion into _sequence. */
 		int reg_seq_id = ++pParse->nMem;
@@ -1329,7 +1336,8 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 	}
 	/* Code creation of FK constraints, if any. */
 	struct fkey_parse *fk_parse;
-	rlist_foreach_entry(fk_parse, &pParse->new_fkey, link) {
+	rlist_foreach_entry(fk_parse, &pParse->create_table_def.new_fkey,
+			    link) {
 		struct fkey_def *fk = fk_parse->fkey;
 		if (fk_parse->selfref_cols != NULL) {
 			struct ExprList *cols = fk_parse->selfref_cols;
@@ -1369,8 +1377,7 @@ cleanup:
 
 void
 sql_create_view(struct Parse *parse_context, struct Token *begin,
-		struct Token *name, struct ExprList *aliases,
-		struct Select *select, bool if_exists)
+		struct ExprList *aliases, struct Select *select)
 {
 	struct sqlite3 *db = parse_context->db;
 	struct Table *sel_tab = NULL;
@@ -1379,8 +1386,8 @@ sql_create_view(struct Parse *parse_context, struct Token *begin,
 				"parameters are not allowed in views");
 		goto create_view_fail;
 	}
-	sqlite3StartTable(parse_context, name, if_exists);
-	struct Table *p = parse_context->pNewTable;
+	sqlite3StartTable(parse_context);
+	struct Table *p = parse_context->create_table_def.new_table;
 	if (p == NULL || parse_context->nErr != 0)
 		goto create_view_fail;
 	sel_tab = sqlite3ResultSetOfSelect(parse_context, select);
@@ -1683,14 +1690,14 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space,
  * This routine is called to do the work of a DROP TABLE statement.
  *
  * @param parse_context Current parsing context.
- * @param table_name_list List containing table name.
  * @param is_view True, if statement is really 'DROP VIEW'.
- * @param if_exists True, if statement contains 'IF EXISTS' clause.
  */
 void
-sql_drop_table(struct Parse *parse_context, struct SrcList *table_name_list,
-	       bool is_view, bool if_exists)
+sql_drop_table(struct Parse *parse_context, bool is_view)
 {
+	struct drop_entity_def drop_def = parse_context->drop_entity_def;
+	assert(drop_def.base.entity_type == ENTITY_TYPE_TABLE);
+	struct SrcList *table_name_list = drop_def.base.entity_name;
 	struct Vdbe *v = sqlite3GetVdbe(parse_context);
 	struct sqlite3 *db = parse_context->db;
 	if (v == NULL || db->mallocFailed) {
@@ -1702,10 +1709,10 @@ sql_drop_table(struct Parse *parse_context, struct SrcList *table_name_list,
 	const char *space_name = table_name_list->a[0].zName;
 	struct space *space = space_by_name(space_name);
 	if (space == NULL) {
-		if (!is_view && !if_exists)
+		if (!is_view && !drop_def.if_exist)
 			sqlite3ErrorMsg(parse_context, "no such table: %s",
 					space_name);
-		if (is_view && !if_exists)
+		if (is_view && !drop_def.if_exist)
 			sqlite3ErrorMsg(parse_context, "no such view: %s",
 					space_name);
 		goto exit_drop_table;
@@ -1788,12 +1795,14 @@ columnno_by_name(struct Parse *parse_context, const struct space *space,
 }
 
 void
-sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
-		       struct Token *constraint, struct ExprList *child_cols,
-		       struct Token *parent, struct ExprList *parent_cols,
-		       bool is_deferred, int actions)
+sql_create_foreign_key(struct Parse *parse_context)
 {
 	struct sqlite3 *db = parse_context->db;
+	struct create_fk_def create_fk_def = parse_context->create_fk_def;
+	struct create_constraint_def create_constr_def = create_fk_def.base;
+	struct create_entity_def create_def = create_constr_def.base;
+	struct alter_entity_def alter_def = create_def.base;
+	assert(alter_def.entity_type == ENTITY_TYPE_FK);
 	/*
 	 * When this function is called second time during
 	 * <CREATE TABLE ...> statement (i.e. at VDBE runtime),
@@ -1812,20 +1821,21 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 	 * Table under construction during CREATE TABLE
 	 * processing. NULL for ALTER TABLE statement handling.
 	 */
-	struct Table *new_tab = parse_context->pNewTable;
+	struct create_table_def *table_def = &parse_context->create_table_def;
+	struct Table *new_tab = table_def->new_table;
 	/* Whether we are processing ALTER TABLE or CREATE TABLE. */
 	bool is_alter = new_tab == NULL;
 	uint32_t child_cols_count;
+	struct ExprList *child_cols = create_fk_def.child_cols;
 	if (child_cols == NULL) {
 		assert(!is_alter);
 		child_cols_count = 1;
 	} else {
 		child_cols_count = child_cols->nExpr;
 	}
-	assert(!is_alter || (child != NULL && child->nSrc == 1));
 	struct space *child_space = NULL;
 	if (is_alter) {
-		const char *child_name = child->a[0].zName;
+		const char *child_name = alter_def.entity_name->a[0].zName;
 		child_space = space_by_name(child_name);
 		if (child_space == NULL) {
 			diag_set(ClientError, ER_NO_SUCH_SPACE, child_name);
@@ -1840,8 +1850,9 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 			goto tnt_error;
 		}
 		memset(fk, 0, sizeof(*fk));
-		rlist_add_entry(&parse_context->new_fkey, fk, link);
+		rlist_add_entry(&table_def->new_fkey, fk, link);
 	}
+	struct Token *parent = create_fk_def.parent_name;
 	assert(parent != NULL);
 	parent_name = sqlite3NameFromToken(db, parent);
 	if (parent_name == NULL)
@@ -1853,11 +1864,12 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 	 */
 	is_self_referenced = !is_alter &&
 			     strcmp(parent_name, new_tab->def->name) == 0;
+	struct ExprList *parent_cols = create_fk_def.parent_cols;
 	struct space *parent_space = space_by_name(parent_name);
 	if (parent_space == NULL) {
 		if (is_self_referenced) {
 			struct fkey_parse *fk =
-				rlist_first_entry(&parse_context->new_fkey,
+				rlist_first_entry(&table_def->new_fkey,
 						  struct fkey_parse, link);
 			fk->selfref_cols = parent_cols;
 			fk->is_self_referenced = true;
@@ -1872,18 +1884,19 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 			goto exit_create_fk;
 		}
 	}
-	if (constraint == NULL && !is_alter) {
-		if (parse_context->constraintName.n == 0) {
+	if (!is_alter) {
+		if (create_def.name.n == 0) {
 			constraint_name =
 				sqlite3MPrintf(db, "FK_CONSTRAINT_%d_%s",
-					       ++parse_context->fkey_count,
+					       ++table_def->fkey_count,
 					       new_tab->def->name);
 		} else {
-			struct Token *cnstr_nm = &parse_context->constraintName;
-			constraint_name = sqlite3NameFromToken(db, cnstr_nm);
+			constraint_name =
+				sqlite3NameFromToken(db, &create_def.name);
 		}
 	} else {
-		constraint_name = sqlite3NameFromToken(db, constraint);
+		constraint_name =
+			sqlite3NameFromToken(db, &create_def.name);
 	}
 	if (constraint_name == NULL)
 		goto exit_create_fk;
@@ -1916,10 +1929,11 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 		diag_set(OutOfMemory, fk_size, "region", "struct fkey");
 		goto tnt_error;
 	}
+	int actions = create_fk_def.actions;
 	fk->field_count = child_cols_count;
 	fk->child_id = child_space != NULL ? child_space->def->id : 0;
 	fk->parent_id = parent_space != NULL ? parent_space->def->id : 0;
-	fk->is_deferred = is_deferred;
+	fk->is_deferred = create_constr_def.is_deferred;
 	fk->match = (enum fkey_match) ((actions >> 16) & 0xff);
 	fk->on_update = (enum fkey_action) ((actions >> 8) & 0xff);
 	fk->on_delete = (enum fkey_action) (actions & 0xff);
@@ -1973,7 +1987,7 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 	 */
 	if (!is_alter) {
 		struct fkey_parse *parse_fk =
-			rlist_first_entry(&parse_context->new_fkey,
+			rlist_first_entry(&table_def->new_fkey,
 					  struct fkey_parse, link);
 		parse_fk->fkey = fk;
 	} else {
@@ -1997,18 +2011,20 @@ void
 fkey_change_defer_mode(struct Parse *parse_context, bool is_deferred)
 {
 	if (parse_context->db->init.busy ||
-	    rlist_empty(&parse_context->new_fkey))
+	    rlist_empty(&parse_context->create_table_def.new_fkey))
 		return;
-	rlist_first_entry(&parse_context->new_fkey, struct fkey_parse,
-			  link)->fkey->is_deferred = is_deferred;
+	rlist_first_entry(&parse_context->create_table_def.new_fkey,
+			  struct fkey_parse, link)->fkey->is_deferred =
+		is_deferred;
 }
 
 void
-sql_drop_foreign_key(struct Parse *parse_context, struct SrcList *table,
-		     struct Token *constraint)
+sql_drop_foreign_key(struct Parse *parse_context)
 {
-	assert(table != NULL && table->nSrc == 1);
-	const char *table_name = table->a[0].zName;
+	struct drop_entity_def drop_def = parse_context->drop_entity_def;
+	assert(drop_def.base.entity_type == ENTITY_TYPE_FK);
+	const char *table_name = drop_def.base.entity_name->a[0].zName;
+	assert(table_name != NULL);
 	struct space *child = space_by_name(table_name);
 	if (child == NULL) {
 		diag_set(ClientError, ER_NO_SUCH_SPACE, table_name);
@@ -2017,7 +2033,7 @@ sql_drop_foreign_key(struct Parse *parse_context, struct SrcList *table,
 		return;
 	}
 	char *constraint_name = sqlite3NameFromToken(parse_context->db,
-						     constraint);
+						     &drop_def.name);
 	if (constraint_name != NULL)
 		vdbe_emit_fkey_drop(parse_context, constraint_name,
 				    child->def->id);
@@ -2211,19 +2227,29 @@ constraint_is_named(const char *name)
 }
 
 void
-sql_create_index(struct Parse *parse, struct Token *token,
-		 struct SrcList *tbl_name, struct ExprList *col_list,
-		 MAYBE_UNUSED struct Token *start, enum sort_order sort_order,
-		 bool if_not_exist, enum sql_index_type idx_type) {
+sql_create_index(struct Parse *parse) {
 	/* The index to be created. */
 	struct index *index = NULL;
 	/* Name of the index. */
 	char *name = NULL;
 	struct sqlite3 *db = parse->db;
 	assert(!db->init.busy);
+	struct create_index_def create_idx_def = parse->create_index_def;
+	struct create_entity_def *create_entity_def =
+		(struct create_entity_def *) &create_idx_def;
+	struct alter_entity_def alter_entity_def = create_entity_def->base;
+	assert(alter_entity_def.entity_type == ENTITY_TYPE_INDEX);
+	/*
+	 * Get list of columns to be indexed. It will be NULL if
+	 * this is a primary key or unique-constraint on the most
+	 * recent column added to the table under construction.
+	 */
+	struct ExprList *col_list = create_idx_def.cols;
+	struct SrcList *tbl_name = alter_entity_def.entity_name;
 
 	if (db->mallocFailed || parse->nErr > 0)
 		goto exit_create_index;
+	enum sql_index_type idx_type = create_idx_def.idx_type;
 	if (idx_type == SQL_INDEX_TYPE_UNIQUE ||
 	    idx_type == SQL_INDEX_TYPE_NON_UNIQUE) {
 		Vdbe *v = sqlite3GetVdbe(parse);
@@ -2238,12 +2264,13 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 */
 	struct space *space = NULL;
 	struct space_def *def = NULL;
+	struct Token token = create_entity_def->name;
 	if (tbl_name != NULL) {
-		assert(token != NULL && token->z != NULL);
+		assert(token.n > 0 && token.z != NULL);
 		const char *name = tbl_name->a[0].zName;
 		space = space_by_name(name);
 		if (space == NULL) {
-			if (! if_not_exist) {
+			if (! create_entity_def->if_not_exist) {
 				diag_set(ClientError, ER_NO_SUCH_SPACE, name);
 				parse->rc = SQL_TARANTOOL_ERROR;
 				parse->nErr++;
@@ -2252,12 +2279,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		}
 		def = space->def;
 	} else {
-		if (parse->pNewTable == NULL)
+		if (parse->create_table_def.new_table == NULL)
 			goto exit_create_index;
-		assert(token == NULL);
-		assert(start == NULL);
-		space = parse->pNewTable->space;
-		def = parse->pNewTable->def;
+		space = parse->create_table_def.new_table->space;
+		def = parse->create_table_def.new_table->def;
 	}
 
 	if (def->opts.is_view) {
@@ -2284,13 +2309,13 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 * 2) UNIQUE constraint is non-named and standard
 	 *    auto-index name will be generated.
 	 */
-	if (token != NULL) {
-		assert(token->z != NULL);
-		name = sqlite3NameFromToken(db, token);
+	if (parse->create_table_def.new_table == NULL) {
+		assert(token.z != NULL);
+		name = sqlite3NameFromToken(db, &token);
 		if (name == NULL)
 			goto exit_create_index;
 		if (sql_space_index_by_name(space, name) != NULL) {
-			if (!if_not_exist) {
+			if (! create_entity_def->if_not_exist) {
 				sqlite3ErrorMsg(parse,
 						"index %s.%s already exists",
 						def->name, name);
@@ -2299,11 +2324,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		}
 	} else {
 		char *constraint_name = NULL;
-		if (parse->constraintName.z != NULL)
+		if (create_entity_def->name.n > 0)
 			constraint_name =
 				sqlite3NameFromToken(db,
-						     &parse->constraintName);
-
+						     &create_entity_def->name);
 	       /*
 		* This naming is temporary. Now it's not
 		* possible (since we implement UNIQUE
@@ -2361,7 +2385,8 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		if (col_list == NULL)
 			goto exit_create_index;
 		assert(col_list->nExpr == 1);
-		sqlite3ExprListSetSortOrder(col_list, sort_order);
+		sqlite3ExprListSetSortOrder(col_list,
+					    create_idx_def.sort_order);
 	} else {
 		sqlite3ExprListCheckLength(parse, col_list, "index");
 	}
@@ -2442,7 +2467,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 * constraint, but has different onError (behavior on
 	 * constraint violation), then an error is raised.
 	 */
-	if (parse->pNewTable != NULL) {
+	if (parse->create_table_def.new_table != NULL) {
 		for (uint32_t i = 0; i < space->index_count; ++i) {
 			struct index *existing_idx = space->index[i];
 			uint32_t iid = existing_idx->def->iid;
@@ -2509,8 +2534,6 @@ sql_create_index(struct Parse *parse, struct Token *token,
 				  (void *)space_by_id(BOX_INDEX_ID),
 				  P4_SPACEPTR);
 		sqlite3VdbeChangeP5(vdbe, OPFLAG_SEEKEQ);
-
-		assert(start != NULL);
 		int index_id = getNewIid(parse, def->id, cursor);
 		sqlite3VdbeAddOp1(vdbe, OP_Close, cursor);
 		vdbe_emit_create_index(parse, def, index->def,
@@ -2534,30 +2557,32 @@ sql_create_index(struct Parse *parse, struct Token *token,
 }
 
 void
-sql_drop_index(struct Parse *parse_context, struct SrcList *index_name_list,
-	       struct Token *table_token, bool if_exists)
+sql_drop_index(struct Parse *parse_context)
 {
+	struct drop_entity_def drop_def = parse_context->drop_entity_def;
+	assert(drop_def.base.entity_type == ENTITY_TYPE_INDEX);
 	struct Vdbe *v = sqlite3GetVdbe(parse_context);
 	assert(v != NULL);
 	struct sqlite3 *db = parse_context->db;
 	/* Never called with prior errors. */
 	assert(parse_context->nErr == 0);
-	assert(table_token != NULL);
-	const char *table_name = sqlite3NameFromToken(db, table_token);
+	struct SrcList *table_list = drop_def.base.entity_name;
+	assert(table_list->nSrc == 1);
+	char *table_name = table_list->a[0].zName;
+	const char *index_name = NULL;
 	if (db->mallocFailed) {
 		goto exit_drop_index;
 	}
 	sqlite3VdbeCountChanges(v);
-	assert(index_name_list->nSrc == 1);
-	assert(table_token->n > 0);
 	struct space *space = space_by_name(table_name);
+	bool if_exists = drop_def.if_exist;
 	if (space == NULL) {
 		if (!if_exists)
 			sqlite3ErrorMsg(parse_context, "no such space: %s",
 					table_name);
 		goto exit_drop_index;
 	}
-	const char *index_name = index_name_list->a[0].zName;
+	index_name = sqlite3NameFromToken(db, &drop_def.name);
 	uint32_t index_id = box_index_id_by_name(space->def->id, index_name,
 						 strlen(index_name));
 	if (index_id == BOX_ID_NIL) {
@@ -2583,8 +2608,8 @@ sql_drop_index(struct Parse *parse_context, struct SrcList *index_name_list,
 	sqlite3VdbeAddOp2(v, OP_SDelete, BOX_INDEX_ID, record_reg);
 	sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
  exit_drop_index:
-	sqlite3SrcListDelete(db, index_name_list);
-	sqlite3DbFree(db, (void *) table_name);
+	sqlite3SrcListDelete(db, table_list);
+	sqlite3DbFree(db, (void *) index_name);
 }
 
 /*
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 0bcf41594..e8b053361 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -168,7 +168,9 @@ cmd ::= ROLLBACK TO savepoint_opt nm(X). {
 //
 cmd ::= create_table create_table_args.
 create_table ::= createkw TABLE ifnotexists(E) nm(Y). {
-   sqlite3StartTable(pParse,&Y,E);
+  alter_entity_def_init(&pParse->create_table_def, NULL, ENTITY_TYPE_TABLE);
+  create_entity_def_init(&pParse->create_table_def, Y, E);
+  sqlite3StartTable(pParse);
 }
 createkw(A) ::= CREATE(A).  {disableLookaside(pParse);}
 
@@ -237,8 +239,15 @@ nm(A) ::= id(A). {
 carglist ::= carglist cconsdef.
 carglist ::= .
 cconsdef ::= cconsname ccons.
-cconsname ::= CONSTRAINT nm(X).           {pParse->constraintName = X;}
-cconsname ::= .                           {pParse->constraintName.n = 0;}
+cconsname ::= cconsname_start cconsname_parse .
+cconsname_start ::= . {
+  /* Prepare base members for re-usage. */
+  memset(&pParse->create_index_def, 0, sizeof(struct create_index_def));
+}
+cconsname_parse ::= CONSTRAINT nm(X). {
+  create_entity_def_init(&pParse->create_index_def, X, false);
+}
+cconsname_parse ::= .
 ccons ::= DEFAULT term(X).            {sqlite3AddDefaultValue(pParse,&X);}
 ccons ::= DEFAULT LP expr(X) RP.      {sqlite3AddDefaultValue(pParse,&X);}
 ccons ::= DEFAULT PLUS term(X).       {sqlite3AddDefaultValue(pParse,&X);}
@@ -260,14 +269,29 @@ ccons ::= NULL onconf(R).        {
         sql_column_add_nullable_action(pParse, R);
 }
 ccons ::= NOT NULL onconf(R).    {sql_column_add_nullable_action(pParse, R);}
-ccons ::= PRIMARY KEY sortorder(Z) autoinc(I).
-                                 {sqlite3AddPrimaryKey(pParse,0,I,Z);}
-ccons ::= UNIQUE.                {sql_create_index(pParse,0,0,0,0,
-                                                   SORT_ORDER_ASC, false,
-                                                   SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
-ccons ::= CHECK LP expr(X) RP.   {sql_add_check_constraint(pParse,&X);}
-ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R).
-                                 {sql_create_foreign_key(pParse, NULL, NULL, NULL, &T, TA, false, R);}
+ccons ::= PRIMARY KEY sortorder(Z) autoinc(I). {
+  pParse->create_table_def.has_autoinc = I;
+  sqlite3AddPrimaryKey(pParse,0,Z);
+}
+ccons ::= UNIQUE. {
+  pParse->create_index_def.idx_type = SQL_INDEX_TYPE_CONSTRAINT_UNIQUE;
+  entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);
+  sql_create_index(pParse);
+}
+
+ccons ::= check_constraint_def .
+
+check_constraint_def ::= CHECK LP expr(X) RP. {
+  pParse->create_ck_def.expr = &X;
+  entity_set_type(&pParse->create_index_def, ENTITY_TYPE_CK);
+  sql_add_check_constraint(pParse);
+}
+
+ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R). {
+  create_fk_def_init(&pParse->create_fk_def, NULL, &T, TA, R);
+  entity_set_type(&pParse->create_fk_def, ENTITY_TYPE_FK);
+  sql_create_foreign_key(pParse);
+}
 ccons ::= defer_subclause(D).    {fkey_change_defer_mode(pParse, D);}
 ccons ::= COLLATE id(C).        {sqlite3AddCollateType(pParse, &C);}
 
@@ -307,20 +331,24 @@ init_deferred_pred_opt(A) ::= .                       {A = 0;}
 init_deferred_pred_opt(A) ::= INITIALLY DEFERRED.     {A = 1;}
 init_deferred_pred_opt(A) ::= INITIALLY IMMEDIATE.    {A = 0;}
 
-tconsdef ::= tconsname tcons.
-tconsname ::= CONSTRAINT nm(X).      {pParse->constraintName = X;}
-tconsname ::= .                      {pParse->constraintName.n = 0;}
-tcons ::= PRIMARY KEY LP sortlist(X) autoinc(I) RP.
-                                 {sqlite3AddPrimaryKey(pParse,X,I,0);}
-tcons ::= UNIQUE LP sortlist(X) RP.
-                                 {sql_create_index(pParse,0,0,X,0,
-                                                   SORT_ORDER_ASC,false,
-                                                   SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
-tcons ::= CHECK LP expr(E) RP onconf.
-                                 {sql_add_check_constraint(pParse,&E);}
+tconsdef ::= cconsname tcons.
+tcons ::= PRIMARY KEY LP sortlist(X) autoinc(I) RP. {
+  pParse->create_table_def.has_autoinc = I;
+  sqlite3AddPrimaryKey(pParse, X, 0);
+}
+tcons ::= UNIQUE LP sortlist(X) RP. {
+  pParse->create_index_def.cols = X;
+  pParse->create_index_def.idx_type = SQL_INDEX_TYPE_CONSTRAINT_UNIQUE;
+  entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);
+  sql_create_index(pParse);
+}
+tcons ::= check_constraint_def .
 tcons ::= FOREIGN KEY LP eidlist(FA) RP
           REFERENCES nm(T) eidlist_opt(TA) refargs(R) defer_subclause_opt(D). {
-    sql_create_foreign_key(pParse, NULL, NULL, FA, &T, TA, D, R);
+  create_fk_def_init(&pParse->create_fk_def, FA, &T, TA, R);
+  entity_set_defer_mode(&pParse->create_fk_def, D);
+  entity_set_type(&pParse->create_fk_def, ENTITY_TYPE_FK);
+  sql_create_foreign_key(pParse);
 }
 %type defer_subclause_opt {int}
 defer_subclause_opt(A) ::= .                    {A = 0;}
@@ -343,9 +371,20 @@ resolvetype(A) ::= REPLACE.                  {A = ON_CONFLICT_ACTION_REPLACE;}
 
 ////////////////////////// The DROP TABLE /////////////////////////////////////
 //
-cmd ::= DROP TABLE ifexists(E) fullname(X). {
-  sql_drop_table(pParse, X, 0, E);
+
+cmd ::= drop_start(X) drop_table . {
+  sql_drop_table(pParse, X);
 }
+
+drop_table ::= ifexists(E) fullname(X) . {
+  alter_entity_def_init(&pParse->drop_entity_def, X, ENTITY_TYPE_TABLE);
+  pParse->drop_entity_def.if_exist = E;
+}
+
+%type drop_start {bool}
+drop_start(X) ::= DROP VIEW . { X = true; }
+drop_start(X) ::= DROP TABLE . { X = false; }
+
 %type ifexists {int}
 ifexists(A) ::= IF EXISTS.   {A = 1;}
 ifexists(A) ::= .            {A = 0;}
@@ -354,13 +393,13 @@ ifexists(A) ::= .            {A = 0;}
 //
 cmd ::= createkw(X) VIEW ifnotexists(E) nm(Y) eidlist_opt(C)
           AS select(S). {
-  if (!pParse->parse_only)
-    sql_create_view(pParse, &X, &Y, C, S, E);
-  else
+  if (!pParse->parse_only) {
+    alter_entity_def_init(&pParse->create_table_def, NULL, ENTITY_TYPE_TABLE);
+    create_entity_def_init(&pParse->create_table_def, Y, E);
+    sql_create_view(pParse, &X, C, S);
+  } else {
     sql_store_select(pParse, S);
-}
-cmd ::= DROP VIEW ifexists(E) fullname(X). {
-  sql_drop_table(pParse, X, 1, E);
+  }
 }
 
 //////////////////////// The SELECT statement /////////////////////////////////
@@ -1198,10 +1237,15 @@ paren_exprlist(A) ::= LP exprlist(X) RP.  {A = X;}
 
 ///////////////////////////// The CREATE INDEX command ///////////////////////
 //
-cmd ::= createkw(S) uniqueflag(U) INDEX ifnotexists(NE) nm(X)
+cmd ::= createkw uniqueflag(U) INDEX ifnotexists(NE) nm(X)
         ON nm(Y) LP sortlist(Z) RP. {
-  sql_create_index(pParse, &X, sqlite3SrcListAppend(pParse->db,0,&Y), Z, &S,
-                   SORT_ORDER_ASC, NE, U);
+  alter_entity_def_init(&pParse->create_index_def,
+                        sqlite3SrcListAppend(pParse->db, 0, &Y),
+                        ENTITY_TYPE_INDEX);
+  create_entity_def_init(&pParse->create_index_def, X, NE);
+  pParse->create_index_def.idx_type = U;
+  pParse->create_index_def.cols = Z;
+  sql_create_index(pParse);
 }
 
 %type uniqueflag {int}
@@ -1263,8 +1307,10 @@ collate(C) ::= COLLATE id.   {C = 1;}
 
 ///////////////////////////// The DROP INDEX command /////////////////////////
 //
-cmd ::= DROP INDEX ifexists(E) fullname(X) ON nm(Y).   {
-    sql_drop_index(pParse, X, &Y, E);
+cmd ::= DROP INDEX ifexists(E) nm(X) ON fullname(Y).   {
+  alter_entity_def_init(&pParse->drop_entity_def, Y, ENTITY_TYPE_INDEX);
+  drop_entity_def_init(&pParse->drop_entity_def, X, E);
+  sql_drop_index(pParse);
 }
 
 ///////////////////////////// The PRAGMA command /////////////////////////////
@@ -1315,7 +1361,14 @@ cmd ::= createkw trigger_decl(A) BEGIN trigger_cmd_list(S) END(Z). {
 trigger_decl(A) ::= TRIGGER ifnotexists(NOERR) nm(B)
                     trigger_time(C) trigger_event(D)
                     ON fullname(E) foreach_clause when_clause(G). {
-  sql_trigger_begin(pParse, &B, C, D.a, D.b, E, G, NOERR);
+  struct create_trigger_def *trigger_def = &pParse->create_trigger_def;
+  alter_entity_def_init(trigger_def, E, ENTITY_TYPE_TRIGGER);
+  create_entity_def_init(trigger_def, B, NOERR);
+  trigger_def->tr_tm = C;
+  trigger_def->op = D.a;
+  trigger_def->cols = D.b;
+  trigger_def->when = G;
+  sql_trigger_begin(pParse);
   A = B; /*A-overwrites-T*/
 }
 
@@ -1425,7 +1478,9 @@ raisetype(A) ::= FAIL.      {A = ON_CONFLICT_ACTION_FAIL;}
 
 ////////////////////////  DROP TRIGGER statement //////////////////////////////
 cmd ::= DROP TRIGGER ifexists(NOERR) fullname(X). {
-  sql_drop_trigger(pParse,X,NOERR);
+  alter_entity_def_init(&pParse->drop_entity_def, X, ENTITY_TYPE_TRIGGER);
+  drop_entity_def_init(&pParse->drop_entity_def, (struct Token){}, NOERR);
+  sql_drop_trigger(pParse);
 }
 
 /////////////////////////////////// ANALYZE ///////////////////////////////////
@@ -1434,17 +1489,25 @@ cmd ::= ANALYZE nm(X).          {sqlite3Analyze(pParse, &X);}
 
 //////////////////////// ALTER TABLE table ... ////////////////////////////////
 cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). {
-  sql_alter_table_rename(pParse,X,&Z);
+  alter_entity_def_init(&pParse->rename_entity_def, X, ENTITY_TYPE_TABLE);
+  pParse->rename_entity_def.new_name = Z;
+  sql_alter_table_rename(pParse);
 }
 
 cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT nm(Z) FOREIGN KEY
         LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) refargs(R)
         defer_subclause_opt(D). {
-    sql_create_foreign_key(pParse, X, &Z, FA, &T, TA, D, R);
+  alter_entity_def_init(&pParse->create_fk_def, X, ENTITY_TYPE_FK);
+  create_entity_def_init(&pParse->create_fk_def, Z, false);
+  entity_set_defer_mode(&pParse->create_fk_def, D);
+  create_fk_def_init(&pParse->create_fk_def, FA, &T, TA, R);
+  sql_create_foreign_key(pParse);
 }
 
 cmd ::= ALTER TABLE fullname(X) DROP CONSTRAINT nm(Z). {
-    sql_drop_foreign_key(pParse, X, &Z);
+  alter_entity_def_init(&pParse->drop_entity_def, X, ENTITY_TYPE_FK);
+  drop_entity_def_init(&pParse->drop_entity_def, Z, false);
+  sql_drop_foreign_key(pParse);
 }
 
 //////////////////////// COMMON TABLE EXPRESSIONS ////////////////////////////
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
new file mode 100644
index 000000000..7a61a27e0
--- /dev/null
+++ b/src/box/sql/parse_def.h
@@ -0,0 +1,260 @@
+#ifndef TARANTOOL_BOX_SQL_PARSE_DEF_H_INCLUDED
+#define TARANTOOL_BOX_SQL_PARSE_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 "box/fkey.h"
+
+/**
+ * This file contains auxiliary structures and functions which
+ * are used only during parsing routine (see parse.y).
+ * Their main purpose is to assemble common parts of altered
+ * entities (such as name, or IF EXISTS clause) and pass them
+ * as a one object to further functions.
+ *
+ * Hierarchy is following:
+ *
+ * Base structure is ALTER.
+ * ALTER is omitted only for CREATE TABLE since table is filled
+ * with meta-information just-in-time of parsing:
+ * for instance, as soon as field's name and type are recognized
+ * they are added to space definition.
+ *
+ * DROP is general for all existing objects and includes
+ * name of object itself, name of parent object (table),
+ * IF EXISTS clause and may contain on-drop behaviour
+ * (CASCADE/RESTRICT, but now it is always RESTRICT).
+ * Hence, it terms of grammar - it is a terminal symbol.
+ *
+ * RENAME can be applied only to table (at least now, since it is
+ * ANSI extension), so it is also terminal symbol.
+ *
+ * CREATE in turn can be expanded to nonterminal symbol
+ * CREATE CONSTRAINT or to terminal CREATE TABLE/INDEX/TRIGGER.
+ * CREATE CONSTRAINT unfolds to FOREIGN KEY or UNIQUE/PRIMARY KEY.
+ *
+ * For instance:
+ * ALTER TABLE t ADD CONSTRAINT c FOREIGN KEY REFERENCES t2(id);
+ * ALTER *TABLE* -> CREATE ENTITY -> CREATE CONSTRAINT -> CREATE FK
+ *
+ * CREATE TRIGGER tr1 ...
+ * ALTER *TABLE* -> CREATE ENTITY -> CREATE TRIGGER
+ *
+ * All terminal symbols are stored as a union within
+ * parsing context (struct Parse).
+ */
+
+/**
+ * Each token coming out of the lexer is an instance of
+ * this structure. Tokens are also used as part of an expression.
+ *
+ * Note if Token.z is NULL then Token.n is undefined.
+ */
+struct Token {
+	/** Text of the token. Not NULL-terminated! */
+	const char *z;
+	/** Number of characters in this token. */
+	unsigned int n;
+	bool isReserved;
+};
+
+/**
+ * Possible SQL index types. Note that PK and UNIQUE constraints
+ * are implemented as indexes and have their own types:
+ * _CONSTRAINT_PK and _CONSTRAINT_UNIQUE.
+ */
+enum sql_index_type {
+	SQL_INDEX_TYPE_NON_UNIQUE = 0,
+	SQL_INDEX_TYPE_UNIQUE,
+	SQL_INDEX_TYPE_CONSTRAINT_UNIQUE,
+	SQL_INDEX_TYPE_CONSTRAINT_PK,
+	sql_index_type_MAX
+};
+
+enum entity_type {
+	ENTITY_TYPE_TABLE = 0,
+	ENTITY_TYPE_INDEX,
+	ENTITY_TYPE_TRIGGER,
+	ENTITY_TYPE_CK,
+	ENTITY_TYPE_FK,
+	entity_type_MAX
+};
+
+struct alter_entity_def {
+	/** Type of topmost entity. */
+	enum entity_type entity_type;
+	/** As a rule it is a name of table to be altered. */
+	struct SrcList *entity_name;
+};
+
+struct rename_entity_def {
+	struct alter_entity_def base;
+	struct Token new_name;
+};
+
+struct create_entity_def {
+	struct alter_entity_def base;
+	struct Token name;
+	/** Statement comes with IF NOT EXISTS clause. */
+	bool if_not_exist;
+};
+
+struct create_table_def {
+	struct create_entity_def base;
+	struct Table *new_table;
+	/**
+	 * Number of FK constraints declared within
+	 * CREATE TABLE statement.
+	 */
+	uint32_t fkey_count;
+	/**
+	 * Foreign key constraint appeared in CREATE TABLE stmt.
+	 */
+	struct rlist new_fkey;
+	/** True, if table to be created has AUTOINCREMENT PK. */
+	bool has_autoinc;
+};
+
+struct drop_entity_def {
+	struct alter_entity_def base;
+	/** Name of index/trigger/constraint to be dropped. */
+	struct Token name;
+	/** Statement comes with IF EXISTS clause. */
+	bool if_exist;
+};
+
+struct create_trigger_def {
+	struct create_entity_def base;
+	/** One of TK_BEFORE, TK_AFTER, TK_INSTEAD. */
+	int tr_tm;
+	/** One of TK_INSERT, TK_UPDATE, TK_DELETE. */
+	int op;
+	/** Column list if this is an UPDATE trigger. */
+	struct IdList *cols;
+	/** When clause. */
+	struct Expr *when;
+};
+
+struct create_constraint_def {
+	struct create_entity_def base;
+	/** One of DEFERRED, IMMEDIATE. */
+	bool is_deferred;
+};
+
+struct create_ck_def {
+	struct create_constraint_def base;
+	/** AST representing check expression. */
+	struct ExprSpan *expr;
+};
+
+struct create_fk_def {
+	struct create_constraint_def base;
+	struct ExprList *child_cols;
+	struct Token *parent_name;
+	struct ExprList *parent_cols;
+	/**
+	 * Encoded actions for MATCH, ON DELETE and
+	 * ON UPDATE clauses.
+	 */
+	int actions;
+};
+
+struct create_index_def {
+	struct create_constraint_def base;
+	/** List of indexed columns. */
+	struct ExprList *cols;
+	/** One of _PRIMARY_KEY, _UNIQUE, _NON_UNIQUE. */
+	enum sql_index_type idx_type;
+	enum sort_order sort_order;
+};
+
+/**
+ * In those functions which accept void * instead of certain type
+ * it was made on purpose: it allows to avoid explicit cast before
+ * passing parameter to function. Hence, we can invoke it like:
+ * entity_set_type(create_fk_def, ...); instead of
+ * entity_set_type((struct alter_entity_def *) create_fk_def, ...);
+ */
+static inline void
+entity_set_type(void *entity_def, enum entity_type type)
+{
+	struct alter_entity_def *alter_def =
+		(struct alter_entity_def *) entity_def;
+	alter_def->entity_type = type;
+}
+
+static inline void
+entity_set_defer_mode(void *entity_def, bool is_deferred)
+{
+	struct create_constraint_def *constr_def =
+		(struct create_constraint_def *) entity_def;
+	constr_def->is_deferred = is_deferred;
+}
+
+static inline void
+alter_entity_def_init(void *entity_def, struct SrcList *entity_name,
+		      enum entity_type type)
+{
+	struct alter_entity_def *alter_def =
+		(struct alter_entity_def *) entity_def;
+	alter_def->entity_name = entity_name;
+	alter_def->entity_type = type;
+}
+
+static inline void
+create_entity_def_init(void *entity_def, struct Token name, bool if_not_exist)
+{
+	struct create_entity_def *create_def =
+		(struct create_entity_def *) entity_def;
+	create_def->name = name;
+	create_def->if_not_exist = if_not_exist;
+}
+
+static inline void
+drop_entity_def_init(struct drop_entity_def *drop_def, struct Token name,
+		     bool if_exist)
+{
+	drop_def->name = name;
+	drop_def->if_exist = if_exist;
+}
+
+static inline void
+create_fk_def_init(struct create_fk_def *fk_def, struct ExprList *child_cols,
+		   struct Token *parent_name, struct ExprList *parent_cols,
+		   int actions)
+{
+	assert(fk_def != NULL);
+	fk_def->child_cols = child_cols;
+	fk_def->parent_name = parent_name;
+	fk_def->parent_cols = parent_cols;
+	fk_def->actions = actions;
+}
+
+#endif /* TARANTOOL_BOX_SQL_PARSE_DEF_H_INCLUDED */
diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index 824578e45..3343c2942 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -273,7 +273,7 @@ sql_parser_create(struct Parse *parser, sqlite3 *db)
 {
 	memset(parser, 0, sizeof(struct Parse));
 	parser->db = db;
-	rlist_create(&parser->new_fkey);
+	rlist_create(&parser->create_table_def.new_fkey);
 	rlist_create(&parser->record_list);
 	region_create(&parser->region, &cord()->slabc);
 }
@@ -287,7 +287,7 @@ sql_parser_destroy(Parse *parser)
 	sqlite3DbFree(db, parser->aLabel);
 	sql_expr_list_delete(db, parser->pConstExpr);
 	struct fkey_parse *fk;
-	rlist_foreach_entry(fk, &parser->new_fkey, link)
+	rlist_foreach_entry(fk, &parser->create_table_def.new_fkey, link)
 		sql_expr_list_delete(db, fk->selfref_cols);
 	if (db != NULL) {
 		assert(db->lookaside.bDisable >=
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 6462467bc..e66f0d6db 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -1616,9 +1616,9 @@ void
 sql_resolve_self_reference(struct Parse *parser, struct Table *table, int type,
 			   struct Expr *expr, struct ExprList *expr_list)
 {
-	/* Fake SrcList for parser->pNewTable */
+	/* Fake SrcList for parser->new_table */
 	SrcList sSrc;
-	/* Name context for parser->pNewTable */
+	/* Name context for parser->new_table */
 	NameContext sNC;
 
 	assert(type == NC_IsCheck || type == NC_IdxExpr);
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index bc617d7ce..4ee2ed5cf 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -67,6 +67,7 @@
 
 #include <stdbool.h>
 
+#include "parse_def.h"
 #include "box/field_def.h"
 #include "box/sql.h"
 #include "box/txn.h"
@@ -1915,19 +1916,6 @@ struct UnpackedRecord {
 				 */
 };
 
-/*
- * Possible SQL index types. Note that PK and UNIQUE constraints
- * are implemented as indexes and have their own types:
- * SQL_INDEX_TYPE_CONSTRAINT_PK and
- * SQL_INDEX_TYPE_CONSTRAINT_UNIQUE.
- */
-enum sql_index_type {
-    SQL_INDEX_TYPE_NON_UNIQUE = 0,
-    SQL_INDEX_TYPE_UNIQUE,
-    SQL_INDEX_TYPE_CONSTRAINT_UNIQUE,
-    SQL_INDEX_TYPE_CONSTRAINT_PK,
-};
-
 /**
  * Fetch statistics concerning tuples to be selected:
  * logarithm of number of tuples which has the same key as for
@@ -1957,20 +1945,6 @@ index_field_tuple_est(const struct index_def *idx, uint32_t field);
 /** [10*log_{2}(1048576)] == 200 */
 #define DEFAULT_TUPLE_LOG_COUNT 200
 
-/*
- * Each token coming out of the lexer is an instance of
- * this structure.  Tokens are also used as part of an expression.
- *
- * Note if Token.z==0 then Token.dyn and Token.n are undefined and
- * may contain random values.  Do not make any assumptions about Token.dyn
- * and Token.n when Token.z==0.
- */
-struct Token {
-	const char *z;		/* Text of the token.  Not NULL-terminated! */
-	unsigned int n;		/* Number of characters in this token */
-	bool isReserved;         /* If reserved keyword or not */
-};
-
 /*
  * An instance of this structure contains information needed to generate
  * code for a SELECT that contains aggregate functions.
@@ -2728,7 +2702,6 @@ struct Parse {
 	int nLabel;		/* Number of labels used */
 	int *aLabel;		/* Space to hold the labels */
 	ExprList *pConstExpr;	/* Constant expressions */
-	Token constraintName;	/* Name of the constraint currently being parsed */
 	int nMaxArg;		/* Max args passed to user function by sub-program */
 	int nSelect;		/* Number of SELECT statements seen */
 	int nSelectIndent;	/* How far to indent SELECTTRACE() output */
@@ -2775,28 +2748,30 @@ struct Parse {
 	VList *pVList;		/* Mapping between variable names and numbers */
 	Vdbe *pReprepare;	/* VM being reprepared (sqlite3Reprepare()) */
 	const char *zTail;	/* All SQL text past the last semicolon parsed */
-	Table *pNewTable;	/* A table being constructed by CREATE TABLE */
 	Table *pZombieTab;	/* List of Table objects to delete after code gen */
 	TriggerPrg *pTriggerPrg;	/* Linked list of coded triggers */
 	With *pWith;		/* Current WITH clause, or NULL */
 	With *pWithToFree;	/* Free this WITH object at the end of the parse */
 	/**
-	 * Number of FK constraints declared within
-	 * CREATE TABLE statement.
-	 */
-	uint32_t fkey_count;
-	/**
-	 * Foreign key constraint appeared in CREATE TABLE stmt.
+	 * One of parse_def structures which are used to
+	 * assemble and carry arguments of DDL routines
+	 * from parse.y
 	 */
-	struct rlist new_fkey;
+	union {
+		struct create_ck_def create_ck_def;
+		struct create_fk_def create_fk_def;
+		struct create_index_def create_index_def;
+		struct create_trigger_def create_trigger_def;
+		struct rename_entity_def rename_entity_def;
+		struct drop_entity_def drop_entity_def;
+	};
+	struct create_table_def create_table_def;
 	/**
 	 * List of all records that were inserted in system spaces
 	 * in current statement.
 	 */
 	struct rlist record_list;
 	bool initiateTTrans;	/* Initiate Tarantool transaction */
-	/** True, if table to be created has AUTOINCREMENT PK. */
-	bool is_new_table_autoinc;
 	/** If set - do not emit byte code at all, just parse.  */
 	bool parse_only;
 	/** Type of parsed_ast member. */
@@ -3357,7 +3332,7 @@ Table *sqlite3ResultSetOfSelect(Parse *, Select *);
 struct index *
 sql_table_primary_key(const struct Table *tab);
 
-void sqlite3StartTable(Parse *, Token *, int);
+void sqlite3StartTable(Parse *);
 void sqlite3AddColumn(Parse *, Token *, struct type_def *);
 
 /**
@@ -3375,16 +3350,15 @@ void
 sql_column_add_nullable_action(struct Parse *parser,
 			       enum on_conflict_action nullable_action);
 
-void sqlite3AddPrimaryKey(Parse *, ExprList *, int, enum sort_order);
+void sqlite3AddPrimaryKey(Parse *, ExprList *, enum sort_order);
 
 /**
  * Add a new CHECK constraint to the table currently under
  * construction.
  * @param parser Parsing context.
- * @param span Expression span object.
  */
 void
-sql_add_check_constraint(Parse *parser, ExprSpan *span);
+sql_add_check_constraint(Parse *parser);
 
 void sqlite3AddDefaultValue(Parse *, ExprSpan *);
 void sqlite3AddCollateType(Parse *, Token *);
@@ -3432,15 +3406,12 @@ int sqlite3FaultSim(int);
  *
  * @param parse_context Current parsing context.
  * @param begin The CREATE token that begins the statement.
- * @param name The token that holds the name of the view.
  * @param aliases Optional list of view column names.
  * @param select A SELECT statement that will become the new view.
- * @param if_exists Suppress error messages if VIEW already exists.
  */
 void
 sql_create_view(struct Parse *parse_context, struct Token *begin,
-		struct Token *name, struct ExprList *aliases,
-		struct Select *select, bool if_exists);
+		struct ExprList *aliases, struct Select *select);
 
 /**
  * Helper to convert SQLite affinity to corresponding
@@ -3472,7 +3443,7 @@ void
 sql_store_select(struct Parse *parse_context, struct Select *select);
 
 void
-sql_drop_table(struct Parse *, struct SrcList *, bool, bool);
+sql_drop_table(struct Parse *, bool);
 void sqlite3DeleteTable(sqlite3 *, Table *);
 void sqlite3Insert(Parse *, SrcList *, Select *, IdList *,
 		   enum on_conflict_action);
@@ -3501,36 +3472,19 @@ void sqlite3IdListDelete(sqlite3 *, IdList *);
  * parse->pNewTable is a table that is currently being
  * constructed by a CREATE TABLE statement.
  *
- * col_list is a list of columns to be indexed.  col_list will be
- * NULL if this is a primary key or unique-constraint on the most
- * recent column added to the table currently under construction.
- *
  * @param parse All information about this parse.
- * @param token Index name. May be NULL.
- * @param tbl_name Table to index. Use pParse->pNewTable ifNULL.
- * @param col_list A list of columns to be indexed.
- * @param start The CREATE token that begins this statement.
- * @param sort_order Sort order of primary key when pList==NULL.
- * @param if_not_exist Omit error if index already exists.
- * @param idx_type The index type.
  */
 void
-sql_create_index(struct Parse *parse, struct Token *token,
-		 struct SrcList *tbl_name, struct ExprList *col_list,
-		 struct Token *start, enum sort_order sort_order,
-		 bool if_not_exist, enum sql_index_type idx_type);
+sql_create_index(struct Parse *parse);
 
 /**
  * This routine will drop an existing named index.  This routine
  * implements the DROP INDEX statement.
  *
  * @param parse_context Current parsing context.
- * @param index_name_list List containing index name.
- * @param table_token Token representing table name.
- * @param if_exists True, if statement contains 'IF EXISTS' clause.
  */
 void
-sql_drop_index(struct Parse *, struct SrcList *, struct Token *, bool);
+sql_drop_index(struct Parse *parse_context);
 
 int sqlite3Select(Parse *, Select *, SelectDest *);
 Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
@@ -3932,20 +3886,9 @@ sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
  * After the trigger actions have been parsed, the
  * sql_trigger_finish() function is called to complete the trigger
  * construction process.
- *
- * @param parse The parse context of the CREATE TRIGGER statement.
- * @param name The name of the trigger.
- * @param tr_tm One of TK_BEFORE, TK_AFTER, TK_INSTEAD.
- * @param op One of TK_INSERT, TK_UPDATE, TK_DELETE.
- * @param columns column list if this is an UPDATE OF trigger.
- * @param table The name of the table/view the trigger applies to.
- * @param when  WHEN clause.
- * @param no_err Suppress errors if the trigger already exists.
  */
 void
-sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
-		  int op, struct IdList *columns, struct SrcList *table,
-		  struct Expr *when, int no_err);
+sql_trigger_begin(struct Parse *parse);
 
 /**
  * This routine is called after all of the trigger actions have
@@ -3965,11 +3908,9 @@ sql_trigger_finish(struct Parse *parse, struct TriggerStep *step_list,
  * VDBE code.
  *
  * @param parser Parser context.
- * @param name The name of trigger to drop.
- * @param no_err Suppress errors if the trigger already exists.
  */
 void
-sql_drop_trigger(struct Parse *parser, struct SrcList *name, bool no_err);
+sql_drop_trigger(struct Parse *parser);
 
 /**
  * Drop a trigger given a pointer to that trigger.
@@ -4149,38 +4090,18 @@ fkey_change_defer_mode(struct Parse *parse_context, bool is_deferred);
  * OR to handle <CREATE TABLE ...>
  *
  * @param parse_context Parsing context.
- * @param child Name of table to be altered. NULL on CREATE TABLE
- *              statement processing.
- * @param constraint Name of the constraint to be created. May be
- *                   NULL on CREATE TABLE statement processing.
- *                   Then, auto-generated name is used.
- * @param child_cols Columns of child table involved in FK.
- *                   May be NULL on CREATE TABLE statement processing.
- *                   If so, the last column added is used.
- * @param parent Name of referenced table.
- * @param parent_cols List of referenced columns. If NULL, columns
- *                    which make up PK of referenced table are used.
- * @param is_deferred Is FK constraint initially deferred.
- * @param actions ON DELETE, UPDATE and INSERT resolution
- *                algorithms (e.g. CASCADE, RESTRICT etc).
  */
 void
-sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
-		       struct Token *constraint, struct ExprList *child_cols,
-		       struct Token *parent, struct ExprList *parent_cols,
-		       bool is_deferred, int actions);
+sql_create_foreign_key(struct Parse *parse_context);
 
 /**
  * Function called from parser to handle
  * <ALTER TABLE table DROP CONSTRAINT constraint> SQL statement.
  *
  * @param parse_context Parsing context.
- * @param table Table to be altered.
- * @param constraint Name of constraint to be dropped.
  */
 void
-sql_drop_foreign_key(struct Parse *parse_context, struct SrcList *table,
-		     struct Token *constraint);
+sql_drop_foreign_key(struct Parse *parse_context);
 
 void sqlite3Detach(Parse *, Expr *);
 int sqlite3AtoF(const char *z, double *, int);
@@ -4398,12 +4319,9 @@ extern int sqlite3PendingByte;
  * command.
  *
  * @param parse Current parsing context.
- * @param src_tab The table to rename.
- * @param new_name_tk Token containing new name of the table.
  */
 void
-sql_alter_table_rename(struct Parse *parse, struct SrcList *src_tab,
-		       struct Token *new_name_tk);
+sql_alter_table_rename(struct Parse *parse);
 
 /**
  * Return the length (in bytes) of the token that begins at z[0].
diff --git a/src/box/sql/tokenize.c b/src/box/sql/tokenize.c
index 4eebfe527..f2088f118 100644
--- a/src/box/sql/tokenize.c
+++ b/src/box/sql/tokenize.c
@@ -449,7 +449,7 @@ sqlite3RunParser(Parse * pParse, const char *zSql, char **pzErrMsg)
 		sqlite3OomFault(db);
 		return SQLITE_NOMEM_BKPT;
 	}
-	assert(pParse->pNewTable == 0);
+	assert(pParse->create_table_def.new_table == NULL);
 	assert(pParse->parsed_ast.trigger == NULL);
 	assert(pParse->nVar == 0);
 	assert(pParse->pVList == 0);
@@ -529,7 +529,7 @@ sqlite3RunParser(Parse * pParse, const char *zSql, char **pzErrMsg)
 		sqlite3VdbeDelete(pParse->pVdbe);
 		pParse->pVdbe = 0;
 	}
-	sqlite3DeleteTable(db, pParse->pNewTable);
+	sqlite3DeleteTable(db, pParse->create_table_def.new_table);
 
 	if (pParse->pWithToFree)
 		sqlite3WithDelete(db, pParse->pWithToFree);
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index 7d5dc9e23..33b5ea7c4 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -62,34 +62,30 @@ sqlite3DeleteTriggerStep(sqlite3 * db, TriggerStep * pTriggerStep)
 }
 
 void
-sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
-		  int op, struct IdList *columns, struct SrcList *table,
-		  struct Expr *when, int no_err)
+sql_trigger_begin(struct Parse *parse)
 {
 	/* The new trigger. */
 	struct sql_trigger *trigger = NULL;
 	/* The database connection. */
 	struct sqlite3 *db = parse->db;
-	/* The name of the Trigger. */
-	char *trigger_name = NULL;
-
-	/* pName->z might be NULL, but not pName itself. */
-	assert(name != NULL);
-	assert(op == TK_INSERT || op == TK_UPDATE || op == TK_DELETE);
-	assert(op > 0 && op < 0xff);
+	struct create_trigger_def trigger_def = parse->create_trigger_def;
+	struct create_entity_def create_def = trigger_def.base;
+	struct alter_entity_def alter_def = create_def.base;
+	assert(alter_def.entity_type == ENTITY_TYPE_TRIGGER);
 
-	if (table == NULL || db->mallocFailed)
+	char *trigger_name = NULL;
+	if (alter_def.entity_name == NULL || db->mallocFailed)
 		goto trigger_cleanup;
-	assert(table->nSrc == 1);
-
-	trigger_name = sqlite3NameFromToken(db, name);
+	assert(alter_def.entity_name->nSrc == 1);
+	assert(create_def.name.n > 0);
+	trigger_name = sqlite3NameFromToken(db, &create_def.name);
 	if (trigger_name == NULL)
 		goto trigger_cleanup;
 
 	if (sqlite3CheckIdentifierName(parse, trigger_name) != SQLITE_OK)
 		goto trigger_cleanup;
 
-	const char *table_name = table->a[0].zName;
+	const char *table_name = alter_def.entity_name->a[0].zName;
 	uint32_t space_id;
 	if (schema_find_id(BOX_SPACE_ID, 2, table_name, strlen(table_name),
 			   &space_id) != 0)
@@ -112,6 +108,7 @@ sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
 		int name_reg = ++parse->nMem;
 		sqlite3VdbeAddOp4(parse->pVdbe, OP_String8, 0, name_reg, 0,
 				  name_copy, P4_DYNAMIC);
+		bool no_err = create_def.if_not_exist;
 		if (vdbe_emit_halt_with_presence_test(parse, BOX_TRIGGER_ID, 0,
 						      name_reg, 1,
 						      ER_TRIGGER_EXISTS,
@@ -129,13 +126,14 @@ sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
 	trigger->space_id = space_id;
 	trigger->zName = trigger_name;
 	trigger_name = NULL;
-
-	trigger->op = (u8) op;
-	trigger->tr_tm = tr_tm;
-	trigger->pWhen = sqlite3ExprDup(db, when, EXPRDUP_REDUCE);
-	trigger->pColumns = sqlite3IdListDup(db, columns);
-	if ((when != NULL && trigger->pWhen == NULL) ||
-	    (columns != NULL && trigger->pColumns == NULL))
+	assert(trigger_def.op == TK_INSERT || trigger_def.op == TK_UPDATE ||
+	       trigger_def.op== TK_DELETE);
+	trigger->op = (u8) trigger_def.op;
+	trigger->tr_tm = trigger_def.tr_tm;
+	trigger->pWhen = sqlite3ExprDup(db, trigger_def.when, EXPRDUP_REDUCE);
+	trigger->pColumns = sqlite3IdListDup(db, trigger_def.cols);
+	if ((trigger->pWhen != NULL && trigger->pWhen == NULL) ||
+	    (trigger->pColumns != NULL && trigger->pColumns == NULL))
 		goto trigger_cleanup;
 	assert(parse->parsed_ast.trigger == NULL);
 	parse->parsed_ast.trigger = trigger;
@@ -143,9 +141,9 @@ sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
 
  trigger_cleanup:
 	sqlite3DbFree(db, trigger_name);
-	sqlite3SrcListDelete(db, table);
-	sqlite3IdListDelete(db, columns);
-	sql_expr_delete(db, when, false);
+	sqlite3SrcListDelete(db, alter_def.entity_name);
+	sqlite3IdListDelete(db, trigger_def.cols);
+	sql_expr_delete(db, trigger_def.when, false);
 	if (parse->parsed_ast.trigger == NULL)
 		sql_trigger_delete(db, trigger);
 	else
@@ -424,9 +422,13 @@ vdbe_code_drop_trigger(struct Parse *parser, const char *trigger_name,
 }
 
 void
-sql_drop_trigger(struct Parse *parser, struct SrcList *name, bool no_err)
+sql_drop_trigger(struct Parse *parser)
 {
-
+	struct drop_entity_def drop_def = parser->drop_entity_def;
+	struct alter_entity_def alter_def = drop_def.base;
+	assert(alter_def.entity_type == ENTITY_TYPE_TRIGGER);
+	struct SrcList *name = alter_def.entity_name;
+	bool no_err = drop_def.if_exist;
 	sqlite3 *db = parser->db;
 	if (db->mallocFailed)
 		goto drop_trigger_cleanup;
diff --git a/test/sql-tap/index7.test.lua b/test/sql-tap/index7.test.lua
index a8ce37f4b..ffb42403c 100755
--- a/test/sql-tap/index7.test.lua
+++ b/test/sql-tap/index7.test.lua
@@ -397,6 +397,6 @@ test:do_catchsql_test(
                 "_index"."id" = "_space"."id" AND
                 "_space"."name"='TEST8';
         ]],
-        {0, {"pk_TEST8_2",0,"unique_C1_1",1}})
+        {0, {"pk_unnamed_TEST8_2",0,"unique_C1_1",1}})
 
 test:finish_test()

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-01-31 19:32     ` n.pettik
@ 2019-02-04 15:25       ` Vladislav Shpilevoy
  2019-02-08 14:25         ` n.pettik
  0 siblings, 1 reply; 34+ messages in thread
From: Vladislav Shpilevoy @ 2019-02-04 15:25 UTC (permalink / raw)
  To: n.pettik, tarantool-patches

Hi! Thanks for the fixes! See 16 comments below.

On 31/01/2019 20:32, n.pettik wrote:
> I’ve took into consideration your comments and pushed
> new version of this patch:
> 
> Branch: np/gh-3914-fix-create-index-v2
> 
> If it is OK (except for minor things mb) I will add it to
> the main patch-set and rebase the rest.
> 
> Author: Nikita Pettik <korablev@tarantool.org>
> Date:   Wed Jan 9 12:28:09 2019 +0200
> 
>      sql: introduce structs assembling DDL arguments during parsing
>      
>      Parser's rules implementing DDL have a lot in common. For instance,
>      to drop any entity it is enough to know its name and name of table it is
>      related to.
>      Thus, it was suggested to arrange arguments of DDL rules into
>      hierarchical structure. The root of chain always includes name of table
>      to be altered: all existing entities are related to some table.  Then
>      comes one of drop/create/rename rules. Indeed, each DDL operation can be
>      classified in these terms, at least until we introduce ALTER TABLE ALTER
>      CONSTRAINT statement. Drop is represented by single structure (as it was
>      mentioned); rename can be applied only to table; create can be applied
>      to CONSTRAINT (indexes are considered as constraints) and TRIGGER, which
>      in turn are different in arguments required to create those objects. And
>      so forth.
>      
>      What is more, we are going to introduce ALTER TABLE ADD CONSTRAINT
>      UNIQUE With new hierarchy we can extend ALTER TABLE statement with ease:
>      basic structures (alter -> create entity -> create constraint) are the
>      same for .. FOREIGN KEY/UNIQUE, but the last one will be different.
>      
>      Patch itself is made up of refactoring; no functional changes are
>      provided.
>      
>      Needed for #3097
> 
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index f92f39d8e..b6f099cf5 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -383,18 +383,16 @@ sql_table_new(Parse *parser, char *name)
>    * when the "TEMP" or "TEMPORARY" keyword occurs in between
>    * CREATE and TABLE.
>    *
> - * The new table record is initialized and put in pParse->pNewTable.
> + * The new table record is initialized and put in pParse->new_table.

1. No member new_table in struct Parse.

>    * As more of the CREATE TABLE statement is parsed, additional action
>    * routines will be called to add more information to this record.
>    * At the end of the CREATE TABLE statement, the sqlite3EndTable() routine
>    * is called to complete the construction of the new table record.
>    *
>    * @param pParse Parser context.
> - * @param pName1 First part of the name of the table or view.
> - * @param noErr Do nothing if table already exists.
>    */
>   void
> -sqlite3StartTable(Parse *pParse, Token *pName, int noErr)
> +sqlite3StartTable(struct Parse *pParse)
>   {
>   	Table *pTable;
>   	char *zName = 0;	/* The name of the new table */
> @@ -403,8 +401,9 @@ sqlite3StartTable(Parse *pParse, Token *pName, int noErr)
>   	if (v == NULL)
>   		goto cleanup;
>   	sqlite3VdbeCountChanges(v);
> -
> -	zName = sqlite3NameFromToken(db, pName);
> +	struct Token name =
> +		((struct create_entity_def *) &pParse->create_table_def)->name;
> +	zName = sqlite3NameFromToken(db, &name);

2. Looks shorter, no?

  	if (v == NULL)
  		goto cleanup;
  	sqlite3VdbeCountChanges(v);
-	struct Token name =
-		((struct create_entity_def *) &pParse->create_table_def)->name;
+	struct Token name = pParse->create_table_def.base.name;
  	zName = sqlite3NameFromToken(db, &name);
  
  	if (zName == 0)

>   
>   	if (zName == 0)
>   		return;
> @@ -1788,12 +1795,14 @@ columnno_by_name(struct Parse *parse_context, const struct space *space,
>   }
>   
>   void
> -sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
> -		       struct Token *constraint, struct ExprList *child_cols,
> -		       struct Token *parent, struct ExprList *parent_cols,
> -		       bool is_deferred, int actions)
> +sql_create_foreign_key(struct Parse *parse_context)
>   {
>   	struct sqlite3 *db = parse_context->db;
> +	struct create_fk_def create_fk_def = parse_context->create_fk_def;
> +	struct create_constraint_def create_constr_def = create_fk_def.base;
> +	struct create_entity_def create_def = create_constr_def.base;
> +	struct alter_entity_def alter_def = create_def.base;

3. Please, do not copy these structs onto the stack. They are too big.
The same in all other places.

> +	assert(alter_def.entity_type == ENTITY_TYPE_FK);
>   	/*
>   	 * When this function is called second time during
>   	 * <CREATE TABLE ...> statement (i.e. at VDBE runtime),
> @@ -2211,19 +2227,29 @@ constraint_is_named(const char *name)
>   }
>   
>   void
> -sql_create_index(struct Parse *parse, struct Token *token,
> -		 struct SrcList *tbl_name, struct ExprList *col_list,
> -		 MAYBE_UNUSED struct Token *start, enum sort_order sort_order,
> -		 bool if_not_exist, enum sql_index_type idx_type) {
> +sql_create_index(struct Parse *parse) {
>   	/* The index to be created. */
>   	struct index *index = NULL;
>   	/* Name of the index. */
>   	char *name = NULL;
>   	struct sqlite3 *db = parse->db;
>   	assert(!db->init.busy);
> +	struct create_index_def create_idx_def = parse->create_index_def;
> +	struct create_entity_def *create_entity_def =
> +		(struct create_entity_def *) &create_idx_def;

4. Please, use 'base'. Code would be smaller and less erroneous than casts.
Or at least be consistent and use everywhere either cast or base.

> +	struct alter_entity_def alter_entity_def = create_entity_def->base;
> +	assert(alter_entity_def.entity_type == ENTITY_TYPE_INDEX);
> +	/*
> +	 * Get list of columns to be indexed. It will be NULL if
> +	 * this is a primary key or unique-constraint on the most
> +	 * recent column added to the table under construction.
> +	 */
> +	struct ExprList *col_list = create_idx_def.cols;
> +	struct SrcList *tbl_name = alter_entity_def.entity_name;
>   
>   	if (db->mallocFailed || parse->nErr > 0)
>   		goto exit_create_index;
> +	enum sql_index_type idx_type = create_idx_def.idx_type;
>   	if (idx_type == SQL_INDEX_TYPE_UNIQUE ||
>   	    idx_type == SQL_INDEX_TYPE_NON_UNIQUE) {
>   		Vdbe *v = sqlite3GetVdbe(parse);
> @@ -2299,11 +2324,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
>   		}
>   	} else {
>   		char *constraint_name = NULL;
> -		if (parse->constraintName.z != NULL)
> +		if (create_entity_def->name.n > 0)

5. In struct Token comment you wrote/copy-pasted, that
if z == NULL, n is undefined. So here n > 0 does
not mean that z != NULL. Or the comment was wrong?

>   			constraint_name =
>   				sqlite3NameFromToken(db,
> -						     &parse->constraintName);
> -
> +						     &create_entity_def->name);
>   	       /*
>   		* This naming is temporary. Now it's not
>   		* possible (since we implement UNIQUE
> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
> index 0bcf41594..e8b053361 100644
> --- a/src/box/sql/parse.y
> +++ b/src/box/sql/parse.y
> @@ -168,7 +168,9 @@ cmd ::= ROLLBACK TO savepoint_opt nm(X). {
>   //
>   cmd ::= create_table create_table_args.
>   create_table ::= createkw TABLE ifnotexists(E) nm(Y). {
> -   sqlite3StartTable(pParse,&Y,E);
> +  alter_entity_def_init(&pParse->create_table_def, NULL, ENTITY_TYPE_TABLE);
> +  create_entity_def_init(&pParse->create_table_def, Y, E);

6. We never use 'init' suffix. Please, use 'create'.

> +  sqlite3StartTable(pParse);
>   }
>   createkw(A) ::= CREATE(A).  {disableLookaside(pParse);}
>   
> @@ -237,8 +239,15 @@ nm(A) ::= id(A). {
>   carglist ::= carglist cconsdef.
>   carglist ::= .
>   cconsdef ::= cconsname ccons.
> -cconsname ::= CONSTRAINT nm(X).           {pParse->constraintName = X;}
> -cconsname ::= .                           {pParse->constraintName.n = 0;}
> +cconsname ::= cconsname_start cconsname_parse .
> +cconsname_start ::= . {
> +  /* Prepare base members for re-usage. */
> +  memset(&pParse->create_index_def, 0, sizeof(struct create_index_def));
> +}
> +cconsname_parse ::= CONSTRAINT nm(X). {
> +  create_entity_def_init(&pParse->create_index_def, X, false);

7. Why in case of any constraint start, you reset only index_def? UNIQUE/PK
are not the only existing constraints. Also, memset(0) is not scalable
solution - you need a separate reset() function, or something like that in
case if in future parse_def.h structures will grow in size and complexity,
and 0 becomes not default value of some attribute.

By the way, 0 is already not default value of entity type. Memset above
makes create_index_def ha ENTITY_TYPE_TABLE type.

> +}
> +cconsname_parse ::= .
>   ccons ::= DEFAULT term(X).            {sqlite3AddDefaultValue(pParse,&X);}
>   ccons ::= DEFAULT LP expr(X) RP.      {sqlite3AddDefaultValue(pParse,&X);}
>   ccons ::= DEFAULT PLUS term(X).       {sqlite3AddDefaultValue(pParse,&X);}
> @@ -260,14 +269,29 @@ ccons ::= NULL onconf(R).        {
>           sql_column_add_nullable_action(pParse, R);
>   }
>   ccons ::= NOT NULL onconf(R).    {sql_column_add_nullable_action(pParse, R);}
> -ccons ::= PRIMARY KEY sortorder(Z) autoinc(I).
> -                                 {sqlite3AddPrimaryKey(pParse,0,I,Z);}
> -ccons ::= UNIQUE.                {sql_create_index(pParse,0,0,0,0,
> -                                                   SORT_ORDER_ASC, false,
> -                                                   SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
> -ccons ::= CHECK LP expr(X) RP.   {sql_add_check_constraint(pParse,&X);}
> -ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R).
> -                                 {sql_create_foreign_key(pParse, NULL, NULL, NULL, &T, TA, false, R);}
> +ccons ::= PRIMARY KEY sortorder(Z) autoinc(I). {
> +  pParse->create_table_def.has_autoinc = I;
> +  sqlite3AddPrimaryKey(pParse,0,Z);
> +}
> +ccons ::= UNIQUE. {
> +  pParse->create_index_def.idx_type = SQL_INDEX_TYPE_CONSTRAINT_UNIQUE;
> +  entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);

8. Why UNIQUE sets entity type index, but PRIMARY KEY does not?

> diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
> new file mode 100644
> index 000000000..7a61a27e0
> --- /dev/null
> +++ b/src/box/sql/parse_def.h
> @@ -0,0 +1,260 @@
> +#ifndef TARANTOOL_BOX_SQL_PARSE_DEF_H_INCLUDED
> +#define TARANTOOL_BOX_SQL_PARSE_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 "box/fkey.h"
> +
> +/**
> + * This file contains auxiliary structures and functions which
> + * are used only during parsing routine (see parse.y).
> + * Their main purpose is to assemble common parts of altered
> + * entities (such as name, or IF EXISTS clause) and pass them
> + * as a one object to further functions.
> + *
> + * Hierarchy is following:
> + *
> + * Base structure is ALTER.
> + * ALTER is omitted only for CREATE TABLE since table is filled
> + * with meta-information just-in-time of parsing:
> + * for instance, as soon as field's name and type are recognized
> + * they are added to space definition.
> + *
> + * DROP is general for all existing objects and includes
> + * name of object itself, name of parent object (table),
> + * IF EXISTS clause and may contain on-drop behaviour
> + * (CASCADE/RESTRICT, but now it is always RESTRICT).
> + * Hence, it terms of grammar - it is a terminal symbol.
> + *
> + * RENAME can be applied only to table (at least now, since it is
> + * ANSI extension), so it is also terminal symbol.
> + *
> + * CREATE in turn can be expanded to nonterminal symbol
> + * CREATE CONSTRAINT or to terminal CREATE TABLE/INDEX/TRIGGER.
> + * CREATE CONSTRAINT unfolds to FOREIGN KEY or UNIQUE/PRIMARY KEY.
> + *
> + * For instance:
> + * ALTER TABLE t ADD CONSTRAINT c FOREIGN KEY REFERENCES t2(id);
> + * ALTER *TABLE* -> CREATE ENTITY -> CREATE CONSTRAINT -> CREATE FK
> + *
> + * CREATE TRIGGER tr1 ...
> + * ALTER *TABLE* -> CREATE ENTITY -> CREATE TRIGGER
> + *
> + * All terminal symbols are stored as a union within
> + * parsing context (struct Parse).
> + */
> +
> +/**
> + * Each token coming out of the lexer is an instance of
> + * this structure. Tokens are also used as part of an expression.
> + *
> + * Note if Token.z is NULL then Token.n is undefined.
> + */
> +struct Token {
> +	/** Text of the token. Not NULL-terminated! */
> +	const char *z;
> +	/** Number of characters in this token. */
> +	unsigned int n;
> +	bool isReserved;
> +};
> +
> +/**
> + * Possible SQL index types. Note that PK and UNIQUE constraints
> + * are implemented as indexes and have their own types:
> + * _CONSTRAINT_PK and _CONSTRAINT_UNIQUE.
> + */
> +enum sql_index_type {
> +	SQL_INDEX_TYPE_NON_UNIQUE = 0,
> +	SQL_INDEX_TYPE_UNIQUE,
> +	SQL_INDEX_TYPE_CONSTRAINT_UNIQUE,
> +	SQL_INDEX_TYPE_CONSTRAINT_PK,
> +	sql_index_type_MAX
> +};
> +
> +enum entity_type {
> +	ENTITY_TYPE_TABLE = 0,
> +	ENTITY_TYPE_INDEX,
> +	ENTITY_TYPE_TRIGGER,
> +	ENTITY_TYPE_CK,
> +	ENTITY_TYPE_FK,
> +	entity_type_MAX

9. These entity_types are useless. Even being used in asserts,
they do not prevent errors, allowing to create an instance of
create_entity_def, but use it as drop_entity_def, because
base.entity_type in both cases can be, for example,
ENTITY_TYPE_INDEX.

Please, use not object entity types, but def types:

     ALTER_DEF_CREATE_TABLE/DROP_TABLE/RENAME.../...INDEX.../...

Or split entity type and action, so as to have something like
this:

     def.entity = ALTER_DEF_TABLE;
     def.action = ALTER_DEF_RENAME;

By the way, please, rename ENTITY_TYPE_CK to ENTITY_TYPE_CHECK.
Three letters 'HEC' economy is not worth the name obfuscation.

> +};
> +
> +struct alter_entity_def {
> +	/** Type of topmost entity. */
> +	enum entity_type entity_type;
> +	/** As a rule it is a name of table to be altered. */
> +	struct SrcList *entity_name;
> +};
> +
> +struct rename_entity_def {
> +	struct alter_entity_def base;
> +	struct Token new_name;
> +};
> +
> +struct create_entity_def {
> +	struct alter_entity_def base;
> +	struct Token name;
> +	/** Statement comes with IF NOT EXISTS clause. */
> +	bool if_not_exist;
> +};
> +
> +struct create_table_def {
> +	struct create_entity_def base;
> +	struct Table *new_table;
> +	/**
> +	 * Number of FK constraints declared within
> +	 * CREATE TABLE statement.
> +	 */
> +	uint32_t fkey_count;
> +	/**
> +	 * Foreign key constraint appeared in CREATE TABLE stmt.
> +	 */
> +	struct rlist new_fkey;
> +	/** True, if table to be created has AUTOINCREMENT PK. */
> +	bool has_autoinc;
> +};
> +
> +struct drop_entity_def {
> +	struct alter_entity_def base;
> +	/** Name of index/trigger/constraint to be dropped. */
> +	struct Token name;
> +	/** Statement comes with IF EXISTS clause. */
> +	bool if_exist;
> +};
> +
> +struct create_trigger_def {
> +	struct create_entity_def base;
> +	/** One of TK_BEFORE, TK_AFTER, TK_INSTEAD. */
> +	int tr_tm;
> +	/** One of TK_INSERT, TK_UPDATE, TK_DELETE. */
> +	int op;
> +	/** Column list if this is an UPDATE trigger. */
> +	struct IdList *cols;
> +	/** When clause. */
> +	struct Expr *when;
> +};
> +
> +struct create_constraint_def {
> +	struct create_entity_def base;
> +	/** One of DEFERRED, IMMEDIATE. */
> +	bool is_deferred;
> +};
> +
> +struct create_ck_def {
> +	struct create_constraint_def base;
> +	/** AST representing check expression. */
> +	struct ExprSpan *expr;
> +};
> +
> +struct create_fk_def {
> +	struct create_constraint_def base;
> +	struct ExprList *child_cols;
> +	struct Token *parent_name;
> +	struct ExprList *parent_cols;
> +	/**
> +	 * Encoded actions for MATCH, ON DELETE and
> +	 * ON UPDATE clauses.
> +	 */
> +	int actions;
> +};
> +
> +struct create_index_def {
> +	struct create_constraint_def base;
> +	/** List of indexed columns. */
> +	struct ExprList *cols;
> +	/** One of _PRIMARY_KEY, _UNIQUE, _NON_UNIQUE. */
> +	enum sql_index_type idx_type;
> +	enum sort_order sort_order;
> +};
> +
> +/**
> + * In those functions which accept void * instead of certain type
> + * it was made on purpose: it allows to avoid explicit cast before
> + * passing parameter to function. Hence, we can invoke it like:
> + * entity_set_type(create_fk_def, ...); instead of
> + * entity_set_type((struct alter_entity_def *) create_fk_def, ...);
> + */
> +static inline void
> +entity_set_type(void *entity_def, enum entity_type type)
> +{
> +	struct alter_entity_def *alter_def =
> +		(struct alter_entity_def *) entity_def;
> +	alter_def->entity_type = type;
> +}

10. Please, do not use void * in either of these functions. Use
normal types. Also, entity_set_type() should not exist as a separate
function. Instead, each terminal structure above should have it own
create() function, which internally sets type.

> +
> +static inline void
> +entity_set_defer_mode(void *entity_def, bool is_deferred)
> +{
> +	struct create_constraint_def *constr_def =
> +		(struct create_constraint_def *) entity_def;
> +	constr_def->is_deferred = is_deferred;
> +}

11. Same. You unsafely cast void * to create_constraint_def *.
Also, you have a special setter for is_deferred, but have no
for if_exist. And nonetheless you set if_exist manually in at
least one place. Why?

> +
> +static inline void
> +alter_entity_def_init(void *entity_def, struct SrcList *entity_name,
> +		      enum entity_type type)

12. This function should not be called by user. It should be part of
terminal def create() functions. It looks really weird when you do
something like this:

     alter_entity_def_init(&pParse->create_fk_def)
     create_entity_def_init(&pParse->create_fk_def)
     entity_set_defer_mode(&pParse->create_fk_def)

So basically you called three!!! constructors. It is not scalable,
sorry.

> +{
> +	struct alter_entity_def *alter_def =
> +		(struct alter_entity_def *) entity_def;
> +	alter_def->entity_name = entity_name;
> +	alter_def->entity_type = type;
> +}
> +
> +static inline void
> +create_entity_def_init(void *entity_def, struct Token name, bool if_not_exist)

13. You if_not_exist passed through the constructor of create_entity, but
you do not do the same for is_exist and drop_entity. Why? I see the function
below, but parse.y:381 directly changes if_exist.

> +{
> +	struct create_entity_def *create_def =
> +		(struct create_entity_def *) entity_def;
> +	create_def->name = name;
> +	create_def->if_not_exist = if_not_exist;
> +}
> +
> +static inline void
> +drop_entity_def_init(struct drop_entity_def *drop_def, struct Token name,
> +		     bool if_exist)
> +{
> +	drop_def->name = name;
> +	drop_def->if_exist = if_exist;
> +}
> +
> +static inline void
> +create_fk_def_init(struct create_fk_def *fk_def, struct ExprList *child_cols,
> +		   struct Token *parent_name, struct ExprList *parent_cols,
> +		   int actions)
> +{
> +	assert(fk_def != NULL);
> +	fk_def->child_cols = child_cols;
> +	fk_def->parent_name = parent_name;
> +	fk_def->parent_cols = parent_cols;
> +	fk_def->actions = actions;
> +}
> +
> +#endif /* TARANTOOL_BOX_SQL_PARSE_DEF_H_INCLUDED */
> diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
> index 824578e45..3343c2942 100644
> --- a/src/box/sql/prepare.c
> +++ b/src/box/sql/prepare.c
> @@ -273,7 +273,7 @@ sql_parser_create(struct Parse *parser, sqlite3 *db)
>   {
>   	memset(parser, 0, sizeof(struct Parse));
>   	parser->db = db;
> -	rlist_create(&parser->new_fkey);
> +	rlist_create(&parser->create_table_def.new_fkey);

14. Please, introduce create_table_def_create() in parse_def.h and do
all necessary actions there.

>   	rlist_create(&parser->record_list);
>   	region_create(&parser->region, &cord()->slabc);
>   }
> @@ -287,7 +287,7 @@ sql_parser_destroy(Parse *parser)
>   	sqlite3DbFree(db, parser->aLabel);
>   	sql_expr_list_delete(db, parser->pConstExpr);
>   	struct fkey_parse *fk;
> -	rlist_foreach_entry(fk, &parser->new_fkey, link)
> +	rlist_foreach_entry(fk, &parser->create_table_def.new_fkey, link)
>   		sql_expr_list_delete(db, fk->selfref_cols);

15. create_table_def_destroy().

>   	if (db != NULL) {
>   		assert(db->lookaside.bDisable >=
> diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
> index 6462467bc..e66f0d6db 100644
> --- a/src/box/sql/resolve.c
> +++ b/src/box/sql/resolve.c
> @@ -1616,9 +1616,9 @@ void
>   sql_resolve_self_reference(struct Parse *parser, struct Table *table, int type,
>   			   struct Expr *expr, struct ExprList *expr_list)
>   {
> -	/* Fake SrcList for parser->pNewTable */
> +	/* Fake SrcList for parser->new_table */
>   	SrcList sSrc;
> -	/* Name context for parser->pNewTable */
> +	/* Name context for parser->new_table */

16. No such member in struct Parse.

>   	NameContext sNC;
>   
>   	assert(type == NC_IsCheck || type == NC_IdxExpr);

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-02-04 15:25       ` Vladislav Shpilevoy
@ 2019-02-08 14:25         ` n.pettik
  2019-02-15 20:13           ` Vladislav Shpilevoy
  0 siblings, 1 reply; 34+ messages in thread
From: n.pettik @ 2019-02-08 14:25 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

Please note, that I failed gracefully separate diffs for
some of comments, so there can be some mess.
I guess it would be better to look at entire diff,
which is attached at the end of letter.

Moreover, I didn’t fix several of your comments completely.
For instance, there are still both ways of getting base structs:
cast and .base invocation. I left my thought on this subject,
but if you feel that it anyway should be fixed, I will do it.

Patch itself is still located at np/gh-3914-fix-create-index-v2

>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>> index f92f39d8e..b6f099cf5 100644
>> --- a/src/box/sql/build.c
>> +++ b/src/box/sql/build.c
>> @@ -383,18 +383,16 @@ sql_table_new(Parse *parser, char *name)
>>   * when the "TEMP" or "TEMPORARY" keyword occurs in between
>>   * CREATE and TABLE.
>>   *
>> - * The new table record is initialized and put in pParse->pNewTable.
>> + * The new table record is initialized and put in pParse->new_table.
> 
> 1. No member new_table in struct Parse.

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index b6f099cf5..80fc16af7 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -383,7 +383,7 @@ sql_table_new(Parse *parser, char *name)
  * when the "TEMP" or "TEMPORARY" keyword occurs in between
  * CREATE and TABLE.
  *
- * The new table record is initialized and put in pParse->new_table.
+ * The new table record is initialized and put in pParse->create_table_def.
  * As more of the CREATE TABLE statement is parsed, additional action
  * routines will be called to add more information to this record.
  * At the end of the CREATE TABLE statement, the sqlite3EndTable() routine

>>   * As more of the CREATE TABLE statement is parsed, additional action
>>   * routines will be called to add more information to this record.
>>   * At the end of the CREATE TABLE statement, the sqlite3EndTable() routine
>>   * is called to complete the construction of the new table record.
>>   *
>>   * @param pParse Parser context.
>> - * @param pName1 First part of the name of the table or view.
>> - * @param noErr Do nothing if table already exists.
>>   */
>>  void
>> -sqlite3StartTable(Parse *pParse, Token *pName, int noErr)
>> +sqlite3StartTable(struct Parse *pParse)
>>  {
>>  	Table *pTable;
>>  	char *zName = 0;	/* The name of the new table */
>> @@ -403,8 +401,9 @@ sqlite3StartTable(Parse *pParse, Token *pName, int noErr)
>>  	if (v == NULL)
>>  		goto cleanup;
>>  	sqlite3VdbeCountChanges(v);
>> -
>> -	zName = sqlite3NameFromToken(db, pName);
>> +	struct Token name =
>> +		((struct create_entity_def *) &pParse->create_table_def)->name;
>> +	zName = sqlite3NameFromToken(db, &name);
> 
> 2. Looks shorter, no?
> 
> 	if (v == NULL)
> 		goto cleanup;
> 	sqlite3VdbeCountChanges(v);
> -	struct Token name =
> -		((struct create_entity_def *) &pParse->create_table_def)->name;
> +	struct Token name = pParse->create_table_def.base.name;
> 	zName = sqlite3NameFromToken(db, &name);
>  	if (zName == 0)

Probably. Applied.

>>    	if (zName == 0)
>>  		return;
>> @@ -1788,12 +1795,14 @@ columnno_by_name(struct Parse *parse_context, const struct space *space,
>>  }
>>    void
>> -sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
>> -		       struct Token *constraint, struct ExprList *child_cols,
>> -		       struct Token *parent, struct ExprList *parent_cols,
>> -		       bool is_deferred, int actions)
>> +sql_create_foreign_key(struct Parse *parse_context)
>>  {
>>  	struct sqlite3 *db = parse_context->db;
>> +	struct create_fk_def create_fk_def = parse_context->create_fk_def;
>> +	struct create_constraint_def create_constr_def = create_fk_def.base;
>> +	struct create_entity_def create_def = create_constr_def.base;
>> +	struct alter_entity_def alter_def = create_def.base;
> 
> 3. Please, do not copy these structs onto the stack. They are too big.
> The same in all other places.

Ok:

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index b6f099cf5..034f811c1 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -1798,11 +1797,11 @@ void
 sql_create_foreign_key(struct Parse *parse_context)
 {
        struct sqlite3 *db = parse_context->db;
-       struct create_fk_def create_fk_def = parse_context->create_fk_def;
-       struct create_constraint_def create_constr_def = create_fk_def.base;
-       struct create_entity_def create_def = create_constr_def.base;
-       struct alter_entity_def alter_def = create_def.base;
-       assert(alter_def.entity_type == ENTITY_TYPE_FK);
+       struct create_fk_def *create_fk_def = &parse_context->create_fk_def;
+       struct create_constraint_def *create_constr_def = &create_fk_def->base;
+       struct create_entity_def *create_def = &create_constr_def->base;
+       struct alter_entity_def *alter_def = &create_def->base;
+       assert(alter_def->entity_type == ENTITY_TYPE_FK);
        /*
         * When this function is called second time during
         * <CREATE TABLE ...> statement (i.e. at VDBE runtime),
@@ -1826,7 +1825,7 @@ sql_create_foreign_key(struct Parse *parse_context)
        /* Whether we are processing ALTER TABLE or CREATE TABLE. */
        bool is_alter = new_tab == NULL;
        uint32_t child_cols_count;
-       struct ExprList *child_cols = create_fk_def.child_cols;
+       struct ExprList *child_cols = create_fk_def->child_cols;
        if (child_cols == NULL) {
                assert(!is_alter);
                child_cols_count = 1;
@@ -1835,7 +1834,7 @@ sql_create_foreign_key(struct Parse *parse_context)
        }
        struct space *child_space = NULL;
        if (is_alter) {
-               const char *child_name = alter_def.entity_name->a[0].zName;
+               const char *child_name = alter_def->entity_name->a[0].zName;
                child_space = space_by_name(child_name);
                if (child_space == NULL) {
                        diag_set(ClientError, ER_NO_SUCH_SPACE, child_name);
@@ -1852,7 +1851,7 @@ sql_create_foreign_key(struct Parse *parse_context)
                memset(fk, 0, sizeof(*fk));
                rlist_add_entry(&table_def->new_fkey, fk, link);
        }
-       struct Token *parent = create_fk_def.parent_name;
+       struct Token *parent = create_fk_def->parent_name;
        assert(parent != NULL);
        parent_name = sqlite3NameFromToken(db, parent);
        if (parent_name == NULL)
@@ -1864,7 +1863,7 @@ sql_create_foreign_key(struct Parse *parse_context)
         */
        is_self_referenced = !is_alter &&
                             strcmp(parent_name, new_tab->def->name) == 0;
-       struct ExprList *parent_cols = create_fk_def.parent_cols;
+       struct ExprList *parent_cols = create_fk_def->parent_cols;
        struct space *parent_space = space_by_name(parent_name);
        if (parent_space == NULL) {
                if (is_self_referenced) {
@@ -1885,18 +1884,18 @@ sql_create_foreign_key(struct Parse *parse_context)
                }
        }
        if (!is_alter) {
-               if (create_def.name.n == 0) {
+               if (create_def->name.n == 0) {
                        constraint_name =
                                sqlite3MPrintf(db, "FK_CONSTRAINT_%d_%s",
                                               ++table_def->fkey_count,
                                               new_tab->def->name);
                } else {
                        constraint_name =
-                               sqlite3NameFromToken(db, &create_def.name);
+                               sqlite3NameFromToken(db, &create_def->name);
                }
        } else {
                constraint_name =
-                       sqlite3NameFromToken(db, &create_def.name);
+                       sqlite3NameFromToken(db, &create_def->name);
        }
        if (constraint_name == NULL)
                goto exit_create_fk;
@@ -1929,11 +1928,11 @@ sql_create_foreign_key(struct Parse *parse_context)
                diag_set(OutOfMemory, fk_size, "region", "struct fkey");
                goto tnt_error;
        }
-       int actions = create_fk_def.actions;
+       int actions = create_fk_def->actions;
        fk->field_count = child_cols_count;
        fk->child_id = child_space != NULL ? child_space->def->id : 0;
        fk->parent_id = parent_space != NULL ? parent_space->def->id : 0;
-       fk->is_deferred = create_constr_def.is_deferred;
+       fk->is_deferred = create_constr_def->is_deferred;
        fk->match = (enum fkey_match) ((actions >> 16) & 0xff);
        fk->on_update = (enum fkey_action) ((actions >> 8) & 0xff);
        fk->on_delete = (enum fkey_action) (actions & 0xff);
@@ -2021,9 +2020,9 @@ fkey_change_defer_mode(struct Parse *parse_context, bool is_deferred)
 void
 sql_drop_foreign_key(struct Parse *parse_context)
 {
-       struct drop_entity_def drop_def = parse_context->drop_entity_def;
-       assert(drop_def.base.entity_type == ENTITY_TYPE_FK);
-       const char *table_name = drop_def.base.entity_name->a[0].zName;
+       struct drop_entity_def *drop_def = &parse_context->drop_entity_def;
+       assert(drop_def->base.entity_type == ENTITY_TYPE_FK);
+       const char *table_name = drop_def->base.entity_name->a[0].zName;
        assert(table_name != NULL);
        struct space *child = space_by_name(table_name);
        if (child == NULL) {
@@ -2033,7 +2032,7 @@ sql_drop_foreign_key(struct Parse *parse_context)
                return;
        }
        char *constraint_name = sqlite3NameFromToken(parse_context->db,
-                                                    &drop_def.name);
+                                                    &drop_def->name);
        if (constraint_name != NULL)
                vdbe_emit_fkey_drop(parse_context, constraint_name,
                                    child->def->id);
@@ -2234,22 +2233,22 @@ sql_create_index(struct Parse *parse) {
        char *name = NULL;
        struct sqlite3 *db = parse->db;
        assert(!db->init.busy);
-       struct create_index_def create_idx_def = parse->create_index_def;
+       struct create_index_def *create_idx_def = &parse->create_index_def;
        struct create_entity_def *create_entity_def =
                (struct create_entity_def *) &create_idx_def;
-       struct alter_entity_def alter_entity_def = create_entity_def->base;
-       assert(alter_entity_def.entity_type == ENTITY_TYPE_INDEX);
+       struct alter_entity_def *alter_entity_def = &create_entity_def->base;
+       assert(alter_entity_def->entity_type == ENTITY_TYPE_INDEX);
        /*
         * Get list of columns to be indexed. It will be NULL if
         * this is a primary key or unique-constraint on the most
         * recent column added to the table under construction.
         */
-       struct ExprList *col_list = create_idx_def.cols;
-       struct SrcList *tbl_name = alter_entity_def.entity_name;
+       struct ExprList *col_list = create_idx_def->cols;
+       struct SrcList *tbl_name = alter_entity_def->entity_name;
 
        if (db->mallocFailed || parse->nErr > 0)
                goto exit_create_index;
-       enum sql_index_type idx_type = create_idx_def.idx_type;
+       enum sql_index_type idx_type = create_idx_def->idx_type;
        if (idx_type == SQL_INDEX_TYPE_UNIQUE ||
            idx_type == SQL_INDEX_TYPE_NON_UNIQUE) {
                Vdbe *v = sqlite3GetVdbe(parse);
@@ -2386,7 +2385,7 @@ sql_create_index(struct Parse *parse) {
                        goto exit_create_index;
                assert(col_list->nExpr == 1);
                sqlite3ExprListSetSortOrder(col_list,
-                                           create_idx_def.sort_order);
+                                           create_idx_def->sort_order);
        } else {
                sqlite3ExprListCheckLength(parse, col_list, "index");
        }
@@ -2559,14 +2558,14 @@ sql_create_index(struct Parse *parse) {
 void
 sql_drop_index(struct Parse *parse_context)
 {
-       struct drop_entity_def drop_def = parse_context->drop_entity_def;
-       assert(drop_def.base.entity_type == ENTITY_TYPE_INDEX);
+       struct drop_entity_def *drop_def = &parse_context->drop_entity_def;
+       assert(drop_def->base.entity_type == ENTITY_TYPE_INDEX);
        struct Vdbe *v = sqlite3GetVdbe(parse_context);
        assert(v != NULL);
        struct sqlite3 *db = parse_context->db;
        /* Never called with prior errors. */
        assert(parse_context->nErr == 0);
-       struct SrcList *table_list = drop_def.base.entity_name;
+       struct SrcList *table_list = drop_def->base.entity_name;
        assert(table_list->nSrc == 1);
        char *table_name = table_list->a[0].zName;
        const char *index_name = NULL;
@@ -2575,14 +2574,14 @@ sql_drop_index(struct Parse *parse_context)
        }
        sqlite3VdbeCountChanges(v);
        struct space *space = space_by_name(table_name);
-       bool if_exists = drop_def.if_exist;
+       bool if_exists = drop_def->if_exist;
        if (space == NULL) {
                if (!if_exists)
                        sqlite3ErrorMsg(parse_context, "no such space: %s",
                                        table_name);
                goto exit_drop_index;
        }
-       index_name = sqlite3NameFromToken(db, &drop_def.name);
+       index_name = sqlite3NameFromToken(db, &drop_def->name);
        uint32_t index_id = box_index_id_by_name(space->def->id, index_name,
                                                 strlen(index_name));
        if (index_id == BOX_ID_NIL) {

diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index 33b5ea7c4..160b3f56c 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -68,24 +68,24 @@ sql_trigger_begin(struct Parse *parse)
        struct sql_trigger *trigger = NULL;
        /* The database connection. */
        struct sqlite3 *db = parse->db;
-       struct create_trigger_def trigger_def = parse->create_trigger_def;
-       struct create_entity_def create_def = trigger_def.base;
-       struct alter_entity_def alter_def = create_def.base;
-       assert(alter_def.entity_type == ENTITY_TYPE_TRIGGER);
+       struct create_trigger_def *trigger_def = &parse->create_trigger_def;
+       struct create_entity_def *create_def = &trigger_def->base;
+       struct alter_entity_def *alter_def = &create_def->base;
+       assert(alter_def->entity_type == ENTITY_TYPE_TRIGGER);
 
        char *trigger_name = NULL;
-       if (alter_def.entity_name == NULL || db->mallocFailed)
+       if (alter_def->entity_name == NULL || db->mallocFailed)
                goto trigger_cleanup;
-       assert(alter_def.entity_name->nSrc == 1);
-       assert(create_def.name.n > 0);
-       trigger_name = sqlite3NameFromToken(db, &create_def.name);
+       assert(alter_def->entity_name->nSrc == 1);
+       assert(create_def->name.n > 0);
+       trigger_name = sqlite3NameFromToken(db, &create_def->name);
        if (trigger_name == NULL)
                goto trigger_cleanup;
 
        if (sqlite3CheckIdentifierName(parse, trigger_name) != SQLITE_OK)
                goto trigger_cleanup;
 
-       const char *table_name = alter_def.entity_name->a[0].zName;
+       const char *table_name = alter_def->entity_name->a[0].zName;
        uint32_t space_id;
        if (schema_find_id(BOX_SPACE_ID, 2, table_name, strlen(table_name),
                           &space_id) != 0)
@@ -108,7 +108,7 @@ sql_trigger_begin(struct Parse *parse)
                int name_reg = ++parse->nMem;
                sqlite3VdbeAddOp4(parse->pVdbe, OP_String8, 0, name_reg, 0,
                                  name_copy, P4_DYNAMIC);
-               bool no_err = create_def.if_not_exist;
+               bool no_err = create_def->if_not_exist;
                if (vdbe_emit_halt_with_presence_test(parse, BOX_TRIGGER_ID, 0,
                                                      name_reg, 1,
                                                      ER_TRIGGER_EXISTS,
@@ -126,12 +126,12 @@ sql_trigger_begin(struct Parse *parse)
        trigger->space_id = space_id;
        trigger->zName = trigger_name;
        trigger_name = NULL;
-       assert(trigger_def.op == TK_INSERT || trigger_def.op == TK_UPDATE ||
-              trigger_def.op== TK_DELETE);
-       trigger->op = (u8) trigger_def.op;
-       trigger->tr_tm = trigger_def.tr_tm;
-       trigger->pWhen = sqlite3ExprDup(db, trigger_def.when, EXPRDUP_REDUCE);
-       trigger->pColumns = sqlite3IdListDup(db, trigger_def.cols);
+       assert(trigger_def->op == TK_INSERT || trigger_def->op == TK_UPDATE ||
+              trigger_def->op== TK_DELETE);
+       trigger->op = (u8) trigger_def->op;
+       trigger->tr_tm = trigger_def->tr_tm;
+       trigger->pWhen = sqlite3ExprDup(db, trigger_def->when, EXPRDUP_REDUCE);
+       trigger->pColumns = sqlite3IdListDup(db, trigger_def->cols);
        if ((trigger->pWhen != NULL && trigger->pWhen == NULL) ||
            (trigger->pColumns != NULL && trigger->pColumns == NULL))
                goto trigger_cleanup;
@@ -141,9 +141,9 @@ sql_trigger_begin(struct Parse *parse)
 
  trigger_cleanup:
        sqlite3DbFree(db, trigger_name);
-       sqlite3SrcListDelete(db, alter_def.entity_name);
-       sqlite3IdListDelete(db, trigger_def.cols);
-       sql_expr_delete(db, trigger_def.when, false);
+       sqlite3SrcListDelete(db, alter_def->entity_name);
+       sqlite3IdListDelete(db, trigger_def->cols);
+       sql_expr_delete(db, trigger_def->when, false);
        if (parse->parsed_ast.trigger == NULL)
                sql_trigger_delete(db, trigger);
        else
@@ -424,11 +424,11 @@ vdbe_code_drop_trigger(struct Parse *parser, const char *trigger_name,
 void
 sql_drop_trigger(struct Parse *parser)
 {
-       struct drop_entity_def drop_def = parser->drop_entity_def;
-       struct alter_entity_def alter_def = drop_def.base;
-       assert(alter_def.entity_type == ENTITY_TYPE_TRIGGER);
-       struct SrcList *name = alter_def.entity_name;
-       bool no_err = drop_def.if_exist;
+       struct drop_entity_def *drop_def = &parser->drop_entity_def;
+       struct alter_entity_def *alter_def = &drop_def->base;
+       assert(alter_def->entity_type == ENTITY_TYPE_TRIGGER);
+       struct SrcList *name = alter_def->entity_name;
+       bool no_err = drop_def->if_exist;
        sqlite3 *db = parser->db;
        if (db->mallocFailed)
                goto drop_trigger_cleanup;

>> +	assert(alter_def.entity_type == ENTITY_TYPE_FK);
>>  	/*
>>  	 * When this function is called second time during
>>  	 * <CREATE TABLE ...> statement (i.e. at VDBE runtime),
>> @@ -2211,19 +2227,29 @@ constraint_is_named(const char *name)
>>  }
>>    void
>> -sql_create_index(struct Parse *parse, struct Token *token,
>> -		 struct SrcList *tbl_name, struct ExprList *col_list,
>> -		 MAYBE_UNUSED struct Token *start, enum sort_order sort_order,
>> -		 bool if_not_exist, enum sql_index_type idx_type) {
>> +sql_create_index(struct Parse *parse) {
>>  	/* The index to be created. */
>>  	struct index *index = NULL;
>>  	/* Name of the index. */
>>  	char *name = NULL;
>>  	struct sqlite3 *db = parse->db;
>>  	assert(!db->init.busy);
>> +	struct create_index_def create_idx_def = parse->create_index_def;
>> +	struct create_entity_def *create_entity_def =
>> +		(struct create_entity_def *) &create_idx_def;
> 
> 4. Please, use 'base'. Code would be smaller and less erroneous than casts.
> Or at least be consistent and use everywhere either cast or base.

Initially I tended to use cast when we didn’t need intermediate structs,
instead of base.base.base…

For instance, in sql_add_check_constraint():

struct create_ck_def *ck_def = &parser->create_ck_def;
struct alter_entity_def *alter_def =
       (struct alter_entity_def *) &parser->create_ck_def;

Becomes:

struct alter_entity_def *alter_def =
       &parser->create_ck_def.base.base.base;

IMHO base looks worse than cast in this case.

I’ve fixed this in several other places, so if I am not
mistaken this case (sql_add_check_constraint()) is
the only one where I use cast; it can be fixed as well,
if you wish.

>> +	struct alter_entity_def alter_entity_def = create_entity_def->base;
>> +	assert(alter_entity_def.entity_type == ENTITY_TYPE_INDEX);
>> +	/*
>> +	 * Get list of columns to be indexed. It will be NULL if
>> +	 * this is a primary key or unique-constraint on the most
>> +	 * recent column added to the table under construction.
>> +	 */
>> +	struct ExprList *col_list = create_idx_def.cols;
>> +	struct SrcList *tbl_name = alter_entity_def.entity_name;
>>    	if (db->mallocFailed || parse->nErr > 0)
>>  		goto exit_create_index;
>> +	enum sql_index_type idx_type = create_idx_def.idx_type;
>>  	if (idx_type == SQL_INDEX_TYPE_UNIQUE ||
>>  	    idx_type == SQL_INDEX_TYPE_NON_UNIQUE) {
>>  		Vdbe *v = sqlite3GetVdbe(parse);
>> @@ -2299,11 +2324,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
>>  		}
>>  	} else {
>>  		char *constraint_name = NULL;
>> -		if (parse->constraintName.z != NULL)
>> +		if (create_entity_def->name.n > 0)
> 
> 5. In struct Token comment you wrote/copy-pasted, that
> if z == NULL, n is undefined. So here n > 0 does
> not mean that z != NULL. Or the comment was wrong?

In this particular case name token is a part of create_entity_def
struct. In turn create_entity_def is filled with 0 or both fields (z and n)
are initialised properly. But anyway, just in case I removed that comment.

>> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
>> index 0bcf41594..e8b053361 100644
>> --- a/src/box/sql/parse.y
>> +++ b/src/box/sql/parse.y
>> @@ -168,7 +168,9 @@ cmd ::= ROLLBACK TO savepoint_opt nm(X). {
>>  //
>>  cmd ::= create_table create_table_args.
>>  create_table ::= createkw TABLE ifnotexists(E) nm(Y). {
>> -   sqlite3StartTable(pParse,&Y,E);
>> +  alter_entity_def_init(&pParse->create_table_def, NULL, ENTITY_TYPE_TABLE);
>> +  create_entity_def_init(&pParse->create_table_def, Y, E);
> 
> 6. We never use 'init' suffix.

I grepped a lot ‘init’ suffixes around source code:

credentials_init()
mpstream_init()
tuple_init()
cmsg_init()
vy_log_record_init()

and so forth.

> Please, use 'create’.

But then it would look a bit weird:

create_entity_def_create()

Two ‘create’ words in one name, isn’t it too much?
Moreover, I believe ‘init’ is more suitable, since in fact
we initialise already created struct. Instead of ‘init’ I can
suggest ‘_prepare’, ‘_assebmle’, ‘_fill'
If you are sure about ‘_create' naming, I will fix it.

>> +  sqlite3StartTable(pParse);
>>  }
>>  createkw(A) ::= CREATE(A).  {disableLookaside(pParse);}
>>  @@ -237,8 +239,15 @@ nm(A) ::= id(A). {
>>  carglist ::= carglist cconsdef.
>>  carglist ::= .
>>  cconsdef ::= cconsname ccons.
>> -cconsname ::= CONSTRAINT nm(X).           {pParse->constraintName = X;}
>> -cconsname ::= .                           {pParse->constraintName.n = 0;}
>> +cconsname ::= cconsname_start cconsname_parse .
>> +cconsname_start ::= . {
>> +  /* Prepare base members for re-usage. */
>> +  memset(&pParse->create_index_def, 0, sizeof(struct create_index_def));
>> +}
>> +cconsname_parse ::= CONSTRAINT nm(X). {
>> +  create_entity_def_init(&pParse->create_index_def, X, false);
> 
> 7. Why in case of any constraint start, you reset only index_def? UNIQUE/PK
> are not the only existing constraints.
> Also, memset(0) is not scalable
> solution - you need a separate reset() function, or something like that in
> case if in future parse_def.h structures will grow in size and complexity,
> and 0 becomes not default value of some attribute.

Well, imho it is too much. Actually, here we really care only about
zeroing several pointers and some size params and don’t care
about the rest. This is first rule to be called when some constraint
is created. What is more, now terminal initialisers fill all fields, except
for base structs. Hence, now it is enough to reset memory layout
up to struct create_constraint_def:

diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 589b13d76..abb2c689e 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -242,7 +243,7 @@ cconsdef ::= cconsname ccons.
 cconsname ::= cconsname_start cconsname_parse .
 cconsname_start ::= . {
   /* Prepare base members for re-usage. */
-  memset(&pParse->create_index_def, 0, sizeof(struct create_index_def));
+  memset(&pParse->create_index_def, 0, sizeof(struct create_constraint_def));
 }
 cconsname_parse ::= CONSTRAINT nm(X). {
   create_entity_def_init(&pParse->create_index_def, X, false);

> By the way, 0 is already not default value of entity type. Memset above
> makes create_index_def ha ENTITY_TYPE_TABLE type.

It is OK, proper type is set in terminal initialisers, as you suggested.

>> @@ -260,14 +269,29 @@ ccons ::= NULL onconf(R).        {
>>          sql_column_add_nullable_action(pParse, R);
>>  }
>>  ccons ::= NOT NULL onconf(R).    {sql_column_add_nullable_action(pParse, R);}
>> -ccons ::= PRIMARY KEY sortorder(Z) autoinc(I).
>> -                                 {sqlite3AddPrimaryKey(pParse,0,I,Z);}
>> -ccons ::= UNIQUE.                {sql_create_index(pParse,0,0,0,0,
>> -                                                   SORT_ORDER_ASC, false,
>> -                                                   SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
>> -ccons ::= CHECK LP expr(X) RP.   {sql_add_check_constraint(pParse,&X);}
>> -ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R).
>> -                                 {sql_create_foreign_key(pParse, NULL, NULL, NULL, &T, TA, false, R);}
>> +ccons ::= PRIMARY KEY sortorder(Z) autoinc(I). {
>> +  pParse->create_table_def.has_autoinc = I;
>> +  sqlite3AddPrimaryKey(pParse,0,Z);
>> +}
>> +ccons ::= UNIQUE. {
>> +  pParse->create_index_def.idx_type = SQL_INDEX_TYPE_CONSTRAINT_UNIQUE;
>> +  entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);
> 
> 8. Why UNIQUE sets entity type index, but PRIMARY KEY does not?

In fact, it does - inside sqlite3AddPrimaryKey function.
But now I see that it would be better to move it outside
function. Refactored a bit:

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index b6f099cf5..475ee58fa 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -668,14 +667,12 @@ field_def_create_for_pk(struct Parse *parser, struct field_def *field,
  * index for the key.  No index is created for INTEGER PRIMARY KEYs.
  */
 void
-sqlite3AddPrimaryKey(Parse * pParse,   /* Parsing context */
-                    ExprList * pList,  /* List of field names to be indexed */
-                    enum sort_order sortOrder
-    )
+sqlite3AddPrimaryKey(struct Parse *pParse)
 {
        Table *pTab = pParse->create_table_def.new_table;
        int iCol = -1, i;
        int nTerm;
+       struct ExprList *pList = pParse->create_index_def.cols;
        if (pTab == 0)
                goto primary_key_exit;
        if (sql_table_primary_key(pTab) != NULL) {
@@ -708,12 +705,8 @@ sqlite3AddPrimaryKey(Parse * pParse,       /* Parsing context */
                        }
                }
        }
-       pParse->create_index_def.idx_type = SQL_INDEX_TYPE_CONSTRAINT_PK;
-       pParse->create_index_def.sort_order = sortOrder;
-       entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);
        if (nTerm == 1 && iCol != -1 &&
-           pTab->def->fields[iCol].type == FIELD_TYPE_INTEGER &&
-           sortOrder != SORT_ORDER_DESC) {
+           pTab->def->fields[iCol].type == FIELD_TYPE_INTEGER) {
                struct sqlite3 *db = pParse->db;
                struct ExprList *list;
                struct Token token;
@@ -732,9 +725,8 @@ sqlite3AddPrimaryKey(Parse * pParse,        /* Parsing context */
                                "INTEGER PRIMARY KEY or INT PRIMARY KEY");
                goto primary_key_exit;
        } else {
-               pParse->create_index_def.cols = pList;
                sql_create_index(pParse);
-               pList = 0;
+               pList = NULL;
                if (pParse->nErr > 0)
                        goto primary_key_exit;
        }
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index e8b053361..cf302f496 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -271,11 +271,15 @@ ccons ::= NULL onconf(R).        {
 ccons ::= NOT NULL onconf(R).    {sql_column_add_nullable_action(pParse, R);}
 ccons ::= PRIMARY KEY sortorder(Z) autoinc(I). {
   pParse->create_table_def.has_autoinc = I;
-  sqlite3AddPrimaryKey(pParse,0,Z);
+  entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);
+  create_index_def_init(&pParse->create_index_def, NULL,
+                        SQL_INDEX_TYPE_CONSTRAINT_PK, Z);
+  sqlite3AddPrimaryKey(pParse);
 }
 ccons ::= UNIQUE. {
-  pParse->create_index_def.idx_type = SQL_INDEX_TYPE_CONSTRAINT_UNIQUE;
   entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);
+  create_index_def_init(&pParse->create_index_def, NULL,
+                        SQL_INDEX_TYPE_CONSTRAINT_UNIQUE, SORT_ORDER_ASC);
   sql_create_index(pParse);
 }
 
@@ -334,11 +338,14 @@ init_deferred_pred_opt(A) ::= INITIALLY IMMEDIATE.    {A = 0;}
 tconsdef ::= cconsname tcons.
 tcons ::= PRIMARY KEY LP sortlist(X) autoinc(I) RP. {
   pParse->create_table_def.has_autoinc = I;
-  sqlite3AddPrimaryKey(pParse, X, 0);
+  entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);
+  create_index_def_init(&pParse->create_index_def, X,
+                        SQL_INDEX_TYPE_CONSTRAINT_PK, SORT_ORDER_ASC);
+  sqlite3AddPrimaryKey(pParse);
 }
 tcons ::= UNIQUE LP sortlist(X) RP. {
-  pParse->create_index_def.cols = X;
-  pParse->create_index_def.idx_type = SQL_INDEX_TYPE_CONSTRAINT_UNIQUE;
+  create_index_def_init(&pParse->create_index_def, X,
+                        SQL_INDEX_TYPE_CONSTRAINT_UNIQUE, SORT_ORDER_ASC);
   entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);
   sql_create_index(pParse);
 }
@@ -1243,8 +1250,7 @@ cmd ::= createkw uniqueflag(U) INDEX ifnotexists(NE) nm(X)
                         sqlite3SrcListAppend(pParse->db, 0, &Y),
                         ENTITY_TYPE_INDEX);
   create_entity_def_init(&pParse->create_index_def, X, NE);
-  pParse->create_index_def.idx_type = U;
-  pParse->create_index_def.cols = Z;
+  create_index_def_init(&pParse->create_index_def, Z, U, SORT_ORDER_ASC);
   sql_create_index(pParse);
 }

index 7a61a27e0..dbeb11892 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -245,6 +245,15 @@ drop_entity_def_init(struct drop_entity_def *drop_def, struct Token name,
        drop_def->if_exist = if_exist;
 }
 
+static inline void
+create_index_def_init(struct create_index_def *index_def, struct ExprList *cols,
+                     enum sql_index_type idx_type, enum sort_order sort_order)
+{
+       index_def->cols = cols;
+       index_def->idx_type = idx_type;
+       index_def->sort_order = sort_order;
+}
+

>> diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
>> +enum entity_type {
>> +	ENTITY_TYPE_TABLE = 0,
>> +	ENTITY_TYPE_INDEX,
>> +	ENTITY_TYPE_TRIGGER,
>> +	ENTITY_TYPE_CK,
>> +	ENTITY_TYPE_FK,
>> +	entity_type_MAX
> 
> 9. These entity_types are useless. Even being used in asserts,
> they do not prevent errors, allowing to create an instance of
> create_entity_def, but use it as drop_entity_def, because
> base.entity_type in both cases can be, for example,
> ENTITY_TYPE_INDEX.
> 
> Please, use not object entity types, but def types:
> 
>    ALTER_DEF_CREATE_TABLE/DROP_TABLE/RENAME.../...INDEX.../...
> 
> Or split entity type and action, so as to have something like
> this:
> 
>    def.entity = ALTER_DEF_TABLE;
>    def.action = ALTER_DEF_RENAME;

I’d better choose this way. Sorry, I can’t spot diff
of particularly this fix, since it has been entangled with diffs
to other comments. But I will attach whole diff at the end of letter.

> By the way, please, rename ENTITY_TYPE_CK to ENTITY_TYPE_CHECK.
> Three letters 'HEC' economy is not worth the name obfuscation.

It is common abbreviation (the same as FK), and AFAIR was even
suggested by Konstantin in one of reviews (mb not to this patch).
It simply makes name be similar to ENTITY_TYPE_FK.

>> +/**
>> + * In those functions which accept void * instead of certain type
>> + * it was made on purpose: it allows to avoid explicit cast before
>> + * passing parameter to function. Hence, we can invoke it like:
>> + * entity_set_type(create_fk_def, ...); instead of
>> + * entity_set_type((struct alter_entity_def *) create_fk_def, ...);
>> + */
>> +static inline void
>> +entity_set_type(void *entity_def, enum entity_type type)
>> +{
>> +	struct alter_entity_def *alter_def =
>> +		(struct alter_entity_def *) entity_def;
>> +	alter_def->entity_type = type;
>> +}
> 
> 10. Please, do not use void * in either of these functions. Use
> normal types. Also, entity_set_type() should not exist as a separate
> function. Instead, each terminal structure above should have it own
> create() function, which internally sets type.

That’s fair:

diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index cf302f496..15b2b478c 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -271,13 +271,11 @@ ccons ::= NULL onconf(R).        {
 ccons ::= NOT NULL onconf(R).    {sql_column_add_nullable_action(pParse, R);}
 ccons ::= PRIMARY KEY sortorder(Z) autoinc(I). {
   pParse->create_table_def.has_autoinc = I;
-  entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);
   create_index_def_init(&pParse->create_index_def, NULL,
                         SQL_INDEX_TYPE_CONSTRAINT_PK, Z);
   sqlite3AddPrimaryKey(pParse);
 }
 ccons ::= UNIQUE. {
-  entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);
   create_index_def_init(&pParse->create_index_def, NULL,
                         SQL_INDEX_TYPE_CONSTRAINT_UNIQUE, SORT_ORDER_ASC);
   sql_create_index(pParse);
@@ -286,14 +284,12 @@ ccons ::= UNIQUE. {
 ccons ::= check_constraint_def .
 
 check_constraint_def ::= CHECK LP expr(X) RP. {
-  pParse->create_ck_def.expr = &X;
-  entity_set_type(&pParse->create_index_def, ENTITY_TYPE_CK);
+  create_ck_def_init(&pParse->create_ck_def, &X);
   sql_add_check_constraint(pParse);
 }
 
 ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R). {
   create_fk_def_init(&pParse->create_fk_def, NULL, &T, TA, R);
-  entity_set_type(&pParse->create_fk_def, ENTITY_TYPE_FK);
   sql_create_foreign_key(pParse);
 }
 ccons ::= defer_subclause(D).    {fkey_change_defer_mode(pParse, D);}
@@ -1369,11 +1365,7 @@ trigger_decl(A) ::= TRIGGER ifnotexists(NOERR) nm(B)
                     ON fullname(E) foreach_clause when_clause(G). {
   struct create_trigger_def *trigger_def = &pParse->create_trigger_def;
   alter_entity_def_init(trigger_def, E, ENTITY_TYPE_TRIGGER);
   create_entity_def_init(trigger_def, B, NOERR);
-  trigger_def->tr_tm = C;
-  trigger_def->op = D.a;
-  trigger_def->cols = D.b;
-  trigger_def->when = G;
+  create_trigger_def_init(&trigger_def, C, D.a, D.b, G);
   sql_trigger_begin(pParse);
   A = B; /*A-overwrites-T*/
 }
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index dbeb11892..2da678d77 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -74,8 +74,6 @@
 /**
  * Each token coming out of the lexer is an instance of
  * this structure. Tokens are also used as part of an expression.
- *
- * Note if Token.z is NULL then Token.n is undefined.
  */
 struct Token {
        /** Text of the token. Not NULL-terminated! */
@@ -107,9 +105,17 @@ enum entity_type {
        entity_type_MAX
 };
 
+enum alter_action {
+       ALTER_ACTION_CREATE = 0,
+       ALTER_ACTION_DROP,
+       ALTER_ACTION_RENAME
+};
+
 struct alter_entity_def {
        /** Type of topmost entity. */
        enum entity_type entity_type;
+       /** Action to be performed using current entity. */
+       enum alter_action alter_action;
        /** As a rule it is a name of table to be altered. */
        struct SrcList *entity_name;
 };
@@ -202,13 +208,6 @@ struct create_index_def {
  * entity_set_type(create_fk_def, ...); instead of
  * entity_set_type((struct alter_entity_def *) create_fk_def, ...);
  */
-static inline void
-entity_set_type(void *entity_def, enum entity_type type)
-{
-       struct alter_entity_def *alter_def =
-               (struct alter_entity_def *) entity_def;
-       alter_def->entity_type = type;
-}
 
 static inline void
 entity_set_defer_mode(void *entity_def, bool is_deferred)
@@ -245,6 +244,25 @@ drop_entity_def_init(struct drop_entity_def *drop_def, struct Token name,
        drop_def->if_exist = if_exist;
 }
 
+static inline void
+create_trigger_def_init(struct create_trigger_def *trigger_def, int tr_tm,
+                       int op, struct IdList *cols, struct Expr *when)
+{
+       trigger_def->tr_tm = tr_tm;
+       trigger_def->op = op;
+       trigger_def->cols = cols;
+       trigger_def->when = when;
+       ((struct alter_entity_def *) trigger_def_def)->entity_type =
+               ENTITY_TYPE_TRIGGER;
+}
+
+static inline void
+create_ck_def_init(struct create_ck_def *ck_def, struct ExprSpan *expr)
+{
+       ck_def->expr = expr;
+       ((struct alter_entity_def *) ck_def)->entity_type = ENTITY_TYPE_CK;
+}
+
 static inline void
 create_index_def_init(struct create_index_def *index_def, struct ExprList *cols,
                      enum sql_index_type idx_type, enum sort_order sort_order)
@@ -252,6 +270,7 @@ create_index_def_init(struct create_index_def *index_def, struct ExprList *cols,
        index_def->cols = cols;
        index_def->idx_type = idx_type;
        index_def->sort_order = sort_order;
+       ((struct alter_entity_def *) index_def)->entity_type = ENTITY_TYPE_INDEX;
 }
 
 static inline void
@@ -264,6 +283,7 @@ create_fk_def_init(struct create_fk_def *fk_def, struct ExprList *child_cols,
        fk_def->parent_name = parent_name;
        fk_def->parent_cols = parent_cols;
        fk_def->actions = actions;
+       ((struct alter_entity_def *) fk_def)->entity_type = ENTITY_TYPE_FK;
 }

I know you asked me to choose between base struct and cast.
However, I think here cast is much more suitable: these structures
are terminated so we have to write base.base.base.
On the other hand, almost in all places one or two nesting
base usage looks better. I can revert this change with ease,
if you want to.

Eliminating void * diff (note that I still use cast to jump over
several base structs at once; again, if you feel that I should
use base.base.base - I will fix it):

diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index dbeb11892..0392c0a87 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -195,54 +229,71 @@ struct create_index_def {
        enum sort_order sort_order;
 };
 
-/**
- * In those functions which accept void * instead of certain type
- * it was made on purpose: it allows to avoid explicit cast before
- * passing parameter to function. Hence, we can invoke it like:
- * entity_set_type(create_fk_def, ...); instead of
- * entity_set_type((struct alter_entity_def *) create_fk_def, ...);
- */
+/** Basic initialisers of parse structures.*/
 static inline void
-entity_set_type(void *entity_def, enum entity_type type)
+alter_entity_def_init(struct alter_entity_def *alter_def,
+                     struct SrcList *entity_name)
 {
-       struct alter_entity_def *alter_def =
-               (struct alter_entity_def *) entity_def;
-       alter_def->entity_type = type;
+       alter_def->entity_name = entity_name;
 }
 
 static inline void
-entity_set_defer_mode(void *entity_def, bool is_deferred)
+rename_entity_def_init(struct rename_entity_def *rename_def,
+                      struct Token new_name)
 {
-       struct create_constraint_def *constr_def =
-               (struct create_constraint_def *) entity_def;
-       constr_def->is_deferred = is_deferred;
+       rename_def->new_name = new_name;
+       struct alter_entity_def *alter_def =
+               (struct alter_entity_def *) rename_def;
+       alter_def->entity_type = ENTITY_TYPE_TABLE;
+       alter_def->alter_action = ALTER_ACTION_RENAME;
 }
 
 static inline void
-alter_entity_def_init(void *entity_def, struct SrcList *entity_name,
-                     enum entity_type type)
+create_entity_def_init(struct create_entity_def *create_def, struct Token name,
+                      bool if_not_exist)
 {
-       struct alter_entity_def *alter_def =
-               (struct alter_entity_def *) entity_def;
-       alter_def->entity_name = entity_name;
-       alter_def->entity_type = type;
+       create_def->name = name;
+       create_def->if_not_exist = if_not_exist;
 }
 
 static inline void
-create_entity_def_init(void *entity_def, struct Token name, bool if_not_exist)
+create_constraint_def_init(struct create_constraint_def *constr_def,
+                          bool is_deferred)
 {
-       struct create_entity_def *create_def =
-               (struct create_entity_def *) entity_def;
-       create_def->name = name;
-       create_def->if_not_exist = if_not_exist;
+       constr_def->is_deferred = is_deferred;
 }

diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 589b13d76..22b5669d8 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -168,8 +168,9 @@ cmd ::= ROLLBACK TO savepoint_opt nm(X). {
 //
 cmd ::= create_table create_table_args.
 create_table ::= createkw TABLE ifnotexists(E) nm(Y). {
-  alter_entity_def_init(&pParse->create_table_def, NULL, ENTITY_TYPE_TABLE);
-  create_entity_def_init(&pParse->create_table_def, Y, E);
+  alter_entity_def_init(&pParse->create_table_def.base.base, NULL);
+  create_entity_def_init(&pParse->create_table_def.base, Y, E);
+  create_table_def_init(&pParse->create_table_def);
   sqlite3StartTable(pParse);
 }
 createkw(A) ::= CREATE(A).  {disableLookaside(pParse);}
@@ -180,6 +181,7 @@ ifnotexists(A) ::= IF NOT EXISTS. {A = 1;}
 
 create_table_args ::= LP columnlist RP(E). {
   sqlite3EndTable(pParse,&E,0);
+  create_table_def_destroy(&pParse->create_table_def);
 }
 create_table_args ::= AS select(S). {
   sqlite3EndTable(pParse,0,S);
@@ -242,10 +244,10 @@ cconsdef ::= cconsname ccons.
 cconsname ::= cconsname_start cconsname_parse .
 cconsname_start ::= . {
   /* Prepare base members for re-usage. */
-  memset(&pParse->create_index_def, 0, sizeof(struct create_index_def));
+  memset(&pParse->create_index_def, 0, sizeof(struct create_constraint_def));
 }
 cconsname_parse ::= CONSTRAINT nm(X). {
-  create_entity_def_init(&pParse->create_index_def, X, false);
+  create_entity_def_init(&pParse->create_index_def.base.base, X, false);
 }
 cconsname_parse ::= .
 ccons ::= DEFAULT term(X).            {sqlite3AddDefaultValue(pParse,&X);}
@@ -346,8 +348,8 @@ tcons ::= UNIQUE LP sortlist(X) RP. {
 tcons ::= check_constraint_def .
 tcons ::= FOREIGN KEY LP eidlist(FA) RP
           REFERENCES nm(T) eidlist_opt(TA) refargs(R) defer_subclause_opt(D). {
+  create_constraint_def_init(&pParse->create_fk_def.base, D);
   create_fk_def_init(&pParse->create_fk_def, FA, &T, TA, R);
-  entity_set_defer_mode(&pParse->create_fk_def, D);
   sql_create_foreign_key(pParse);
 }
 %type defer_subclause_opt {int}
@@ -377,8 +379,9 @@ cmd ::= drop_start(X) drop_table . {
 }
 
 drop_table ::= ifexists(E) fullname(X) . {
-  alter_entity_def_init(&pParse->drop_entity_def, X, ENTITY_TYPE_TABLE);
-  pParse->drop_entity_def.if_exist = E;
+  alter_entity_def_init(&pParse->drop_entity_def.base, X);
+  drop_entity_def_init(&pParse->drop_entity_def, (struct Token) {}, E,
+                       ENTITY_TYPE_TABLE);
 }
 
 %type drop_start {bool}
@@ -394,8 +397,7 @@ ifexists(A) ::= .            {A = 0;}
 cmd ::= createkw(X) VIEW ifnotexists(E) nm(Y) eidlist_opt(C)
           AS select(S). {
   if (!pParse->parse_only) {
-    alter_entity_def_init(&pParse->create_table_def, NULL, ENTITY_TYPE_TABLE);
-    create_entity_def_init(&pParse->create_table_def, Y, E);
+    create_entity_def_init(&pParse->create_table_def.base, Y, E);
     sql_create_view(pParse, &X, C, S);
   } else {
     sql_store_select(pParse, S);
@@ -1239,10 +1241,10 @@ paren_exprlist(A) ::= LP exprlist(X) RP.  {A = X;}
 //
 cmd ::= createkw uniqueflag(U) INDEX ifnotexists(NE) nm(X)
         ON nm(Y) LP sortlist(Z) RP. {
-  alter_entity_def_init(&pParse->create_index_def,
-                        sqlite3SrcListAppend(pParse->db, 0, &Y),
-                        ENTITY_TYPE_INDEX);
-  create_entity_def_init(&pParse->create_index_def, X, NE);
+  alter_entity_def_init((struct alter_entity_def *) &pParse->create_index_def,
+                        sqlite3SrcListAppend(pParse->db, 0, &Y));
+  create_entity_def_init((struct create_entity_def *) &pParse->create_index_def,
+                         X, NE);
   create_index_def_init(&pParse->create_index_def, Z, U, SORT_ORDER_ASC);
   sql_create_index(pParse);
 }
@@ -1307,8 +1309,8 @@ collate(C) ::= COLLATE id.   {C = 1;}
 ///////////////////////////// The DROP INDEX command /////////////////////////
 //
 cmd ::= DROP INDEX ifexists(E) nm(X) ON fullname(Y).   {
-  alter_entity_def_init(&pParse->drop_entity_def, Y, ENTITY_TYPE_INDEX);
-  drop_entity_def_init(&pParse->drop_entity_def, X, E);
+  alter_entity_def_init(&pParse->drop_entity_def.base, Y);
+  drop_entity_def_init(&pParse->drop_entity_def, X, E, ENTITY_TYPE_INDEX);
   sql_drop_index(pParse);
 }
 
@@ -1361,8 +1363,8 @@ trigger_decl(A) ::= TRIGGER ifnotexists(NOERR) nm(B)
                     trigger_time(C) trigger_event(D)
                     ON fullname(E) foreach_clause when_clause(G). {
   struct create_trigger_def *trigger_def = &pParse->create_trigger_def;
-  alter_entity_def_init(trigger_def, E, ENTITY_TYPE_TRIGGER);
-  create_entity_def_init(trigger_def, B, NOERR);
+  alter_entity_def_init((struct alter_entity_def *) trigger_def, E);
+  create_entity_def_init((struct create_entity_def *) trigger_def, B, NOERR);
   create_trigger_def_init(trigger_def, C, D.a, D.b, G);
   sql_trigger_begin(pParse);
   A = B; /*A-overwrites-T*/
@@ -1474,8 +1476,9 @@ raisetype(A) ::= FAIL.      {A = ON_CONFLICT_ACTION_FAIL;}
 
 ////////////////////////  DROP TRIGGER statement //////////////////////////////
 cmd ::= DROP TRIGGER ifexists(NOERR) fullname(X). {
-  alter_entity_def_init(&pParse->drop_entity_def, X, ENTITY_TYPE_TRIGGER);
-  drop_entity_def_init(&pParse->drop_entity_def, (struct Token){}, NOERR);
+  alter_entity_def_init(&pParse->drop_entity_def.base, X);
+  drop_entity_def_init(&pParse->drop_entity_def, (struct Token){}, NOERR,
+                       ENTITY_TYPE_TRIGGER);
   sql_drop_trigger(pParse);
 }
 
@@ -1485,24 +1488,24 @@ cmd ::= ANALYZE nm(X).          {sqlite3Analyze(pParse, &X);}
 
 //////////////////////// ALTER TABLE table ... ////////////////////////////////
 cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). {
-  alter_entity_def_init(&pParse->rename_entity_def, X, ENTITY_TYPE_TABLE);
-  pParse->rename_entity_def.new_name = Z;
+  alter_entity_def_init(&pParse->rename_entity_def.base, X);
+  rename_entity_def_init(&pParse->rename_entity_def, Z);
   sql_alter_table_rename(pParse);
 }
 
 cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT nm(Z) FOREIGN KEY
         LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) refargs(R)
         defer_subclause_opt(D). {
-  alter_entity_def_init(&pParse->create_fk_def, X, ENTITY_TYPE_FK);
-  create_entity_def_init(&pParse->create_fk_def, Z, false);
-  entity_set_defer_mode(&pParse->create_fk_def, D);
+  alter_entity_def_init((struct alter_entity_def *) &pParse->create_fk_def, X);
+  create_entity_def_init(&pParse->create_fk_def.base.base, Z, false);
+  create_constraint_def_init(&pParse->create_fk_def.base, D);
   create_fk_def_init(&pParse->create_fk_def, FA, &T, TA, R);
   sql_create_foreign_key(pParse);
 }
 
 cmd ::= ALTER TABLE fullname(X) DROP CONSTRAINT nm(Z). {
-  alter_entity_def_init(&pParse->drop_entity_def, X, ENTITY_TYPE_FK);
-  drop_entity_def_init(&pParse->drop_entity_def, Z, false);
+  alter_entity_def_init(&pParse->drop_entity_def.base, X);
+  drop_entity_def_init(&pParse->drop_entity_def, Z, false, ENTITY_TYPE_FK);
   sql_drop_foreign_key(pParse);
 }

>> +static inline void
>> +entity_set_defer_mode(void *entity_def, bool is_deferred)
>> +{
>> +	struct create_constraint_def *constr_def =
>> +		(struct create_constraint_def *) entity_def;
>> +	constr_def->is_deferred = is_deferred;
>> +}
> 
> 11. Same. You unsafely cast void * to create_constraint_def *.
> Also, you have a special setter for is_deferred, but have no
> for if_exist. And nonetheless you set if_exist manually in at
> least one place. Why?

if_exist is an attribute of terminal struct (drop_entity_def), so
it can be fetched without cast/references to base struct.
On the other hand, to reach create_constraint_def we always
need to go through base structs.

In all places except this one, if_exist is initialised in drop_entity_def_init.
In this one it also can be used, but I decided to simply initialise only if_exist.
Here we don’t need set name of entity to be dropped (it is already saved in
alter_entity_def). But ok, it is not a problem to use init function here as well:

diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 589b13d76..21112a49a 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -378,7 +378,7 @@ cmd ::= drop_start(X) drop_table . {
 
 drop_table ::= ifexists(E) fullname(X) . {
   alter_entity_def_init(&pParse->drop_entity_def, X, ENTITY_TYPE_TABLE);
-  pParse->drop_entity_def.if_exist = E;
+  drop_entity_def_init(&pParse->drop_entity_def, (struct Token) {}, E);
 }

>> +
>> +static inline void
>> +alter_entity_def_init(void *entity_def, struct SrcList *entity_name,
>> +		      enum entity_type type)
> 
> 12. This function should not be called by user. It should be part of
> terminal def create() functions. It looks really weird when you do
> something like this:
> 
>    alter_entity_def_init(&pParse->create_fk_def)
>    create_entity_def_init(&pParse->create_fk_def)
>    entity_set_defer_mode(&pParse->create_fk_def)
> 
> So basically you called three!!! constructors. It is not scalable,
> sorry.

But not always we can initialise all properties.
For instance:

ALTER TABLE t1 ADD CONSTRAINT c1 FK;

Firstly, we parse ALTER TABLE t1 and initialise
struct alter_entity_def. Then we parse ADD CONSTRAINT c1,
so initialise struct create_entity_def and struct create_constraint_def.
Finally, we get to FK properties (like list of child cols etc) and initialise
them. OK, we can set type of entity (and action) in terminal initialisation,
but we can’t set name of table, name of constraint etc. Actually, I thought
that all these refactoring is aimed at such step-by-step initialisation.
If you could suggest better approach, I would appreciate it.

>> +{
>> +	struct alter_entity_def *alter_def =
>> +		(struct alter_entity_def *) entity_def;
>> +	alter_def->entity_name = entity_name;
>> +	alter_def->entity_type = type;
>> +}
>> +
>> +static inline void
>> +create_entity_def_init(void *entity_def, struct Token name, bool if_not_exist)
> 
> 13. You if_not_exist passed through the constructor of create_entity, but
> you do not do the same for is_exist and drop_entity. Why? I see the function
> below, but parse.y:381 directly changes if_exist.

Nevermind, it was only one such usage.
I fixed it, see answer to comment 11.

>> diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
>> index 824578e45..3343c2942 100644
>> --- a/src/box/sql/prepare.c
>> +++ b/src/box/sql/prepare.c
>> @@ -273,7 +273,7 @@ sql_parser_create(struct Parse *parser, sqlite3 *db)
>>  {
>>  	memset(parser, 0, sizeof(struct Parse));
>>  	parser->db = db;
>> -	rlist_create(&parser->new_fkey);
>> +	rlist_create(&parser->create_table_def.new_fkey);
> 
> 14. Please, introduce create_table_def_create() in parse_def.h and do
> all necessary actions there.

Ok (nevertheless it is the only action to be used in preparation),
see diff below.

>>  	rlist_create(&parser->record_list);
>>  	region_create(&parser->region, &cord()->slabc);
>>  }
>> @@ -287,7 +287,7 @@ sql_parser_destroy(Parse *parser)
>>  	sqlite3DbFree(db, parser->aLabel);
>>  	sql_expr_list_delete(db, parser->pConstExpr);
>>  	struct fkey_parse *fk;
>> -	rlist_foreach_entry(fk, &parser->new_fkey, link)
>> +	rlist_foreach_entry(fk, &parser->create_table_def.new_fkey, link)
>>  		sql_expr_list_delete(db, fk->selfref_cols);
> 
> 15. create_table_def_destroy().

Ok, but then we have to a) include sql.h to use sql_expr_list_delete()
b) move struct fkey_parse to parse_def as well (mb it is even better place).
Diff:

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 475ee58fa..5ddbad6e3 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -1291,15 +1291,19 @@ sqlite3EndTable(Parse * pParse, /* Parse context */
                return;
        int reg_space_id = getNewSpaceId(pParse);
        createSpace(pParse, reg_space_id);
-       /* Indexes aren't required for VIEW's.. */
-       if (!p->def->opts.is_view) {
-               for (uint32_t i = 0; i < p->space->index_count; ++i) {
-                       struct index *idx = p->space->index[i];
-                       vdbe_emit_create_index(pParse, p->def, idx->def,
-                                              reg_space_id, idx->def->iid);
-               }
-       }
+       /*
+        * VIEWs can't have any functional parts such as
+        * indexes or foreign keys, so we can terminate
+        * right here.
+        */
+       if (p->def->opts.is_view)
+               return;
 
+       for (uint32_t i = 0; i < p->space->index_count; ++i) {
+               struct index *idx = p->space->index[i];
+               vdbe_emit_create_index(pParse, p->def, idx->def, reg_space_id,
+                                      idx->def->iid);
+       }
        /*
         * Check to see if we need to create an _sequence table
         * for keeping track of autoincrement keys.

diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 589b13d76..3b5e03712 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -168,8 +168,9 @@ cmd ::= ROLLBACK TO savepoint_opt nm(X). {
 //
 cmd ::= create_table create_table_args.
 create_table ::= createkw TABLE ifnotexists(E) nm(Y). {
   alter_entity_def_init(&pParse->create_table_def, NULL);
   create_entity_def_init(&pParse->create_table_def, Y, E);
+  create_table_def_init(&pParse->create_table_def);
   sqlite3StartTable(pParse);
 }
 createkw(A) ::= CREATE(A).  {disableLookaside(pParse);}
@@ -180,6 +181,7 @@ ifnotexists(A) ::= IF NOT EXISTS. {A = 1;}
 
 create_table_args ::= LP columnlist RP(E). {
   sqlite3EndTable(pParse,&E,0);
+  create_table_def_destroy(&pParse->create_table_def);
 }
 create_table_args ::= AS select(S). {
   sqlite3EndTable(pParse,0,S);

diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index dbeb11892..d16be2b5d 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -31,6 +31,7 @@
  * SUCH DAMAGE.
  */
 #include "box/fkey.h"
+#include "box/sql.h"
 
 /**
  * This file contains auxiliary structures and functions which
@@ -85,6 +84,33 @@ struct Token {
        bool isReserved;
 };
 
+/**
+ * Structure representing foreign keys constraints appeared
+ * within CREATE TABLE statement. Used only during parsing.
+ */
+struct fkey_parse {
+       /**
+        * Foreign keys constraint declared in <CREATE TABLE ...>
+        * statement. They must be coded after space creation.
+        */
+       struct fkey_def *fkey;
+       /**
+        * If inside CREATE TABLE statement we want to declare
+        * self-referenced FK constraint, we must delay their
+        * resolution until the end of parsing of all columns.
+        * E.g.: CREATE TABLE t1(id REFERENCES t1(b), b);
+        */
+       struct ExprList *selfref_cols;
+       /**
+        * Still, self-referenced columns might be NULL, if
+        * we declare FK constraints referencing PK:
+        * CREATE TABLE t1(id REFERENCES t1) - it is a valid case.
+        */
+       bool is_self_referenced;
+       /** Organize these structs into linked list. */
+       struct rlist link;
+};
+
@@ -264,6 +329,28 @@ create_fk_def_init(struct create_fk_def *fk_def, struct ExprList *child_cols,
+static inline void
+create_table_def_init(struct create_table_def *table_def)
+{
+       rlist_create(&table_def->new_fkey);
+       struct alter_entity_def *alter_def =
+               (struct alter_entity_def *) &table_def;
+       alter_def->entity_type = ENTITY_TYPE_TABLE;
+       alter_def->alter_action = ALTER_ACTION_CREATE;
+}
+
+static inline void
+create_table_def_destroy(struct create_table_def *table_def)
+{
+       struct fkey_parse *fk;
+       rlist_foreach_entry(fk, &table_def->new_fkey, link)
+               sql_expr_list_delete(sql_get(), fk->selfref_cols);
 }
 
 #endif /* TARANTOOL_BOX_SQL_PARSE_DEF_H_INCLUDED */

>>  	if (db != NULL) {
>>  		assert(db->lookaside.bDisable >=
>> diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
>> index 6462467bc..e66f0d6db 100644
>> --- a/src/box/sql/resolve.c
>> +++ b/src/box/sql/resolve.c
>> @@ -1616,9 +1616,9 @@ void
>>  sql_resolve_self_reference(struct Parse *parser, struct Table *table, int type,
>>  			   struct Expr *expr, struct ExprList *expr_list)
>>  {
>> -	/* Fake SrcList for parser->pNewTable */
>> +	/* Fake SrcList for parser->new_table */
>>  	SrcList sSrc;
>> -	/* Name context for parser->pNewTable */
>> +	/* Name context for parser->new_table */
> 
> 16. No such member in struct Parse.

diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index e66f0d6db..50d787d11 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -1616,9 +1616,9 @@ void
 sql_resolve_self_reference(struct Parse *parser, struct Table *table, int type,
                           struct Expr *expr, struct ExprList *expr_list)
 {
-       /* Fake SrcList for parser->new_table */
+       /* Fake SrcList for parser->create_table_def */
        SrcList sSrc;
-       /* Name context for parser->new_table */
+       /* Name context for parser->create_table_def  */
        NameContext sNC;
 
        assert(type == NC_IsCheck || type == NC_IdxExpr);




Whole diff:

diff --git a/src/box/sql/alter.c b/src/box/sql/alter.c
index e88b57b59..6eb14fdb5 100644
--- a/src/box/sql/alter.c
+++ b/src/box/sql/alter.c
@@ -40,12 +40,13 @@
 void
 sql_alter_table_rename(struct Parse *parse)
 {
-       struct rename_entity_def rename_def = parse->rename_entity_def;
-       struct SrcList *src_tab = rename_def.base.entity_name;
-       assert(rename_def.base.entity_type == ENTITY_TYPE_TABLE);
+       struct rename_entity_def *rename_def = &parse->rename_entity_def;
+       struct SrcList *src_tab = rename_def->base.entity_name;
+       assert(rename_def->base.entity_type == ENTITY_TYPE_TABLE);
+       assert(rename_def->base.alter_action == ALTER_ACTION_RENAME);
        assert(src_tab->nSrc == 1);
        struct sqlite3 *db = parse->db;
-       char *new_name = sqlite3NameFromToken(db, &rename_def.new_name);
+       char *new_name = sqlite3NameFromToken(db, &rename_def->new_name);
        if (new_name == NULL)
                goto exit_rename_table;
        /* Check that new name isn't occupied by another table. */
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index b6f099cf5..5ddbad6e3 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -383,7 +383,7 @@ sql_table_new(Parse *parser, char *name)
  * when the "TEMP" or "TEMPORARY" keyword occurs in between
  * CREATE and TABLE.
  *
- * The new table record is initialized and put in pParse->new_table.
+ * The new table record is initialized and put in pParse->create_table_def.
  * As more of the CREATE TABLE statement is parsed, additional action
  * routines will be called to add more information to this record.
  * At the end of the CREATE TABLE statement, the sqlite3EndTable() routine
@@ -401,8 +401,7 @@ sqlite3StartTable(struct Parse *pParse)
        if (v == NULL)
                goto cleanup;
        sqlite3VdbeCountChanges(v);
-       struct Token name =
-               ((struct create_entity_def *) &pParse->create_table_def)->name;
+       struct Token name = pParse->create_table_def.base.name;
        zName = sqlite3NameFromToken(db, &name);
 
        if (zName == 0)
@@ -668,14 +667,12 @@ field_def_create_for_pk(struct Parse *parser, struct field_def *field,
  * index for the key.  No index is created for INTEGER PRIMARY KEYs.
  */
 void
-sqlite3AddPrimaryKey(Parse * pParse,   /* Parsing context */
-                    ExprList * pList,  /* List of field names to be indexed */
-                    enum sort_order sortOrder
-    )
+sqlite3AddPrimaryKey(struct Parse *pParse)
 {
        Table *pTab = pParse->create_table_def.new_table;
        int iCol = -1, i;
        int nTerm;
+       struct ExprList *pList = pParse->create_index_def.cols;
        if (pTab == 0)
                goto primary_key_exit;
        if (sql_table_primary_key(pTab) != NULL) {
@@ -708,12 +705,8 @@ sqlite3AddPrimaryKey(Parse * pParse,       /* Parsing context */
                        }
                }
        }
-       pParse->create_index_def.idx_type = SQL_INDEX_TYPE_CONSTRAINT_PK;
-       pParse->create_index_def.sort_order = sortOrder;
-       entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);
        if (nTerm == 1 && iCol != -1 &&
-           pTab->def->fields[iCol].type == FIELD_TYPE_INTEGER &&
-           sortOrder != SORT_ORDER_DESC) {
+           pTab->def->fields[iCol].type == FIELD_TYPE_INTEGER) {
                struct sqlite3 *db = pParse->db;
                struct ExprList *list;
                struct Token token;
@@ -732,9 +725,8 @@ sqlite3AddPrimaryKey(Parse * pParse,        /* Parsing context */
                                "INTEGER PRIMARY KEY or INT PRIMARY KEY");
                goto primary_key_exit;
        } else {
-               pParse->create_index_def.cols = pList;
                sql_create_index(pParse);
-               pList = 0;
+               pList = NULL;
                if (pParse->nErr > 0)
                        goto primary_key_exit;
        }
@@ -777,8 +769,7 @@ sql_add_check_constraint(struct Parse *parser)
                        sqlite3DbFree(parser->db, expr->u.zToken);
                        goto release_expr;
                }
-               struct create_entity_def *entity_def =
-                       (struct create_entity_def *) ck_def;
+               struct create_entity_def *entity_def = &ck_def->base.base;
                if (entity_def->name.n > 0) {
                        sqlite3ExprListSetName(parser, table->def->opts.checks,
                                               &entity_def->name, 1);
@@ -1300,15 +1291,19 @@ sqlite3EndTable(Parse * pParse, /* Parse context */
                return;
        int reg_space_id = getNewSpaceId(pParse);
        createSpace(pParse, reg_space_id);
-       /* Indexes aren't required for VIEW's.. */
-       if (!p->def->opts.is_view) {
-               for (uint32_t i = 0; i < p->space->index_count; ++i) {
-                       struct index *idx = p->space->index[i];
-                       vdbe_emit_create_index(pParse, p->def, idx->def,
-                                              reg_space_id, idx->def->iid);
-               }
-       }
+       /*
+        * VIEWs can't have any functional parts such as
+        * indexes or foreign keys, so we can terminate
+        * right here.
+        */
+       if (p->def->opts.is_view)
+               return;
 
+       for (uint32_t i = 0; i < p->space->index_count; ++i) {
+               struct index *idx = p->space->index[i];
+               vdbe_emit_create_index(pParse, p->def, idx->def, reg_space_id,
+                                      idx->def->iid);
+       }
        /*
         * Check to see if we need to create an _sequence table
         * for keeping track of autoincrement keys.
@@ -1697,6 +1692,7 @@ sql_drop_table(struct Parse *parse_context, bool is_view)
 {
        struct drop_entity_def drop_def = parse_context->drop_entity_def;
        assert(drop_def.base.entity_type == ENTITY_TYPE_TABLE);
+       assert(drop_def.base.alter_action == ALTER_ACTION_DROP);
        struct SrcList *table_name_list = drop_def.base.entity_name;
        struct Vdbe *v = sqlite3GetVdbe(parse_context);
        struct sqlite3 *db = parse_context->db;
@@ -1798,11 +1794,12 @@ void
 sql_create_foreign_key(struct Parse *parse_context)
 {
        struct sqlite3 *db = parse_context->db;
-       struct create_fk_def create_fk_def = parse_context->create_fk_def;
-       struct create_constraint_def create_constr_def = create_fk_def.base;
-       struct create_entity_def create_def = create_constr_def.base;
-       struct alter_entity_def alter_def = create_def.base;
-       assert(alter_def.entity_type == ENTITY_TYPE_FK);
+       struct create_fk_def *create_fk_def = &parse_context->create_fk_def;
+       struct create_constraint_def *create_constr_def = &create_fk_def->base;
+       struct create_entity_def *create_def = &create_constr_def->base;
+       struct alter_entity_def *alter_def = &create_def->base;
+       assert(alter_def->entity_type == ENTITY_TYPE_FK);
+       assert(alter_def->alter_action == ALTER_ACTION_CREATE);
        /*
         * When this function is called second time during
         * <CREATE TABLE ...> statement (i.e. at VDBE runtime),
@@ -1826,7 +1823,7 @@ sql_create_foreign_key(struct Parse *parse_context)
        /* Whether we are processing ALTER TABLE or CREATE TABLE. */
        bool is_alter = new_tab == NULL;
        uint32_t child_cols_count;
-       struct ExprList *child_cols = create_fk_def.child_cols;
+       struct ExprList *child_cols = create_fk_def->child_cols;
        if (child_cols == NULL) {
                assert(!is_alter);
                child_cols_count = 1;
@@ -1835,7 +1832,7 @@ sql_create_foreign_key(struct Parse *parse_context)
        }
        struct space *child_space = NULL;
        if (is_alter) {
-               const char *child_name = alter_def.entity_name->a[0].zName;
+               const char *child_name = alter_def->entity_name->a[0].zName;
                child_space = space_by_name(child_name);
                if (child_space == NULL) {
                        diag_set(ClientError, ER_NO_SUCH_SPACE, child_name);
@@ -1852,7 +1849,7 @@ sql_create_foreign_key(struct Parse *parse_context)
                memset(fk, 0, sizeof(*fk));
                rlist_add_entry(&table_def->new_fkey, fk, link);
        }
-       struct Token *parent = create_fk_def.parent_name;
+       struct Token *parent = create_fk_def->parent_name;
        assert(parent != NULL);
        parent_name = sqlite3NameFromToken(db, parent);
        if (parent_name == NULL)
@@ -1864,7 +1861,7 @@ sql_create_foreign_key(struct Parse *parse_context)
         */
        is_self_referenced = !is_alter &&
                             strcmp(parent_name, new_tab->def->name) == 0;
-       struct ExprList *parent_cols = create_fk_def.parent_cols;
+       struct ExprList *parent_cols = create_fk_def->parent_cols;
        struct space *parent_space = space_by_name(parent_name);
        if (parent_space == NULL) {
                if (is_self_referenced) {
@@ -1885,18 +1882,18 @@ sql_create_foreign_key(struct Parse *parse_context)
                }
        }
        if (!is_alter) {
-               if (create_def.name.n == 0) {
+               if (create_def->name.n == 0) {
                        constraint_name =
                                sqlite3MPrintf(db, "FK_CONSTRAINT_%d_%s",
                                               ++table_def->fkey_count,
                                               new_tab->def->name);
                } else {
                        constraint_name =
-                               sqlite3NameFromToken(db, &create_def.name);
+                               sqlite3NameFromToken(db, &create_def->name);
                }
        } else {
                constraint_name =
-                       sqlite3NameFromToken(db, &create_def.name);
+                       sqlite3NameFromToken(db, &create_def->name);
        }
        if (constraint_name == NULL)
                goto exit_create_fk;
@@ -1929,11 +1926,11 @@ sql_create_foreign_key(struct Parse *parse_context)
                diag_set(OutOfMemory, fk_size, "region", "struct fkey");
                goto tnt_error;
        }
-       int actions = create_fk_def.actions;
+       int actions = create_fk_def->actions;
        fk->field_count = child_cols_count;
        fk->child_id = child_space != NULL ? child_space->def->id : 0;
        fk->parent_id = parent_space != NULL ? parent_space->def->id : 0;
-       fk->is_deferred = create_constr_def.is_deferred;
+       fk->is_deferred = create_constr_def->is_deferred;
        fk->match = (enum fkey_match) ((actions >> 16) & 0xff);
        fk->on_update = (enum fkey_action) ((actions >> 8) & 0xff);
        fk->on_delete = (enum fkey_action) (actions & 0xff);
@@ -2021,9 +2018,10 @@ fkey_change_defer_mode(struct Parse *parse_context, bool is_deferred)
 void
 sql_drop_foreign_key(struct Parse *parse_context)
 {
-       struct drop_entity_def drop_def = parse_context->drop_entity_def;
-       assert(drop_def.base.entity_type == ENTITY_TYPE_FK);
-       const char *table_name = drop_def.base.entity_name->a[0].zName;
+       struct drop_entity_def *drop_def = &parse_context->drop_entity_def;
+       assert(drop_def->base.entity_type == ENTITY_TYPE_FK);
+       assert(drop_def->base.alter_action == ALTER_ACTION_DROP);
+       const char *table_name = drop_def->base.entity_name->a[0].zName;
        assert(table_name != NULL);
        struct space *child = space_by_name(table_name);
        if (child == NULL) {
@@ -2033,7 +2031,7 @@ sql_drop_foreign_key(struct Parse *parse_context)
                return;
        }
        char *constraint_name = sqlite3NameFromToken(parse_context->db,
-                                                    &drop_def.name);
+                                                    &drop_def->name);
        if (constraint_name != NULL)
                vdbe_emit_fkey_drop(parse_context, constraint_name,
                                    child->def->id);
@@ -2234,22 +2232,22 @@ sql_create_index(struct Parse *parse) {
        char *name = NULL;
        struct sqlite3 *db = parse->db;
        assert(!db->init.busy);
-       struct create_index_def create_idx_def = parse->create_index_def;
-       struct create_entity_def *create_entity_def =
-               (struct create_entity_def *) &create_idx_def;
-       struct alter_entity_def alter_entity_def = create_entity_def->base;
-       assert(alter_entity_def.entity_type == ENTITY_TYPE_INDEX);
+       struct create_index_def *create_idx_def = &parse->create_index_def;
+       struct create_entity_def *create_entity_def = &create_idx_def->base.base;
+       struct alter_entity_def *alter_entity_def = &create_entity_def->base;
+       assert(alter_entity_def->entity_type == ENTITY_TYPE_INDEX);
+       assert(alter_entity_def->alter_action == ALTER_ACTION_CREATE);
        /*
         * Get list of columns to be indexed. It will be NULL if
         * this is a primary key or unique-constraint on the most
         * recent column added to the table under construction.
         */
-       struct ExprList *col_list = create_idx_def.cols;
-       struct SrcList *tbl_name = alter_entity_def.entity_name;
+       struct ExprList *col_list = create_idx_def->cols;
+       struct SrcList *tbl_name = alter_entity_def->entity_name;
 
        if (db->mallocFailed || parse->nErr > 0)
                goto exit_create_index;
-       enum sql_index_type idx_type = create_idx_def.idx_type;
+       enum sql_index_type idx_type = create_idx_def->idx_type;
        if (idx_type == SQL_INDEX_TYPE_UNIQUE ||
            idx_type == SQL_INDEX_TYPE_NON_UNIQUE) {
                Vdbe *v = sqlite3GetVdbe(parse);
@@ -2386,7 +2384,7 @@ sql_create_index(struct Parse *parse) {
                        goto exit_create_index;
                assert(col_list->nExpr == 1);
                sqlite3ExprListSetSortOrder(col_list,
-                                           create_idx_def.sort_order);
+                                           create_idx_def->sort_order);
        } else {
                sqlite3ExprListCheckLength(parse, col_list, "index");
        }
@@ -2559,14 +2557,15 @@ sql_create_index(struct Parse *parse) {
 void
 sql_drop_index(struct Parse *parse_context)
 {
-       struct drop_entity_def drop_def = parse_context->drop_entity_def;
-       assert(drop_def.base.entity_type == ENTITY_TYPE_INDEX);
+       struct drop_entity_def *drop_def = &parse_context->drop_entity_def;
+       assert(drop_def->base.entity_type == ENTITY_TYPE_INDEX);
+       assert(drop_def->base.alter_action == ALTER_ACTION_DROP);
        struct Vdbe *v = sqlite3GetVdbe(parse_context);
        assert(v != NULL);
        struct sqlite3 *db = parse_context->db;
        /* Never called with prior errors. */
        assert(parse_context->nErr == 0);
-       struct SrcList *table_list = drop_def.base.entity_name;
+       struct SrcList *table_list = drop_def->base.entity_name;
        assert(table_list->nSrc == 1);
        char *table_name = table_list->a[0].zName;
        const char *index_name = NULL;
@@ -2575,14 +2574,14 @@ sql_drop_index(struct Parse *parse_context)
        }
        sqlite3VdbeCountChanges(v);
        struct space *space = space_by_name(table_name);
-       bool if_exists = drop_def.if_exist;
+       bool if_exists = drop_def->if_exist;
        if (space == NULL) {
                if (!if_exists)
                        sqlite3ErrorMsg(parse_context, "no such space: %s",
                                        table_name);
                goto exit_drop_index;
        }
-       index_name = sqlite3NameFromToken(db, &drop_def.name);
+       index_name = sqlite3NameFromToken(db, &drop_def->name);
        uint32_t index_id = box_index_id_by_name(space->def->id, index_name,
                                                 strlen(index_name));
        if (index_id == BOX_ID_NIL) {
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index e8b053361..22b5669d8 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -168,8 +168,9 @@ cmd ::= ROLLBACK TO savepoint_opt nm(X). {
 //
 cmd ::= create_table create_table_args.
 create_table ::= createkw TABLE ifnotexists(E) nm(Y). {
-  alter_entity_def_init(&pParse->create_table_def, NULL, ENTITY_TYPE_TABLE);
-  create_entity_def_init(&pParse->create_table_def, Y, E);
+  alter_entity_def_init(&pParse->create_table_def.base.base, NULL);
+  create_entity_def_init(&pParse->create_table_def.base, Y, E);
+  create_table_def_init(&pParse->create_table_def);
   sqlite3StartTable(pParse);
 }
 createkw(A) ::= CREATE(A).  {disableLookaside(pParse);}
@@ -180,6 +181,7 @@ ifnotexists(A) ::= IF NOT EXISTS. {A = 1;}
 
 create_table_args ::= LP columnlist RP(E). {
   sqlite3EndTable(pParse,&E,0);
+  create_table_def_destroy(&pParse->create_table_def);
 }
 create_table_args ::= AS select(S). {
   sqlite3EndTable(pParse,0,S);
@@ -242,10 +244,10 @@ cconsdef ::= cconsname ccons.
 cconsname ::= cconsname_start cconsname_parse .
 cconsname_start ::= . {
   /* Prepare base members for re-usage. */
-  memset(&pParse->create_index_def, 0, sizeof(struct create_index_def));
+  memset(&pParse->create_index_def, 0, sizeof(struct create_constraint_def));
 }
 cconsname_parse ::= CONSTRAINT nm(X). {
-  create_entity_def_init(&pParse->create_index_def, X, false);
+  create_entity_def_init(&pParse->create_index_def.base.base, X, false);
 }
 cconsname_parse ::= .
 ccons ::= DEFAULT term(X).            {sqlite3AddDefaultValue(pParse,&X);}
@@ -271,25 +273,25 @@ ccons ::= NULL onconf(R).        {
 ccons ::= NOT NULL onconf(R).    {sql_column_add_nullable_action(pParse, R);}
 ccons ::= PRIMARY KEY sortorder(Z) autoinc(I). {
   pParse->create_table_def.has_autoinc = I;
-  sqlite3AddPrimaryKey(pParse,0,Z);
+  create_index_def_init(&pParse->create_index_def, NULL,
+                        SQL_INDEX_TYPE_CONSTRAINT_PK, Z);
+  sqlite3AddPrimaryKey(pParse);
 }
 ccons ::= UNIQUE. {
-  pParse->create_index_def.idx_type = SQL_INDEX_TYPE_CONSTRAINT_UNIQUE;
-  entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);
+  create_index_def_init(&pParse->create_index_def, NULL,
+                        SQL_INDEX_TYPE_CONSTRAINT_UNIQUE, SORT_ORDER_ASC);
   sql_create_index(pParse);
 }
 
 ccons ::= check_constraint_def .
 
 check_constraint_def ::= CHECK LP expr(X) RP. {
-  pParse->create_ck_def.expr = &X;
-  entity_set_type(&pParse->create_index_def, ENTITY_TYPE_CK);
+  create_ck_def_init(&pParse->create_ck_def, &X);
   sql_add_check_constraint(pParse);
 }
 
 ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R). {
   create_fk_def_init(&pParse->create_fk_def, NULL, &T, TA, R);
-  entity_set_type(&pParse->create_fk_def, ENTITY_TYPE_FK);
   sql_create_foreign_key(pParse);
 }
 ccons ::= defer_subclause(D).    {fkey_change_defer_mode(pParse, D);}
@@ -334,20 +336,20 @@ init_deferred_pred_opt(A) ::= INITIALLY IMMEDIATE.    {A = 0;}
 tconsdef ::= cconsname tcons.
 tcons ::= PRIMARY KEY LP sortlist(X) autoinc(I) RP. {
   pParse->create_table_def.has_autoinc = I;
-  sqlite3AddPrimaryKey(pParse, X, 0);
+  create_index_def_init(&pParse->create_index_def, X,
+                        SQL_INDEX_TYPE_CONSTRAINT_PK, SORT_ORDER_ASC);
+  sqlite3AddPrimaryKey(pParse);
 }
 tcons ::= UNIQUE LP sortlist(X) RP. {
-  pParse->create_index_def.cols = X;
-  pParse->create_index_def.idx_type = SQL_INDEX_TYPE_CONSTRAINT_UNIQUE;
-  entity_set_type(&pParse->create_index_def, ENTITY_TYPE_INDEX);
+  create_index_def_init(&pParse->create_index_def, X,
+                        SQL_INDEX_TYPE_CONSTRAINT_UNIQUE, SORT_ORDER_ASC);
   sql_create_index(pParse);
 }
 tcons ::= check_constraint_def .
 tcons ::= FOREIGN KEY LP eidlist(FA) RP
           REFERENCES nm(T) eidlist_opt(TA) refargs(R) defer_subclause_opt(D). {
+  create_constraint_def_init(&pParse->create_fk_def.base, D);
   create_fk_def_init(&pParse->create_fk_def, FA, &T, TA, R);
-  entity_set_defer_mode(&pParse->create_fk_def, D);
-  entity_set_type(&pParse->create_fk_def, ENTITY_TYPE_FK);
   sql_create_foreign_key(pParse);
 }
 %type defer_subclause_opt {int}
@@ -377,8 +379,9 @@ cmd ::= drop_start(X) drop_table . {
 }
 
 drop_table ::= ifexists(E) fullname(X) . {
-  alter_entity_def_init(&pParse->drop_entity_def, X, ENTITY_TYPE_TABLE);
-  pParse->drop_entity_def.if_exist = E;
+  alter_entity_def_init(&pParse->drop_entity_def.base, X);
+  drop_entity_def_init(&pParse->drop_entity_def, (struct Token) {}, E,
+                       ENTITY_TYPE_TABLE);
 }
 
 %type drop_start {bool}
@@ -394,8 +397,7 @@ ifexists(A) ::= .            {A = 0;}
 cmd ::= createkw(X) VIEW ifnotexists(E) nm(Y) eidlist_opt(C)
           AS select(S). {
   if (!pParse->parse_only) {
-    alter_entity_def_init(&pParse->create_table_def, NULL, ENTITY_TYPE_TABLE);
-    create_entity_def_init(&pParse->create_table_def, Y, E);
+    create_entity_def_init(&pParse->create_table_def.base, Y, E);
     sql_create_view(pParse, &X, C, S);
   } else {
     sql_store_select(pParse, S);
@@ -1239,12 +1241,11 @@ paren_exprlist(A) ::= LP exprlist(X) RP.  {A = X;}
 //
 cmd ::= createkw uniqueflag(U) INDEX ifnotexists(NE) nm(X)
         ON nm(Y) LP sortlist(Z) RP. {
-  alter_entity_def_init(&pParse->create_index_def,
-                        sqlite3SrcListAppend(pParse->db, 0, &Y),
-                        ENTITY_TYPE_INDEX);
-  create_entity_def_init(&pParse->create_index_def, X, NE);
-  pParse->create_index_def.idx_type = U;
-  pParse->create_index_def.cols = Z;
+  alter_entity_def_init((struct alter_entity_def *) &pParse->create_index_def,
+                        sqlite3SrcListAppend(pParse->db, 0, &Y));
+  create_entity_def_init((struct create_entity_def *) &pParse->create_index_def,
+                         X, NE);
+  create_index_def_init(&pParse->create_index_def, Z, U, SORT_ORDER_ASC);
   sql_create_index(pParse);
 }
 
@@ -1308,8 +1309,8 @@ collate(C) ::= COLLATE id.   {C = 1;}
 ///////////////////////////// The DROP INDEX command /////////////////////////
 //
 cmd ::= DROP INDEX ifexists(E) nm(X) ON fullname(Y).   {
-  alter_entity_def_init(&pParse->drop_entity_def, Y, ENTITY_TYPE_INDEX);
-  drop_entity_def_init(&pParse->drop_entity_def, X, E);
+  alter_entity_def_init(&pParse->drop_entity_def.base, Y);
+  drop_entity_def_init(&pParse->drop_entity_def, X, E, ENTITY_TYPE_INDEX);
   sql_drop_index(pParse);
 }
 
@@ -1362,12 +1363,9 @@ trigger_decl(A) ::= TRIGGER ifnotexists(NOERR) nm(B)
                     trigger_time(C) trigger_event(D)
                     ON fullname(E) foreach_clause when_clause(G). {
   struct create_trigger_def *trigger_def = &pParse->create_trigger_def;
-  alter_entity_def_init(trigger_def, E, ENTITY_TYPE_TRIGGER);
-  create_entity_def_init(trigger_def, B, NOERR);
-  trigger_def->tr_tm = C;
-  trigger_def->op = D.a;
-  trigger_def->cols = D.b;
-  trigger_def->when = G;
+  alter_entity_def_init((struct alter_entity_def *) trigger_def, E);
+  create_entity_def_init((struct create_entity_def *) trigger_def, B, NOERR);
+  create_trigger_def_init(trigger_def, C, D.a, D.b, G);
   sql_trigger_begin(pParse);
   A = B; /*A-overwrites-T*/
 }
@@ -1478,8 +1476,9 @@ raisetype(A) ::= FAIL.      {A = ON_CONFLICT_ACTION_FAIL;}
 
 ////////////////////////  DROP TRIGGER statement //////////////////////////////
 cmd ::= DROP TRIGGER ifexists(NOERR) fullname(X). {
-  alter_entity_def_init(&pParse->drop_entity_def, X, ENTITY_TYPE_TRIGGER);
-  drop_entity_def_init(&pParse->drop_entity_def, (struct Token){}, NOERR);
+  alter_entity_def_init(&pParse->drop_entity_def.base, X);
+  drop_entity_def_init(&pParse->drop_entity_def, (struct Token){}, NOERR,
+                       ENTITY_TYPE_TRIGGER);
   sql_drop_trigger(pParse);
 }
 
@@ -1489,24 +1488,24 @@ cmd ::= ANALYZE nm(X).          {sqlite3Analyze(pParse, &X);}
 
 //////////////////////// ALTER TABLE table ... ////////////////////////////////
 cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). {
-  alter_entity_def_init(&pParse->rename_entity_def, X, ENTITY_TYPE_TABLE);
-  pParse->rename_entity_def.new_name = Z;
+  alter_entity_def_init(&pParse->rename_entity_def.base, X);
+  rename_entity_def_init(&pParse->rename_entity_def, Z);
   sql_alter_table_rename(pParse);
 }
 
 cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT nm(Z) FOREIGN KEY
         LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) refargs(R)
         defer_subclause_opt(D). {
-  alter_entity_def_init(&pParse->create_fk_def, X, ENTITY_TYPE_FK);
-  create_entity_def_init(&pParse->create_fk_def, Z, false);
-  entity_set_defer_mode(&pParse->create_fk_def, D);
+  alter_entity_def_init((struct alter_entity_def *) &pParse->create_fk_def, X);
+  create_entity_def_init(&pParse->create_fk_def.base.base, Z, false);
+  create_constraint_def_init(&pParse->create_fk_def.base, D);
   create_fk_def_init(&pParse->create_fk_def, FA, &T, TA, R);
   sql_create_foreign_key(pParse);
 }
 
 cmd ::= ALTER TABLE fullname(X) DROP CONSTRAINT nm(Z). {
-  alter_entity_def_init(&pParse->drop_entity_def, X, ENTITY_TYPE_FK);
-  drop_entity_def_init(&pParse->drop_entity_def, Z, false);
+  alter_entity_def_init(&pParse->drop_entity_def.base, X);
+  drop_entity_def_init(&pParse->drop_entity_def, Z, false, ENTITY_TYPE_FK);
   sql_drop_foreign_key(pParse);
 }
 
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index 7a61a27e0..0392c0a87 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -31,6 +31,7 @@
  * SUCH DAMAGE.
  */
 #include "box/fkey.h"
+#include "box/sql.h"
 
 /**
  * This file contains auxiliary structures and functions which
@@ -74,8 +75,6 @@
 /**
  * Each token coming out of the lexer is an instance of
  * this structure. Tokens are also used as part of an expression.
- *
- * Note if Token.z is NULL then Token.n is undefined.
  */
 struct Token {
        /** Text of the token. Not NULL-terminated! */
@@ -85,6 +84,33 @@ struct Token {
        bool isReserved;
 };
 
+/**
+ * Structure representing foreign keys constraints appeared
+ * within CREATE TABLE statement. Used only during parsing.
+ */
+struct fkey_parse {
+       /**
+        * Foreign keys constraint declared in <CREATE TABLE ...>
+        * statement. They must be coded after space creation.
+        */
+       struct fkey_def *fkey;
+       /**
+        * If inside CREATE TABLE statement we want to declare
+        * self-referenced FK constraint, we must delay their
+        * resolution until the end of parsing of all columns.
+        * E.g.: CREATE TABLE t1(id REFERENCES t1(b), b);
+        */
+       struct ExprList *selfref_cols;
+       /**
+        * Still, self-referenced columns might be NULL, if
+        * we declare FK constraints referencing PK:
+        * CREATE TABLE t1(id REFERENCES t1) - it is a valid case.
+        */
+       bool is_self_referenced;
+       /** Organize these structs into linked list. */
+       struct rlist link;
+};
+
 /**
  * Possible SQL index types. Note that PK and UNIQUE constraints
  * are implemented as indexes and have their own types:
@@ -107,9 +133,17 @@ enum entity_type {
        entity_type_MAX
 };
 
+enum alter_action {
+       ALTER_ACTION_CREATE = 0,
+       ALTER_ACTION_DROP,
+       ALTER_ACTION_RENAME
+};
+
 struct alter_entity_def {
        /** Type of topmost entity. */
        enum entity_type entity_type;
+       /** Action to be performed using current entity. */
+       enum alter_action alter_action;
        /** As a rule it is a name of table to be altered. */
        struct SrcList *entity_name;
 };
@@ -195,54 +229,84 @@ struct create_index_def {
        enum sort_order sort_order;
 };
 
-/**
- * In those functions which accept void * instead of certain type
- * it was made on purpose: it allows to avoid explicit cast before
- * passing parameter to function. Hence, we can invoke it like:
- * entity_set_type(create_fk_def, ...); instead of
- * entity_set_type((struct alter_entity_def *) create_fk_def, ...);
- */
+/** Basic initialisers of parse structures.*/
 static inline void
-entity_set_type(void *entity_def, enum entity_type type)
+alter_entity_def_init(struct alter_entity_def *alter_def,
+                     struct SrcList *entity_name)
 {
-       struct alter_entity_def *alter_def =
-               (struct alter_entity_def *) entity_def;
-       alter_def->entity_type = type;
+       alter_def->entity_name = entity_name;
 }
 
 static inline void
-entity_set_defer_mode(void *entity_def, bool is_deferred)
+rename_entity_def_init(struct rename_entity_def *rename_def,
+                      struct Token new_name)
 {
-       struct create_constraint_def *constr_def =
-               (struct create_constraint_def *) entity_def;
-       constr_def->is_deferred = is_deferred;
+       rename_def->new_name = new_name;
+       struct alter_entity_def *alter_def =
+               (struct alter_entity_def *) rename_def;
+       alter_def->entity_type = ENTITY_TYPE_TABLE;
+       alter_def->alter_action = ALTER_ACTION_RENAME;
 }
 
 static inline void
-alter_entity_def_init(void *entity_def, struct SrcList *entity_name,
-                     enum entity_type type)
+create_entity_def_init(struct create_entity_def *create_def, struct Token name,
+                      bool if_not_exist)
 {
-       struct alter_entity_def *alter_def =
-               (struct alter_entity_def *) entity_def;
-       alter_def->entity_name = entity_name;
-       alter_def->entity_type = type;
+       create_def->name = name;
+       create_def->if_not_exist = if_not_exist;
 }
 
 static inline void
-create_entity_def_init(void *entity_def, struct Token name, bool if_not_exist)
+create_constraint_def_init(struct create_constraint_def *constr_def,
+                          bool is_deferred)
 {
-       struct create_entity_def *create_def =
-               (struct create_entity_def *) entity_def;
-       create_def->name = name;
-       create_def->if_not_exist = if_not_exist;
+       constr_def->is_deferred = is_deferred;
 }
 
 static inline void
 drop_entity_def_init(struct drop_entity_def *drop_def, struct Token name,
-                    bool if_exist)
+                    bool if_exist, enum entity_type entity_type)
 {
        drop_def->name = name;
        drop_def->if_exist = if_exist;
+       drop_def->base.entity_type = entity_type;
+       drop_def->base.alter_action = ALTER_ACTION_DROP;
+}
+
+static inline void
+create_trigger_def_init(struct create_trigger_def *trigger_def, int tr_tm,
+                       int op, struct IdList *cols, struct Expr *when) {
+       trigger_def->tr_tm = tr_tm;
+       trigger_def->op = op;
+       trigger_def->cols = cols;
+       trigger_def->when = when;
+       struct alter_entity_def *alter_def =
+               (struct alter_entity_def *) trigger_def;
+       alter_def->entity_type = ENTITY_TYPE_TRIGGER;
+       alter_def->alter_action = ALTER_ACTION_CREATE;
+}
+
+static inline void
+create_ck_def_init(struct create_ck_def *ck_def, struct ExprSpan *expr)
+{
+       ck_def->expr = expr;
+       struct alter_entity_def *alter_def =
+               (struct alter_entity_def *) ck_def;
+       alter_def->entity_type = ENTITY_TYPE_CK;
+       alter_def->alter_action = ALTER_ACTION_CREATE;
+}
+
+static inline void
+create_index_def_init(struct create_index_def *index_def, struct ExprList *cols,
+                     enum sql_index_type idx_type, enum sort_order sort_order)
+{
+       index_def->cols = cols;
+       index_def->idx_type = idx_type;
+       index_def->sort_order = sort_order;
+       struct alter_entity_def *alter_def =
+               (struct alter_entity_def *) index_def;
+       alter_def->entity_type = ENTITY_TYPE_INDEX;
+       alter_def->alter_action = ALTER_ACTION_CREATE;
 }
 
 static inline void
@@ -255,6 +319,28 @@ create_fk_def_init(struct create_fk_def *fk_def, struct ExprList *child_cols,
        fk_def->parent_name = parent_name;
        fk_def->parent_cols = parent_cols;
        fk_def->actions = actions;
+       struct alter_entity_def *alter_def =
+               (struct alter_entity_def *) fk_def;
+       alter_def->entity_type = ENTITY_TYPE_FK;
+       alter_def->alter_action = ALTER_ACTION_CREATE;
+}
+
+static inline void
+create_table_def_init(struct create_table_def *table_def)
+{
+       rlist_create(&table_def->new_fkey);
+       struct alter_entity_def *alter_def =
+               (struct alter_entity_def *) table_def;
+       alter_def->entity_type = ENTITY_TYPE_TABLE;
+       alter_def->alter_action = ALTER_ACTION_CREATE;
+}
+
+static inline void
+create_table_def_destroy(struct create_table_def *table_def)
+{
+       struct fkey_parse *fk;
+       rlist_foreach_entry(fk, &table_def->new_fkey, link)
+               sql_expr_list_delete(sql_get(), fk->selfref_cols);
 }
 
 #endif /* TARANTOOL_BOX_SQL_PARSE_DEF_H_INCLUDED */
diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index 3343c2942..c2660eeff 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -273,7 +273,6 @@ sql_parser_create(struct Parse *parser, sqlite3 *db)
 {
        memset(parser, 0, sizeof(struct Parse));
        parser->db = db;
-       rlist_create(&parser->create_table_def.new_fkey);
        rlist_create(&parser->record_list);
        region_create(&parser->region, &cord()->slabc);
 }
@@ -286,9 +285,6 @@ sql_parser_destroy(Parse *parser)
        sqlite3 *db = parser->db;
        sqlite3DbFree(db, parser->aLabel);
        sql_expr_list_delete(db, parser->pConstExpr);
-       struct fkey_parse *fk;
-       rlist_foreach_entry(fk, &parser->create_table_def.new_fkey, link)
-               sql_expr_list_delete(db, fk->selfref_cols);
        if (db != NULL) {
                assert(db->lookaside.bDisable >=
                       parser->disableLookaside);
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index e66f0d6db..50d787d11 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -1616,9 +1616,9 @@ void
 sql_resolve_self_reference(struct Parse *parser, struct Table *table, int type,
                           struct Expr *expr, struct ExprList *expr_list)
 {
-       /* Fake SrcList for parser->new_table */
+       /* Fake SrcList for parser->create_table_def */
        SrcList sSrc;
-       /* Name context for parser->new_table */
+       /* Name context for parser->create_table_def  */
        NameContext sNC;
 
        assert(type == NC_IsCheck || type == NC_IdxExpr);
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 4ee2ed5cf..4e6806be8 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2638,33 +2638,6 @@ enum ast_type {
        ast_type_MAX
 };
 
-/**
- * Structure representing foreign keys constraints appeared
- * within CREATE TABLE statement. Used only during parsing.
- */
-struct fkey_parse {
-       /**
-        * Foreign keys constraint declared in <CREATE TABLE ...>
-        * statement. They must be coded after space creation.
-        */
-       struct fkey_def *fkey;
-       /**
-        * If inside CREATE TABLE statement we want to declare
-        * self-referenced FK constraint, we must delay their
-        * resolution until the end of parsing of all columns.
-        * E.g.: CREATE TABLE t1(id REFERENCES t1(b), b);
-        */
-       struct ExprList *selfref_cols;
-       /**
-        * Still, self-referenced columns might be NULL, if
-        * we declare FK constraints referencing PK:
-        * CREATE TABLE t1(id REFERENCES t1) - it is a valid case.
-        */
-       bool is_self_referenced;
-       /** Organize these structs into linked list. */
-       struct rlist link;
-};
-
 /*
  * An SQL parser context.  A copy of this structure is passed through
  * the parser and down into all the parser action routine in order to
@@ -3350,7 +3323,8 @@ void
 sql_column_add_nullable_action(struct Parse *parser,
                               enum on_conflict_action nullable_action);
 
-void sqlite3AddPrimaryKey(Parse *, ExprList *, enum sort_order);
+void
+sqlite3AddPrimaryKey(struct Parse *parse);
 
 /**
  * Add a new CHECK constraint to the table currently under
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index 33b5ea7c4..3456a244d 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -68,24 +68,25 @@ sql_trigger_begin(struct Parse *parse)
        struct sql_trigger *trigger = NULL;
        /* The database connection. */
        struct sqlite3 *db = parse->db;
-       struct create_trigger_def trigger_def = parse->create_trigger_def;
-       struct create_entity_def create_def = trigger_def.base;
-       struct alter_entity_def alter_def = create_def.base;
-       assert(alter_def.entity_type == ENTITY_TYPE_TRIGGER);
+       struct create_trigger_def *trigger_def = &parse->create_trigger_def;
+       struct create_entity_def *create_def = &trigger_def->base;
+       struct alter_entity_def *alter_def = &create_def->base;
+       assert(alter_def->entity_type == ENTITY_TYPE_TRIGGER);
+       assert(alter_def->alter_action == ALTER_ACTION_CREATE);
 
        char *trigger_name = NULL;
-       if (alter_def.entity_name == NULL || db->mallocFailed)
+       if (alter_def->entity_name == NULL || db->mallocFailed)
                goto trigger_cleanup;
-       assert(alter_def.entity_name->nSrc == 1);
-       assert(create_def.name.n > 0);
-       trigger_name = sqlite3NameFromToken(db, &create_def.name);
+       assert(alter_def->entity_name->nSrc == 1);
+       assert(create_def->name.n > 0);
+       trigger_name = sqlite3NameFromToken(db, &create_def->name);
        if (trigger_name == NULL)
                goto trigger_cleanup;
 
        if (sqlite3CheckIdentifierName(parse, trigger_name) != SQLITE_OK)
                goto trigger_cleanup;
 
-       const char *table_name = alter_def.entity_name->a[0].zName;
+       const char *table_name = alter_def->entity_name->a[0].zName;
        uint32_t space_id;
        if (schema_find_id(BOX_SPACE_ID, 2, table_name, strlen(table_name),
                           &space_id) != 0)
@@ -108,7 +109,7 @@ sql_trigger_begin(struct Parse *parse)
                int name_reg = ++parse->nMem;
                sqlite3VdbeAddOp4(parse->pVdbe, OP_String8, 0, name_reg, 0,
                                  name_copy, P4_DYNAMIC);
-               bool no_err = create_def.if_not_exist;
+               bool no_err = create_def->if_not_exist;
                if (vdbe_emit_halt_with_presence_test(parse, BOX_TRIGGER_ID, 0,
                                                      name_reg, 1,
                                                      ER_TRIGGER_EXISTS,
@@ -126,12 +127,12 @@ sql_trigger_begin(struct Parse *parse)
        trigger->space_id = space_id;
        trigger->zName = trigger_name;
        trigger_name = NULL;
-       assert(trigger_def.op == TK_INSERT || trigger_def.op == TK_UPDATE ||
-              trigger_def.op== TK_DELETE);
-       trigger->op = (u8) trigger_def.op;
-       trigger->tr_tm = trigger_def.tr_tm;
-       trigger->pWhen = sqlite3ExprDup(db, trigger_def.when, EXPRDUP_REDUCE);
-       trigger->pColumns = sqlite3IdListDup(db, trigger_def.cols);
+       assert(trigger_def->op == TK_INSERT || trigger_def->op == TK_UPDATE ||
+              trigger_def->op== TK_DELETE);
+       trigger->op = (u8) trigger_def->op;
+       trigger->tr_tm = trigger_def->tr_tm;
+       trigger->pWhen = sqlite3ExprDup(db, trigger_def->when, EXPRDUP_REDUCE);
+       trigger->pColumns = sqlite3IdListDup(db, trigger_def->cols);
        if ((trigger->pWhen != NULL && trigger->pWhen == NULL) ||
            (trigger->pColumns != NULL && trigger->pColumns == NULL))
                goto trigger_cleanup;
@@ -141,9 +142,9 @@ sql_trigger_begin(struct Parse *parse)
 
  trigger_cleanup:
        sqlite3DbFree(db, trigger_name);
-       sqlite3SrcListDelete(db, alter_def.entity_name);
-       sqlite3IdListDelete(db, trigger_def.cols);
-       sql_expr_delete(db, trigger_def.when, false);
+       sqlite3SrcListDelete(db, alter_def->entity_name);
+       sqlite3IdListDelete(db, trigger_def->cols);
+       sql_expr_delete(db, trigger_def->when, false);
        if (parse->parsed_ast.trigger == NULL)
                sql_trigger_delete(db, trigger);
        else
@@ -424,11 +425,12 @@ vdbe_code_drop_trigger(struct Parse *parser, const char *trigger_name,
 void
 sql_drop_trigger(struct Parse *parser)
 {
-       struct drop_entity_def drop_def = parser->drop_entity_def;
-       struct alter_entity_def alter_def = drop_def.base;
-       assert(alter_def.entity_type == ENTITY_TYPE_TRIGGER);
-       struct SrcList *name = alter_def.entity_name;
-       bool no_err = drop_def.if_exist;
+       struct drop_entity_def *drop_def = &parser->drop_entity_def;
+       struct alter_entity_def *alter_def = &drop_def->base;
+       assert(alter_def->entity_type == ENTITY_TYPE_TRIGGER);
+       assert(alter_def->alter_action == ALTER_ACTION_DROP);
+       struct SrcList *name = alter_def->entity_name;
+       bool no_err = drop_def->if_exist;
        sqlite3 *db = parser->db;
        if (db->mallocFailed)
                goto drop_trigger_cleanup;

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

* [tarantool-patches] Re: [PATCH v2 4/5] sql: fix error message for improperly created index
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 4/5] sql: fix error message for improperly created index Nikita Pettik
@ 2019-02-08 17:14   ` Konstantin Osipov
  0 siblings, 0 replies; 34+ messages in thread
From: Konstantin Osipov @ 2019-02-08 17:14 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, Nikita Pettik

* Nikita Pettik <korablev@tarantool.org> [19/01/23 21:03]:
> Table can be created without any indexes (for instance, from Lua-land).
> On the other hand, bytecode generated for CREATE INDEX statement
> attempts at finding entry in _index space with given space id.
> In case it is not found error "wrong space id" is raised. On the other
> hand, it is obvious that such message is appeared when table doesn't
> have any created indexes yet. Hence, lets fix this message to indicate
> that primary key should be created before any secondary indexes.

I hope when Mergen pushes his patch you will be able to use
diag_set for that.

We should prefer using diag_set because errcode.h can be i18n-zed
at least in theory, while hard-coded messages in the source code -
can't.

>  	sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_ERROR, ON_CONFLICT_ACTION_FAIL, 0,
> -			  sqlite3MPrintf(parse->db, "Invalid space id: %d",
> -					 space_id), P4_DYNAMIC);
> +			  "can not add a secondary key before primary",
> +			  P4_STATIC);
>  
>  	sqlite3VdbeJumpHere(v, goto_succ_addr);

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* [tarantool-patches] Re: [PATCH v2 5/5] sql: introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PRIMARY KEY
  2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 5/5] sql: introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PRIMARY KEY Nikita Pettik
  2019-01-24  8:31   ` [tarantool-patches] " Konstantin Osipov
  2019-01-29 19:29   ` Vladislav Shpilevoy
@ 2019-02-08 17:16   ` Konstantin Osipov
  2019-02-08 17:36     ` n.pettik
  2 siblings, 1 reply; 34+ messages in thread
From: Konstantin Osipov @ 2019-02-08 17:16 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy, Nikita Pettik

* Nikita Pettik <korablev@tarantool.org> [19/01/23 21:03]:
> +static void
> +pk_check_existence(struct Parse *parse, uint32_t space_id, int _index_cursor)

Tarantool function naming scheme is
modulename-subject-verb-object.

I applaud to an attempt to make the code follow Tarantool coding
style, but please do follow it then.

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

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

* [tarantool-patches] Re: [PATCH v2 5/5] sql: introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PRIMARY KEY
  2019-02-08 17:16   ` Konstantin Osipov
@ 2019-02-08 17:36     ` n.pettik
  0 siblings, 0 replies; 34+ messages in thread
From: n.pettik @ 2019-02-08 17:36 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Konstantin Osipov, Vladislav Shpilevoy



> On 8 Feb 2019, at 20:16, Konstantin Osipov <kostja@tarantool.org> wrote:
> 
> * Nikita Pettik <korablev@tarantool.org> [19/01/23 21:03]:
>> +static void
>> +pk_check_existence(struct Parse *parse, uint32_t space_id, int _index_cursor)
> 
> Tarantool function naming scheme is
> modulename-subject-verb-object.
> 
> I applaud to an attempt to make the code follow Tarantool coding
> style, but please do follow it then.

I remember that I should fix this naming.
But firstly, I must deal with the first part of current patch-set.
When I get to the last commit, I will rename function to
“vdbe_emit_pk_existence_check” or sort of
(hope module name is redundant since it is a static function).

> 
> -- 
> Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
> http://tarantool.io - www.twitter.com/kostja_osipov
> 

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-02-08 14:25         ` n.pettik
@ 2019-02-15 20:13           ` Vladislav Shpilevoy
  2019-02-27 22:56             ` n.pettik
  0 siblings, 1 reply; 34+ messages in thread
From: Vladislav Shpilevoy @ 2019-02-15 20:13 UTC (permalink / raw)
  To: n.pettik, tarantool-patches

Hi! Thanks for the fixes!

> suggest ‘_prepare’, ‘_assebmle’, ‘_fill'
> If you are sure about ‘_create' naming, I will fix it.
> 
>>> +  sqlite3StartTable(pParse);
>>>   }
>>>   createkw(A) ::= CREATE(A).  {disableLookaside(pParse);}
>>>   @@ -237,8 +239,15 @@ nm(A) ::= id(A). {
>>>   carglist ::= carglist cconsdef.
>>>   carglist ::= .
>>>   cconsdef ::= cconsname ccons.
>>> -cconsname ::= CONSTRAINT nm(X).           {pParse->constraintName = X;}
>>> -cconsname ::= .                           {pParse->constraintName.n = 0;}
>>> +cconsname ::= cconsname_start cconsname_parse .
>>> +cconsname_start ::= . {
>>> +  /* Prepare base members for re-usage. */
>>> +  memset(&pParse->create_index_def, 0, sizeof(struct create_index_def));
>>> +}
>>> +cconsname_parse ::= CONSTRAINT nm(X). {
>>> +  create_entity_def_init(&pParse->create_index_def, X, false);
>>
>> 7. Why in case of any constraint start, you reset only index_def? UNIQUE/PK
>> are not the only existing constraints.
>> Also, memset(0) is not scalable
>> solution - you need a separate reset() function, or something like that in
>> case if in future parse_def.h structures will grow in size and complexity,
>> and 0 becomes not default value of some attribute.
> 
> Well, imho it is too much.

I did not understand. What is too much?

> Actually, here we really care only about
> zeroing several pointers and some size params and don’t care
> about the rest.

Yes, but here you assume, that index_def always lies in the
same memory as all other constraint objects, and we will never
store more pointers in the rest memory > sizeof(index_def).

> This is first rule to be called when some constraint
> is created. What is more, now terminal initialisers fill all fields, except
> for base structs. Hence, now it is enough to reset memory layout
> up to struct create_constraint_def:

Your code below assumes the thing I said above, so please, add a
struct create_constraint_def to union in struct Parse and use it
here. Like this:

diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 22b5669d8..83c0fc465 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -244,7 +244,7 @@ cconsdef ::= cconsname ccons.
  cconsname ::= cconsname_start cconsname_parse .
  cconsname_start ::= . {
    /* Prepare base members for re-usage. */
-  memset(&pParse->create_index_def, 0, sizeof(struct create_constraint_def));
+  memset(&pParse->constraint_def, 0, sizeof(pParse->constraint_def));
  }
  cconsname_parse ::= CONSTRAINT nm(X). {
    create_entity_def_init(&pParse->create_index_def.base.base, X, false);
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 4e6806be8..8566fe404 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2731,6 +2731,7 @@ struct Parse {
  	 * from parse.y
  	 */
  	union {
+		struct create_constraint_def constraint_def;
  		struct create_ck_def create_ck_def;
  		struct create_fk_def create_fk_def;
  		struct create_index_def create_index_def;

>>> diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
>>> +enum entity_type {
>>> +	ENTITY_TYPE_TABLE = 0,
>>> +	ENTITY_TYPE_INDEX,
>>> +	ENTITY_TYPE_TRIGGER,
>>> +	ENTITY_TYPE_CK,
>>> +	ENTITY_TYPE_FK,
>>> +	entity_type_MAX
>>
>> 9. These entity_types are useless. Even being used in asserts,
>> they do not prevent errors, allowing to create an instance of
>> create_entity_def, but use it as drop_entity_def, because
>> base.entity_type in both cases can be, for example,
>> ENTITY_TYPE_INDEX.
>>
>> Please, use not object entity types, but def types:
>>
>>     ALTER_DEF_CREATE_TABLE/DROP_TABLE/RENAME.../...INDEX.../...
>>
>> Or split entity type and action, so as to have something like
>> this:
>>
>>     def.entity = ALTER_DEF_TABLE;
>>     def.action = ALTER_DEF_RENAME;
> 
> I’d better choose this way. Sorry, I can’t spot diff
> of particularly this fix, since it has been entangled with diffs
> to other comments. But I will attach whole diff at the end of letter.
> 
>> By the way, please, rename ENTITY_TYPE_CK to ENTITY_TYPE_CHECK.
>> Three letters 'HEC' economy is not worth the name obfuscation.
> 
> It is common abbreviation (the same as FK), and AFAIR was even
> suggested by Konstantin in one of reviews (mb not to this patch).
> It simply makes name be similar to ENTITY_TYPE_FK.

I see CK here first time. We've never used this abbreviation as I
remember. We use FK not because it is a policy, but because it is
just too long without contraction.

Otherwise we would have ENTITY_TYPE_IN instead of _INDEX,
ENTITY_TYPE_TA instead of _TABLE, ENTITY_TYPE_TR instead of _TRIGGER.

> @@ -264,6 +283,7 @@ create_fk_def_init(struct create_fk_def *fk_def, struct ExprList *child_cols,
>          fk_def->parent_name = parent_name;
>          fk_def->parent_cols = parent_cols;
>          fk_def->actions = actions;
> +       ((struct alter_entity_def *) fk_def)->entity_type = ENTITY_TYPE_FK;
>   }
> 
> I know you asked me to choose between base struct and cast.
> However, I think here cast is much more suitable: these structures
> are terminated so we have to write base.base.base.
> On the other hand, almost in all places one or two nesting
> base usage looks better. I can revert this change with ease,
> if you want to.

base.base.base is bad, but just one base is good, where possible.


Below I listed the calls you do to create each 'terminal' structure.

	TABLE RENAME:
	alter_entity_def_init
	rename_entity_def_init

	TABLE CREATE
	alter_entity_def_init
	create_entity_def_init
	create_table_def_init

	TABLE DROP:
	alter_entity_def_init
	drop_entity_def_init

	TABLE ADD FK:
	alter_entity_def_init
	create_entity_def_init
	create_constraint_def_init
	create_fk_def_init

	DROP FK:
	alter_entity_def_init
	drop_entity_def_init

	DROP INDEX:
	alter_entity_def_init
	drop_entity_def_init

	ADD INDEX:
	alter_entity_def_init
	create_entity_def_init
	create_index_def_init

	CHECK:
         alter_entity_def_init
         create_entity_def_init
	create_ck_def_init

	CREATE TRIGGER:
	alter_entity_def_init
	create_entity_def_init
	create_trigger_def_init

	DROP TRIGGER:
	alter_entity_def_init
	drop_entity_def_init

Here are some problems with what you said earlier and with
the whole structure.

Firstly, some of sequences are not finished with a an explicit
terminal. For example, *all* DROPs are finished with an abstract
drop_entity_def_init() taking an entity type explicitly. Despite
the words you said earlier that you initialize everything in steps.
So in the case of DROP the rule is violated already. I think, that
you should define static inline helpers in parse_def.h with names
drop_table_def_init(), drop_index_def_init(), drop_check_def_init()
etc.

Secondly, some words about create_entity_def_init(). It is the
most arguable function for a public usage (out of parse_def.h)
because hardly I can imagine a situation when you parsed a word
CREATE, but can not parse a next one: TABLE, TRIGGER, CONSTRAINT etc.
And we see it in the lists above. Below I listed all of this function
usage cases and how you can replace that function here.

- create_entity_def_init() in create_table ::= rule, line 172. Here
   it is followed by create_table_def_init() on the next line.

- create_entity_def_init() in cconsname_parse ::= rule line 250.
   This rule is a prefix for a constraint definition, so this function
   can be substituted with create_constraint_def_init().

- create_entity_def_init() in cmd ::= rule (about CREATE VIEW),
   line 400. Here we see, that 1) you missed ENTITY_TYPE_VIEW,
   2) create_view_def_init() can be called here.

- create_entity_def_init() in cmd ::= rule (about CREATE INDEX),
   line 1246. Here it is followed by create_index_def_init() on the
   next line.

- create_entity_def_init() in trigger_decl ::= rule, line 1367.
   Here it is followed by create_trigger_def_init().

- create_entity_def_init() in cmd ::= rule (about ALTER TABLE ADD
   CONSTRAINT FOREIGN KEY), line 1500. Here it is followed by
   create_constraint_def_init().

So as you can see create_entity_def_init() is not necessary to use
out of parse_def.h. You can put it into
create_table/index/constraint_def().

Now about create_constraint_def_init(). In your patch it is always
followed by create_fk_def_init(). But after you did the comment above
about create_entity_def_init(), you will have create_constraint_def_init()
in cconsname_parse ::= CONSTRAINT nm(X) rule. And I do not see any
concrete reasons why can not you remove it from here and use only
create_index_def_init(), create_ck_def_init() etc in ccons ::= rule.
You said, that some of pointers should be nullifed, but what pointers
and why? What if you do not nullify them right after CONSTRAINT word
is met?

Thirdly, about alter_entity_def_init. Even now it is *always*
followed by create_*_init(), or drop_*_init(), or rename_*_init().
So I advise you to make this function internal for parse_def.h and
put its calls into create/drop/rename_*_init().

The whole idea, as I understood it, was not to just split into a
random steps, but so as to build an automaton:


                   +- drop_check
                   |
                   +- drop_fk
                   |
                   +- drop_table
                   |
    drop_entity ---+- drop_index
   /
-- rename_entity --- rename_table
   \
    create_entity -+- create_index
                   |
                   +- create_table
                   |
                   +- create_fk
                   |
                   +- create_check

Which in future should be easily expanded to this:

                    +- drop_check
                    |
                    +- drop_fk
                    |
                    +- drop_table
                    |
   +- drop_entity --+- drop_index
   |
   |                +- index_rename
   |                |
   |                +- table_add_column
   |                |
--+- alter_entity -+- table_rename
   |                |
   |                +- table_drop_column
   |                |
   |                +- fk_rename
   |
   +- create_entity +- create_index
                    |
                    +- create_table
                    |
                    +- create_fk
                    |
                    +- create_check

So any rule can by finished in two calls. First defines an action,
second defines an entity and action-entity details. Now some of your
rules are finished in 2, some in 3, some in 4 steps, and at the end
of some of them you still are not in a terminal position.

Although I am almost sure, that all of my proposals above are wrong
in K.O. opinion, so please, consult him. If he wants something else,
then I do not understand what, and I will respond LGTM.

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-02-15 20:13           ` Vladislav Shpilevoy
@ 2019-02-27 22:56             ` n.pettik
  2019-03-12 12:50               ` Vladislav Shpilevoy
  0 siblings, 1 reply; 34+ messages in thread
From: n.pettik @ 2019-02-27 22:56 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

First of all, many thanks for providing detailed analysis,
even despite insignificance of this patch (very first version
was ready about two months ago and IMHO it was OK;
didn’t expect that suggested refactoring might take so long).

> On 15 Feb 2019, at 23:13, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> Hi! Thanks for the fixes!
>> suggest ‘_prepare’, ‘_assebmle’, ‘_fill'
>> If you are sure about ‘_create' naming, I will fix it.
>>>> +  sqlite3StartTable(pParse);
>>>>  }
>>>>  createkw(A) ::= CREATE(A).  {disableLookaside(pParse);}
>>>>  @@ -237,8 +239,15 @@ nm(A) ::= id(A). {
>>>>  carglist ::= carglist cconsdef.
>>>>  carglist ::= .
>>>>  cconsdef ::= cconsname ccons.
>>>> -cconsname ::= CONSTRAINT nm(X).           {pParse->constraintName = X;}
>>>> -cconsname ::= .                           {pParse->constraintName.n = 0;}
>>>> +cconsname ::= cconsname_start cconsname_parse .
>>>> +cconsname_start ::= . {
>>>> +  /* Prepare base members for re-usage. */
>>>> +  memset(&pParse->create_index_def, 0, sizeof(struct create_index_def));
>>>> +}
>>>> +cconsname_parse ::= CONSTRAINT nm(X). {
>>>> +  create_entity_def_init(&pParse->create_index_def, X, false);
>>> 
>>> 7. Why in case of any constraint start, you reset only index_def? UNIQUE/PK
>>> are not the only existing constraints.
>>> Also, memset(0) is not scalable
>>> solution - you need a separate reset() function, or something like that in
>>> case if in future parse_def.h structures will grow in size and complexity,
>>> and 0 becomes not default value of some attribute.
>> Well, imho it is too much.
> I did not understand. What is too much?

Too much assuming that some day we will have to initialize
members with non-zero values. Anyway, it doesn’t really
matter now, I just passed pointer to create_constraint_def:

@@ -242,11 +241,10 @@ carglist ::= .
 cconsdef ::= cconsname ccons.
 cconsname ::= cconsname_start cconsname_parse .
 cconsname_start ::= . {
-  /* Prepare base members for re-usage. */
-  memset(&pParse->create_index_def, 0, sizeof(struct create_constraint_def));
+  memset(&pParse->create_index_def.base, 0, sizeof(struct create_constraint_def));
 }

So, we don’t need to add to union one more member. Is this way OK?

>> Actually, here we really care only about
>> zeroing several pointers and some size params and don’t care
>> about the rest.
> 
> Yes, but here you assume, that index_def always lies in the
> same memory as all other constraint objects, and we will never
> store more pointers in the rest memory > sizeof(index_def).
> 
>> This is first rule to be called when some constraint
>> is created. What is more, now terminal initialisers fill all fields, except
>> for base structs. Hence, now it is enough to reset memory layout
>> up to struct create_constraint_def:
> 
> Your code below assumes the thing I said above, so please, add a
> struct create_constraint_def to union in struct Parse and use it
> here. Like this:

See solution above, I guess it is more suitable.

> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
> index 22b5669d8..83c0fc465 100644
> --- a/src/box/sql/parse.y
> +++ b/src/box/sql/parse.y
> @@ -244,7 +244,7 @@ cconsdef ::= cconsname ccons.
> cconsname ::= cconsname_start cconsname_parse .
> cconsname_start ::= . {
>   /* Prepare base members for re-usage. */
> -  memset(&pParse->create_index_def, 0, sizeof(struct create_constraint_def));
> +  memset(&pParse->constraint_def, 0, sizeof(pParse->constraint_def));
> }
> cconsname_parse ::= CONSTRAINT nm(X). {
>   create_entity_def_init(&pParse->create_index_def.base.base, X, false);
> diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
> index 4e6806be8..8566fe404 100644
> --- a/src/box/sql/sqliteInt.h
> +++ b/src/box/sql/sqliteInt.h
> @@ -2731,6 +2731,7 @@ struct Parse {
> 	 * from parse.y
> 	 */
> 	union {
> +		struct create_constraint_def constraint_def;
> 		struct create_ck_def create_ck_def;
> 		struct create_fk_def create_fk_def;
> 		struct create_index_def create_index_def;
> 
>>>> diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
>>>> +enum entity_type {
>>>> +	ENTITY_TYPE_TABLE = 0,
>>>> +	ENTITY_TYPE_INDEX,
>>>> +	ENTITY_TYPE_TRIGGER,
>>>> +	ENTITY_TYPE_CK,
>>>> +	ENTITY_TYPE_FK,
>>>> +	entity_type_MAX
>>> 
>>> 9. These entity_types are useless. Even being used in asserts,
>>> they do not prevent errors, allowing to create an instance of
>>> create_entity_def, but use it as drop_entity_def, because
>>> base.entity_type in both cases can be, for example,
>>> ENTITY_TYPE_INDEX.
>>> 
>>> Please, use not object entity types, but def types:
>>> 
>>>    ALTER_DEF_CREATE_TABLE/DROP_TABLE/RENAME.../...INDEX.../...
>>> 
>>> Or split entity type and action, so as to have something like
>>> this:
>>> 
>>>    def.entity = ALTER_DEF_TABLE;
>>>    def.action = ALTER_DEF_RENAME;
>> I’d better choose this way. Sorry, I can’t spot diff
>> of particularly this fix, since it has been entangled with diffs
>> to other comments. But I will attach whole diff at the end of letter.
>>> By the way, please, rename ENTITY_TYPE_CK to ENTITY_TYPE_CHECK.
>>> Three letters 'HEC' economy is not worth the name obfuscation.
>> It is common abbreviation (the same as FK), and AFAIR was even
>> suggested by Konstantin in one of reviews (mb not to this patch).
>> It simply makes name be similar to ENTITY_TYPE_FK.
> 
> I see CK here first time. We've never used this abbreviation as I
> remember. We use FK not because it is a policy, but because it is
> just too long without contraction.
> 
> Otherwise we would have ENTITY_TYPE_IN instead of _INDEX,
> ENTITY_TYPE_TA instead of _TABLE, ENTITY_TYPE_TR instead of _TRIGGER.

Ok, re-asked Konstantin about using CK as an abbreviation for CHECK,
and he gave positive answer. 

>> @@ -264,6 +283,7 @@ create_fk_def_init(struct create_fk_def *fk_def, struct ExprList *child_cols,
>>         fk_def->parent_name = parent_name;
>>         fk_def->parent_cols = parent_cols;
>>         fk_def->actions = actions;
>> +       ((struct alter_entity_def *) fk_def)->entity_type = ENTITY_TYPE_FK;
>>  }
>> I know you asked me to choose between base struct and cast.
>> However, I think here cast is much more suitable: these structures
>> are terminated so we have to write base.base.base.
>> On the other hand, almost in all places one or two nesting
>> base usage looks better. I can revert this change with ease,
>> if you want to.
> 
> base.base.base is bad, but just one base is good, where possible.
> 
> 
> Below I listed the calls you do to create each 'terminal' structure.
> 
> 	TABLE RENAME:
> 	alter_entity_def_init
> 	rename_entity_def_init
> 
> 	TABLE CREATE
> 	alter_entity_def_init
> 	create_entity_def_init
> 	create_table_def_init
> 
> 	TABLE DROP:
> 	alter_entity_def_init
> 	drop_entity_def_init
> 
> 	TABLE ADD FK:
> 	alter_entity_def_init
> 	create_entity_def_init
> 	create_constraint_def_init
> 	create_fk_def_init
> 
> 	DROP FK:
> 	alter_entity_def_init
> 	drop_entity_def_init
> 
> 	DROP INDEX:
> 	alter_entity_def_init
> 	drop_entity_def_init
> 
> 	ADD INDEX:
> 	alter_entity_def_init
> 	create_entity_def_init
> 	create_index_def_init
> 
> 	CHECK:
>        alter_entity_def_init
>        create_entity_def_init
> 	create_ck_def_init
> 
> 	CREATE TRIGGER:
> 	alter_entity_def_init
> 	create_entity_def_init
> 	create_trigger_def_init
> 
> 	DROP TRIGGER:
> 	alter_entity_def_init
> 	drop_entity_def_init
> 
> Here are some problems with what you said earlier and with
> the whole structure.
> 
> Firstly, some of sequences are not finished with a an explicit
> terminal. For example, *all* DROPs are finished with an abstract
> drop_entity_def_init() taking an entity type explicitly. Despite
> the words you said earlier that you initialize everything in steps.
> So in the case of DROP the rule is violated already. I think, that
> you should define static inline helpers in parse_def.h with names
> drop_table_def_init(), drop_index_def_init(), drop_check_def_init()
> etc.

Drop procedure (i.e. arguments filling) is the same for all entities
(this situation is likely to remain unchanged). Does it make any
sense to duplicate code without any necessity? What these
helpers are supposed to do?

> Secondly, some words about create_entity_def_init(). It is the
> most arguable function for a public usage (out of parse_def.h)
> because hardly I can imagine a situation when you parsed a word
> CREATE, but can not parse a next one: TABLE, TRIGGER, CONSTRAINT etc.
> And we see it in the lists above. Below I listed all of this function
> usage cases and how you can replace that function here.
> 
> - create_entity_def_init() in create_table ::= rule, line 172. Here
>  it is followed by create_table_def_init() on the next line.
> 
> - create_entity_def_init() in cconsname_parse ::= rule line 250.
>  This rule is a prefix for a constraint definition, so this function
>  can be substituted with create_constraint_def_init().
> 
> - create_entity_def_init() in cmd ::= rule (about CREATE VIEW),
>  line 400. Here we see, that 1) you missed ENTITY_TYPE_VIEW,
>  2) create_view_def_init() can be called here.
> 
> - create_entity_def_init() in cmd ::= rule (about CREATE INDEX),
>  line 1246. Here it is followed by create_index_def_init() on the
>  next line.
> 
> - create_entity_def_init() in trigger_decl ::= rule, line 1367.
>  Here it is followed by create_trigger_def_init().
> 
> - create_entity_def_init() in cmd ::= rule (about ALTER TABLE ADD
>  CONSTRAINT FOREIGN KEY), line 1500. Here it is followed by
>  create_constraint_def_init().
> 
> So as you can see create_entity_def_init() is not necessary to use
> out of parse_def.h. You can put it into
> create_table/index/constraint_def().

Ok, done. See updated version at the end of letter.

> Now about create_constraint_def_init(). In your patch it is always
> followed by create_fk_def_init().

I didn’t get this point. create_constraint_def_init() may come before
create_fk_def_init() as well as before create_index_def/create_ck_def.

> But after you did the comment above
> about create_entity_def_init(), you will have create_constraint_def_init()
> in cconsname_parse ::= CONSTRAINT nm(X) rule. And I do not see any
> concrete reasons why can not you remove it from here and use only
> create_index_def_init(), create_ck_def_init() etc in ccons ::= rule.
> You said, that some of pointers should be nullifed, but what pointers
> and why? What if you do not nullify them right after CONSTRAINT word
> is met?

We had to nullify them since in previous versions members of some
structures might leave uninitialised. Now _init funs fill all members,
so there is no any problem.

> Thirdly, about alter_entity_def_init. Even now it is *always*
> followed by create_*_init(), or drop_*_init(), or rename_*_init().
> So I advise you to make this function internal for parse_def.h and
> put its calls into create/drop/rename_*_init().

That’s OK as well.

> The whole idea, as I understood it, was not to just split into a
> random steps, but so as to build an automaton:
> 
> 
>                  +- drop_check
>                  |
>                  +- drop_fk
>                  |
>                  +- drop_table
>                  |
>   drop_entity ---+- drop_index
>  /
> -- rename_entity --- rename_table
>  \
>   create_entity -+- create_index
>                  |
>                  +- create_table
>                  |
>                  +- create_fk
>                  |
>                  +- create_check
> 
> Which in future should be easily expanded to this:
> 
>                   +- drop_check
>                   |
>                   +- drop_fk
>                   |
>                   +- drop_table
>                   |
>  +- drop_entity --+- drop_index
>  |
>  |                +- index_rename
>  |                |
>  |                +- table_add_column
>  |                |
> --+- alter_entity -+- table_rename
>  |                |
>  |                +- table_drop_column
>  |                |
>  |                +- fk_rename
>  |
>  +- create_entity +- create_index
>                   |
>                   +- create_table
>                   |
>                   +- create_fk
>                   |
>                   +- create_check
> 
> So any rule can by finished in two calls. First defines an action,
> second defines an entity and action-entity details. Now some of your
> rules are finished in 2, some in 3, some in 4 steps, and at the end
> of some of them you still are not in a terminal position.

Also, I put two straight preparatory patches which simplify
view creation procedure:

sql: don't compute string for VIEW twice
sql: simplify VIEW creation procedure

Will send them separately after approval of current patch.

Diff (it may be inconsistent a bit, so I also attach whole patch):

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 5ef0f0bd8..ee2ed55ad 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -391,27 +391,26 @@ sql_table_new(Parse *parser, char *name)
  *
  * @param pParse Parser context.
  */
-void
-sqlite3StartTable(struct Parse *pParse)
+struct Table *
+sqlite3StartTable(struct Parse *pParse, struct Token name, bool if_not_exists)
 {
-       Table *pTable;
+       Table *pTable = NULL;
        char *zName = 0;        /* The name of the new table */
        sqlite3 *db = pParse->db;
        struct Vdbe *v = sqlite3GetVdbe(pParse);
        if (v == NULL)
                goto cleanup;
        sqlite3VdbeCountChanges(v);
-       struct Token name = pParse->create_table_def.base.name;
        zName = sqlite3NameFromToken(db, &name);
 
        if (zName == 0)
-               return;
+               return NULL;
        if (sqlite3CheckIdentifierName(pParse, zName) != SQLITE_OK)
                goto cleanup;
 
        struct space *space = space_by_name(zName);
        if (space != NULL) {
-               if (!pParse->create_table_def.base.if_not_exist) {
+               if (!if_not_exists) {
                        sqlite3ErrorMsg(pParse, "table %s already exists",
                                        zName);
                } else {
@@ -423,15 +422,12 @@ sqlite3StartTable(struct Parse *pParse)
        pTable = sql_table_new(pParse, zName);
        if (pTable == NULL)
                goto cleanup;
-
-       pParse->create_table_def.new_table = pTable;
-
        if (!db->init.busy && (v = sqlite3GetVdbe(pParse)) != 0)
                sql_set_multi_write(pParse, true);
 
  cleanup:
        sqlite3DbFree(db, zName);
-       return;
+       return pTable;
 }
 
 /**
@@ -1297,12 +1293,6 @@ sqlite3EndTable(Parse * pParse,  /* Parse context */
                vdbe_emit_create_index(pParse, p->def, idx->def,
                                       reg_space_id, idx->def->iid);
        }
-
-       for (uint32_t i = 0; i < p->space->index_count; ++i) {
-               struct index *idx = p->space->index[i];
-               vdbe_emit_create_index(pParse, p->def, idx->def, reg_space_id,
-                                      idx->def->iid);
-       }
        /*
         * Check to see if we need to create an _sequence table
         * for keeping track of autoincrement keys.
@@ -1370,9 +1360,13 @@ cleanup:
 }
 
 void
-sql_create_view(struct Parse *parse_context, struct Token *begin,
-               struct ExprList *aliases, struct Select *select)
+sql_create_view(struct Parse *parse_context)
 {
+       struct create_view_def *view_def = &parse_context->create_view_def;
+       struct create_entity_def *create_entity_def = &view_def->base;
+       struct alter_entity_def *alter_entity_def = &create_entity_def->base;
+       assert(alter_entity_def->entity_type == ENTITY_TYPE_VIEW);
+       assert(alter_entity_def->alter_action == ALTER_ACTION_CREATE);
        struct sqlite3 *db = parse_context->db;
        struct Table *sel_tab = NULL;
        if (parse_context->nVar > 0) {
@@ -1380,13 +1374,15 @@ sql_create_view(struct Parse *parse_context, struct Token *begin,
                                "parameters are not allowed in views");
                goto create_view_fail;
        }
-       sqlite3StartTable(parse_context);
-       struct Table *p = parse_context->create_table_def.new_table;
+       struct Table *p = sqlite3StartTable(parse_context,
+                                           create_entity_def->name,
+                                           create_entity_def->if_not_exist);
        if (p == NULL || parse_context->nErr != 0)
                goto create_view_fail;
-       sel_tab = sqlite3ResultSetOfSelect(parse_context, select);
+       sel_tab = sqlite3ResultSetOfSelect(parse_context, view_def->select);
        if (sel_tab == NULL)
                goto create_view_fail;
+       struct ExprList *aliases = view_def->aliases;
        if (aliases != NULL) {
                if ((int)sel_tab->def->field_count != aliases->nExpr) {
                        sqlite3ErrorMsg(parse_context, "expected %d columns "\
@@ -1397,7 +1393,7 @@ sql_create_view(struct Parse *parse_context, struct Token *begin,
                }
                sqlite3ColumnsFromExprList(parse_context, aliases, p);
                sqlite3SelectAddColumnTypeAndCollation(parse_context, p,
-                                                      select);
+                                                      view_def->select);
        } else {
                assert(sel_tab->def->opts.is_temporary);
                p->def->fields = sel_tab->def->fields;
@@ -1415,6 +1411,7 @@ sql_create_view(struct Parse *parse_context, struct Token *begin,
        if (end.z[0] != ';')
                end.z += end.n;
        end.n = 0;
+       struct Token *begin = view_def->create_start;
        int n = end.z - begin->z;
        assert(n > 0);
        const char *z = begin->z;
@@ -1429,12 +1426,13 @@ sql_create_view(struct Parse *parse_context, struct Token *begin,
                parse_context->nErr++;
                goto create_view_fail;
        }
+
        vdbe_emit_space_create(parse_context, getNewSpaceId(parse_context), p);
 
  create_view_fail:
        sqlite3DbFree(db, sel_tab);
-       sql_expr_list_delete(db, aliases);
-       sql_select_delete(db, select);
+       sql_expr_list_delete(db, view_def->aliases);
+       sql_select_delete(db, view_def->select);
        return;
 }
 
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 22b5669d8..4b13fca8b 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -168,10 +168,8 @@ cmd ::= ROLLBACK TO savepoint_opt nm(X). {
 //
 cmd ::= create_table create_table_args.
 create_table ::= createkw TABLE ifnotexists(E) nm(Y). {
-  alter_entity_def_init(&pParse->create_table_def.base.base, NULL);
-  create_entity_def_init(&pParse->create_table_def.base, Y, E);
-  create_table_def_init(&pParse->create_table_def);
-  sqlite3StartTable(pParse);
+  create_table_def_init(&pParse->create_table_def, Y, E);
+  pParse->create_table_def.new_table = sqlite3StartTable(pParse, Y, E);
 }
 createkw(A) ::= CREATE(A).  {disableLookaside(pParse);}
 
@@ -243,11 +241,11 @@ carglist ::= .
 cconsdef ::= cconsname ccons.
 cconsname ::= cconsname_start cconsname_parse .
 cconsname_start ::= . {
-  /* Prepare base members for re-usage. */
-  memset(&pParse->create_index_def, 0, sizeof(struct create_constraint_def));
+  memset(&pParse->create_index_def.base, 0, sizeof(struct create_constraint_def));
 }
 cconsname_parse ::= CONSTRAINT nm(X). {
-  create_entity_def_init(&pParse->create_index_def.base.base, X, false);
+  create_constraint_def_init(&pParse->create_index_def.base, NULL, X, false,
+                             false);
 }
 cconsname_parse ::= .
 ccons ::= DEFAULT term(X).            {sqlite3AddDefaultValue(pParse,&X);}
@@ -348,7 +346,7 @@ tcons ::= UNIQUE LP sortlist(X) RP. {
 tcons ::= check_constraint_def .
 tcons ::= FOREIGN KEY LP eidlist(FA) RP
           REFERENCES nm(T) eidlist_opt(TA) refargs(R) defer_subclause_opt(D). {
-  create_constraint_def_init(&pParse->create_fk_def.base, D);
+  ((struct create_constraint_def *) &pParse->create_fk_def)->is_deferred = D;
   create_fk_def_init(&pParse->create_fk_def, FA, &T, TA, R);
   sql_create_foreign_key(pParse);
 }
@@ -379,8 +377,7 @@ cmd ::= drop_start(X) drop_table . {
 }
 
 drop_table ::= ifexists(E) fullname(X) . {
-  alter_entity_def_init(&pParse->drop_entity_def.base, X);
-  drop_entity_def_init(&pParse->drop_entity_def, (struct Token) {}, E,
+  drop_entity_def_init(&pParse->drop_entity_def, X, (struct Token) {}, E,
                        ENTITY_TYPE_TABLE);
 }
 
@@ -397,8 +394,8 @@ ifexists(A) ::= .            {A = 0;}
 cmd ::= createkw(X) VIEW ifnotexists(E) nm(Y) eidlist_opt(C)
           AS select(S). {
   if (!pParse->parse_only) {
-    create_entity_def_init(&pParse->create_table_def.base, Y, E);
-    sql_create_view(pParse, &X, C, S);
+    create_view_def_init(&pParse->create_view_def, Y, &X, C, S, E);
+    sql_create_view(pParse);
   } else {
     sql_store_select(pParse, S);
   }
@@ -1241,10 +1238,9 @@ paren_exprlist(A) ::= LP exprlist(X) RP.  {A = X;}
 //
 cmd ::= createkw uniqueflag(U) INDEX ifnotexists(NE) nm(X)
         ON nm(Y) LP sortlist(Z) RP. {
-  alter_entity_def_init((struct alter_entity_def *) &pParse->create_index_def,
-                        sqlite3SrcListAppend(pParse->db, 0, &Y));
-  create_entity_def_init((struct create_entity_def *) &pParse->create_index_def,
-                         X, NE);
+  create_constraint_def_init(&pParse->create_index_def.base,
+                             sqlite3SrcListAppend(pParse->db, 0, &Y), X, NE,
+                             false);
   create_index_def_init(&pParse->create_index_def, Z, U, SORT_ORDER_ASC);
   sql_create_index(pParse);
 }
@@ -1309,8 +1305,7 @@ collate(C) ::= COLLATE id.   {C = 1;}
 ///////////////////////////// The DROP INDEX command /////////////////////////
 //
 cmd ::= DROP INDEX ifexists(E) nm(X) ON fullname(Y).   {
-  alter_entity_def_init(&pParse->drop_entity_def.base, Y);
-  drop_entity_def_init(&pParse->drop_entity_def, X, E, ENTITY_TYPE_INDEX);
+  drop_entity_def_init(&pParse->drop_entity_def, Y, X, E, ENTITY_TYPE_INDEX);
   sql_drop_index(pParse);
 }
 
@@ -1362,10 +1357,8 @@ cmd ::= createkw trigger_decl(A) BEGIN trigger_cmd_list(S) END(Z). {
 trigger_decl(A) ::= TRIGGER ifnotexists(NOERR) nm(B)
                     trigger_time(C) trigger_event(D)
                     ON fullname(E) foreach_clause when_clause(G). {
-  struct create_trigger_def *trigger_def = &pParse->create_trigger_def;
-  alter_entity_def_init((struct alter_entity_def *) trigger_def, E);
-  create_entity_def_init((struct create_entity_def *) trigger_def, B, NOERR);
-  create_trigger_def_init(trigger_def, C, D.a, D.b, G);
+  create_trigger_def_init(&pParse->create_trigger_def, E, B, C, D.a, D.b,
+                          G, NOERR);
   sql_trigger_begin(pParse);
   A = B; /*A-overwrites-T*/
 }
@@ -1476,8 +1469,7 @@ raisetype(A) ::= FAIL.      {A = ON_CONFLICT_ACTION_FAIL;}
 
 ////////////////////////  DROP TRIGGER statement //////////////////////////////
 cmd ::= DROP TRIGGER ifexists(NOERR) fullname(X). {
-  alter_entity_def_init(&pParse->drop_entity_def.base, X);
-  drop_entity_def_init(&pParse->drop_entity_def, (struct Token){}, NOERR,
+  drop_entity_def_init(&pParse->drop_entity_def, X, (struct Token){}, NOERR,
                        ENTITY_TYPE_TRIGGER);
   sql_drop_trigger(pParse);
 }
@@ -1488,24 +1480,20 @@ cmd ::= ANALYZE nm(X).          {sqlite3Analyze(pParse, &X);}
 
 //////////////////////// ALTER TABLE table ... ////////////////////////////////
 cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). {
-  alter_entity_def_init(&pParse->rename_entity_def.base, X);
-  rename_entity_def_init(&pParse->rename_entity_def, Z);
+  rename_entity_def_init(&pParse->rename_entity_def, X, Z);
   sql_alter_table_rename(pParse);
 }
 
 cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT nm(Z) FOREIGN KEY
         LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) refargs(R)
         defer_subclause_opt(D). {
-  alter_entity_def_init((struct alter_entity_def *) &pParse->create_fk_def, X);
-  create_entity_def_init(&pParse->create_fk_def.base.base, Z, false);
-  create_constraint_def_init(&pParse->create_fk_def.base, D);
+  create_constraint_def_init(&pParse->create_fk_def.base, X, Z, D, false);
   create_fk_def_init(&pParse->create_fk_def, FA, &T, TA, R);
   sql_create_foreign_key(pParse);
 }
 
 cmd ::= ALTER TABLE fullname(X) DROP CONSTRAINT nm(Z). {
-  alter_entity_def_init(&pParse->drop_entity_def.base, X);
-  drop_entity_def_init(&pParse->drop_entity_def, Z, false, ENTITY_TYPE_FK);
+  drop_entity_def_init(&pParse->drop_entity_def, X, Z, false, ENTITY_TYPE_FK);
   sql_drop_foreign_key(pParse);
 }
 
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index 0392c0a87..c3ed2f72d 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -126,6 +126,7 @@ enum sql_index_type {
 
 enum entity_type {
        ENTITY_TYPE_TABLE = 0,
+       ENTITY_TYPE_VIEW,
        ENTITY_TYPE_INDEX,
        ENTITY_TYPE_TRIGGER,
        ENTITY_TYPE_CK,
@@ -176,6 +177,19 @@ struct create_table_def {
        bool has_autoinc;
 };
 
+struct create_view_def {
+       struct create_entity_def base;
+       /**
+        * Starting position of CREATE VIEW ... statement.
+        * It is used to fetch whole statement, which is
+        * saved as raw string to space options.
+        */
+       struct Token *create_start;
+       /** List of column aliases (SELECT x AS y ...). */
+       struct ExprList *aliases;
+       struct Select *select;
+};
+
 struct drop_entity_def {
        struct alter_entity_def base;
        /** Name of index/trigger/constraint to be dropped. */
@@ -239,8 +253,9 @@ alter_entity_def_init(struct alter_entity_def *alter_def,
 
 static inline void
 rename_entity_def_init(struct rename_entity_def *rename_def,
-                      struct Token new_name)
+                      struct SrcList *table_name, struct Token new_name)
 {
+       alter_entity_def_init(&rename_def->base, table_name);
        rename_def->new_name = new_name;
        struct alter_entity_def *alter_def =
                (struct alter_entity_def *) rename_def;
@@ -258,15 +273,20 @@ create_entity_def_init(struct create_entity_def *create_def, struct Token name,
 
 static inline void
 create_constraint_def_init(struct create_constraint_def *constr_def,
-                          bool is_deferred)
+                          struct SrcList *parent_name, struct Token name,
+                          bool if_not_exists, bool is_deferred)
 {
+       alter_entity_def_init(&constr_def->base.base, parent_name);
+       create_entity_def_init(&constr_def->base, name, if_not_exists);
        constr_def->is_deferred = is_deferred;
 }
 
 static inline void
-drop_entity_def_init(struct drop_entity_def *drop_def, struct Token name,
+drop_entity_def_init(struct drop_entity_def *drop_def,
+                    struct SrcList *parent_name, struct Token name,
                     bool if_exist, enum entity_type entity_type)
 {
+       alter_entity_def_init(&drop_def->base, parent_name);
        drop_def->name = name;
        drop_def->if_exist = if_exist;
        drop_def->base.entity_type = entity_type;
@@ -274,8 +294,13 @@ drop_entity_def_init(struct drop_entity_def *drop_def, struct Token name,
 }
 
 static inline void
-create_trigger_def_init(struct create_trigger_def *trigger_def, int tr_tm,
-                       int op, struct IdList *cols, struct Expr *when) {
+create_trigger_def_init(struct create_trigger_def *trigger_def,
+                       struct SrcList *table_name, struct Token name,
+                       int tr_tm, int op, struct IdList *cols,
+                       struct Expr *when, bool if_not_exists)
+{
+       alter_entity_def_init(&trigger_def->base.base, table_name);
+       create_entity_def_init(&trigger_def->base, name, if_not_exists);
        trigger_def->tr_tm = tr_tm;
        trigger_def->op = op;
        trigger_def->cols = cols;
@@ -326,8 +351,10 @@ create_fk_def_init(struct create_fk_def *fk_def, struct ExprList *child_cols,
 }
 
 static inline void
-create_table_def_init(struct create_table_def *table_def)
+create_table_def_init(struct create_table_def *table_def, struct Token name,
+                     bool if_not_exists)
 {
+       create_entity_def_init(&table_def->base, name, if_not_exists);
        rlist_create(&table_def->new_fkey);
        struct alter_entity_def *alter_def =
                (struct alter_entity_def *) table_def;
@@ -335,6 +362,21 @@ create_table_def_init(struct create_table_def *table_def)
        alter_def->alter_action = ALTER_ACTION_CREATE;
 }
 
+static inline void
+create_view_def_init(struct create_view_def *view_def, struct Token name,
+                    struct Token *create, struct ExprList *aliases,
+                    struct Select *select, bool if_not_exists)
+{
+       create_entity_def_init(&view_def->base, name, if_not_exists);
+       view_def->create_start = create;
+       view_def->select = select;
+       view_def->aliases = aliases;
+       struct alter_entity_def *alter_def =
+               (struct alter_entity_def *) view_def;
+       alter_def->entity_type = ENTITY_TYPE_VIEW;
+       alter_def->alter_action = ALTER_ACTION_CREATE;
+}
+
 static inline void
 create_table_def_destroy(struct create_table_def *table_def)
 {
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 4e6806be8..a6a4183db 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2735,6 +2735,7 @@ struct Parse {
                struct create_fk_def create_fk_def;
                struct create_index_def create_index_def;
                struct create_trigger_def create_trigger_def;
+               struct create_view_def create_view_def;
                struct rename_entity_def rename_entity_def;
                struct drop_entity_def drop_entity_def;
        };
@@ -3305,7 +3306,9 @@ Table *sqlite3ResultSetOfSelect(Parse *, Select *);
 struct index *
 sql_table_primary_key(const struct Table *tab);
 
-void sqlite3StartTable(Parse *);
+struct Table *
+sqlite3StartTable(struct Parse *parse, struct Token name, bool);
+
 void sqlite3AddColumn(Parse *, Token *, struct type_def *);
 
 /**
@@ -3379,13 +3382,9 @@ int sqlite3FaultSim(int);
  * The parser calls this routine in order to create a new VIEW.
  *
  * @param parse_context Current parsing context.
- * @param begin The CREATE token that begins the statement.
- * @param aliases Optional list of view column names.
- * @param select A SELECT statement that will become the new view.
  */
 void
-sql_create_view(struct Parse *parse_context, struct Token *begin,
-               struct ExprList *aliases, struct Select *select);
+sql_create_view(struct Parse *parse_context);
 
 /**
  * Helper to convert SQLite affinity to corresponding


Whole patch:

commit 9eb8234b22a55a2524b560b38f7a49959371ccaf
Author: Nikita Pettik <korablev@tarantool.org>
Date:   Wed Jan 9 12:28:09 2019 +0200

    sql: introduce structs assembling DDL arguments during parsing
    
    Parser's rules implementing DDL have a lot in common. For instance,
    to drop any entity it is enough to know its name and name of table it is
    related to.
    Thus, it was suggested to arrange arguments of DDL rules into
    hierarchical structure. The root of chain always includes name of table
    to be altered: all existing entities are related to some table.  Then
    comes one of drop/create/rename rules. Indeed, each DDL operation can be
    classified in these terms, at least until we introduce ALTER TABLE ALTER
    CONSTRAINT statement. Drop is represented by single structure (as it was
    mentioned); rename can be applied only to table; create can be applied
    to CONSTRAINT (indexes are considered as constraints) and TRIGGER, which
    in turn are different in arguments required to create those objects. And
    so forth.
    
    What is more, we are going to introduce ALTER TABLE ADD CONSTRAINT
    UNIQUE With new hierarchy we can extend ALTER TABLE statement with ease:
    basic structures (alter -> create entity -> create constraint) are the
    same for .. FOREIGN KEY/UNIQUE, but the last one will be different.
    
    Patch itself is made up of refactoring; no functional changes are
    provided.
    
    Needed for #3097

diff --git a/src/box/sql/alter.c b/src/box/sql/alter.c
index d0ce9d893..6eb14fdb5 100644
--- a/src/box/sql/alter.c
+++ b/src/box/sql/alter.c
@@ -38,12 +38,15 @@
 #include "box/schema.h"
 
 void
-sql_alter_table_rename(struct Parse *parse, struct SrcList *src_tab,
-		       struct Token *new_name_tk)
+sql_alter_table_rename(struct Parse *parse)
 {
+	struct rename_entity_def *rename_def = &parse->rename_entity_def;
+	struct SrcList *src_tab = rename_def->base.entity_name;
+	assert(rename_def->base.entity_type == ENTITY_TYPE_TABLE);
+	assert(rename_def->base.alter_action == ALTER_ACTION_RENAME);
 	assert(src_tab->nSrc == 1);
 	struct sqlite3 *db = parse->db;
-	char *new_name = sqlite3NameFromToken(db, new_name_tk);
+	char *new_name = sqlite3NameFromToken(db, &rename_def->new_name);
 	if (new_name == NULL)
 		goto exit_rename_table;
 	/* Check that new name isn't occupied by another table. */
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 68e83ec8b..0bc967bf7 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -383,7 +383,7 @@ sql_table_new(Parse *parser, char *name)
  * when the "TEMP" or "TEMPORARY" keyword occurs in between
  * CREATE and TABLE.
  *
- * The new table record is initialized and put in pParse->pNewTable.
+ * The new table record is initialized and put in pParse->create_table_def.
  * As more of the CREATE TABLE statement is parsed, additional action
  * routines will be called to add more information to this record.
  * At the end of the CREATE TABLE statement, the sqlite3EndTable() routine
@@ -393,21 +393,20 @@ sql_table_new(Parse *parser, char *name)
  * @param pName1 First part of the name of the table or view.
  * @param noErr Do nothing if table already exists.
  */
-void
+struct Table *
 sqlite3StartTable(Parse *pParse, Token *pName, int noErr)
 {
-	Table *pTable;
+	Table *pTable = NULL;
 	char *zName = 0;	/* The name of the new table */
 	sqlite3 *db = pParse->db;
 	struct Vdbe *v = sqlite3GetVdbe(pParse);
 	if (v == NULL)
 		goto cleanup;
 	sqlite3VdbeCountChanges(v);
-
 	zName = sqlite3NameFromToken(db, pName);
 
 	if (zName == 0)
-		return;
+		return NULL;
 	if (sqlite3CheckIdentifierName(pParse, zName) != SQLITE_OK)
 		goto cleanup;
 
@@ -425,16 +424,12 @@ sqlite3StartTable(Parse *pParse, Token *pName, int noErr)
 	pTable = sql_table_new(pParse, zName);
 	if (pTable == NULL)
 		goto cleanup;
-
-	assert(pParse->pNewTable == 0);
-	pParse->pNewTable = pTable;
-
 	if (!db->init.busy && (v = sqlite3GetVdbe(pParse)) != 0)
 		sql_set_multi_write(pParse, true);
 
  cleanup:
 	sqlite3DbFree(db, zName);
-	return;
+	return pTable;
 }
 
 /**
@@ -516,7 +511,7 @@ sqlite3AddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
 	int i;
 	char *z;
 	sqlite3 *db = pParse->db;
-	if ((p = pParse->pNewTable) == 0)
+	if ((p = pParse->create_table_def.new_table) == NULL)
 		return;
 #if SQLITE_MAX_COLUMN
 	if ((int)p->def->field_count + 1 > db->aLimit[SQLITE_LIMIT_COLUMN]) {
@@ -565,14 +560,13 @@ sqlite3AddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
 	column_def->affinity = type_def->type;
 	column_def->type = sql_affinity_to_field_type(column_def->affinity);
 	p->def->field_count++;
-	pParse->constraintName.n = 0;
 }
 
 void
 sql_column_add_nullable_action(struct Parse *parser,
 			       enum on_conflict_action nullable_action)
 {
-	struct Table *p = parser->pNewTable;
+	struct Table *p = parser->create_table_def.new_table;
 	if (p == NULL || NEVER(p->def->field_count < 1))
 		return;
 	struct field_def *field = &p->def->fields[p->def->field_count - 1];
@@ -609,7 +603,7 @@ sqlite3AddDefaultValue(Parse * pParse, ExprSpan * pSpan)
 {
 	Table *p;
 	sqlite3 *db = pParse->db;
-	p = pParse->pNewTable;
+	p = pParse->create_table_def.new_table;
 	assert(p->def->opts.is_temporary);
 	if (p != 0) {
 		if (!sqlite3ExprIsConstantOrFunction
@@ -671,15 +665,12 @@ field_def_create_for_pk(struct Parse *parser, struct field_def *field,
  * index for the key.  No index is created for INTEGER PRIMARY KEYs.
  */
 void
-sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
-		     ExprList * pList,	/* List of field names to be indexed */
-		     int autoInc,	/* True if the AUTOINCREMENT keyword is present */
-		     enum sort_order sortOrder
-    )
+sqlite3AddPrimaryKey(struct Parse *pParse)
 {
-	Table *pTab = pParse->pNewTable;
+	Table *pTab = pParse->create_table_def.new_table;
 	int iCol = -1, i;
 	int nTerm;
+	struct ExprList *pList = pParse->create_index_def.cols;
 	if (pTab == 0)
 		goto primary_key_exit;
 	if (sql_table_primary_key(pTab) != NULL) {
@@ -713,10 +704,7 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 		}
 	}
 	if (nTerm == 1 && iCol != -1 &&
-	    pTab->def->fields[iCol].type == FIELD_TYPE_INTEGER &&
-	    sortOrder != SORT_ORDER_DESC) {
-		assert(autoInc == 0 || autoInc == 1);
-		pParse->is_new_table_autoinc = autoInc;
+	    pTab->def->fields[iCol].type == FIELD_TYPE_INTEGER) {
 		struct sqlite3 *db = pParse->db;
 		struct ExprList *list;
 		struct Token token;
@@ -726,18 +714,17 @@ sqlite3AddPrimaryKey(Parse * pParse,	/* Parsing context */
 							     &token, 0));
 		if (list == NULL)
 			goto primary_key_exit;
-		sql_create_index(pParse, 0, 0, list, 0, SORT_ORDER_ASC,
-				 false, SQL_INDEX_TYPE_CONSTRAINT_PK);
+		pParse->create_index_def.cols = list;
+		sql_create_index(pParse);
 		if (db->mallocFailed)
 			goto primary_key_exit;
-	} else if (autoInc) {
+	} else if (pParse->create_table_def.has_autoinc) {
 		sqlite3ErrorMsg(pParse, "AUTOINCREMENT is only allowed on an "
 				"INTEGER PRIMARY KEY or INT PRIMARY KEY");
 		goto primary_key_exit;
 	} else {
-		sql_create_index(pParse, 0, 0, pList, 0, sortOrder, false,
-				 SQL_INDEX_TYPE_CONSTRAINT_PK);
-		pList = 0;
+		sql_create_index(pParse);
+		pList = NULL;
 		if (pParse->nErr > 0)
 			goto primary_key_exit;
 	}
@@ -756,14 +743,21 @@ primary_key_exit:
 }
 
 void
-sql_add_check_constraint(struct Parse *parser, struct ExprSpan *span)
-{
-	struct Expr *expr = span->pExpr;
-	struct Table *table = parser->pNewTable;
+sql_add_check_constraint(struct Parse *parser)
+{
+	struct create_ck_def *ck_def = &parser->create_ck_def;
+	struct alter_entity_def *alter_def =
+		(struct alter_entity_def *) &parser->create_ck_def;
+	assert(alter_def->entity_type == ENTITY_TYPE_CK);
+	(void) alter_def;
+	struct Expr *expr = ck_def->expr->pExpr;
+	struct Table *table = parser->create_table_def.new_table;
 	if (table != NULL) {
 		expr->u.zToken =
-			sqlite3DbStrNDup(parser->db, (char *)span->zStart,
-					 (int)(span->zEnd - span->zStart));
+			sqlite3DbStrNDup(parser->db,
+					 (char *) ck_def->expr->zStart,
+					 (int) (ck_def->expr->zEnd -
+					        ck_def->expr->zStart));
 		if (expr->u.zToken == NULL)
 			goto release_expr;
 		table->def->opts.checks =
@@ -773,9 +767,10 @@ sql_add_check_constraint(struct Parse *parser, struct ExprSpan *span)
 			sqlite3DbFree(parser->db, expr->u.zToken);
 			goto release_expr;
 		}
-		if (parser->constraintName.n) {
+		struct create_entity_def *entity_def = &ck_def->base.base;
+		if (entity_def->name.n > 0) {
 			sqlite3ExprListSetName(parser, table->def->opts.checks,
-					       &parser->constraintName, 1);
+					       &entity_def->name, 1);
 		}
 	} else {
 release_expr:
@@ -790,7 +785,7 @@ release_expr:
 void
 sqlite3AddCollateType(Parse * pParse, Token * pToken)
 {
-	Table *p = pParse->pNewTable;
+	Table *p = pParse->create_table_def.new_table;
 	if (p == NULL)
 		return;
 	uint32_t i = p->def->field_count - 1;
@@ -923,7 +918,7 @@ vdbe_emit_create_index(struct Parse *parse, struct space_def *def,
 	memcpy(raw, index_parts, index_parts_sz);
 	index_parts = raw;
 
-	if (parse->pNewTable != NULL) {
+	if (parse->create_table_def.new_table != NULL) {
 		sqlite3VdbeAddOp2(v, OP_SCopy, space_id_reg, entry_reg);
 		sqlite3VdbeAddOp2(v, OP_Integer, idx_def->iid, entry_reg + 1);
 	} else {
@@ -1116,14 +1111,15 @@ vdbe_emit_fkey_create(struct Parse *parse_context, const struct fkey_def *fk)
 	 * of <CREATE TABLE ...> statement, we don't have child
 	 * id, but we know register where it will be stored.
 	 */
-	if (parse_context->pNewTable != NULL) {
+	if (parse_context->create_table_def.new_table != NULL) {
 		sqlite3VdbeAddOp2(vdbe, OP_SCopy, fk->child_id,
 				  constr_tuple_reg + 1);
 	} else {
 		sqlite3VdbeAddOp2(vdbe, OP_Integer, fk->child_id,
 				  constr_tuple_reg + 1);
 	}
-	if (parse_context->pNewTable != NULL && fkey_is_self_referenced(fk)) {
+	if (parse_context->create_table_def.new_table != NULL &&
+	    fkey_is_self_referenced(fk)) {
 		sqlite3VdbeAddOp2(vdbe, OP_SCopy, fk->parent_id,
 				  constr_tuple_reg + 2);
 	} else {
@@ -1186,7 +1182,7 @@ vdbe_emit_fkey_create(struct Parse *parse_context, const struct fkey_def *fk)
 			  constr_tuple_reg + 9);
 	sqlite3VdbeAddOp3(vdbe, OP_SInsert, BOX_FK_CONSTRAINT_ID, 0,
 			  constr_tuple_reg + 9);
-	if (parse_context->pNewTable == NULL)
+	if (parse_context->create_table_def.new_table == NULL)
 		sqlite3VdbeChangeP5(vdbe, OPFLAG_NCHANGE);
 	save_record(parse_context, BOX_FK_CONSTRAINT_ID, constr_tuple_reg, 2,
 		    vdbe->nOp - 1);
@@ -1260,7 +1256,7 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 		return;
 	}
 	assert(!db->mallocFailed);
-	p = pParse->pNewTable;
+	p = pParse->create_table_def.new_table;
 	if (p == 0)
 		return;
 
@@ -1304,7 +1300,7 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 	 * Check to see if we need to create an _sequence table
 	 * for keeping track of autoincrement keys.
 	 */
-	if (pParse->is_new_table_autoinc) {
+	if (pParse->create_table_def.has_autoinc) {
 		assert(reg_space_id != 0);
 		/* Do an insertion into _sequence. */
 		int reg_seq_id = ++pParse->nMem;
@@ -1327,7 +1323,8 @@ sqlite3EndTable(Parse * pParse,	/* Parse context */
 	}
 	/* Code creation of FK constraints, if any. */
 	struct fkey_parse *fk_parse;
-	rlist_foreach_entry(fk_parse, &pParse->new_fkey, link) {
+	rlist_foreach_entry(fk_parse, &pParse->create_table_def.new_fkey,
+			    link) {
 		struct fkey_def *fk = fk_parse->fkey;
 		if (fk_parse->selfref_cols != NULL) {
 			struct ExprList *cols = fk_parse->selfref_cols;
@@ -1366,10 +1363,14 @@ cleanup:
 }
 
 void
-sql_create_view(struct Parse *parse_context, struct Token *begin,
-		struct Token *name, struct ExprList *aliases,
-		struct Select *select, bool if_exists)
-{
+sql_create_view(struct Parse *parse_context)
+{
+	struct create_view_def *view_def = &parse_context->create_view_def;
+	struct create_entity_def *create_entity_def = &view_def->base;
+	struct alter_entity_def *alter_entity_def = &create_entity_def->base;
+	assert(alter_entity_def->entity_type == ENTITY_TYPE_VIEW);
+	assert(alter_entity_def->alter_action == ALTER_ACTION_CREATE);
+	(void) alter_entity_def;
 	struct sqlite3 *db = parse_context->db;
 	struct Table *sel_tab = NULL;
 	if (parse_context->nVar > 0) {
@@ -1377,13 +1378,15 @@ sql_create_view(struct Parse *parse_context, struct Token *begin,
 				"parameters are not allowed in views");
 		goto create_view_fail;
 	}
-	sqlite3StartTable(parse_context, name, if_exists);
-	struct Table *p = parse_context->pNewTable;
+	struct Table *p = sqlite3StartTable(parse_context,
+					    &create_entity_def->name,
+					    create_entity_def->if_not_exist);
 	if (p == NULL || parse_context->nErr != 0)
 		goto create_view_fail;
-	sel_tab = sqlite3ResultSetOfSelect(parse_context, select);
+	sel_tab = sqlite3ResultSetOfSelect(parse_context, view_def->select);
 	if (sel_tab == NULL)
 		goto create_view_fail;
+	struct ExprList *aliases = view_def->aliases;
 	if (aliases != NULL) {
 		if ((int)sel_tab->def->field_count != aliases->nExpr) {
 			sqlite3ErrorMsg(parse_context, "expected %d columns "\
@@ -1394,7 +1397,7 @@ sql_create_view(struct Parse *parse_context, struct Token *begin,
 		}
 		sqlite3ColumnsFromExprList(parse_context, aliases, p);
 		sqlite3SelectAddColumnTypeAndCollation(parse_context, p,
-						       select);
+						       view_def->select);
 	} else {
 		assert(sel_tab->def->opts.is_temporary);
 		p->def->fields = sel_tab->def->fields;
@@ -1412,6 +1415,7 @@ sql_create_view(struct Parse *parse_context, struct Token *begin,
 	if (end.z[0] != ';')
 		end.z += end.n;
 	end.n = 0;
+	struct Token *begin = view_def->create_start;
 	int n = end.z - begin->z;
 	assert(n > 0);
 	const char *z = begin->z;
@@ -1430,8 +1434,8 @@ sql_create_view(struct Parse *parse_context, struct Token *begin,
 
  create_view_fail:
 	sqlite3DbFree(db, sel_tab);
-	sql_expr_list_delete(db, aliases);
-	sql_select_delete(db, select);
+	sql_expr_list_delete(db, view_def->aliases);
+	sql_select_delete(db, view_def->select);
 	return;
 }
 
@@ -1679,14 +1683,15 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space,
  * This routine is called to do the work of a DROP TABLE statement.
  *
  * @param parse_context Current parsing context.
- * @param table_name_list List containing table name.
  * @param is_view True, if statement is really 'DROP VIEW'.
- * @param if_exists True, if statement contains 'IF EXISTS' clause.
  */
 void
-sql_drop_table(struct Parse *parse_context, struct SrcList *table_name_list,
-	       bool is_view, bool if_exists)
+sql_drop_table(struct Parse *parse_context, bool is_view)
 {
+	struct drop_entity_def drop_def = parse_context->drop_entity_def;
+	assert(drop_def.base.entity_type == ENTITY_TYPE_TABLE);
+	assert(drop_def.base.alter_action == ALTER_ACTION_DROP);
+	struct SrcList *table_name_list = drop_def.base.entity_name;
 	struct Vdbe *v = sqlite3GetVdbe(parse_context);
 	struct sqlite3 *db = parse_context->db;
 	if (v == NULL || db->mallocFailed) {
@@ -1698,10 +1703,10 @@ sql_drop_table(struct Parse *parse_context, struct SrcList *table_name_list,
 	const char *space_name = table_name_list->a[0].zName;
 	struct space *space = space_by_name(space_name);
 	if (space == NULL) {
-		if (!is_view && !if_exists)
+		if (!is_view && !drop_def.if_exist)
 			sqlite3ErrorMsg(parse_context, "no such table: %s",
 					space_name);
-		if (is_view && !if_exists)
+		if (is_view && !drop_def.if_exist)
 			sqlite3ErrorMsg(parse_context, "no such view: %s",
 					space_name);
 		goto exit_drop_table;
@@ -1784,12 +1789,15 @@ columnno_by_name(struct Parse *parse_context, const struct space *space,
 }
 
 void
-sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
-		       struct Token *constraint, struct ExprList *child_cols,
-		       struct Token *parent, struct ExprList *parent_cols,
-		       bool is_deferred, int actions)
+sql_create_foreign_key(struct Parse *parse_context)
 {
 	struct sqlite3 *db = parse_context->db;
+	struct create_fk_def *create_fk_def = &parse_context->create_fk_def;
+	struct create_constraint_def *create_constr_def = &create_fk_def->base;
+	struct create_entity_def *create_def = &create_constr_def->base;
+	struct alter_entity_def *alter_def = &create_def->base;
+	assert(alter_def->entity_type == ENTITY_TYPE_FK);
+	assert(alter_def->alter_action == ALTER_ACTION_CREATE);
 	/*
 	 * When this function is called second time during
 	 * <CREATE TABLE ...> statement (i.e. at VDBE runtime),
@@ -1808,20 +1816,21 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 	 * Table under construction during CREATE TABLE
 	 * processing. NULL for ALTER TABLE statement handling.
 	 */
-	struct Table *new_tab = parse_context->pNewTable;
+	struct create_table_def *table_def = &parse_context->create_table_def;
+	struct Table *new_tab = table_def->new_table;
 	/* Whether we are processing ALTER TABLE or CREATE TABLE. */
 	bool is_alter = new_tab == NULL;
 	uint32_t child_cols_count;
+	struct ExprList *child_cols = create_fk_def->child_cols;
 	if (child_cols == NULL) {
 		assert(!is_alter);
 		child_cols_count = 1;
 	} else {
 		child_cols_count = child_cols->nExpr;
 	}
-	assert(!is_alter || (child != NULL && child->nSrc == 1));
 	struct space *child_space = NULL;
 	if (is_alter) {
-		const char *child_name = child->a[0].zName;
+		const char *child_name = alter_def->entity_name->a[0].zName;
 		child_space = space_by_name(child_name);
 		if (child_space == NULL) {
 			diag_set(ClientError, ER_NO_SUCH_SPACE, child_name);
@@ -1836,8 +1845,9 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 			goto tnt_error;
 		}
 		memset(fk, 0, sizeof(*fk));
-		rlist_add_entry(&parse_context->new_fkey, fk, link);
+		rlist_add_entry(&table_def->new_fkey, fk, link);
 	}
+	struct Token *parent = create_fk_def->parent_name;
 	assert(parent != NULL);
 	parent_name = sqlite3NameFromToken(db, parent);
 	if (parent_name == NULL)
@@ -1849,11 +1859,12 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 	 */
 	is_self_referenced = !is_alter &&
 			     strcmp(parent_name, new_tab->def->name) == 0;
+	struct ExprList *parent_cols = create_fk_def->parent_cols;
 	struct space *parent_space = space_by_name(parent_name);
 	if (parent_space == NULL) {
 		if (is_self_referenced) {
 			struct fkey_parse *fk =
-				rlist_first_entry(&parse_context->new_fkey,
+				rlist_first_entry(&table_def->new_fkey,
 						  struct fkey_parse, link);
 			fk->selfref_cols = parent_cols;
 			fk->is_self_referenced = true;
@@ -1868,18 +1879,19 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 			goto exit_create_fk;
 		}
 	}
-	if (constraint == NULL && !is_alter) {
-		if (parse_context->constraintName.n == 0) {
+	if (!is_alter) {
+		if (create_def->name.n == 0) {
 			constraint_name =
 				sqlite3MPrintf(db, "FK_CONSTRAINT_%d_%s",
-					       ++parse_context->fkey_count,
+					       ++table_def->fkey_count,
 					       new_tab->def->name);
 		} else {
-			struct Token *cnstr_nm = &parse_context->constraintName;
-			constraint_name = sqlite3NameFromToken(db, cnstr_nm);
+			constraint_name =
+				sqlite3NameFromToken(db, &create_def->name);
 		}
 	} else {
-		constraint_name = sqlite3NameFromToken(db, constraint);
+		constraint_name =
+			sqlite3NameFromToken(db, &create_def->name);
 	}
 	if (constraint_name == NULL)
 		goto exit_create_fk;
@@ -1912,10 +1924,11 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 		diag_set(OutOfMemory, fk_size, "region", "struct fkey");
 		goto tnt_error;
 	}
+	int actions = create_fk_def->actions;
 	fk->field_count = child_cols_count;
 	fk->child_id = child_space != NULL ? child_space->def->id : 0;
 	fk->parent_id = parent_space != NULL ? parent_space->def->id : 0;
-	fk->is_deferred = is_deferred;
+	fk->is_deferred = create_constr_def->is_deferred;
 	fk->match = (enum fkey_match) ((actions >> 16) & 0xff);
 	fk->on_update = (enum fkey_action) ((actions >> 8) & 0xff);
 	fk->on_delete = (enum fkey_action) (actions & 0xff);
@@ -1969,7 +1982,7 @@ sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
 	 */
 	if (!is_alter) {
 		struct fkey_parse *parse_fk =
-			rlist_first_entry(&parse_context->new_fkey,
+			rlist_first_entry(&table_def->new_fkey,
 					  struct fkey_parse, link);
 		parse_fk->fkey = fk;
 	} else {
@@ -1993,18 +2006,21 @@ void
 fkey_change_defer_mode(struct Parse *parse_context, bool is_deferred)
 {
 	if (parse_context->db->init.busy ||
-	    rlist_empty(&parse_context->new_fkey))
+	    rlist_empty(&parse_context->create_table_def.new_fkey))
 		return;
-	rlist_first_entry(&parse_context->new_fkey, struct fkey_parse,
-			  link)->fkey->is_deferred = is_deferred;
+	rlist_first_entry(&parse_context->create_table_def.new_fkey,
+			  struct fkey_parse, link)->fkey->is_deferred =
+		is_deferred;
 }
 
 void
-sql_drop_foreign_key(struct Parse *parse_context, struct SrcList *table,
-		     struct Token *constraint)
+sql_drop_foreign_key(struct Parse *parse_context)
 {
-	assert(table != NULL && table->nSrc == 1);
-	const char *table_name = table->a[0].zName;
+	struct drop_entity_def *drop_def = &parse_context->drop_entity_def;
+	assert(drop_def->base.entity_type == ENTITY_TYPE_FK);
+	assert(drop_def->base.alter_action == ALTER_ACTION_DROP);
+	const char *table_name = drop_def->base.entity_name->a[0].zName;
+	assert(table_name != NULL);
 	struct space *child = space_by_name(table_name);
 	if (child == NULL) {
 		diag_set(ClientError, ER_NO_SUCH_SPACE, table_name);
@@ -2013,7 +2029,7 @@ sql_drop_foreign_key(struct Parse *parse_context, struct SrcList *table,
 		return;
 	}
 	char *constraint_name = sqlite3NameFromToken(parse_context->db,
-						     constraint);
+						     &drop_def->name);
 	if (constraint_name != NULL)
 		vdbe_emit_fkey_drop(parse_context, constraint_name,
 				    child->def->id);
@@ -2207,19 +2223,29 @@ constraint_is_named(const char *name)
 }
 
 void
-sql_create_index(struct Parse *parse, struct Token *token,
-		 struct SrcList *tbl_name, struct ExprList *col_list,
-		 MAYBE_UNUSED struct Token *start, enum sort_order sort_order,
-		 bool if_not_exist, enum sql_index_type idx_type) {
+sql_create_index(struct Parse *parse) {
 	/* The index to be created. */
 	struct index *index = NULL;
 	/* Name of the index. */
 	char *name = NULL;
 	struct sqlite3 *db = parse->db;
 	assert(!db->init.busy);
+	struct create_index_def *create_idx_def = &parse->create_index_def;
+	struct create_entity_def *create_entity_def = &create_idx_def->base.base;
+	struct alter_entity_def *alter_entity_def = &create_entity_def->base;
+	assert(alter_entity_def->entity_type == ENTITY_TYPE_INDEX);
+	assert(alter_entity_def->alter_action == ALTER_ACTION_CREATE);
+	/*
+	 * Get list of columns to be indexed. It will be NULL if
+	 * this is a primary key or unique-constraint on the most
+	 * recent column added to the table under construction.
+	 */
+	struct ExprList *col_list = create_idx_def->cols;
+	struct SrcList *tbl_name = alter_entity_def->entity_name;
 
 	if (db->mallocFailed || parse->nErr > 0)
 		goto exit_create_index;
+	enum sql_index_type idx_type = create_idx_def->idx_type;
 	if (idx_type == SQL_INDEX_TYPE_UNIQUE ||
 	    idx_type == SQL_INDEX_TYPE_NON_UNIQUE) {
 		Vdbe *v = sqlite3GetVdbe(parse);
@@ -2234,12 +2260,13 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 */
 	struct space *space = NULL;
 	struct space_def *def = NULL;
+	struct Token token = create_entity_def->name;
 	if (tbl_name != NULL) {
-		assert(token != NULL && token->z != NULL);
+		assert(token.n > 0 && token.z != NULL);
 		const char *name = tbl_name->a[0].zName;
 		space = space_by_name(name);
 		if (space == NULL) {
-			if (! if_not_exist) {
+			if (! create_entity_def->if_not_exist) {
 				diag_set(ClientError, ER_NO_SUCH_SPACE, name);
 				parse->rc = SQL_TARANTOOL_ERROR;
 				parse->nErr++;
@@ -2248,12 +2275,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		}
 		def = space->def;
 	} else {
-		if (parse->pNewTable == NULL)
+		if (parse->create_table_def.new_table == NULL)
 			goto exit_create_index;
-		assert(token == NULL);
-		assert(start == NULL);
-		space = parse->pNewTable->space;
-		def = parse->pNewTable->def;
+		space = parse->create_table_def.new_table->space;
+		def = parse->create_table_def.new_table->def;
 	}
 
 	if (def->opts.is_view) {
@@ -2280,13 +2305,13 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 * 2) UNIQUE constraint is non-named and standard
 	 *    auto-index name will be generated.
 	 */
-	if (token != NULL) {
-		assert(token->z != NULL);
-		name = sqlite3NameFromToken(db, token);
+	if (parse->create_table_def.new_table == NULL) {
+		assert(token.z != NULL);
+		name = sqlite3NameFromToken(db, &token);
 		if (name == NULL)
 			goto exit_create_index;
 		if (sql_space_index_by_name(space, name) != NULL) {
-			if (!if_not_exist) {
+			if (! create_entity_def->if_not_exist) {
 				sqlite3ErrorMsg(parse,
 						"index %s.%s already exists",
 						def->name, name);
@@ -2295,11 +2320,10 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		}
 	} else {
 		char *constraint_name = NULL;
-		if (parse->constraintName.z != NULL)
+		if (create_entity_def->name.n > 0)
 			constraint_name =
 				sqlite3NameFromToken(db,
-						     &parse->constraintName);
-
+						     &create_entity_def->name);
 	       /*
 		* This naming is temporary. Now it's not
 		* possible (since we implement UNIQUE
@@ -2357,7 +2381,8 @@ sql_create_index(struct Parse *parse, struct Token *token,
 		if (col_list == NULL)
 			goto exit_create_index;
 		assert(col_list->nExpr == 1);
-		sqlite3ExprListSetSortOrder(col_list, sort_order);
+		sqlite3ExprListSetSortOrder(col_list,
+					    create_idx_def->sort_order);
 	} else {
 		sqlite3ExprListCheckLength(parse, col_list, "index");
 	}
@@ -2438,7 +2463,7 @@ sql_create_index(struct Parse *parse, struct Token *token,
 	 * constraint, but has different onError (behavior on
 	 * constraint violation), then an error is raised.
 	 */
-	if (parse->pNewTable != NULL) {
+	if (parse->create_table_def.new_table != NULL) {
 		for (uint32_t i = 0; i < space->index_count; ++i) {
 			struct index *existing_idx = space->index[i];
 			uint32_t iid = existing_idx->def->iid;
@@ -2505,8 +2530,6 @@ sql_create_index(struct Parse *parse, struct Token *token,
 				  (void *)space_by_id(BOX_INDEX_ID),
 				  P4_SPACEPTR);
 		sqlite3VdbeChangeP5(vdbe, OPFLAG_SEEKEQ);
-
-		assert(start != NULL);
 		int index_id = getNewIid(parse, def->id, cursor);
 		sqlite3VdbeAddOp1(vdbe, OP_Close, cursor);
 		vdbe_emit_create_index(parse, def, index->def,
@@ -2530,30 +2553,33 @@ sql_create_index(struct Parse *parse, struct Token *token,
 }
 
 void
-sql_drop_index(struct Parse *parse_context, struct SrcList *index_name_list,
-	       struct Token *table_token, bool if_exists)
+sql_drop_index(struct Parse *parse_context)
 {
+	struct drop_entity_def *drop_def = &parse_context->drop_entity_def;
+	assert(drop_def->base.entity_type == ENTITY_TYPE_INDEX);
+	assert(drop_def->base.alter_action == ALTER_ACTION_DROP);
 	struct Vdbe *v = sqlite3GetVdbe(parse_context);
 	assert(v != NULL);
 	struct sqlite3 *db = parse_context->db;
 	/* Never called with prior errors. */
 	assert(parse_context->nErr == 0);
-	assert(table_token != NULL);
-	const char *table_name = sqlite3NameFromToken(db, table_token);
+	struct SrcList *table_list = drop_def->base.entity_name;
+	assert(table_list->nSrc == 1);
+	char *table_name = table_list->a[0].zName;
+	const char *index_name = NULL;
 	if (db->mallocFailed) {
 		goto exit_drop_index;
 	}
 	sqlite3VdbeCountChanges(v);
-	assert(index_name_list->nSrc == 1);
-	assert(table_token->n > 0);
 	struct space *space = space_by_name(table_name);
+	bool if_exists = drop_def->if_exist;
 	if (space == NULL) {
 		if (!if_exists)
 			sqlite3ErrorMsg(parse_context, "no such space: %s",
 					table_name);
 		goto exit_drop_index;
 	}
-	const char *index_name = index_name_list->a[0].zName;
+	index_name = sqlite3NameFromToken(db, &drop_def->name);
 	uint32_t index_id = box_index_id_by_name(space->def->id, index_name,
 						 strlen(index_name));
 	if (index_id == BOX_ID_NIL) {
@@ -2579,8 +2605,8 @@ sql_drop_index(struct Parse *parse_context, struct SrcList *index_name_list,
 	sqlite3VdbeAddOp2(v, OP_SDelete, BOX_INDEX_ID, record_reg);
 	sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE);
  exit_drop_index:
-	sqlite3SrcListDelete(db, index_name_list);
-	sqlite3DbFree(db, (void *) table_name);
+	sqlite3SrcListDelete(db, table_list);
+	sqlite3DbFree(db, (void *) index_name);
 }
 
 /*
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 0bcf41594..1f7678a4b 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -168,7 +168,8 @@ cmd ::= ROLLBACK TO savepoint_opt nm(X). {
 //
 cmd ::= create_table create_table_args.
 create_table ::= createkw TABLE ifnotexists(E) nm(Y). {
-   sqlite3StartTable(pParse,&Y,E);
+  create_table_def_init(&pParse->create_table_def, Y, E);
+  pParse->create_table_def.new_table = sqlite3StartTable(pParse, &Y, E);
 }
 createkw(A) ::= CREATE(A).  {disableLookaside(pParse);}
 
@@ -178,6 +179,7 @@ ifnotexists(A) ::= IF NOT EXISTS. {A = 1;}
 
 create_table_args ::= LP columnlist RP(E). {
   sqlite3EndTable(pParse,&E,0);
+  create_table_def_destroy(&pParse->create_table_def);
 }
 create_table_args ::= AS select(S). {
   sqlite3EndTable(pParse,0,S);
@@ -237,8 +239,15 @@ nm(A) ::= id(A). {
 carglist ::= carglist cconsdef.
 carglist ::= .
 cconsdef ::= cconsname ccons.
-cconsname ::= CONSTRAINT nm(X).           {pParse->constraintName = X;}
-cconsname ::= .                           {pParse->constraintName.n = 0;}
+cconsname ::= cconsname_start cconsname_parse .
+cconsname_start ::= . {
+  memset(&pParse->create_index_def.base, 0, sizeof(struct create_constraint_def));
+}
+cconsname_parse ::= CONSTRAINT nm(X). {
+  create_constraint_def_init(&pParse->create_index_def.base, NULL, X, false,
+                             false);
+}
+cconsname_parse ::= .
 ccons ::= DEFAULT term(X).            {sqlite3AddDefaultValue(pParse,&X);}
 ccons ::= DEFAULT LP expr(X) RP.      {sqlite3AddDefaultValue(pParse,&X);}
 ccons ::= DEFAULT PLUS term(X).       {sqlite3AddDefaultValue(pParse,&X);}
@@ -260,14 +269,29 @@ ccons ::= NULL onconf(R).        {
         sql_column_add_nullable_action(pParse, R);
 }
 ccons ::= NOT NULL onconf(R).    {sql_column_add_nullable_action(pParse, R);}
-ccons ::= PRIMARY KEY sortorder(Z) autoinc(I).
-                                 {sqlite3AddPrimaryKey(pParse,0,I,Z);}
-ccons ::= UNIQUE.                {sql_create_index(pParse,0,0,0,0,
-                                                   SORT_ORDER_ASC, false,
-                                                   SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
-ccons ::= CHECK LP expr(X) RP.   {sql_add_check_constraint(pParse,&X);}
-ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R).
-                                 {sql_create_foreign_key(pParse, NULL, NULL, NULL, &T, TA, false, R);}
+ccons ::= PRIMARY KEY sortorder(Z) autoinc(I). {
+  pParse->create_table_def.has_autoinc = I;
+  create_index_def_init(&pParse->create_index_def, NULL,
+                        SQL_INDEX_TYPE_CONSTRAINT_PK, Z);
+  sqlite3AddPrimaryKey(pParse);
+}
+ccons ::= UNIQUE. {
+  create_index_def_init(&pParse->create_index_def, NULL,
+                        SQL_INDEX_TYPE_CONSTRAINT_UNIQUE, SORT_ORDER_ASC);
+  sql_create_index(pParse);
+}
+
+ccons ::= check_constraint_def .
+
+check_constraint_def ::= CHECK LP expr(X) RP. {
+  create_ck_def_init(&pParse->create_ck_def, &X);
+  sql_add_check_constraint(pParse);
+}
+
+ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R). {
+  create_fk_def_init(&pParse->create_fk_def, NULL, &T, TA, R);
+  sql_create_foreign_key(pParse);
+}
 ccons ::= defer_subclause(D).    {fkey_change_defer_mode(pParse, D);}
 ccons ::= COLLATE id(C).        {sqlite3AddCollateType(pParse, &C);}
 
@@ -307,20 +331,24 @@ init_deferred_pred_opt(A) ::= .                       {A = 0;}
 init_deferred_pred_opt(A) ::= INITIALLY DEFERRED.     {A = 1;}
 init_deferred_pred_opt(A) ::= INITIALLY IMMEDIATE.    {A = 0;}
 
-tconsdef ::= tconsname tcons.
-tconsname ::= CONSTRAINT nm(X).      {pParse->constraintName = X;}
-tconsname ::= .                      {pParse->constraintName.n = 0;}
-tcons ::= PRIMARY KEY LP sortlist(X) autoinc(I) RP.
-                                 {sqlite3AddPrimaryKey(pParse,X,I,0);}
-tcons ::= UNIQUE LP sortlist(X) RP.
-                                 {sql_create_index(pParse,0,0,X,0,
-                                                   SORT_ORDER_ASC,false,
-                                                   SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
-tcons ::= CHECK LP expr(E) RP onconf.
-                                 {sql_add_check_constraint(pParse,&E);}
+tconsdef ::= cconsname tcons.
+tcons ::= PRIMARY KEY LP sortlist(X) autoinc(I) RP. {
+  pParse->create_table_def.has_autoinc = I;
+  create_index_def_init(&pParse->create_index_def, X,
+                        SQL_INDEX_TYPE_CONSTRAINT_PK, SORT_ORDER_ASC);
+  sqlite3AddPrimaryKey(pParse);
+}
+tcons ::= UNIQUE LP sortlist(X) RP. {
+  create_index_def_init(&pParse->create_index_def, X,
+                        SQL_INDEX_TYPE_CONSTRAINT_UNIQUE, SORT_ORDER_ASC);
+  sql_create_index(pParse);
+}
+tcons ::= check_constraint_def .
 tcons ::= FOREIGN KEY LP eidlist(FA) RP
           REFERENCES nm(T) eidlist_opt(TA) refargs(R) defer_subclause_opt(D). {
-    sql_create_foreign_key(pParse, NULL, NULL, FA, &T, TA, D, R);
+  ((struct create_constraint_def *) &pParse->create_fk_def)->is_deferred = D;
+  create_fk_def_init(&pParse->create_fk_def, FA, &T, TA, R);
+  sql_create_foreign_key(pParse);
 }
 %type defer_subclause_opt {int}
 defer_subclause_opt(A) ::= .                    {A = 0;}
@@ -343,9 +371,20 @@ resolvetype(A) ::= REPLACE.                  {A = ON_CONFLICT_ACTION_REPLACE;}
 
 ////////////////////////// The DROP TABLE /////////////////////////////////////
 //
-cmd ::= DROP TABLE ifexists(E) fullname(X). {
-  sql_drop_table(pParse, X, 0, E);
+
+cmd ::= drop_start(X) drop_table . {
+  sql_drop_table(pParse, X);
 }
+
+drop_table ::= ifexists(E) fullname(X) . {
+  drop_entity_def_init(&pParse->drop_entity_def, X, (struct Token) {}, E,
+                       ENTITY_TYPE_TABLE);
+}
+
+%type drop_start {bool}
+drop_start(X) ::= DROP VIEW . { X = true; }
+drop_start(X) ::= DROP TABLE . { X = false; }
+
 %type ifexists {int}
 ifexists(A) ::= IF EXISTS.   {A = 1;}
 ifexists(A) ::= .            {A = 0;}
@@ -354,13 +393,12 @@ ifexists(A) ::= .            {A = 0;}
 //
 cmd ::= createkw(X) VIEW ifnotexists(E) nm(Y) eidlist_opt(C)
           AS select(S). {
-  if (!pParse->parse_only)
-    sql_create_view(pParse, &X, &Y, C, S, E);
-  else
+  if (!pParse->parse_only) {
+    create_view_def_init(&pParse->create_view_def, Y, &X, C, S, E);
+    sql_create_view(pParse);
+  } else {
     sql_store_select(pParse, S);
-}
-cmd ::= DROP VIEW ifexists(E) fullname(X). {
-  sql_drop_table(pParse, X, 1, E);
+  }
 }
 
 //////////////////////// The SELECT statement /////////////////////////////////
@@ -1198,10 +1236,13 @@ paren_exprlist(A) ::= LP exprlist(X) RP.  {A = X;}
 
 ///////////////////////////// The CREATE INDEX command ///////////////////////
 //
-cmd ::= createkw(S) uniqueflag(U) INDEX ifnotexists(NE) nm(X)
+cmd ::= createkw uniqueflag(U) INDEX ifnotexists(NE) nm(X)
         ON nm(Y) LP sortlist(Z) RP. {
-  sql_create_index(pParse, &X, sqlite3SrcListAppend(pParse->db,0,&Y), Z, &S,
-                   SORT_ORDER_ASC, NE, U);
+  create_constraint_def_init(&pParse->create_index_def.base,
+                             sqlite3SrcListAppend(pParse->db, 0, &Y), X, NE,
+                             false);
+  create_index_def_init(&pParse->create_index_def, Z, U, SORT_ORDER_ASC);
+  sql_create_index(pParse);
 }
 
 %type uniqueflag {int}
@@ -1263,8 +1304,9 @@ collate(C) ::= COLLATE id.   {C = 1;}
 
 ///////////////////////////// The DROP INDEX command /////////////////////////
 //
-cmd ::= DROP INDEX ifexists(E) fullname(X) ON nm(Y).   {
-    sql_drop_index(pParse, X, &Y, E);
+cmd ::= DROP INDEX ifexists(E) nm(X) ON fullname(Y).   {
+  drop_entity_def_init(&pParse->drop_entity_def, Y, X, E, ENTITY_TYPE_INDEX);
+  sql_drop_index(pParse);
 }
 
 ///////////////////////////// The PRAGMA command /////////////////////////////
@@ -1315,7 +1357,9 @@ cmd ::= createkw trigger_decl(A) BEGIN trigger_cmd_list(S) END(Z). {
 trigger_decl(A) ::= TRIGGER ifnotexists(NOERR) nm(B)
                     trigger_time(C) trigger_event(D)
                     ON fullname(E) foreach_clause when_clause(G). {
-  sql_trigger_begin(pParse, &B, C, D.a, D.b, E, G, NOERR);
+  create_trigger_def_init(&pParse->create_trigger_def, E, B, C, D.a, D.b,
+                          G, NOERR);
+  sql_trigger_begin(pParse);
   A = B; /*A-overwrites-T*/
 }
 
@@ -1425,7 +1469,9 @@ raisetype(A) ::= FAIL.      {A = ON_CONFLICT_ACTION_FAIL;}
 
 ////////////////////////  DROP TRIGGER statement //////////////////////////////
 cmd ::= DROP TRIGGER ifexists(NOERR) fullname(X). {
-  sql_drop_trigger(pParse,X,NOERR);
+  drop_entity_def_init(&pParse->drop_entity_def, X, (struct Token){}, NOERR,
+                       ENTITY_TYPE_TRIGGER);
+  sql_drop_trigger(pParse);
 }
 
 /////////////////////////////////// ANALYZE ///////////////////////////////////
@@ -1434,17 +1480,21 @@ cmd ::= ANALYZE nm(X).          {sqlite3Analyze(pParse, &X);}
 
 //////////////////////// ALTER TABLE table ... ////////////////////////////////
 cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). {
-  sql_alter_table_rename(pParse,X,&Z);
+  rename_entity_def_init(&pParse->rename_entity_def, X, Z);
+  sql_alter_table_rename(pParse);
 }
 
 cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT nm(Z) FOREIGN KEY
         LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) refargs(R)
         defer_subclause_opt(D). {
-    sql_create_foreign_key(pParse, X, &Z, FA, &T, TA, D, R);
+  create_constraint_def_init(&pParse->create_fk_def.base, X, Z, D, false);
+  create_fk_def_init(&pParse->create_fk_def, FA, &T, TA, R);
+  sql_create_foreign_key(pParse);
 }
 
 cmd ::= ALTER TABLE fullname(X) DROP CONSTRAINT nm(Z). {
-    sql_drop_foreign_key(pParse, X, &Z);
+  drop_entity_def_init(&pParse->drop_entity_def, X, Z, false, ENTITY_TYPE_FK);
+  sql_drop_foreign_key(pParse);
 }
 
 //////////////////////// COMMON TABLE EXPRESSIONS ////////////////////////////
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
new file mode 100644
index 000000000..c3ed2f72d
--- /dev/null
+++ b/src/box/sql/parse_def.h
@@ -0,0 +1,388 @@
+#ifndef TARANTOOL_BOX_SQL_PARSE_DEF_H_INCLUDED
+#define TARANTOOL_BOX_SQL_PARSE_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 "box/fkey.h"
+#include "box/sql.h"
+
+/**
+ * This file contains auxiliary structures and functions which
+ * are used only during parsing routine (see parse.y).
+ * Their main purpose is to assemble common parts of altered
+ * entities (such as name, or IF EXISTS clause) and pass them
+ * as a one object to further functions.
+ *
+ * Hierarchy is following:
+ *
+ * Base structure is ALTER.
+ * ALTER is omitted only for CREATE TABLE since table is filled
+ * with meta-information just-in-time of parsing:
+ * for instance, as soon as field's name and type are recognized
+ * they are added to space definition.
+ *
+ * DROP is general for all existing objects and includes
+ * name of object itself, name of parent object (table),
+ * IF EXISTS clause and may contain on-drop behaviour
+ * (CASCADE/RESTRICT, but now it is always RESTRICT).
+ * Hence, it terms of grammar - it is a terminal symbol.
+ *
+ * RENAME can be applied only to table (at least now, since it is
+ * ANSI extension), so it is also terminal symbol.
+ *
+ * CREATE in turn can be expanded to nonterminal symbol
+ * CREATE CONSTRAINT or to terminal CREATE TABLE/INDEX/TRIGGER.
+ * CREATE CONSTRAINT unfolds to FOREIGN KEY or UNIQUE/PRIMARY KEY.
+ *
+ * For instance:
+ * ALTER TABLE t ADD CONSTRAINT c FOREIGN KEY REFERENCES t2(id);
+ * ALTER *TABLE* -> CREATE ENTITY -> CREATE CONSTRAINT -> CREATE FK
+ *
+ * CREATE TRIGGER tr1 ...
+ * ALTER *TABLE* -> CREATE ENTITY -> CREATE TRIGGER
+ *
+ * All terminal symbols are stored as a union within
+ * parsing context (struct Parse).
+ */
+
+/**
+ * Each token coming out of the lexer is an instance of
+ * this structure. Tokens are also used as part of an expression.
+ */
+struct Token {
+	/** Text of the token. Not NULL-terminated! */
+	const char *z;
+	/** Number of characters in this token. */
+	unsigned int n;
+	bool isReserved;
+};
+
+/**
+ * Structure representing foreign keys constraints appeared
+ * within CREATE TABLE statement. Used only during parsing.
+ */
+struct fkey_parse {
+	/**
+	 * Foreign keys constraint declared in <CREATE TABLE ...>
+	 * statement. They must be coded after space creation.
+	 */
+	struct fkey_def *fkey;
+	/**
+	 * If inside CREATE TABLE statement we want to declare
+	 * self-referenced FK constraint, we must delay their
+	 * resolution until the end of parsing of all columns.
+	 * E.g.: CREATE TABLE t1(id REFERENCES t1(b), b);
+	 */
+	struct ExprList *selfref_cols;
+	/**
+	 * Still, self-referenced columns might be NULL, if
+	 * we declare FK constraints referencing PK:
+	 * CREATE TABLE t1(id REFERENCES t1) - it is a valid case.
+	 */
+	bool is_self_referenced;
+	/** Organize these structs into linked list. */
+	struct rlist link;
+};
+
+/**
+ * Possible SQL index types. Note that PK and UNIQUE constraints
+ * are implemented as indexes and have their own types:
+ * _CONSTRAINT_PK and _CONSTRAINT_UNIQUE.
+ */
+enum sql_index_type {
+	SQL_INDEX_TYPE_NON_UNIQUE = 0,
+	SQL_INDEX_TYPE_UNIQUE,
+	SQL_INDEX_TYPE_CONSTRAINT_UNIQUE,
+	SQL_INDEX_TYPE_CONSTRAINT_PK,
+	sql_index_type_MAX
+};
+
+enum entity_type {
+	ENTITY_TYPE_TABLE = 0,
+	ENTITY_TYPE_VIEW,
+	ENTITY_TYPE_INDEX,
+	ENTITY_TYPE_TRIGGER,
+	ENTITY_TYPE_CK,
+	ENTITY_TYPE_FK,
+	entity_type_MAX
+};
+
+enum alter_action {
+	ALTER_ACTION_CREATE = 0,
+	ALTER_ACTION_DROP,
+	ALTER_ACTION_RENAME
+};
+
+struct alter_entity_def {
+	/** Type of topmost entity. */
+	enum entity_type entity_type;
+	/** Action to be performed using current entity. */
+	enum alter_action alter_action;
+	/** As a rule it is a name of table to be altered. */
+	struct SrcList *entity_name;
+};
+
+struct rename_entity_def {
+	struct alter_entity_def base;
+	struct Token new_name;
+};
+
+struct create_entity_def {
+	struct alter_entity_def base;
+	struct Token name;
+	/** Statement comes with IF NOT EXISTS clause. */
+	bool if_not_exist;
+};
+
+struct create_table_def {
+	struct create_entity_def base;
+	struct Table *new_table;
+	/**
+	 * Number of FK constraints declared within
+	 * CREATE TABLE statement.
+	 */
+	uint32_t fkey_count;
+	/**
+	 * Foreign key constraint appeared in CREATE TABLE stmt.
+	 */
+	struct rlist new_fkey;
+	/** True, if table to be created has AUTOINCREMENT PK. */
+	bool has_autoinc;
+};
+
+struct create_view_def {
+	struct create_entity_def base;
+	/**
+	 * Starting position of CREATE VIEW ... statement.
+	 * It is used to fetch whole statement, which is
+	 * saved as raw string to space options.
+	 */
+	struct Token *create_start;
+	/** List of column aliases (SELECT x AS y ...). */
+	struct ExprList *aliases;
+	struct Select *select;
+};
+
+struct drop_entity_def {
+	struct alter_entity_def base;
+	/** Name of index/trigger/constraint to be dropped. */
+	struct Token name;
+	/** Statement comes with IF EXISTS clause. */
+	bool if_exist;
+};
+
+struct create_trigger_def {
+	struct create_entity_def base;
+	/** One of TK_BEFORE, TK_AFTER, TK_INSTEAD. */
+	int tr_tm;
+	/** One of TK_INSERT, TK_UPDATE, TK_DELETE. */
+	int op;
+	/** Column list if this is an UPDATE trigger. */
+	struct IdList *cols;
+	/** When clause. */
+	struct Expr *when;
+};
+
+struct create_constraint_def {
+	struct create_entity_def base;
+	/** One of DEFERRED, IMMEDIATE. */
+	bool is_deferred;
+};
+
+struct create_ck_def {
+	struct create_constraint_def base;
+	/** AST representing check expression. */
+	struct ExprSpan *expr;
+};
+
+struct create_fk_def {
+	struct create_constraint_def base;
+	struct ExprList *child_cols;
+	struct Token *parent_name;
+	struct ExprList *parent_cols;
+	/**
+	 * Encoded actions for MATCH, ON DELETE and
+	 * ON UPDATE clauses.
+	 */
+	int actions;
+};
+
+struct create_index_def {
+	struct create_constraint_def base;
+	/** List of indexed columns. */
+	struct ExprList *cols;
+	/** One of _PRIMARY_KEY, _UNIQUE, _NON_UNIQUE. */
+	enum sql_index_type idx_type;
+	enum sort_order sort_order;
+};
+
+/** Basic initialisers of parse structures.*/
+static inline void
+alter_entity_def_init(struct alter_entity_def *alter_def,
+		      struct SrcList *entity_name)
+{
+	alter_def->entity_name = entity_name;
+}
+
+static inline void
+rename_entity_def_init(struct rename_entity_def *rename_def,
+		       struct SrcList *table_name, struct Token new_name)
+{
+	alter_entity_def_init(&rename_def->base, table_name);
+	rename_def->new_name = new_name;
+	struct alter_entity_def *alter_def =
+		(struct alter_entity_def *) rename_def;
+	alter_def->entity_type = ENTITY_TYPE_TABLE;
+	alter_def->alter_action = ALTER_ACTION_RENAME;
+}
+
+static inline void
+create_entity_def_init(struct create_entity_def *create_def, struct Token name,
+		       bool if_not_exist)
+{
+	create_def->name = name;
+	create_def->if_not_exist = if_not_exist;
+}
+
+static inline void
+create_constraint_def_init(struct create_constraint_def *constr_def,
+			   struct SrcList *parent_name, struct Token name,
+			   bool if_not_exists, bool is_deferred)
+{
+	alter_entity_def_init(&constr_def->base.base, parent_name);
+	create_entity_def_init(&constr_def->base, name, if_not_exists);
+	constr_def->is_deferred = is_deferred;
+}
+
+static inline void
+drop_entity_def_init(struct drop_entity_def *drop_def,
+		     struct SrcList *parent_name, struct Token name,
+		     bool if_exist, enum entity_type entity_type)
+{
+	alter_entity_def_init(&drop_def->base, parent_name);
+	drop_def->name = name;
+	drop_def->if_exist = if_exist;
+	drop_def->base.entity_type = entity_type;
+	drop_def->base.alter_action = ALTER_ACTION_DROP;
+}
+
+static inline void
+create_trigger_def_init(struct create_trigger_def *trigger_def,
+			struct SrcList *table_name, struct Token name,
+			int tr_tm, int op, struct IdList *cols,
+			struct Expr *when, bool if_not_exists)
+{
+	alter_entity_def_init(&trigger_def->base.base, table_name);
+	create_entity_def_init(&trigger_def->base, name, if_not_exists);
+	trigger_def->tr_tm = tr_tm;
+	trigger_def->op = op;
+	trigger_def->cols = cols;
+	trigger_def->when = when;
+	struct alter_entity_def *alter_def =
+		(struct alter_entity_def *) trigger_def;
+	alter_def->entity_type = ENTITY_TYPE_TRIGGER;
+	alter_def->alter_action = ALTER_ACTION_CREATE;
+}
+
+static inline void
+create_ck_def_init(struct create_ck_def *ck_def, struct ExprSpan *expr)
+{
+	ck_def->expr = expr;
+	struct alter_entity_def *alter_def =
+		(struct alter_entity_def *) ck_def;
+	alter_def->entity_type = ENTITY_TYPE_CK;
+	alter_def->alter_action = ALTER_ACTION_CREATE;
+}
+
+static inline void
+create_index_def_init(struct create_index_def *index_def, struct ExprList *cols,
+		      enum sql_index_type idx_type, enum sort_order sort_order)
+{
+	index_def->cols = cols;
+	index_def->idx_type = idx_type;
+	index_def->sort_order = sort_order;
+	struct alter_entity_def *alter_def =
+		(struct alter_entity_def *) index_def;
+	alter_def->entity_type = ENTITY_TYPE_INDEX;
+	alter_def->alter_action = ALTER_ACTION_CREATE;
+}
+
+static inline void
+create_fk_def_init(struct create_fk_def *fk_def, struct ExprList *child_cols,
+		   struct Token *parent_name, struct ExprList *parent_cols,
+		   int actions)
+{
+	assert(fk_def != NULL);
+	fk_def->child_cols = child_cols;
+	fk_def->parent_name = parent_name;
+	fk_def->parent_cols = parent_cols;
+	fk_def->actions = actions;
+	struct alter_entity_def *alter_def =
+		(struct alter_entity_def *) fk_def;
+	alter_def->entity_type = ENTITY_TYPE_FK;
+	alter_def->alter_action = ALTER_ACTION_CREATE;
+}
+
+static inline void
+create_table_def_init(struct create_table_def *table_def, struct Token name,
+		      bool if_not_exists)
+{
+	create_entity_def_init(&table_def->base, name, if_not_exists);
+	rlist_create(&table_def->new_fkey);
+	struct alter_entity_def *alter_def =
+		(struct alter_entity_def *) table_def;
+	alter_def->entity_type = ENTITY_TYPE_TABLE;
+	alter_def->alter_action = ALTER_ACTION_CREATE;
+}
+
+static inline void
+create_view_def_init(struct create_view_def *view_def, struct Token name,
+		     struct Token *create, struct ExprList *aliases,
+		     struct Select *select, bool if_not_exists)
+{
+	create_entity_def_init(&view_def->base, name, if_not_exists);
+	view_def->create_start = create;
+	view_def->select = select;
+	view_def->aliases = aliases;
+	struct alter_entity_def *alter_def =
+		(struct alter_entity_def *) view_def;
+	alter_def->entity_type = ENTITY_TYPE_VIEW;
+	alter_def->alter_action = ALTER_ACTION_CREATE;
+}
+
+static inline void
+create_table_def_destroy(struct create_table_def *table_def)
+{
+	struct fkey_parse *fk;
+	rlist_foreach_entry(fk, &table_def->new_fkey, link)
+		sql_expr_list_delete(sql_get(), fk->selfref_cols);
+}
+
+#endif /* TARANTOOL_BOX_SQL_PARSE_DEF_H_INCLUDED */
diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index 824578e45..c2660eeff 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -273,7 +273,6 @@ sql_parser_create(struct Parse *parser, sqlite3 *db)
 {
 	memset(parser, 0, sizeof(struct Parse));
 	parser->db = db;
-	rlist_create(&parser->new_fkey);
 	rlist_create(&parser->record_list);
 	region_create(&parser->region, &cord()->slabc);
 }
@@ -286,9 +285,6 @@ sql_parser_destroy(Parse *parser)
 	sqlite3 *db = parser->db;
 	sqlite3DbFree(db, parser->aLabel);
 	sql_expr_list_delete(db, parser->pConstExpr);
-	struct fkey_parse *fk;
-	rlist_foreach_entry(fk, &parser->new_fkey, link)
-		sql_expr_list_delete(db, fk->selfref_cols);
 	if (db != NULL) {
 		assert(db->lookaside.bDisable >=
 		       parser->disableLookaside);
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 6462467bc..50d787d11 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -1616,9 +1616,9 @@ void
 sql_resolve_self_reference(struct Parse *parser, struct Table *table, int type,
 			   struct Expr *expr, struct ExprList *expr_list)
 {
-	/* Fake SrcList for parser->pNewTable */
+	/* Fake SrcList for parser->create_table_def */
 	SrcList sSrc;
-	/* Name context for parser->pNewTable */
+	/* Name context for parser->create_table_def  */
 	NameContext sNC;
 
 	assert(type == NC_IsCheck || type == NC_IdxExpr);
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index bc617d7ce..139c3c763 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -67,6 +67,7 @@
 
 #include <stdbool.h>
 
+#include "parse_def.h"
 #include "box/field_def.h"
 #include "box/sql.h"
 #include "box/txn.h"
@@ -1915,19 +1916,6 @@ struct UnpackedRecord {
 				 */
 };
 
-/*
- * Possible SQL index types. Note that PK and UNIQUE constraints
- * are implemented as indexes and have their own types:
- * SQL_INDEX_TYPE_CONSTRAINT_PK and
- * SQL_INDEX_TYPE_CONSTRAINT_UNIQUE.
- */
-enum sql_index_type {
-    SQL_INDEX_TYPE_NON_UNIQUE = 0,
-    SQL_INDEX_TYPE_UNIQUE,
-    SQL_INDEX_TYPE_CONSTRAINT_UNIQUE,
-    SQL_INDEX_TYPE_CONSTRAINT_PK,
-};
-
 /**
  * Fetch statistics concerning tuples to be selected:
  * logarithm of number of tuples which has the same key as for
@@ -1957,20 +1945,6 @@ index_field_tuple_est(const struct index_def *idx, uint32_t field);
 /** [10*log_{2}(1048576)] == 200 */
 #define DEFAULT_TUPLE_LOG_COUNT 200
 
-/*
- * Each token coming out of the lexer is an instance of
- * this structure.  Tokens are also used as part of an expression.
- *
- * Note if Token.z==0 then Token.dyn and Token.n are undefined and
- * may contain random values.  Do not make any assumptions about Token.dyn
- * and Token.n when Token.z==0.
- */
-struct Token {
-	const char *z;		/* Text of the token.  Not NULL-terminated! */
-	unsigned int n;		/* Number of characters in this token */
-	bool isReserved;         /* If reserved keyword or not */
-};
-
 /*
  * An instance of this structure contains information needed to generate
  * code for a SELECT that contains aggregate functions.
@@ -2664,33 +2638,6 @@ enum ast_type {
 	ast_type_MAX
 };
 
-/**
- * Structure representing foreign keys constraints appeared
- * within CREATE TABLE statement. Used only during parsing.
- */
-struct fkey_parse {
-	/**
-	 * Foreign keys constraint declared in <CREATE TABLE ...>
-	 * statement. They must be coded after space creation.
-	 */
-	struct fkey_def *fkey;
-	/**
-	 * If inside CREATE TABLE statement we want to declare
-	 * self-referenced FK constraint, we must delay their
-	 * resolution until the end of parsing of all columns.
-	 * E.g.: CREATE TABLE t1(id REFERENCES t1(b), b);
-	 */
-	struct ExprList *selfref_cols;
-	/**
-	 * Still, self-referenced columns might be NULL, if
-	 * we declare FK constraints referencing PK:
-	 * CREATE TABLE t1(id REFERENCES t1) - it is a valid case.
-	 */
-	bool is_self_referenced;
-	/** Organize these structs into linked list. */
-	struct rlist link;
-};
-
 /*
  * An SQL parser context.  A copy of this structure is passed through
  * the parser and down into all the parser action routine in order to
@@ -2728,7 +2675,6 @@ struct Parse {
 	int nLabel;		/* Number of labels used */
 	int *aLabel;		/* Space to hold the labels */
 	ExprList *pConstExpr;	/* Constant expressions */
-	Token constraintName;	/* Name of the constraint currently being parsed */
 	int nMaxArg;		/* Max args passed to user function by sub-program */
 	int nSelect;		/* Number of SELECT statements seen */
 	int nSelectIndent;	/* How far to indent SELECTTRACE() output */
@@ -2775,28 +2721,31 @@ struct Parse {
 	VList *pVList;		/* Mapping between variable names and numbers */
 	Vdbe *pReprepare;	/* VM being reprepared (sqlite3Reprepare()) */
 	const char *zTail;	/* All SQL text past the last semicolon parsed */
-	Table *pNewTable;	/* A table being constructed by CREATE TABLE */
 	Table *pZombieTab;	/* List of Table objects to delete after code gen */
 	TriggerPrg *pTriggerPrg;	/* Linked list of coded triggers */
 	With *pWith;		/* Current WITH clause, or NULL */
 	With *pWithToFree;	/* Free this WITH object at the end of the parse */
 	/**
-	 * Number of FK constraints declared within
-	 * CREATE TABLE statement.
+	 * One of parse_def structures which are used to
+	 * assemble and carry arguments of DDL routines
+	 * from parse.y
 	 */
-	uint32_t fkey_count;
-	/**
-	 * Foreign key constraint appeared in CREATE TABLE stmt.
-	 */
-	struct rlist new_fkey;
+	union {
+		struct create_ck_def create_ck_def;
+		struct create_fk_def create_fk_def;
+		struct create_index_def create_index_def;
+		struct create_trigger_def create_trigger_def;
+		struct create_view_def create_view_def;
+		struct rename_entity_def rename_entity_def;
+		struct drop_entity_def drop_entity_def;
+	};
+	struct create_table_def create_table_def;
 	/**
 	 * List of all records that were inserted in system spaces
 	 * in current statement.
 	 */
 	struct rlist record_list;
 	bool initiateTTrans;	/* Initiate Tarantool transaction */
-	/** True, if table to be created has AUTOINCREMENT PK. */
-	bool is_new_table_autoinc;
 	/** If set - do not emit byte code at all, just parse.  */
 	bool parse_only;
 	/** Type of parsed_ast member. */
@@ -3357,7 +3306,9 @@ Table *sqlite3ResultSetOfSelect(Parse *, Select *);
 struct index *
 sql_table_primary_key(const struct Table *tab);
 
-void sqlite3StartTable(Parse *, Token *, int);
+struct Table *
+sqlite3StartTable(Parse *, Token *, int);
+
 void sqlite3AddColumn(Parse *, Token *, struct type_def *);
 
 /**
@@ -3375,16 +3326,16 @@ void
 sql_column_add_nullable_action(struct Parse *parser,
 			       enum on_conflict_action nullable_action);
 
-void sqlite3AddPrimaryKey(Parse *, ExprList *, int, enum sort_order);
+void
+sqlite3AddPrimaryKey(struct Parse *parse);
 
 /**
  * Add a new CHECK constraint to the table currently under
  * construction.
  * @param parser Parsing context.
- * @param span Expression span object.
  */
 void
-sql_add_check_constraint(Parse *parser, ExprSpan *span);
+sql_add_check_constraint(Parse *parser);
 
 void sqlite3AddDefaultValue(Parse *, ExprSpan *);
 void sqlite3AddCollateType(Parse *, Token *);
@@ -3431,16 +3382,9 @@ int sqlite3FaultSim(int);
  * The parser calls this routine in order to create a new VIEW.
  *
  * @param parse_context Current parsing context.
- * @param begin The CREATE token that begins the statement.
- * @param name The token that holds the name of the view.
- * @param aliases Optional list of view column names.
- * @param select A SELECT statement that will become the new view.
- * @param if_exists Suppress error messages if VIEW already exists.
  */
 void
-sql_create_view(struct Parse *parse_context, struct Token *begin,
-		struct Token *name, struct ExprList *aliases,
-		struct Select *select, bool if_exists);
+sql_create_view(struct Parse *parse_context);
 
 /**
  * Helper to convert SQLite affinity to corresponding
@@ -3472,7 +3416,7 @@ void
 sql_store_select(struct Parse *parse_context, struct Select *select);
 
 void
-sql_drop_table(struct Parse *, struct SrcList *, bool, bool);
+sql_drop_table(struct Parse *, bool);
 void sqlite3DeleteTable(sqlite3 *, Table *);
 void sqlite3Insert(Parse *, SrcList *, Select *, IdList *,
 		   enum on_conflict_action);
@@ -3501,36 +3445,19 @@ void sqlite3IdListDelete(sqlite3 *, IdList *);
  * parse->pNewTable is a table that is currently being
  * constructed by a CREATE TABLE statement.
  *
- * col_list is a list of columns to be indexed.  col_list will be
- * NULL if this is a primary key or unique-constraint on the most
- * recent column added to the table currently under construction.
- *
  * @param parse All information about this parse.
- * @param token Index name. May be NULL.
- * @param tbl_name Table to index. Use pParse->pNewTable ifNULL.
- * @param col_list A list of columns to be indexed.
- * @param start The CREATE token that begins this statement.
- * @param sort_order Sort order of primary key when pList==NULL.
- * @param if_not_exist Omit error if index already exists.
- * @param idx_type The index type.
  */
 void
-sql_create_index(struct Parse *parse, struct Token *token,
-		 struct SrcList *tbl_name, struct ExprList *col_list,
-		 struct Token *start, enum sort_order sort_order,
-		 bool if_not_exist, enum sql_index_type idx_type);
+sql_create_index(struct Parse *parse);
 
 /**
  * This routine will drop an existing named index.  This routine
  * implements the DROP INDEX statement.
  *
  * @param parse_context Current parsing context.
- * @param index_name_list List containing index name.
- * @param table_token Token representing table name.
- * @param if_exists True, if statement contains 'IF EXISTS' clause.
  */
 void
-sql_drop_index(struct Parse *, struct SrcList *, struct Token *, bool);
+sql_drop_index(struct Parse *parse_context);
 
 int sqlite3Select(Parse *, Select *, SelectDest *);
 Select *sqlite3SelectNew(Parse *, ExprList *, SrcList *, Expr *, ExprList *,
@@ -3932,20 +3859,9 @@ sql_materialize_view(struct Parse *parse, const char *name, struct Expr *where,
  * After the trigger actions have been parsed, the
  * sql_trigger_finish() function is called to complete the trigger
  * construction process.
- *
- * @param parse The parse context of the CREATE TRIGGER statement.
- * @param name The name of the trigger.
- * @param tr_tm One of TK_BEFORE, TK_AFTER, TK_INSTEAD.
- * @param op One of TK_INSERT, TK_UPDATE, TK_DELETE.
- * @param columns column list if this is an UPDATE OF trigger.
- * @param table The name of the table/view the trigger applies to.
- * @param when  WHEN clause.
- * @param no_err Suppress errors if the trigger already exists.
  */
 void
-sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
-		  int op, struct IdList *columns, struct SrcList *table,
-		  struct Expr *when, int no_err);
+sql_trigger_begin(struct Parse *parse);
 
 /**
  * This routine is called after all of the trigger actions have
@@ -3965,11 +3881,9 @@ sql_trigger_finish(struct Parse *parse, struct TriggerStep *step_list,
  * VDBE code.
  *
  * @param parser Parser context.
- * @param name The name of trigger to drop.
- * @param no_err Suppress errors if the trigger already exists.
  */
 void
-sql_drop_trigger(struct Parse *parser, struct SrcList *name, bool no_err);
+sql_drop_trigger(struct Parse *parser);
 
 /**
  * Drop a trigger given a pointer to that trigger.
@@ -4149,38 +4063,18 @@ fkey_change_defer_mode(struct Parse *parse_context, bool is_deferred);
  * OR to handle <CREATE TABLE ...>
  *
  * @param parse_context Parsing context.
- * @param child Name of table to be altered. NULL on CREATE TABLE
- *              statement processing.
- * @param constraint Name of the constraint to be created. May be
- *                   NULL on CREATE TABLE statement processing.
- *                   Then, auto-generated name is used.
- * @param child_cols Columns of child table involved in FK.
- *                   May be NULL on CREATE TABLE statement processing.
- *                   If so, the last column added is used.
- * @param parent Name of referenced table.
- * @param parent_cols List of referenced columns. If NULL, columns
- *                    which make up PK of referenced table are used.
- * @param is_deferred Is FK constraint initially deferred.
- * @param actions ON DELETE, UPDATE and INSERT resolution
- *                algorithms (e.g. CASCADE, RESTRICT etc).
  */
 void
-sql_create_foreign_key(struct Parse *parse_context, struct SrcList *child,
-		       struct Token *constraint, struct ExprList *child_cols,
-		       struct Token *parent, struct ExprList *parent_cols,
-		       bool is_deferred, int actions);
+sql_create_foreign_key(struct Parse *parse_context);
 
 /**
  * Function called from parser to handle
  * <ALTER TABLE table DROP CONSTRAINT constraint> SQL statement.
  *
  * @param parse_context Parsing context.
- * @param table Table to be altered.
- * @param constraint Name of constraint to be dropped.
  */
 void
-sql_drop_foreign_key(struct Parse *parse_context, struct SrcList *table,
-		     struct Token *constraint);
+sql_drop_foreign_key(struct Parse *parse_context);
 
 void sqlite3Detach(Parse *, Expr *);
 int sqlite3AtoF(const char *z, double *, int);
@@ -4398,12 +4292,9 @@ extern int sqlite3PendingByte;
  * command.
  *
  * @param parse Current parsing context.
- * @param src_tab The table to rename.
- * @param new_name_tk Token containing new name of the table.
  */
 void
-sql_alter_table_rename(struct Parse *parse, struct SrcList *src_tab,
-		       struct Token *new_name_tk);
+sql_alter_table_rename(struct Parse *parse);
 
 /**
  * Return the length (in bytes) of the token that begins at z[0].
diff --git a/src/box/sql/tokenize.c b/src/box/sql/tokenize.c
index 4eebfe527..f2088f118 100644
--- a/src/box/sql/tokenize.c
+++ b/src/box/sql/tokenize.c
@@ -449,7 +449,7 @@ sqlite3RunParser(Parse * pParse, const char *zSql, char **pzErrMsg)
 		sqlite3OomFault(db);
 		return SQLITE_NOMEM_BKPT;
 	}
-	assert(pParse->pNewTable == 0);
+	assert(pParse->create_table_def.new_table == NULL);
 	assert(pParse->parsed_ast.trigger == NULL);
 	assert(pParse->nVar == 0);
 	assert(pParse->pVList == 0);
@@ -529,7 +529,7 @@ sqlite3RunParser(Parse * pParse, const char *zSql, char **pzErrMsg)
 		sqlite3VdbeDelete(pParse->pVdbe);
 		pParse->pVdbe = 0;
 	}
-	sqlite3DeleteTable(db, pParse->pNewTable);
+	sqlite3DeleteTable(db, pParse->create_table_def.new_table);
 
 	if (pParse->pWithToFree)
 		sqlite3WithDelete(db, pParse->pWithToFree);
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index 7d5dc9e23..3456a244d 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -62,34 +62,31 @@ sqlite3DeleteTriggerStep(sqlite3 * db, TriggerStep * pTriggerStep)
 }
 
 void
-sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
-		  int op, struct IdList *columns, struct SrcList *table,
-		  struct Expr *when, int no_err)
+sql_trigger_begin(struct Parse *parse)
 {
 	/* The new trigger. */
 	struct sql_trigger *trigger = NULL;
 	/* The database connection. */
 	struct sqlite3 *db = parse->db;
-	/* The name of the Trigger. */
-	char *trigger_name = NULL;
-
-	/* pName->z might be NULL, but not pName itself. */
-	assert(name != NULL);
-	assert(op == TK_INSERT || op == TK_UPDATE || op == TK_DELETE);
-	assert(op > 0 && op < 0xff);
+	struct create_trigger_def *trigger_def = &parse->create_trigger_def;
+	struct create_entity_def *create_def = &trigger_def->base;
+	struct alter_entity_def *alter_def = &create_def->base;
+	assert(alter_def->entity_type == ENTITY_TYPE_TRIGGER);
+	assert(alter_def->alter_action == ALTER_ACTION_CREATE);
 
-	if (table == NULL || db->mallocFailed)
+	char *trigger_name = NULL;
+	if (alter_def->entity_name == NULL || db->mallocFailed)
 		goto trigger_cleanup;
-	assert(table->nSrc == 1);
-
-	trigger_name = sqlite3NameFromToken(db, name);
+	assert(alter_def->entity_name->nSrc == 1);
+	assert(create_def->name.n > 0);
+	trigger_name = sqlite3NameFromToken(db, &create_def->name);
 	if (trigger_name == NULL)
 		goto trigger_cleanup;
 
 	if (sqlite3CheckIdentifierName(parse, trigger_name) != SQLITE_OK)
 		goto trigger_cleanup;
 
-	const char *table_name = table->a[0].zName;
+	const char *table_name = alter_def->entity_name->a[0].zName;
 	uint32_t space_id;
 	if (schema_find_id(BOX_SPACE_ID, 2, table_name, strlen(table_name),
 			   &space_id) != 0)
@@ -112,6 +109,7 @@ sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
 		int name_reg = ++parse->nMem;
 		sqlite3VdbeAddOp4(parse->pVdbe, OP_String8, 0, name_reg, 0,
 				  name_copy, P4_DYNAMIC);
+		bool no_err = create_def->if_not_exist;
 		if (vdbe_emit_halt_with_presence_test(parse, BOX_TRIGGER_ID, 0,
 						      name_reg, 1,
 						      ER_TRIGGER_EXISTS,
@@ -129,13 +127,14 @@ sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
 	trigger->space_id = space_id;
 	trigger->zName = trigger_name;
 	trigger_name = NULL;
-
-	trigger->op = (u8) op;
-	trigger->tr_tm = tr_tm;
-	trigger->pWhen = sqlite3ExprDup(db, when, EXPRDUP_REDUCE);
-	trigger->pColumns = sqlite3IdListDup(db, columns);
-	if ((when != NULL && trigger->pWhen == NULL) ||
-	    (columns != NULL && trigger->pColumns == NULL))
+	assert(trigger_def->op == TK_INSERT || trigger_def->op == TK_UPDATE ||
+	       trigger_def->op== TK_DELETE);
+	trigger->op = (u8) trigger_def->op;
+	trigger->tr_tm = trigger_def->tr_tm;
+	trigger->pWhen = sqlite3ExprDup(db, trigger_def->when, EXPRDUP_REDUCE);
+	trigger->pColumns = sqlite3IdListDup(db, trigger_def->cols);
+	if ((trigger->pWhen != NULL && trigger->pWhen == NULL) ||
+	    (trigger->pColumns != NULL && trigger->pColumns == NULL))
 		goto trigger_cleanup;
 	assert(parse->parsed_ast.trigger == NULL);
 	parse->parsed_ast.trigger = trigger;
@@ -143,9 +142,9 @@ sql_trigger_begin(struct Parse *parse, struct Token *name, int tr_tm,
 
  trigger_cleanup:
 	sqlite3DbFree(db, trigger_name);
-	sqlite3SrcListDelete(db, table);
-	sqlite3IdListDelete(db, columns);
-	sql_expr_delete(db, when, false);
+	sqlite3SrcListDelete(db, alter_def->entity_name);
+	sqlite3IdListDelete(db, trigger_def->cols);
+	sql_expr_delete(db, trigger_def->when, false);
 	if (parse->parsed_ast.trigger == NULL)
 		sql_trigger_delete(db, trigger);
 	else
@@ -424,9 +423,14 @@ vdbe_code_drop_trigger(struct Parse *parser, const char *trigger_name,
 }
 
 void
-sql_drop_trigger(struct Parse *parser, struct SrcList *name, bool no_err)
+sql_drop_trigger(struct Parse *parser)
 {
-
+	struct drop_entity_def *drop_def = &parser->drop_entity_def;
+	struct alter_entity_def *alter_def = &drop_def->base;
+	assert(alter_def->entity_type == ENTITY_TYPE_TRIGGER);
+	assert(alter_def->alter_action == ALTER_ACTION_DROP);
+	struct SrcList *name = alter_def->entity_name;
+	bool no_err = drop_def->if_exist;
 	sqlite3 *db = parser->db;
 	if (db->mallocFailed)
 		goto drop_trigger_cleanup;
diff --git a/test/sql-tap/index7.test.lua b/test/sql-tap/index7.test.lua
index a8ce37f4b..ffb42403c 100755
--- a/test/sql-tap/index7.test.lua
+++ b/test/sql-tap/index7.test.lua
@@ -397,6 +397,6 @@ test:do_catchsql_test(
                 "_index"."id" = "_space"."id" AND
                 "_space"."name"='TEST8';
         ]],
-        {0, {"pk_TEST8_2",0,"unique_C1_1",1}})
+        {0, {"pk_unnamed_TEST8_2",0,"unique_C1_1",1}})
 
 test:finish_test()

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-02-27 22:56             ` n.pettik
@ 2019-03-12 12:50               ` Vladislav Shpilevoy
  2019-03-14 18:13                 ` n.pettik
  0 siblings, 1 reply; 34+ messages in thread
From: Vladislav Shpilevoy @ 2019-03-12 12:50 UTC (permalink / raw)
  To: tarantool-patches, n.pettik

Hi! Thanks for the fixes! See my comments below,
fixes at the end of the email, and on the branch
(it is np/gh-3914-fix-create-index-v2).

>> Below I listed the calls you do to create each 'terminal' structure.
>>
>> 	TABLE RENAME:
>> 	alter_entity_def_init
>> 	rename_entity_def_init
>>
>> 	TABLE CREATE
>> 	alter_entity_def_init
>> 	create_entity_def_init
>> 	create_table_def_init
>>
>> 	TABLE DROP:
>> 	alter_entity_def_init
>> 	drop_entity_def_init
>>
>> 	TABLE ADD FK:
>> 	alter_entity_def_init
>> 	create_entity_def_init
>> 	create_constraint_def_init
>> 	create_fk_def_init
>>
>> 	DROP FK:
>> 	alter_entity_def_init
>> 	drop_entity_def_init
>>
>> 	DROP INDEX:
>> 	alter_entity_def_init
>> 	drop_entity_def_init
>>
>> 	ADD INDEX:
>> 	alter_entity_def_init
>> 	create_entity_def_init
>> 	create_index_def_init
>>
>> 	CHECK:
>>         alter_entity_def_init
>>         create_entity_def_init
>> 	create_ck_def_init
>>
>> 	CREATE TRIGGER:
>> 	alter_entity_def_init
>> 	create_entity_def_init
>> 	create_trigger_def_init
>>
>> 	DROP TRIGGER:
>> 	alter_entity_def_init
>> 	drop_entity_def_init
>>
>> Here are some problems with what you said earlier and with
>> the whole structure.
>>
>> Firstly, some of sequences are not finished with a an explicit
>> terminal. For example, *all* DROPs are finished with an abstract
>> drop_entity_def_init() taking an entity type explicitly. Despite
>> the words you said earlier that you initialize everything in steps.
>> So in the case of DROP the rule is violated already. I think, that
>> you should define static inline helpers in parse_def.h with names
>> drop_table_def_init(), drop_index_def_init(), drop_check_def_init()
>> etc.
> 
> Drop procedure (i.e. arguments filling) is the same for all entities
> (this situation is likely to remain unchanged). Does it make any
> sense to duplicate code without any necessity? What these
> helpers are supposed to do?

Nothing functional. Just syntax sugar and API consistency. Otherwise
your statement, that we have a tree of structures with terminal nodes,
is false. For other nodes you have _table/index/fk etc suffix, but
drop is just 'entity'. For me it looks odd. The same for rename_entity.

Strictly speaking, your hierarchy of structures is even not a tree -
in your code create_table_def and create_view_def store alter_entity_def,
but do not initialize nor use it.

Additionally, for not named constraints you do not call
alter_entity_def_init nor even create_constraint_def_init - you just
memset all the structure. So your tree in fact is really corrupted and
ill, sorry.

Talking of duplication - each wrapper around drop_entity and
rename_entity would take 11 simple lines:

     struct drop_<term>_def {
             struct drop_entity_def base;
     }

     static inline void
     drop_<term>_def_init(struct drop_<term>_def *def,
                          struct SrcList *parent_name, struct Token *name,
                          bool if_exist)
     {
             drop_entity_def_init(&def->base, parent_name, name, if_exist,
                                  ENTITY_TYPE_<term>);
     }

For compiler it is nothing - functions are inlined, for binary file
as well. For code and naming consistency it is a big deal. IMO. But
probably I should stop teaching here - you can leave it as is in terms
of list entities or listen to my objectives. Anyway you are likely to be a
single maintainer of all of this for a long time.

> 
>> Secondly, some words about create_entity_def_init(). It is the
>> most arguable function for a public usage (out of parse_def.h)
>> because hardly I can imagine a situation when you parsed a word
>> CREATE, but can not parse a next one: TABLE, TRIGGER, CONSTRAINT etc.
>> And we see it in the lists above. Below I listed all of this function
>> usage cases and how you can replace that function here.
>>
>> - create_entity_def_init() in create_table ::= rule, line 172. Here
>>   it is followed by create_table_def_init() on the next line.
>>
>> - create_entity_def_init() in cconsname_parse ::= rule line 250.
>>   This rule is a prefix for a constraint definition, so this function
>>   can be substituted with create_constraint_def_init().
>>
>> - create_entity_def_init() in cmd ::= rule (about CREATE VIEW),
>>   line 400. Here we see, that 1) you missed ENTITY_TYPE_VIEW,
>>   2) create_view_def_init() can be called here.
>>
>> - create_entity_def_init() in cmd ::= rule (about CREATE INDEX),
>>   line 1246. Here it is followed by create_index_def_init() on the
>>   next line.
>>
>> - create_entity_def_init() in trigger_decl ::= rule, line 1367.
>>   Here it is followed by create_trigger_def_init().
>>
>> - create_entity_def_init() in cmd ::= rule (about ALTER TABLE ADD
>>   CONSTRAINT FOREIGN KEY), line 1500. Here it is followed by
>>   create_constraint_def_init().
>>
>> So as you can see create_entity_def_init() is not necessary to use
>> out of parse_def.h. You can put it into
>> create_table/index/constraint_def().
> 
> Ok, done. See updated version at the end of letter.
> 
>> Now about create_constraint_def_init(). In your patch it is always
>> followed by create_fk_def_init().
> 
> I didn’t get this point. create_constraint_def_init() may come before
> create_fk_def_init() as well as before create_index_def/create_ck_def.

The only place, where create_constraint_def_init can not be replaced with
a next level term, is a rule "cconsname_parse ::= CONSTRAINT nm(X).". But
you did not answer below why do you need it here.

> 
>> But after you did the comment above
>> about create_entity_def_init(), you will have create_constraint_def_init()
>> in cconsname_parse ::= CONSTRAINT nm(X) rule. And I do not see any
>> concrete reasons why can not you remove it from here and use only
>> create_index_def_init(), create_ck_def_init() etc in ccons ::= rule.
>> You said, that some of pointers should be nullifed, but what pointers
>> and why? What if you do not nullify them right after CONSTRAINT word
>> is met?
> 
> We had to nullify them since in previous versions members of some
> structures might leave uninitialised. Now _init funs fill all members,
> so there is no any problem.

I see, that you nullify them, but you did not answer my questions. Why
can not you postpone that nullification until a next word is met -
UNIQUE, PK, FK, CK etc? It would allow you to fold
create_constraint_def_init and always use a term def.

What is more, you initialize named constraints twice - first you
nullify them in cconsname_start with memset, and then call
create_constraint_def_init.

I did my own investigation of this matter, and managed to remove
cconsname_start rule, as well as double init. After that I have
cconsname_parse followed by many term constraint rules, and I tried to
figure out why can't we just inline cconsname into terms ccons and
tcons and remove create_constraint_def_init from public. Especially
taking into account that it is not many changes thanks to that:
https://github.com/tarantool/tarantool/issues/3820.

It means, that we could inline 'CONSTRAINT nm(X)' in 8 places only:

     1) "ccons ::= PRIMARY KEY"
     2) "ccons ::= UNIQUE"
     3) "ccons ::= check_constraint_def"
     4) "ccons ::= REFERENCES"

      + the same for tcons.

But then I realized, that cconsname helps us to keep the name
optional. To solve that we need to somehow save nm(X) result until
a terminal constraint is reached, or nullify it. I think, that we
could add struct Token constraint_name to union of alter entities in
struct Parse, and fetch it from there in terminal constraint
definitions (PK, UNIQUE, CK, FK). It is possible, because in such
a case this union's memory is not occupied by create_constraint_def
until term. It will allow us to remove create_constraint_def_init
from public usage. And to close the issue 3820 alongside.

This is what I wanted to hear from you.



Besides, I have some lowlevel comments on the code.

1) Usually we do not pass structures by value, even such small
as struct Token.

2) You've moved struct Token into parse_def.h, but kept its
routines in sqlite3Int.h. Please, move them too. And please,
rebase - 'sqlite3' has already been replaced with 'sql'.

3) Since now and after point 2 we have internal and long functions
in parse_def, I think it is time to introduce parse_def.c and hide
them here.

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

My diff below and on the branch:

commit 68499ad902d7315447e83e77b83ee8c93db5e239
Author: Vladislav Shpilevoy <v.shpilevoy@tarantool.org>
Date:   Tue Mar 12 13:49:30 2019 +0300

     Review fixes

diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 1f7678a4b..e9643a71a 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -168,7 +168,7 @@ cmd ::= ROLLBACK TO savepoint_opt nm(X). {
  //
  cmd ::= create_table create_table_args.
  create_table ::= createkw TABLE ifnotexists(E) nm(Y). {
-  create_table_def_init(&pParse->create_table_def, Y, E);
+  create_table_def_init(&pParse->create_table_def, &Y, E);
    pParse->create_table_def.new_table = sqlite3StartTable(pParse, &Y, E);
  }
  createkw(A) ::= CREATE(A).  {disableLookaside(pParse);}
@@ -239,15 +239,15 @@ nm(A) ::= id(A). {
  carglist ::= carglist cconsdef.
  carglist ::= .
  cconsdef ::= cconsname ccons.
-cconsname ::= cconsname_start cconsname_parse .
-cconsname_start ::= . {
-  memset(&pParse->create_index_def.base, 0, sizeof(struct create_constraint_def));
+cconsname ::= CONSTRAINT nm(X). {
+  create_constraint_def_init(&pParse->create_constraint_def, NULL, &X, false,
+                             false);
  }
-cconsname_parse ::= CONSTRAINT nm(X). {
-  create_constraint_def_init(&pParse->create_index_def.base, NULL, X, false,
+cconsname ::= . {
+  struct Token t = Token_nil;
+  create_constraint_def_init(&pParse->create_constraint_def, NULL, &t, false,
			      false);
  }
-cconsname_parse ::= .
  ccons ::= DEFAULT term(X).            {sqlite3AddDefaultValue(pParse,&X);}
  ccons ::= DEFAULT LP expr(X) RP.      {sqlite3AddDefaultValue(pParse,&X);}
  ccons ::= DEFAULT PLUS term(X).       {sqlite3AddDefaultValue(pParse,&X);}
@@ -346,7 +346,7 @@ tcons ::= UNIQUE LP sortlist(X) RP. {
  tcons ::= check_constraint_def .
  tcons ::= FOREIGN KEY LP eidlist(FA) RP
	   REFERENCES nm(T) eidlist_opt(TA) refargs(R) defer_subclause_opt(D). {
-  ((struct create_constraint_def *) &pParse->create_fk_def)->is_deferred = D;
+  pParse->create_fk_def.base.is_deferred = D;
    create_fk_def_init(&pParse->create_fk_def, FA, &T, TA, R);
    sql_create_foreign_key(pParse);
  }
@@ -377,8 +377,8 @@ cmd ::= drop_start(X) drop_table . {
  }
  
  drop_table ::= ifexists(E) fullname(X) . {
-  drop_entity_def_init(&pParse->drop_entity_def, X, (struct Token) {}, E,
-                       ENTITY_TYPE_TABLE);
+  struct Token t = Token_nil;
+  drop_entity_def_init(&pParse->drop_entity_def, X, &t, E, ENTITY_TYPE_TABLE);
  }
  
  %type drop_start {bool}
@@ -394,7 +394,7 @@ ifexists(A) ::= .            {A = 0;}
  cmd ::= createkw(X) VIEW ifnotexists(E) nm(Y) eidlist_opt(C)
	   AS select(S). {
    if (!pParse->parse_only) {
-    create_view_def_init(&pParse->create_view_def, Y, &X, C, S, E);
+    create_view_def_init(&pParse->create_view_def, &Y, &X, C, S, E);
      sql_create_view(pParse);
    } else {
      sql_store_select(pParse, S);
@@ -1239,7 +1239,7 @@ paren_exprlist(A) ::= LP exprlist(X) RP.  {A = X;}
  cmd ::= createkw uniqueflag(U) INDEX ifnotexists(NE) nm(X)
	 ON nm(Y) LP sortlist(Z) RP. {
    create_constraint_def_init(&pParse->create_index_def.base,
-                             sqlite3SrcListAppend(pParse->db, 0, &Y), X, NE,
+                             sqlite3SrcListAppend(pParse->db, 0, &Y), &X, NE,
			      false);
    create_index_def_init(&pParse->create_index_def, Z, U, SORT_ORDER_ASC);
    sql_create_index(pParse);
@@ -1305,7 +1305,7 @@ collate(C) ::= COLLATE id.   {C = 1;}
  ///////////////////////////// The DROP INDEX command /////////////////////////
  //
  cmd ::= DROP INDEX ifexists(E) nm(X) ON fullname(Y).   {
-  drop_entity_def_init(&pParse->drop_entity_def, Y, X, E, ENTITY_TYPE_INDEX);
+  drop_entity_def_init(&pParse->drop_entity_def, Y, &X, E, ENTITY_TYPE_INDEX);
    sql_drop_index(pParse);
  }
  
@@ -1357,8 +1357,8 @@ cmd ::= createkw trigger_decl(A) BEGIN trigger_cmd_list(S) END(Z). {
  trigger_decl(A) ::= TRIGGER ifnotexists(NOERR) nm(B)
		     trigger_time(C) trigger_event(D)
		     ON fullname(E) foreach_clause when_clause(G). {
-  create_trigger_def_init(&pParse->create_trigger_def, E, B, C, D.a, D.b,
-                          G, NOERR);
+  create_trigger_def_init(&pParse->create_trigger_def, E, &B, C, D.a, D.b, G,
+                          NOERR);
    sql_trigger_begin(pParse);
    A = B; /*A-overwrites-T*/
  }
@@ -1469,7 +1469,8 @@ raisetype(A) ::= FAIL.      {A = ON_CONFLICT_ACTION_FAIL;}
  
  ////////////////////////  DROP TRIGGER statement //////////////////////////////
  cmd ::= DROP TRIGGER ifexists(NOERR) fullname(X). {
-  drop_entity_def_init(&pParse->drop_entity_def, X, (struct Token){}, NOERR,
+  struct Token t = Token_nil;
+  drop_entity_def_init(&pParse->drop_entity_def, X, &t, NOERR,
			ENTITY_TYPE_TRIGGER);
    sql_drop_trigger(pParse);
  }
@@ -1480,20 +1481,20 @@ cmd ::= ANALYZE nm(X).          {sqlite3Analyze(pParse, &X);}
  
  //////////////////////// ALTER TABLE table ... ////////////////////////////////
  cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). {
-  rename_entity_def_init(&pParse->rename_entity_def, X, Z);
+  rename_entity_def_init(&pParse->rename_entity_def, X, &Z);
    sql_alter_table_rename(pParse);
  }
  
  cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT nm(Z) FOREIGN KEY
	 LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) refargs(R)
	 defer_subclause_opt(D). {
-  create_constraint_def_init(&pParse->create_fk_def.base, X, Z, D, false);
+  create_constraint_def_init(&pParse->create_fk_def.base, X, &Z, D, false);
    create_fk_def_init(&pParse->create_fk_def, FA, &T, TA, R);
    sql_create_foreign_key(pParse);
  }
  
  cmd ::= ALTER TABLE fullname(X) DROP CONSTRAINT nm(Z). {
-  drop_entity_def_init(&pParse->drop_entity_def, X, Z, false, ENTITY_TYPE_FK);
+  drop_entity_def_init(&pParse->drop_entity_def, X, &Z, false, ENTITY_TYPE_FK);
    sql_drop_foreign_key(pParse);
  }
  
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index c3ed2f72d..c7640c523 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -84,6 +84,8 @@ struct Token {
	bool isReserved;
  };
  
+#define Token_nil ((struct Token) {NULL, 0, false})
+
  /**
   * Structure representing foreign keys constraints appeared
   * within CREATE TABLE statement. Used only during parsing.
@@ -131,7 +133,11 @@ enum entity_type {
	ENTITY_TYPE_TRIGGER,
	ENTITY_TYPE_CK,
	ENTITY_TYPE_FK,
-	entity_type_MAX
+	/**
+	 * For assertion checks that constraint definition is
+	 * created before initialization of a term constraint.
+	 */
+	ENTITY_TYPE_CONSTRAINT,
  };
  
  enum alter_action {
@@ -246,92 +252,91 @@ struct create_index_def {
  /** Basic initialisers of parse structures.*/
  static inline void
  alter_entity_def_init(struct alter_entity_def *alter_def,
-		      struct SrcList *entity_name)
+		      struct SrcList *entity_name, enum entity_type type,
+		      enum alter_action action)
  {
	alter_def->entity_name = entity_name;
+	alter_def->entity_type = type;
+	alter_def->alter_action = action;
  }
  
  static inline void
  rename_entity_def_init(struct rename_entity_def *rename_def,
-		       struct SrcList *table_name, struct Token new_name)
+		       struct SrcList *table_name, struct Token *new_name)
  {
-	alter_entity_def_init(&rename_def->base, table_name);
-	rename_def->new_name = new_name;
-	struct alter_entity_def *alter_def =
-		(struct alter_entity_def *) rename_def;
-	alter_def->entity_type = ENTITY_TYPE_TABLE;
-	alter_def->alter_action = ALTER_ACTION_RENAME;
+	alter_entity_def_init(&rename_def->base, table_name, ENTITY_TYPE_TABLE,
+			      ALTER_ACTION_RENAME);
+	rename_def->new_name = *new_name;
  }
  
  static inline void
-create_entity_def_init(struct create_entity_def *create_def, struct Token name,
-		       bool if_not_exist)
+create_entity_def_init(struct create_entity_def *create_def,
+		       enum entity_type type, struct SrcList *parent_name,
+		       struct Token *name, bool if_not_exist)
  {
-	create_def->name = name;
+	alter_entity_def_init(&create_def->base, parent_name, type,
+			      ALTER_ACTION_CREATE);
+	create_def->name = *name;
	create_def->if_not_exist = if_not_exist;
  }
  
  static inline void
  create_constraint_def_init(struct create_constraint_def *constr_def,
-			   struct SrcList *parent_name, struct Token name,
+			   struct SrcList *parent_name, struct Token *name,
			   bool if_not_exists, bool is_deferred)
  {
-	alter_entity_def_init(&constr_def->base.base, parent_name);
-	create_entity_def_init(&constr_def->base, name, if_not_exists);
+	create_entity_def_init(&constr_def->base, ENTITY_TYPE_CONSTRAINT,
+			       parent_name, name, if_not_exists);
	constr_def->is_deferred = is_deferred;
  }
  
  static inline void
  drop_entity_def_init(struct drop_entity_def *drop_def,
-		     struct SrcList *parent_name, struct Token name,
+		     struct SrcList *parent_name, struct Token *name,
		     bool if_exist, enum entity_type entity_type)
  {
-	alter_entity_def_init(&drop_def->base, parent_name);
-	drop_def->name = name;
+	alter_entity_def_init(&drop_def->base, parent_name, entity_type,
+			      ALTER_ACTION_DROP);
+	drop_def->name = *name;
	drop_def->if_exist = if_exist;
-	drop_def->base.entity_type = entity_type;
-	drop_def->base.alter_action = ALTER_ACTION_DROP;
  }
  
  static inline void
  create_trigger_def_init(struct create_trigger_def *trigger_def,
-			struct SrcList *table_name, struct Token name,
+			struct SrcList *table_name, struct Token *name,
			int tr_tm, int op, struct IdList *cols,
			struct Expr *when, bool if_not_exists)
  {
-	alter_entity_def_init(&trigger_def->base.base, table_name);
-	create_entity_def_init(&trigger_def->base, name, if_not_exists);
+	create_entity_def_init(&trigger_def->base, ENTITY_TYPE_TRIGGER,
+			       table_name, name, if_not_exists);
	trigger_def->tr_tm = tr_tm;
	trigger_def->op = op;
	trigger_def->cols = cols;
	trigger_def->when = when;
-	struct alter_entity_def *alter_def =
-		(struct alter_entity_def *) trigger_def;
-	alter_def->entity_type = ENTITY_TYPE_TRIGGER;
-	alter_def->alter_action = ALTER_ACTION_CREATE;
  }
  
  static inline void
  create_ck_def_init(struct create_ck_def *ck_def, struct ExprSpan *expr)
  {
-	ck_def->expr = expr;
-	struct alter_entity_def *alter_def =
-		(struct alter_entity_def *) ck_def;
+	struct alter_entity_def *alter_def = (struct alter_entity_def *) ck_def;
+	assert(alter_def->alter_action == ALTER_ACTION_CREATE);
+	assert(alter_def->entity_type == ENTITY_TYPE_CONSTRAINT);
	alter_def->entity_type = ENTITY_TYPE_CK;
-	alter_def->alter_action = ALTER_ACTION_CREATE;
+	ck_def->expr = expr;
  }
  
  static inline void
  create_index_def_init(struct create_index_def *index_def, struct ExprList *cols,
		      enum sql_index_type idx_type, enum sort_order sort_order)
  {
-	index_def->cols = cols;
-	index_def->idx_type = idx_type;
-	index_def->sort_order = sort_order;
	struct alter_entity_def *alter_def =
		(struct alter_entity_def *) index_def;
+	assert(alter_def->alter_action == ALTER_ACTION_CREATE);
+	assert(alter_def->entity_type == ENTITY_TYPE_CONSTRAINT);
	alter_def->entity_type = ENTITY_TYPE_INDEX;
-	alter_def->alter_action = ALTER_ACTION_CREATE;
+	index_def->cols = cols;
+	index_def->idx_type = idx_type;
+	index_def->sort_order = sort_order;
  }
  
  static inline void
@@ -339,42 +344,35 @@ create_fk_def_init(struct create_fk_def *fk_def, struct ExprList *child_cols,
		   struct Token *parent_name, struct ExprList *parent_cols,
		   int actions)
  {
-	assert(fk_def != NULL);
+	struct alter_entity_def *alter_def = (struct alter_entity_def *) fk_def;
+	assert(alter_def->alter_action == ALTER_ACTION_CREATE);
+	assert(alter_def->entity_type == ENTITY_TYPE_CONSTRAINT);
+	alter_def->entity_type = ENTITY_TYPE_FK;
	fk_def->child_cols = child_cols;
	fk_def->parent_name = parent_name;
	fk_def->parent_cols = parent_cols;
	fk_def->actions = actions;
-	struct alter_entity_def *alter_def =
-		(struct alter_entity_def *) fk_def;
-	alter_def->entity_type = ENTITY_TYPE_FK;
-	alter_def->alter_action = ALTER_ACTION_CREATE;
  }
  
  static inline void
-create_table_def_init(struct create_table_def *table_def, struct Token name,
+create_table_def_init(struct create_table_def *table_def, struct Token *name,
		      bool if_not_exists)
  {
-	create_entity_def_init(&table_def->base, name, if_not_exists);
+	create_entity_def_init(&table_def->base, ENTITY_TYPE_TABLE, NULL, name,
+			       if_not_exists);
	rlist_create(&table_def->new_fkey);
-	struct alter_entity_def *alter_def =
-		(struct alter_entity_def *) table_def;
-	alter_def->entity_type = ENTITY_TYPE_TABLE;
-	alter_def->alter_action = ALTER_ACTION_CREATE;
  }
  
  static inline void
-create_view_def_init(struct create_view_def *view_def, struct Token name,
+create_view_def_init(struct create_view_def *view_def, struct Token *name,
		     struct Token *create, struct ExprList *aliases,
		     struct Select *select, bool if_not_exists)
  {
-	create_entity_def_init(&view_def->base, name, if_not_exists);
+	create_entity_def_init(&view_def->base, ENTITY_TYPE_VIEW, NULL, name,
+			       if_not_exists);
	view_def->create_start = create;
	view_def->select = select;
	view_def->aliases = aliases;
-	struct alter_entity_def *alter_def =
-		(struct alter_entity_def *) view_def;
-	alter_def->entity_type = ENTITY_TYPE_VIEW;
-	alter_def->alter_action = ALTER_ACTION_CREATE;
  }
  
  static inline void
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index 139c3c763..f8ec3519e 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2734,6 +2734,7 @@ struct Parse {
		struct create_ck_def create_ck_def;
		struct create_fk_def create_fk_def;
		struct create_index_def create_index_def;
+		struct create_constraint_def create_constraint_def;
		struct create_trigger_def create_trigger_def;
		struct create_view_def create_view_def;
		struct rename_entity_def rename_entity_def;

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-03-12 12:50               ` Vladislav Shpilevoy
@ 2019-03-14 18:13                 ` n.pettik
  2019-03-25 11:25                   ` Vladislav Shpilevoy
  0 siblings, 1 reply; 34+ messages in thread
From: n.pettik @ 2019-03-14 18:13 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy



> On 12 Mar 2019, at 15:50, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Hi! Thanks for the fixes! See my comments below,
> fixes at the end of the email, and on the branch
> (it is np/gh-3914-fix-create-index-v2).
> 
>>> Below I listed the calls you do to create each 'terminal' structure.
>>> 
>>> 	TABLE RENAME:
>>> 	alter_entity_def_init
>>> 	rename_entity_def_init
>>> 
>>> 	TABLE CREATE
>>> 	alter_entity_def_init
>>> 	create_entity_def_init
>>> 	create_table_def_init
>>> 
>>> 	TABLE DROP:
>>> 	alter_entity_def_init
>>> 	drop_entity_def_init
>>> 
>>> 	TABLE ADD FK:
>>> 	alter_entity_def_init
>>> 	create_entity_def_init
>>> 	create_constraint_def_init
>>> 	create_fk_def_init
>>> 
>>> 	DROP FK:
>>> 	alter_entity_def_init
>>> 	drop_entity_def_init
>>> 
>>> 	DROP INDEX:
>>> 	alter_entity_def_init
>>> 	drop_entity_def_init
>>> 
>>> 	ADD INDEX:
>>> 	alter_entity_def_init
>>> 	create_entity_def_init
>>> 	create_index_def_init
>>> 
>>> 	CHECK:
>>>        alter_entity_def_init
>>>        create_entity_def_init
>>> 	create_ck_def_init
>>> 
>>> 	CREATE TRIGGER:
>>> 	alter_entity_def_init
>>> 	create_entity_def_init
>>> 	create_trigger_def_init
>>> 
>>> 	DROP TRIGGER:
>>> 	alter_entity_def_init
>>> 	drop_entity_def_init
>>> 
>>> Here are some problems with what you said earlier and with
>>> the whole structure.
>>> 
>>> Firstly, some of sequences are not finished with a an explicit
>>> terminal. For example, *all* DROPs are finished with an abstract
>>> drop_entity_def_init() taking an entity type explicitly. Despite
>>> the words you said earlier that you initialize everything in steps.
>>> So in the case of DROP the rule is violated already. I think, that
>>> you should define static inline helpers in parse_def.h with names
>>> drop_table_def_init(), drop_index_def_init(), drop_check_def_init()
>>> etc.
>> Drop procedure (i.e. arguments filling) is the same for all entities
>> (this situation is likely to remain unchanged). Does it make any
>> sense to duplicate code without any necessity? What these
>> helpers are supposed to do?
> 
> Nothing functional. Just syntax sugar and API consistency. Otherwise
> your statement, that we have a tree of structures with terminal nodes,
> is false. For other nodes you have _table/index/fk etc suffix, but
> drop is just 'entity'. For me it looks odd. The same for rename_entity.
> 
> Strictly speaking, your hierarchy of structures is even not a tree -
> in your code create_table_def and create_view_def store alter_entity_def,
> but do not initialize nor use it.
> 
> Additionally, for not named constraints you do not call
> alter_entity_def_init nor even create_constraint_def_init - you just
> memset all the structure. So your tree in fact is really corrupted and
> ill, sorry.
> 
> Talking of duplication - each wrapper around drop_entity and
> rename_entity would take 11 simple lines:
> 
>    struct drop_<term>_def {
>            struct drop_entity_def base;
>    }
> 
>    static inline void
>    drop_<term>_def_init(struct drop_<term>_def *def,
>                         struct SrcList *parent_name, struct Token *name,
>                         bool if_exist)
>    {
>            drop_entity_def_init(&def->base, parent_name, name, if_exist,
>                                 ENTITY_TYPE_<term>);
>    }
> 
> For compiler it is nothing - functions are inlined, for binary file
> as well. For code and naming consistency it is a big deal. IMO. But
> probably I should stop teaching here - you can leave it as is in terms
> of list entities or listen to my objectives.

Just want to hear pros of this approach (cons are obvious).
Implemented your proposal (also fixed a bit parsing of
DROP TABLE statement):

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 0bc967bf7..2a825284e 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -1680,20 +1680,21 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space,
 }
 
 /**
- * This routine is called to do the work of a DROP TABLE statement.
+ * This routine is called to do the work of a DROP TABLE and
+ * DROP VIEW statements.
  *
  * @param parse_context Current parsing context.
- * @param is_view True, if statement is really 'DROP VIEW'.
  */
 void
-sql_drop_table(struct Parse *parse_context, bool is_view)
+sql_drop_table(struct Parse *parse_context)
 {
-       struct drop_entity_def drop_def = parse_context->drop_entity_def;
-       assert(drop_def.base.entity_type == ENTITY_TYPE_TABLE);
+       struct drop_entity_def drop_def = parse_context->drop_table_def.base;
        assert(drop_def.base.alter_action == ALTER_ACTION_DROP);
        struct SrcList *table_name_list = drop_def.base.entity_name;
        struct Vdbe *v = sqlite3GetVdbe(parse_context);
        struct sqlite3 *db = parse_context->db;
+       bool is_view = drop_def.base.entity_type == ENTITY_TYPE_VIEW;
+       assert(is_view || drop_def.base.entity_type == ENTITY_TYPE_TABLE);
        if (v == NULL || db->mallocFailed) {
                goto exit_drop_table;
        }
@@ -2016,7 +2017,7 @@ fkey_change_defer_mode(struct Parse *parse_context, bool is_deferred)
 void
 sql_drop_foreign_key(struct Parse *parse_context)
 {
-       struct drop_entity_def *drop_def = &parse_context->drop_entity_def;
+       struct drop_entity_def *drop_def = &parse_context->drop_fk_def.base;
        assert(drop_def->base.entity_type == ENTITY_TYPE_FK);
        assert(drop_def->base.alter_action == ALTER_ACTION_DROP);
        const char *table_name = drop_def->base.entity_name->a[0].zName;
@@ -2555,7 +2556,7 @@ sql_create_index(struct Parse *parse) {
 void
 sql_drop_index(struct Parse *parse_context)
 {
-       struct drop_entity_def *drop_def = &parse_context->drop_entity_def;
+       struct drop_entity_def *drop_def = &parse_context->drop_index_def.base;
        assert(drop_def->base.entity_type == ENTITY_TYPE_INDEX);
        assert(drop_def->base.alter_action == ALTER_ACTION_DROP);
        struct Vdbe *v = sqlite3GetVdbe(parse_context);
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index e9643a71a..9aa45752a 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -372,19 +372,18 @@ resolvetype(A) ::= REPLACE.                  {A = ON_CONFLICT_ACTION_REPLACE;}
 ////////////////////////// The DROP TABLE /////////////////////////////////////
 //
 
-cmd ::= drop_start(X) drop_table . {
-  sql_drop_table(pParse, X);
+cmd ::= DROP TABLE ifexists(E) fullname(X) . {
+  struct Token t = Token_nil;
+  drop_table_def_init(&pParse->drop_table_def, X, &t, E);
+  sql_drop_table(pParse);
 }
 
-drop_table ::= ifexists(E) fullname(X) . {
+cmd ::= DROP VIEW ifexists(E) fullname(X) . {
   struct Token t = Token_nil;
-  drop_entity_def_init(&pParse->drop_entity_def, X, &t, E, ENTITY_TYPE_TABLE);
+  drop_view_def_init(&pParse->drop_view_def, X, &t, E);
+  sql_drop_table(pParse);
 }
 
-%type drop_start {bool}
-drop_start(X) ::= DROP VIEW . { X = true; }
-drop_start(X) ::= DROP TABLE . { X = false; }
-
 %type ifexists {int}
 ifexists(A) ::= IF EXISTS.   {A = 1;}
 ifexists(A) ::= .            {A = 0;}
@@ -1305,7 +1304,7 @@ collate(C) ::= COLLATE id.   {C = 1;}
 ///////////////////////////// The DROP INDEX command /////////////////////////
 //
 cmd ::= DROP INDEX ifexists(E) nm(X) ON fullname(Y).   {
-  drop_entity_def_init(&pParse->drop_entity_def, Y, &X, E, ENTITY_TYPE_INDEX);
+  drop_index_def_init(&pParse->drop_index_def, Y, &X, E);
   sql_drop_index(pParse);
 }
 
@@ -1470,8 +1469,7 @@ raisetype(A) ::= FAIL.      {A = ON_CONFLICT_ACTION_FAIL;}
 ////////////////////////  DROP TRIGGER statement //////////////////////////////
 cmd ::= DROP TRIGGER ifexists(NOERR) fullname(X). {
   struct Token t = Token_nil;
-  drop_entity_def_init(&pParse->drop_entity_def, X, &t, NOERR,
-                       ENTITY_TYPE_TRIGGER);
+  drop_trigger_def_init(&pParse->drop_trigger_def, X, &t, NOERR);
   sql_drop_trigger(pParse);
 }
 
@@ -1494,7 +1492,7 @@ cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT nm(Z) FOREIGN KEY
 }
 
 cmd ::= ALTER TABLE fullname(X) DROP CONSTRAINT nm(Z). {
-  drop_entity_def_init(&pParse->drop_entity_def, X, &Z, false, ENTITY_TYPE_FK);
+  drop_fk_def_init(&pParse->drop_fk_def, X, &Z, false);
   sql_drop_foreign_key(pParse);
 }
 
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index c7640c523..3fc65b065 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -204,6 +204,35 @@ struct drop_entity_def {
        bool if_exist;
 };
 
+/**
+ * Identical wrappers around drop_entity_def to make hierarchy of
+ * structures be consistent. Arguments for drop procedures are
+ * the same.
+ */
+struct drop_table_def {
+       struct drop_entity_def base;
+};
+
+struct drop_view_def {
+       struct drop_entity_def base;
+};
+
+struct drop_trigger_def {
+       struct drop_entity_def base;
+};
+
+struct drop_fk_def {
+       struct drop_entity_def base;
+};
+
+struct drop_index_def {
+       struct drop_entity_def base;
+};
+
 struct create_trigger_def {
        struct create_entity_def base;
        /** One of TK_BEFORE, TK_AFTER, TK_INSTEAD. */
@@ -301,6 +330,60 @@ drop_entity_def_init(struct drop_entity_def *drop_def,
        drop_def->if_exist = if_exist;
 }
 
+static inline void
+drop_table_def_init(struct drop_table_def *drop_table_def,
+                   struct SrcList *parent_name, struct Token *name,
+                   bool if_exist)
+{
+       drop_entity_def_init(&drop_table_def->base, parent_name, name, if_exist,
+                            ENTITY_TYPE_TABLE);
+}
+
+static inline void
+drop_view_def_init(struct drop_view_def *drop_view_def,
+                  struct SrcList *parent_name, struct Token *name,
+                  bool if_exist)
+{
+       drop_entity_def_init(&drop_view_def->base, parent_name, name, if_exist,
+                            ENTITY_TYPE_VIEW);
+}
+
+static inline void
+drop_trigger_def_init(struct drop_trigger_def *drop_trigger_def,
+                     struct SrcList *parent_name, struct Token *name,
+                     bool if_exist)
+{
+       drop_entity_def_init(&drop_trigger_def->base, parent_name, name,
+                            if_exist, ENTITY_TYPE_TRIGGER);
+}
+
+static inline void
+drop_fk_def_init(struct drop_fk_def *drop_fk_def,
+                struct SrcList *parent_name, struct Token *name,
+                bool if_exist)
+{
+       drop_entity_def_init(&drop_fk_def->base, parent_name, name, if_exist,
+                            ENTITY_TYPE_FK);
+}
+
+static inline void
+drop_index_def_init(struct drop_index_def *drop_index_def,
+                   struct SrcList *parent_name, struct Token *name,
+                   bool if_exist)
+{
+       drop_entity_def_init(&drop_index_def->base, parent_name, name, if_exist,
+                            ENTITY_TYPE_INDEX);
+}
+
 static inline void
 create_trigger_def_init(struct create_trigger_def *trigger_def,
                        struct SrcList *table_name, struct Token *name,
diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h
index f8ec3519e..a0eb9ab38 100644
--- a/src/box/sql/sqliteInt.h
+++ b/src/box/sql/sqliteInt.h
@@ -2738,7 +2738,12 @@ struct Parse {
                struct create_trigger_def create_trigger_def;
                struct create_view_def create_view_def;
                struct rename_entity_def rename_entity_def;
-               struct drop_entity_def drop_entity_def;
+               struct drop_fk_def drop_fk_def;
+               struct drop_index_def drop_index_def;
+               struct drop_table_def drop_table_def;
+               struct drop_trigger_def drop_trigger_def;
+               struct drop_view_def drop_view_def;
        };
        struct create_table_def create_table_def;
        /**
@@ -3417,7 +3422,7 @@ void
 sql_store_select(struct Parse *parse_context, struct Select *select);
 
 void
-sql_drop_table(struct Parse *, bool);
+sql_drop_table(struct Parse *parse);
 void sqlite3DeleteTable(sqlite3 *, Table *);
 void sqlite3Insert(Parse *, SrcList *, Select *, IdList *,
                   enum on_conflict_action);
diff --git a/src/box/sql/trigger.c b/src/box/sql/trigger.c
index 3456a244d..f85f0dc35 100644
--- a/src/box/sql/trigger.c
+++ b/src/box/sql/trigger.c
@@ -425,7 +425,7 @@ vdbe_code_drop_trigger(struct Parse *parser, const char *trigger_name,
 void
 sql_drop_trigger(struct Parse *parser)
 {
-       struct drop_entity_def *drop_def = &parser->drop_entity_def;
+       struct drop_entity_def *drop_def = &parser->drop_trigger_def.base;
        struct alter_entity_def *alter_def = &drop_def->base;
        assert(alter_def->entity_type == ENTITY_TYPE_TRIGGER);
        assert(alter_def->alter_action == ALTER_ACTION_DROP);

> Anyway you are likely to be a
> single maintainer of all of this for a long time.

Sounds like a threat. I’d rather not :)

>>> Secondly, some words about create_entity_def_init(). It is the
>>> most arguable function for a public usage (out of parse_def.h)
>>> because hardly I can imagine a situation when you parsed a word
>>> CREATE, but can not parse a next one: TABLE, TRIGGER, CONSTRAINT etc.
>>> And we see it in the lists above. Below I listed all of this function
>>> usage cases and how you can replace that function here.
>>> 
>>> - create_entity_def_init() in create_table ::= rule, line 172. Here
>>>  it is followed by create_table_def_init() on the next line.
>>> 
>>> - create_entity_def_init() in cconsname_parse ::= rule line 250.
>>>  This rule is a prefix for a constraint definition, so this function
>>>  can be substituted with create_constraint_def_init().
>>> 
>>> - create_entity_def_init() in cmd ::= rule (about CREATE VIEW),
>>>  line 400. Here we see, that 1) you missed ENTITY_TYPE_VIEW,
>>>  2) create_view_def_init() can be called here.
>>> 
>>> - create_entity_def_init() in cmd ::= rule (about CREATE INDEX),
>>>  line 1246. Here it is followed by create_index_def_init() on the
>>>  next line.
>>> 
>>> - create_entity_def_init() in trigger_decl ::= rule, line 1367.
>>>  Here it is followed by create_trigger_def_init().
>>> 
>>> - create_entity_def_init() in cmd ::= rule (about ALTER TABLE ADD
>>>  CONSTRAINT FOREIGN KEY), line 1500. Here it is followed by
>>>  create_constraint_def_init().
>>> 
>>> So as you can see create_entity_def_init() is not necessary to use
>>> out of parse_def.h. You can put it into
>>> create_table/index/constraint_def().
>> Ok, done. See updated version at the end of letter.
>>> Now about create_constraint_def_init(). In your patch it is always
>>> followed by create_fk_def_init().
>> I didn’t get this point. create_constraint_def_init() may come before
>> create_fk_def_init() as well as before create_index_def/create_ck_def.
> 
> The only place, where create_constraint_def_init can not be replaced with
> a next level term, is a rule "cconsname_parse ::= CONSTRAINT nm(X).". But
> you did not answer below why do you need it here.
> 
>>> But after you did the comment above
>>> about create_entity_def_init(), you will have create_constraint_def_init()
>>> in cconsname_parse ::= CONSTRAINT nm(X) rule. And I do not see any
>>> concrete reasons why can not you remove it from here and use only
>>> create_index_def_init(), create_ck_def_init() etc in ccons ::= rule.
>>> You said, that some of pointers should be nullifed, but what pointers
>>> and why? What if you do not nullify them right after CONSTRAINT word
>>> is met?
>> We had to nullify them since in previous versions members of some
>> structures might leave uninitialised. Now _init funs fill all members,
>> so there is no any problem.
> 
> I see, that you nullify them, but you did not answer my questions. Why
> can not you postpone that nullification until a next word is met -
> UNIQUE, PK, FK, CK etc? It would allow you to fold
> create_constraint_def_init and always use a term def.
> 
> What is more, you initialize named constraints twice - first you
> nullify them in cconsname_start with memset, and then call
> create_constraint_def_init.
> 
> I did my own investigation of this matter, and managed to remove
> cconsname_start rule, as well as double init. After that I have
> cconsname_parse followed by many term constraint rules, and I tried to
> figure out why can't we just inline cconsname into terms ccons and
> tcons and remove create_constraint_def_init from public. Especially
> taking into account that it is not many changes thanks to that:
> https://github.com/tarantool/tarantool/issues/3820.
> 
> It means, that we could inline 'CONSTRAINT nm(X)' in 8 places only:
> 
>    1) "ccons ::= PRIMARY KEY"
>    2) "ccons ::= UNIQUE"
>    3) "ccons ::= check_constraint_def"
>    4) "ccons ::= REFERENCES"
> 
>     + the same for tcons.
> 
> But then I realized, that cconsname helps us to keep the name
> optional. To solve that we need to somehow save nm(X) result until
> a terminal constraint is reached, or nullify it. I think, that we
> could add struct Token constraint_name to union of alter entities in
> struct Parse, and fetch it from there in terminal constraint
> definitions (PK, UNIQUE, CK, FK). It is possible, because in such
> a case this union's memory is not occupied by create_constraint_def
> until term. It will allow us to remove create_constraint_def_init
> from public usage. And to close the issue 3820 alongside.

This approach brings us to the very first implementation,
when name of constraint was saved as Token in struct Parse…

But I’ve done this (I’m still not sure whether you meant this approach or not):

diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index b66ac08e2..aa16cb874 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -185,10 +185,10 @@ create_table_args ::= AS select(S). {
   sqlEndTable(pParse,0,S);
   sql_select_delete(pParse->db, S);
 }
-columnlist ::= columnlist COMMA tconsdef.
+columnlist ::= columnlist COMMA tcons.
 columnlist ::= columnlist COMMA columnname carglist.
 columnlist ::= columnname carglist.
-columnlist ::= tconsdef.
+columnlist ::= tcons.
 columnname(A) ::= nm(A) typedef(Y). {sqlAddColumn(pParse,&A,&Y);}
 
 // An IDENTIFIER can be a generic identifier, or one of several
@@ -236,18 +236,11 @@ nm(A) ::= id(A). {
 // "carglist" is a list of additional constraints that come after the
 // column name and column type in a CREATE TABLE statement.
 //
-carglist ::= carglist cconsdef.
+carglist ::= carglist ccons.
 carglist ::= .
-cconsdef ::= cconsname ccons.
-cconsname ::= CONSTRAINT nm(X). {
-  create_constraint_def_init(&pParse->create_constraint_def, NULL, &X, false,
-                             false);
-}
-cconsname ::= . {
-  struct Token t = Token_nil;
-  create_constraint_def_init(&pParse->create_constraint_def, NULL, &t, false,
-                             false);
-}
+%type cconsname { struct Token }
+cconsname(N) ::= CONSTRAINT nm(X). { N = X; }
+cconsname(N) ::= . { N = Token_nil; }
 ccons ::= DEFAULT term(X).            {sqlAddDefaultValue(pParse,&X);}
 ccons ::= DEFAULT LP expr(X) RP.      {sqlAddDefaultValue(pParse,&X);}
 ccons ::= DEFAULT PLUS term(X).       {sqlAddDefaultValue(pParse,&X);}
@@ -269,27 +262,29 @@ ccons ::= NULL onconf(R).        {
         sql_column_add_nullable_action(pParse, R);
 }
 ccons ::= NOT NULL onconf(R).    {sql_column_add_nullable_action(pParse, R);}
-ccons ::= PRIMARY KEY sortorder(Z) autoinc(I). {
+ccons ::= cconsname(N) PRIMARY KEY sortorder(Z) autoinc(I). {
   pParse->create_table_def.has_autoinc = I;
-  create_index_def_init(&pParse->create_index_def, NULL,
-                        SQL_INDEX_TYPE_CONSTRAINT_PK, Z);
+  create_index_def_init(&pParse->create_index_def, NULL, &N, NULL,
+                        SQL_INDEX_TYPE_CONSTRAINT_PK, Z, false);
   sqlAddPrimaryKey(pParse);
 }
-ccons ::= UNIQUE. {
-  create_index_def_init(&pParse->create_index_def, NULL,
-                        SQL_INDEX_TYPE_CONSTRAINT_UNIQUE, SORT_ORDER_ASC);
+ccons ::= cconsname(N) UNIQUE. {
+  create_index_def_init(&pParse->create_index_def, NULL, &N, NULL,
+                        SQL_INDEX_TYPE_CONSTRAINT_UNIQUE, SORT_ORDER_ASC,
+                        false);
   sql_create_index(pParse);
 }
 
 ccons ::= check_constraint_def .
 
-check_constraint_def ::= CHECK LP expr(X) RP. {
-  create_ck_def_init(&pParse->create_ck_def, &X);
+check_constraint_def ::= cconsname(N) CHECK LP expr(X) RP. {
+  create_ck_def_init(&pParse->create_ck_def, &N, &X);
   sql_add_check_constraint(pParse);
 }
 
-ccons ::= REFERENCES nm(T) eidlist_opt(TA) matcharg(M) refargs(R). {
-  create_fk_def_init(&pParse->create_fk_def, NULL, &T, TA, M, R);
+ccons ::= cconsname(N) REFERENCES nm(T) eidlist_opt(TA) matcharg(M) refargs(R). {
+  create_fk_def_init(&pParse->create_fk_def, NULL, &N, NULL, &T, TA, M, R,
+                     false);
   sql_create_foreign_key(pParse);
 }
 ccons ::= defer_subclause(D).    {fk_constraint_change_defer_mode(pParse, D);}
@@ -337,23 +332,22 @@ init_deferred_pred_opt(A) ::= .                       {A = 0;}
 init_deferred_pred_opt(A) ::= INITIALLY DEFERRED.     {A = 1;}
 init_deferred_pred_opt(A) ::= INITIALLY IMMEDIATE.    {A = 0;}
 
-tconsdef ::= cconsname tcons.
-tcons ::= PRIMARY KEY LP sortlist(X) autoinc(I) RP. {
+tcons ::= cconsname(N) PRIMARY KEY LP sortlist(X) autoinc(I) RP. {
   pParse->create_table_def.has_autoinc = I;
-  create_index_def_init(&pParse->create_index_def, X,
-                        SQL_INDEX_TYPE_CONSTRAINT_PK, SORT_ORDER_ASC);
+  create_index_def_init(&pParse->create_index_def, NULL, &N, X,
+                        SQL_INDEX_TYPE_CONSTRAINT_PK, SORT_ORDER_ASC, false);
   sqlAddPrimaryKey(pParse);
 }
-tcons ::= UNIQUE LP sortlist(X) RP. {
-  create_index_def_init(&pParse->create_index_def, X,
-                        SQL_INDEX_TYPE_CONSTRAINT_UNIQUE, SORT_ORDER_ASC);
+tcons ::= cconsname(N) UNIQUE LP sortlist(X) RP. {
+  create_index_def_init(&pParse->create_index_def, NULL, &N, X,
+                        SQL_INDEX_TYPE_CONSTRAINT_UNIQUE, SORT_ORDER_ASC,
+                        false);
   sql_create_index(pParse);
 }
 tcons ::= check_constraint_def .
-tcons ::= FOREIGN KEY LP eidlist(FA) RP
+tcons ::= cconsname(N) FOREIGN KEY LP eidlist(FA) RP
           REFERENCES nm(T) eidlist_opt(TA) matcharg(M) refargs(R) defer_subclause_opt(D). {
-  pParse->create_fk_def.base.is_deferred = D;
-  create_fk_def_init(&pParse->create_fk_def, FA, &T, TA, M, R);
+  create_fk_def_init(&pParse->create_fk_def, NULL, &N, FA, &T, TA, M, R, D);
   sql_create_foreign_key(pParse);
 }
 %type defer_subclause_opt {int}
@@ -1257,10 +1251,9 @@ paren_exprlist(A) ::= LP exprlist(X) RP.  {A = X;}
 //
 cmd ::= createkw uniqueflag(U) INDEX ifnotexists(NE) nm(X)
         ON nm(Y) LP sortlist(Z) RP. {
-  create_constraint_def_init(&pParse->create_index_def.base,
-                             sqlSrcListAppend(pParse->db, 0, &Y), &X, NE,
-                             false);
-  create_index_def_init(&pParse->create_index_def, Z, U, SORT_ORDER_ASC);
+  create_index_def_init(&pParse->create_index_def,
+                        sqlSrcListAppend(pParse->db, 0, &Y), &X, Z, U,
+                        SORT_ORDER_ASC, NE);
   sql_create_index(pParse);
 }
 
@@ -1494,8 +1487,7 @@ cmd ::= ALTER TABLE fullname(X) RENAME TO nm(Z). {
 cmd ::= ALTER TABLE fullname(X) ADD CONSTRAINT nm(Z) FOREIGN KEY
         LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) matcharg(M)
         refargs(R) defer_subclause_opt(D). {
-  create_constraint_def_init(&pParse->create_fk_def.base, X, &Z, D, false);
-  create_fk_def_init(&pParse->create_fk_def, FA, &T, TA, M, R);
+  create_fk_def_init(&pParse->create_fk_def, X, &Z, FA, &T, TA, M, R, D);
   sql_create_foreign_key(pParse);
 }
 
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index becbf99f4..374d655fb 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -320,9 +320,10 @@ create_entity_def_init(struct create_entity_def *create_def,
 static inline void
 create_constraint_def_init(struct create_constraint_def *constr_def,
                           struct SrcList *parent_name, struct Token *name,
-                          bool if_not_exists, bool is_deferred)
+                          bool if_not_exists, bool is_deferred,
+                          enum entity_type entity_type)
 {
-       create_entity_def_init(&constr_def->base, ENTITY_TYPE_CONSTRAINT,
+       create_entity_def_init(&constr_def->base, entity_type,
                               parent_name, name, if_not_exists);
        constr_def->is_deferred = is_deferred;
 }
@@ -405,38 +406,35 @@ create_trigger_def_init(struct create_trigger_def *trigger_def,
 }
 
 static inline void
-create_ck_def_init(struct create_ck_def *ck_def, struct ExprSpan *expr)
+create_ck_def_init(struct create_ck_def *ck_def, struct Token *name,
+                  struct ExprSpan *expr)
 {
-       struct alter_entity_def *alter_def = (struct alter_entity_def *) ck_def;
-       assert(alter_def->alter_action == ALTER_ACTION_CREATE);
-       assert(alter_def->entity_type == ENTITY_TYPE_CONSTRAINT);
-       alter_def->entity_type = ENTITY_TYPE_CK;
+       create_constraint_def_init(&ck_def->base, NULL, name, false,
+                                  false, ENTITY_TYPE_CK);
        ck_def->expr = expr;
 }
 
 static inline void
-create_index_def_init(struct create_index_def *index_def, struct ExprList *cols,
-                     enum sql_index_type idx_type, enum sort_order sort_order)
+create_index_def_init(struct create_index_def *index_def,
+                     struct SrcList *table_name,  struct Token *name,
+                     struct ExprList *cols, enum sql_index_type idx_type,
+                     enum sort_order sort_order, bool if_not_exists)
 {
-       struct alter_entity_def *alter_def =
-               (struct alter_entity_def *) index_def;
-       assert(alter_def->alter_action == ALTER_ACTION_CREATE);
-       assert(alter_def->entity_type == ENTITY_TYPE_CONSTRAINT);
-       alter_def->entity_type = ENTITY_TYPE_INDEX;
+       create_constraint_def_init(&index_def->base, table_name, name,
+                                  if_not_exists, false, ENTITY_TYPE_INDEX);
        index_def->cols = cols;
        index_def->idx_type = idx_type;
        index_def->sort_order = sort_order;
 }
 
 static inline void
-create_fk_def_init(struct create_fk_def *fk_def, struct ExprList *child_cols,
+create_fk_def_init(struct create_fk_def *fk_def, struct SrcList *table_name,
+                  struct Token *name, struct ExprList *child_cols,
                   struct Token *parent_name, struct ExprList *parent_cols,
-                  int match, int actions)
+                  int match, int actions, bool is_deferred)
 {
-       struct alter_entity_def *alter_def = (struct alter_entity_def *) fk_def;
-       assert(alter_def->alter_action == ALTER_ACTION_CREATE);
-       assert(alter_def->entity_type == ENTITY_TYPE_CONSTRAINT);
-       alter_def->entity_type = ENTITY_TYPE_FK;
+       create_constraint_def_init(&fk_def->base, table_name, name,
+                                  false, is_deferred, ENTITY_TYPE_FK);
        fk_def->child_cols = child_cols;
        fk_def->parent_name = parent_name;
        fk_def->parent_cols = parent_cols;

diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 97bf30ec6..2048e12ea 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -2684,7 +2684,6 @@ struct Parse {
                struct create_ck_def create_ck_def;
                struct create_fk_def create_fk_def;
                struct create_index_def create_index_def;
-               struct create_constraint_def create_constraint_def;
                struct create_trigger_def create_trigger_def;
                struct create_view_def create_view_def;
                struct rename_entity_def rename_entity_def;

diff --git a/test/sql/misc.result b/test/sql/misc.result
index ef104c1c5..5afae3360 100644
--- a/test/sql/misc.result
+++ b/test/sql/misc.result
@@ -40,3 +40,17 @@ box.sql.execute('\n\n\n\t\t\t   ')
 ---
 - error: 'syntax error: empty request'
 ...
+-- gh-3820: only table constraints can have a name.
+--
+box.sql.execute('CREATE TABLE test (id INTEGER PRIMARY KEY, b INTEGER CONSTRAINT c1 NULL)')
+---
+- error: keyword "NULL" is reserved
+...
+box.sql.execute('CREATE TABLE test (id INTEGER PRIMARY KEY, b INTEGER CONSTRAINT c1 DEFAULT 300)')
+---
+- error: keyword "DEFAULT" is reserved
+...
+box.sql.execute('CREATE TABLE test (id INTEGER PRIMARY KEY, b TEXT CONSTRAINT c1 COLLATE "binary")')
+---
+- error: keyword "COLLATE" is reserved
+...
diff --git a/test/sql/misc.test.lua b/test/sql/misc.test.lua
index 994e64f3a..e79e4841b 100644
--- a/test/sql/misc.test.lua
+++ b/test/sql/misc.test.lua
@@ -11,3 +11,9 @@ box.sql.execute(';')
 box.sql.execute('')
 box.sql.execute('     ;')
 box.sql.execute('\n\n\n\t\t\t   ')
+
+-- gh-3820: only table constraints can have a name.
+--
+box.sql.execute('CREATE TABLE test (id INTEGER PRIMARY KEY, b INTEGER CONSTRAINT c1 NULL)')
+box.sql.execute('CREATE TABLE test (id INTEGER PRIMARY KEY, b INTEGER CONSTRAINT c1 DEFAULT 300)')
+box.sql.execute('CREATE TABLE test (id INTEGER PRIMARY KEY, b TEXT CONSTRAINT c1 COLLATE "binary")’)

So, what I did:

1. Incapsulated call of create_constraint_def into parse_def.h
2. Fixed 3820 issue: moved cconsname to rules which are related to constraints
3. Avoided adding struct Token constraint_name to struct Parse and removed
    struct create_constraint_def from union.

> This is what I wanted to hear from you.
> 
> Besides, I have some lowlevel comments on the code.
> 
> 1) Usually we do not pass structures by value, even such small
> as struct Token.

Ok, I see you’ve already fixed this point, so skipped.

> 2) You've moved struct Token into parse_def.h, but kept its
> routines in sqlite3Int.h. Please, move them too.

It’s not that easy. For instance, sqlTokenInit() uses sqlStrlen30(),
which declaration is placed in sqlInt.h. Ok, we can use common
strlen(). Next, sqlNameFromToken() uses sqlDbStrNDup() and
sqlNormalizeName(), which in turn again are declared in sqlInt.h
We can’t include sqlInt.h, otherwise we will get cyclic dependencies.
Also I wanted to move sqlJoinType() (it is used only in parse.y and
involves tokens routine), but it uses struct Parse to set error.
I guess after replacing saving error message in struct Parse with
diag_set(), we will be able to move it to parse.y

So, the only things I’ve really managed to move are
sqlTokenInit() and array sqlIntTokens[]:

diff --git a/src/box/sql/CMakeLists.txt b/src/box/sql/CMakeLists.txt
index 87fb83358..b9dbe141a 100644
--- a/src/box/sql/CMakeLists.txt
+++ b/src/box/sql/CMakeLists.txt
@@ -43,6 +43,7 @@ add_library(sql STATIC
     malloc.c
     os.c
     os_unix.c
+    parse_def.c
     pragma.c
     prepare.c
     printf.c
diff --git a/src/box/sql/global.c b/src/box/sql/global.c
index 95ad71c38..159b35e64 100644
--- a/src/box/sql/global.c
+++ b/src/box/sql/global.c
@@ -222,14 +222,6 @@ SQL_WSD struct sqlConfig sqlConfig = {
  */
 FuncDefHash sqlBuiltinFunctions;
 
-/*
- * Constant tokens for values 0 and 1.
- */
-const Token sqlIntTokens[] = {
-       {"0", 1, false},
-       {"1", 1, false}
-};
-
 /*
  * The value of the "pending" byte must be 0x40000000 (1 byte past the
  * 1-gibabyte boundary) in a compatible database.  sql never uses
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index b68ca64c2..becbf99f4 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -87,6 +87,13 @@ struct Token {
        bool isReserved;
 };
 
+/** Constant tokens for values 0 and 1. */
+extern const struct Token sqlIntTokens[];
+
+/** Generate a Token object from a string. */
+void
+sqlTokenInit(struct Token *p, char *z);
+
 #define Token_nil ((struct Token) {NULL, 0, false})
 
 /**
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index e3740b46b..97bf30ec6 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -3151,7 +3151,6 @@ void sqlSetString(char **, sql *, const char *);
 void sqlErrorMsg(Parse *, const char *, ...);
 void sqlDequote(char *);
 void sqlNormalizeName(char *z);
-void sqlTokenInit(Token *, char *);
 int sqlKeywordCode(const unsigned char *, int);
 int sqlRunParser(Parse *, const char *, char **);
 
diff --git a/src/box/sql/util.c b/src/box/sql/util.c
index c89e2e8ab..f6ec763bc 100644
--- a/src/box/sql/util.c
+++ b/src/box/sql/util.c
@@ -311,16 +311,6 @@ sqlNormalizeName(char *z)
        }
 }
 
-/*
- * Generate a Token object from a string
- */
-void
-sqlTokenInit(Token * p, char *z)
-{
-       p->z = z;
-       p->n = sqlStrlen30(z);
-}
-
 /* Convenient short-hand */
 #define UpperToLower sqlUpperToLower 

diff --git a/src/box/sql/parse_def.c b/src/box/sql/parse_def.c
new file mode 100644
index 000000000..7310726ff
--- /dev/null
+++ b/src/box/sql/parse_def.c
@@ -0,0 +1,45 @@
+/*
+ * 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 <string.h>
+
+#include "parse_def.h"
+
+const struct Token sqlIntTokens[] = {
+       {"0", 1, false},
+       {"1", 1, false}
+};
+
+void
+sqlTokenInit(struct Token *p, char *z)
+{
+       p->z = z;
+       p->n = strlen(z);
+}

I’m not sure whether these tiny things deserve their own
implementation file. On the other hand, it is to be extended
in future (when we decide to split whole sqlInt.h into ordinary
headers).

> And please,
> rebase - 'sqlite3' has already been replaced with 'sql’.

Ok, rebase has been done.

> 3) Since now and after point 2 we have internal and long functions
> in parse_def, I think it is time to introduce parse_def.c and hide
> them here.
> 
> ====================================================================
> 
> My diff below and on the branch:

Thx, applied as quite straightforward.

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-03-14 18:13                 ` n.pettik
@ 2019-03-25 11:25                   ` Vladislav Shpilevoy
  2019-03-26 18:01                     ` n.pettik
  0 siblings, 1 reply; 34+ messages in thread
From: Vladislav Shpilevoy @ 2019-03-25 11:25 UTC (permalink / raw)
  To: n.pettik, tarantool-patches

Hi! Thanks for the fixes!

On 14/03/2019 21:13, n.pettik wrote:
> 
> 
>> On 12 Mar 2019, at 15:50, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
>>
>> Hi! Thanks for the fixes! See my comments below,
>> fixes at the end of the email, and on the branch
>> (it is np/gh-3914-fix-create-index-v2).
>>
>>>> Below I listed the calls you do to create each 'terminal' structure.
>>>>
>>>> 	TABLE RENAME:
>>>> 	alter_entity_def_init
>>>> 	rename_entity_def_init
>>>>
>>>> 	TABLE CREATE
>>>> 	alter_entity_def_init
>>>> 	create_entity_def_init
>>>> 	create_table_def_init
>>>>
>>>> 	TABLE DROP:
>>>> 	alter_entity_def_init
>>>> 	drop_entity_def_init
>>>>
>>>> 	TABLE ADD FK:
>>>> 	alter_entity_def_init
>>>> 	create_entity_def_init
>>>> 	create_constraint_def_init
>>>> 	create_fk_def_init
>>>>
>>>> 	DROP FK:
>>>> 	alter_entity_def_init
>>>> 	drop_entity_def_init
>>>>
>>>> 	DROP INDEX:
>>>> 	alter_entity_def_init
>>>> 	drop_entity_def_init
>>>>
>>>> 	ADD INDEX:
>>>> 	alter_entity_def_init
>>>> 	create_entity_def_init
>>>> 	create_index_def_init
>>>>
>>>> 	CHECK:
>>>>         alter_entity_def_init
>>>>         create_entity_def_init
>>>> 	create_ck_def_init
>>>>
>>>> 	CREATE TRIGGER:
>>>> 	alter_entity_def_init
>>>> 	create_entity_def_init
>>>> 	create_trigger_def_init
>>>>
>>>> 	DROP TRIGGER:
>>>> 	alter_entity_def_init
>>>> 	drop_entity_def_init
>>>>
>>>> Here are some problems with what you said earlier and with
>>>> the whole structure.
>>>>
>>>> Firstly, some of sequences are not finished with a an explicit
>>>> terminal. For example, *all* DROPs are finished with an abstract
>>>> drop_entity_def_init() taking an entity type explicitly. Despite
>>>> the words you said earlier that you initialize everything in steps.
>>>> So in the case of DROP the rule is violated already. I think, that
>>>> you should define static inline helpers in parse_def.h with names
>>>> drop_table_def_init(), drop_index_def_init(), drop_check_def_init()
>>>> etc.
>>> Drop procedure (i.e. arguments filling) is the same for all entities
>>> (this situation is likely to remain unchanged). Does it make any
>>> sense to duplicate code without any necessity? What these
>>> helpers are supposed to do?
>>
>> Nothing functional. Just syntax sugar and API consistency. Otherwise
>> your statement, that we have a tree of structures with terminal nodes,
>> is false. For other nodes you have _table/index/fk etc suffix, but
>> drop is just 'entity'. For me it looks odd. The same for rename_entity.
>>
>> Strictly speaking, your hierarchy of structures is even not a tree -
>> in your code create_table_def and create_view_def store alter_entity_def,
>> but do not initialize nor use it.
>>
>> Additionally, for not named constraints you do not call
>> alter_entity_def_init nor even create_constraint_def_init - you just
>> memset all the structure. So your tree in fact is really corrupted and
>> ill, sorry.
>>
>> Talking of duplication - each wrapper around drop_entity and
>> rename_entity would take 11 simple lines:
>>
>>     struct drop_<term>_def {
>>             struct drop_entity_def base;
>>     }
>>
>>     static inline void
>>     drop_<term>_def_init(struct drop_<term>_def *def,
>>                          struct SrcList *parent_name, struct Token *name,
>>                          bool if_exist)
>>     {
>>             drop_entity_def_init(&def->base, parent_name, name, if_exist,
>>                                  ENTITY_TYPE_<term>);
>>     }
>>
>> For compiler it is nothing - functions are inlined, for binary file
>> as well. For code and naming consistency it is a big deal. IMO. But
>> probably I should stop teaching here - you can leave it as is in terms
>> of list entities or listen to my objectives.
> 
> Just want to hear pros of this approach (cons are obvious).

For me cons are not obvious. Pros - more proper code structure.

>> Anyway you are likely to be a
>> single maintainer of all of this for a long time.
> 
> Sounds like a threat. I’d rather not :)

It is not a threat. It is a fact. At this moment you are a single
not-trainee developer on SQL. And I do not see any forthcoming
changes in that.

> 
>>>> Secondly, some words about create_entity_def_init(). It is the
>>>> most arguable function for a public usage (out of parse_def.h)
>>>> because hardly I can imagine a situation when you parsed a word
>>>> CREATE, but can not parse a next one: TABLE, TRIGGER, CONSTRAINT etc.
>>>> And we see it in the lists above. Below I listed all of this function
>>>> usage cases and how you can replace that function here.
>>>>
>>>> - create_entity_def_init() in create_table ::= rule, line 172. Here
>>>>   it is followed by create_table_def_init() on the next line.
>>>>
>>>> - create_entity_def_init() in cconsname_parse ::= rule line 250.
>>>>   This rule is a prefix for a constraint definition, so this function
>>>>   can be substituted with create_constraint_def_init().
>>>>
>>>> - create_entity_def_init() in cmd ::= rule (about CREATE VIEW),
>>>>   line 400. Here we see, that 1) you missed ENTITY_TYPE_VIEW,
>>>>   2) create_view_def_init() can be called here.
>>>>
>>>> - create_entity_def_init() in cmd ::= rule (about CREATE INDEX),
>>>>   line 1246. Here it is followed by create_index_def_init() on the
>>>>   next line.
>>>>
>>>> - create_entity_def_init() in trigger_decl ::= rule, line 1367.
>>>>   Here it is followed by create_trigger_def_init().
>>>>
>>>> - create_entity_def_init() in cmd ::= rule (about ALTER TABLE ADD
>>>>   CONSTRAINT FOREIGN KEY), line 1500. Here it is followed by
>>>>   create_constraint_def_init().
>>>>
>>>> So as you can see create_entity_def_init() is not necessary to use
>>>> out of parse_def.h. You can put it into
>>>> create_table/index/constraint_def().
>>> Ok, done. See updated version at the end of letter.
>>>> Now about create_constraint_def_init(). In your patch it is always
>>>> followed by create_fk_def_init().
>>> I didn’t get this point. create_constraint_def_init() may come before
>>> create_fk_def_init() as well as before create_index_def/create_ck_def.
>>
>> The only place, where create_constraint_def_init can not be replaced with
>> a next level term, is a rule "cconsname_parse ::= CONSTRAINT nm(X).". But
>> you did not answer below why do you need it here.
>>
>>>> But after you did the comment above
>>>> about create_entity_def_init(), you will have create_constraint_def_init()
>>>> in cconsname_parse ::= CONSTRAINT nm(X) rule. And I do not see any
>>>> concrete reasons why can not you remove it from here and use only
>>>> create_index_def_init(), create_ck_def_init() etc in ccons ::= rule.
>>>> You said, that some of pointers should be nullifed, but what pointers
>>>> and why? What if you do not nullify them right after CONSTRAINT word
>>>> is met?
>>> We had to nullify them since in previous versions members of some
>>> structures might leave uninitialised. Now _init funs fill all members,
>>> so there is no any problem.
>>
>> I see, that you nullify them, but you did not answer my questions. Why
>> can not you postpone that nullification until a next word is met -
>> UNIQUE, PK, FK, CK etc? It would allow you to fold
>> create_constraint_def_init and always use a term def.
>>
>> What is more, you initialize named constraints twice - first you
>> nullify them in cconsname_start with memset, and then call
>> create_constraint_def_init.
>>
>> I did my own investigation of this matter, and managed to remove
>> cconsname_start rule, as well as double init. After that I have
>> cconsname_parse followed by many term constraint rules, and I tried to
>> figure out why can't we just inline cconsname into terms ccons and
>> tcons and remove create_constraint_def_init from public. Especially
>> taking into account that it is not many changes thanks to that:
>> https://github.com/tarantool/tarantool/issues/3820.
>>
>> It means, that we could inline 'CONSTRAINT nm(X)' in 8 places only:
>>
>>     1) "ccons ::= PRIMARY KEY"
>>     2) "ccons ::= UNIQUE"
>>     3) "ccons ::= check_constraint_def"
>>     4) "ccons ::= REFERENCES"
>>
>>      + the same for tcons.
>>
>> But then I realized, that cconsname helps us to keep the name
>> optional. To solve that we need to somehow save nm(X) result until
>> a terminal constraint is reached, or nullify it. I think, that we
>> could add struct Token constraint_name to union of alter entities in
>> struct Parse, and fetch it from there in terminal constraint
>> definitions (PK, UNIQUE, CK, FK). It is possible, because in such
>> a case this union's memory is not occupied by create_constraint_def
>> until term. It will allow us to remove create_constraint_def_init
>> from public usage. And to close the issue 3820 alongside.
> 
> This approach brings us to the very first implementation,
> when name of constraint was saved as Token in struct Parse…

The very first approach was not only about this attribute. Also
it was about struct space *new_space, uint32_t fk_constraint_count,
struct rlist new_fk_constraint, bool is_new_table_autoinc.

>> This is what I wanted to hear from you.
>>
>> Besides, I have some lowlevel comments on the code.
>>
>> 1) Usually we do not pass structures by value, even such small
>> as struct Token.
> 
> Ok, I see you’ve already fixed this point, so skipped.
> 
>> 2) You've moved struct Token into parse_def.h, but kept its
>> routines in sqlite3Int.h. Please, move them too.
> 
> It’s not that easy. For instance, sqlTokenInit() uses sqlStrlen30(),
> which declaration is placed in sqlInt.h. Ok, we can use common
> strlen(). Next, sqlNameFromToken() uses sqlDbStrNDup() and
> sqlNormalizeName(), which in turn again are declared in sqlInt.h
> We can’t include sqlInt.h, otherwise we will get cyclic dependencies.
> Also I wanted to move sqlJoinType() (it is used only in parse.y and
> involves tokens routine), but it uses struct Parse to set error.
> I guess after replacing saving error message in struct Parse with
> diag_set(), we will be able to move it to parse.y
> 
> So, the only things I’ve really managed to move are
> sqlTokenInit() and array sqlIntTokens[]:

Ok, understand. Thanks.

See 4 comments below.

> 
> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
> index 996f55d37..aa16cb874 100644
> --- a/src/box/sql/parse.y
> +++ b/src/box/sql/parse.y
> @@ -178,15 +179,16 @@ ifnotexists(A) ::= IF NOT EXISTS. {A = 1;}
>  
>  create_table_args ::= LP columnlist RP(E). {
>    sqlEndTable(pParse,&E,0);
> +  create_table_def_destroy(&pParse->create_table_def);
>  }
>  create_table_args ::= AS select(S). {
>    sqlEndTable(pParse,0,S);

1. Why do not you destroy table def here?

>    sql_select_delete(pParse->db, S);
>  }
> -columnlist ::= columnlist COMMA tconsdef.
> +columnlist ::= columnlist COMMA tcons.
>  columnlist ::= columnlist COMMA columnname carglist.
>  columnlist ::= columnname carglist.
> -columnlist ::= tconsdef.
> +columnlist ::= tcons.
>  columnname(A) ::= nm(A) typedef(Y). {sqlAddColumn(pParse,&A,&Y);}
>  
>  // An IDENTIFIER can be a generic identifier, or one of several
2. As I understand, now table def leaks if parser stops after sqlStartTable
but before sqlEndTable.

3. sql_create_index comment still refers to parse->new_space.

4. Please, write a comment above struct create_table_def create_table_def in
struct Parse why it is not in the union.

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-03-25 11:25                   ` Vladislav Shpilevoy
@ 2019-03-26 18:01                     ` n.pettik
  2019-03-26 18:06                       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 34+ messages in thread
From: n.pettik @ 2019-03-26 18:01 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


> See 4 comments below.
> 
>> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
>> index 996f55d37..aa16cb874 100644
>> --- a/src/box/sql/parse.y
>> +++ b/src/box/sql/parse.y
>> @@ -178,15 +179,16 @@ ifnotexists(A) ::= IF NOT EXISTS. {A = 1;}
>>  create_table_args ::= LP columnlist RP(E). {
>>   sqlEndTable(pParse,&E,0);
>> +  create_table_def_destroy(&pParse->create_table_def);
>> }
>> create_table_args ::= AS select(S). {
>>   sqlEndTable(pParse,0,S);
> 
> 1. Why do not you destroy table def here?

Because CREATE TABLE AS SELECT is completely broken.
I’ve removed remains of this feature from parser and sqlEndTable().
To be re-implemented in https://github.com/tarantool/tarantool/issues/3223

On the other hand, even if it wasn’t broken, there would be no need
to call table_def_destroy(): AS SELECT implies that there is no FK
constraints related to table being created (destroy cleans only FKs).

    sql: remove remains of CREATE TABLE AS SELECT stmt
    
    This statement is completely broken and to be re-implemented in
    scope of #3223 issue. Current patch removes remains of this feature.

diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index f82bcd7bc..e5af8c3bd 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -1132,23 +1132,12 @@ resolve_link(struct Parse *parse_context, const struct space_def *def,
  *
  * During this routine byte code for creation of new Tarantool
  * space and all necessary Tarantool indexes is emitted.
- *
- * If the pSelect argument is not NULL, it means that this routine
- * was called to create a space generated from a
- * "CREATE TABLE ... AS SELECT ..." statement.  The column names of
- * the new space will match the result set of the SELECT.
  */
 void
-sqlEndTable(Parse * pParse,    /* Parse context */
-               Token * pEnd,   /* The ')' before options in the CREATE TABLE */
-               Select * pSelect        /* Select from a "CREATE ... AS SELECT" */
-    )
+sqlEndTable(struct Parse *pParse)
 {
        sql *db = pParse->db;   /* The database connection */
 
-       if (pEnd == NULL && pSelect == NULL) {
-               return;
-       }
        assert(!db->mallocFailed);
        struct space *new_space = pParse->new_space;
        if (new_space == NULL)
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 996f55d37..c3a0e6245 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -176,13 +176,19 @@ createkw(A) ::= CREATE(A).  {disableLookaside(pParse);}
 ifnotexists(A) ::= .              {A = 0;}
 ifnotexists(A) ::= IF NOT EXISTS. {A = 1;}
 
-create_table_args ::= LP columnlist RP(E). {
-  sqlEndTable(pParse,&E,0);
-}
-create_table_args ::= AS select(S). {
-  sqlEndTable(pParse,0,S);
-  sql_select_delete(pParse->db, S);
+create_table_args ::= LP columnlist RP. {
+  sqlEndTable(pParse);
 }
+
+/*
+ * CREATE TABLE AS SELECT is broken. To be re-implemented
+ * in gh-3223.
+ *
+ * create_table_args ::= AS select(S). {
+ *   sqlEndTable(pParse);
+ *   sql_select_delete(pParse->db, S);
+ * }
+ */
 columnlist ::= columnlist COMMA tconsdef.
 columnlist ::= columnlist COMMA columnname carglist.
 columnlist ::= columnname carglist.
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index cd66e5383..fb2062b37 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -3333,7 +3333,8 @@ void sqlAddCollateType(Parse *, Token *);
 struct coll *
 sql_column_collation(struct space_def *def, uint32_t column, uint32_t *coll_id);
 
-void sqlEndTable(Parse *, Token *, Select *);
+void
+sqlEndTable(struct Parse *parse);
 
 /**
  * Create cursor which will be positioned to the space/index.

> 
>>   sql_select_delete(pParse->db, S);
>> }
>> -columnlist ::= columnlist COMMA tconsdef.
>> +columnlist ::= columnlist COMMA tcons.
>> columnlist ::= columnlist COMMA columnname carglist.
>> columnlist ::= columnname carglist.
>> -columnlist ::= tconsdef.
>> +columnlist ::= tcons.
>> columnname(A) ::= nm(A) typedef(Y). {sqlAddColumn(pParse,&A,&Y);}
>>  // An IDENTIFIER can be a generic identifier, or one of several
> 2. As I understand, now table def leaks if parser stops after sqlStartTable
> but before sqlEndTable.

Unfortunately, you are right. I thought that parsing process can’t be stopped
between these stages. Diff:

diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index fa71b2a42..818e9f461 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -179,7 +179,6 @@ ifnotexists(A) ::= IF NOT EXISTS. {A = 1;}
 
 create_table_args ::= LP columnlist RP. {
   sqlEndTable(pParse);
-  create_table_def_destroy(&pParse->create_table_def);
 }
 
 /*
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index 98234aa8c..a1af2bacd 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -454,6 +454,8 @@ create_view_def_init(struct create_view_def *view_def, struct Token *name,
 static inline void
 create_table_def_destroy(struct create_table_def *table_def)
 {
+       if (table_def->new_space == NULL)
+               return;
        struct fk_constraint_parse *fk;
        rlist_foreach_entry(fk, &table_def->new_fkey, link)
                sql_expr_list_delete(sql_get(), fk->selfref_cols);
diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index e449c1fcb..034fd1492 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -308,6 +308,7 @@ sql_parser_destroy(Parse *parser)
        sql *db = parser->db;
        sqlDbFree(db, parser->aLabel);
        sql_expr_list_delete(db, parser->pConstExpr);
+       create_table_def_destroy(&parser->create_table_def);
        if (db != NULL) {
                assert(db->lookaside.bDisable >=
                       parser->disableLookaside);


> 
> 3. sql_create_index comment still refers to parse->new_space.

diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 9a1fe5be0..8a05fa9f3 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -3369,8 +3369,8 @@ void sqlIdListDelete(sql *, IdList *);
  * indexed.  Both will be NULL for a primary key or an index that
  * is created to satisfy a UNIQUE constraint.  If tbl_name and
  * name are NULL, use parse->new_space as the table to be indexed.
- * parse->new_space is a space that is currently being
- * constructed by a CREATE TABLE statement.
+ * parse->create_tale_def->new_space is a space that is currently
+ * being constructed by a CREATE TABLE statement.
  *
  * @param parse All information about this parse.
  */

> 4. Please, write a comment above struct create_table_def create_table_def in
> struct Parse why it is not in the union.

diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 9a1fe5be0..c4becd5f6 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -2693,6 +2693,12 @@ struct Parse {
                struct drop_trigger_def drop_trigger_def;
                struct drop_view_def drop_view_def;
        };
+       /**
+        * Table def is not part of union since information
+        * being held must survive till the end of parsing of
+        * whole CREATE TABLE statement (to pass it to
+        * sqlEndTable() function).
+        */
        struct create_table_def create_table_def;
        /**
         * List of all records that were inserted in system spaces

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-03-26 18:01                     ` n.pettik
@ 2019-03-26 18:06                       ` Vladislav Shpilevoy
  2019-03-27 13:00                         ` n.pettik
  0 siblings, 1 reply; 34+ messages in thread
From: Vladislav Shpilevoy @ 2019-03-26 18:06 UTC (permalink / raw)
  To: n.pettik, tarantool-patches

Thanks for the fixes! This commit LGTM.
Lets proceed to the next patches, and start
with a rebase, which is going to be hard.

On 26/03/2019 21:01, n.pettik wrote:
> 
>> See 4 comments below.
>>
>>> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
>>> index 996f55d37..aa16cb874 100644
>>> --- a/src/box/sql/parse.y
>>> +++ b/src/box/sql/parse.y
>>> @@ -178,15 +179,16 @@ ifnotexists(A) ::= IF NOT EXISTS. {A = 1;}
>>>  create_table_args ::= LP columnlist RP(E). {
>>>   sqlEndTable(pParse,&E,0);
>>> +  create_table_def_destroy(&pParse->create_table_def);
>>> }
>>> create_table_args ::= AS select(S). {
>>>   sqlEndTable(pParse,0,S);
>>
>> 1. Why do not you destroy table def here?
> 
> Because CREATE TABLE AS SELECT is completely broken.
> I’ve removed remains of this feature from parser and sqlEndTable().
> To be re-implemented in https://github.com/tarantool/tarantool/issues/3223
> 
> On the other hand, even if it wasn’t broken, there would be no need
> to call table_def_destroy(): AS SELECT implies that there is no FK
> constraints related to table being created (destroy cleans only FKs).
> 
>     sql: remove remains of CREATE TABLE AS SELECT stmt
>     
>     This statement is completely broken and to be re-implemented in
>     scope of #3223 issue. Current patch removes remains of this feature.
> 
> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index f82bcd7bc..e5af8c3bd 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -1132,23 +1132,12 @@ resolve_link(struct Parse *parse_context, const struct space_def *def,
>   *
>   * During this routine byte code for creation of new Tarantool
>   * space and all necessary Tarantool indexes is emitted.
> - *
> - * If the pSelect argument is not NULL, it means that this routine
> - * was called to create a space generated from a
> - * "CREATE TABLE ... AS SELECT ..." statement.  The column names of
> - * the new space will match the result set of the SELECT.
>   */
>  void
> -sqlEndTable(Parse * pParse,    /* Parse context */
> -               Token * pEnd,   /* The ')' before options in the CREATE TABLE */
> -               Select * pSelect        /* Select from a "CREATE ... AS SELECT" */
> -    )
> +sqlEndTable(struct Parse *pParse)
>  {
>         sql *db = pParse->db;   /* The database connection */
>  
> -       if (pEnd == NULL && pSelect == NULL) {
> -               return;
> -       }
>         assert(!db->mallocFailed);
>         struct space *new_space = pParse->new_space;
>         if (new_space == NULL)
> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
> index 996f55d37..c3a0e6245 100644
> --- a/src/box/sql/parse.y
> +++ b/src/box/sql/parse.y
> @@ -176,13 +176,19 @@ createkw(A) ::= CREATE(A).  {disableLookaside(pParse);}
>  ifnotexists(A) ::= .              {A = 0;}
>  ifnotexists(A) ::= IF NOT EXISTS. {A = 1;}
>  
> -create_table_args ::= LP columnlist RP(E). {
> -  sqlEndTable(pParse,&E,0);
> -}
> -create_table_args ::= AS select(S). {
> -  sqlEndTable(pParse,0,S);
> -  sql_select_delete(pParse->db, S);
> +create_table_args ::= LP columnlist RP. {
> +  sqlEndTable(pParse);
>  }
> +
> +/*
> + * CREATE TABLE AS SELECT is broken. To be re-implemented
> + * in gh-3223.
> + *
> + * create_table_args ::= AS select(S). {
> + *   sqlEndTable(pParse);
> + *   sql_select_delete(pParse->db, S);
> + * }
> + */
>  columnlist ::= columnlist COMMA tconsdef.
>  columnlist ::= columnlist COMMA columnname carglist.
>  columnlist ::= columnname carglist.
> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index cd66e5383..fb2062b37 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -3333,7 +3333,8 @@ void sqlAddCollateType(Parse *, Token *);
>  struct coll *
>  sql_column_collation(struct space_def *def, uint32_t column, uint32_t *coll_id);
>  
> -void sqlEndTable(Parse *, Token *, Select *);
> +void
> +sqlEndTable(struct Parse *parse);
>  
>  /**
>   * Create cursor which will be positioned to the space/index.
> 
>>
>>>   sql_select_delete(pParse->db, S);
>>> }
>>> -columnlist ::= columnlist COMMA tconsdef.
>>> +columnlist ::= columnlist COMMA tcons.
>>> columnlist ::= columnlist COMMA columnname carglist.
>>> columnlist ::= columnname carglist.
>>> -columnlist ::= tconsdef.
>>> +columnlist ::= tcons.
>>> columnname(A) ::= nm(A) typedef(Y). {sqlAddColumn(pParse,&A,&Y);}
>>>  // An IDENTIFIER can be a generic identifier, or one of several
>> 2. As I understand, now table def leaks if parser stops after sqlStartTable
>> but before sqlEndTable.
> 
> Unfortunately, you are right. I thought that parsing process can’t be stopped
> between these stages. Diff:
> 
> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
> index fa71b2a42..818e9f461 100644
> --- a/src/box/sql/parse.y
> +++ b/src/box/sql/parse.y
> @@ -179,7 +179,6 @@ ifnotexists(A) ::= IF NOT EXISTS. {A = 1;}
>  
>  create_table_args ::= LP columnlist RP. {
>    sqlEndTable(pParse);
> -  create_table_def_destroy(&pParse->create_table_def);
>  }
>  
>  /*
> diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
> index 98234aa8c..a1af2bacd 100644
> --- a/src/box/sql/parse_def.h
> +++ b/src/box/sql/parse_def.h
> @@ -454,6 +454,8 @@ create_view_def_init(struct create_view_def *view_def, struct Token *name,
>  static inline void
>  create_table_def_destroy(struct create_table_def *table_def)
>  {
> +       if (table_def->new_space == NULL)
> +               return;
>         struct fk_constraint_parse *fk;
>         rlist_foreach_entry(fk, &table_def->new_fkey, link)
>                 sql_expr_list_delete(sql_get(), fk->selfref_cols);
> diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
> index e449c1fcb..034fd1492 100644
> --- a/src/box/sql/prepare.c
> +++ b/src/box/sql/prepare.c
> @@ -308,6 +308,7 @@ sql_parser_destroy(Parse *parser)
>         sql *db = parser->db;
>         sqlDbFree(db, parser->aLabel);
>         sql_expr_list_delete(db, parser->pConstExpr);
> +       create_table_def_destroy(&parser->create_table_def);
>         if (db != NULL) {
>                 assert(db->lookaside.bDisable >=
>                        parser->disableLookaside);
> 
> 
>>
>> 3. sql_create_index comment still refers to parse->new_space.
> 
> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index 9a1fe5be0..8a05fa9f3 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -3369,8 +3369,8 @@ void sqlIdListDelete(sql *, IdList *);
>   * indexed.  Both will be NULL for a primary key or an index that
>   * is created to satisfy a UNIQUE constraint.  If tbl_name and
>   * name are NULL, use parse->new_space as the table to be indexed.
> - * parse->new_space is a space that is currently being
> - * constructed by a CREATE TABLE statement.
> + * parse->create_tale_def->new_space is a space that is currently
> + * being constructed by a CREATE TABLE statement.
>   *
>   * @param parse All information about this parse.
>   */
> 
>> 4. Please, write a comment above struct create_table_def create_table_def in
>> struct Parse why it is not in the union.
> 
> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index 9a1fe5be0..c4becd5f6 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -2693,6 +2693,12 @@ struct Parse {
>                 struct drop_trigger_def drop_trigger_def;
>                 struct drop_view_def drop_view_def;
>         };
> +       /**
> +        * Table def is not part of union since information
> +        * being held must survive till the end of parsing of
> +        * whole CREATE TABLE statement (to pass it to
> +        * sqlEndTable() function).
> +        */
>         struct create_table_def create_table_def;
>         /**
>          * List of all records that were inserted in system spaces
> 

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-03-26 18:06                       ` Vladislav Shpilevoy
@ 2019-03-27 13:00                         ` n.pettik
  2019-03-27 13:29                           ` Vladislav Shpilevoy
  0 siblings, 1 reply; 34+ messages in thread
From: n.pettik @ 2019-03-27 13:00 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy



> On 26 Mar 2019, at 21:06, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Thanks for the fixes! This commit LGTM.
> Lets proceed to the next patches, and start
> with a rebase, which is going to be hard.

Ok. Then I would like to clarify some details to avoid wasting time.
In previous patch version, I used next (reworked) grammar to add
FK constraints using ALTER:

cmd ::= alter_table_start alter_table_action .

alter_table_start ::= ALTER TABLE fullname(Z) . (1)

alter_table_action ::= add_constraint_def.
alter_table_action ::= drop_constraint_def.
alter_table_action ::= rename.

add_constraint_def ::= add_constraint_start constraint_def.

add_constraint_start(N) ::= ADD CONSTRAINT nm(Z). (2)
constraint_def ::= foreign_key_def.

foreign_key_def ::= FOREIGN KEY LP eidlist(FA) RP REFERENCES nm(T)
                       eidlist_opt(TA) matcharg(M) refargs(R) defer_subclause_opt(D).

Now obviously I can’t use it since foreign_key_def should call
create_fk_def_init() which in turn requires table name and name
of constraint defined in rules (1) and (2).

Why I want to use grammar mentioned above: it allows to remove
code duplication. Rules to parse constraints are defined three times:

1. ccons rule - that is part of column definition: …, a INT REFERENCES t1);
2. tcons rule - that is part of CREATE TABLE: …, CONSTRAINT c FOREIGN KEY …);
3. ALTER TABLE statement

All of them use the same grammar to parse statement starting from
REFERENCES keyword. The same applies to UNIQUE and CHECK
constraints. 

IDK how to avoid using alter_entity_def_init() and create_constraint_def_init()
and at the same time divide constraint definition into several stages.

Ofc, we can still use simple approach like:

cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  FOREIGN KEY
             LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) matcharg(M)
             refargs(R) defer_subclause_opt(D)

cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  UNIQUE
             LP sortlist(X) RP

cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  PRIMARY KEY
             LP sortlist(X) RP

cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z) CHECK …

cmd ::= ALTER TABLE fullname(Z) RENAME TO nm(N) .

Is this OK?

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-03-27 13:00                         ` n.pettik
@ 2019-03-27 13:29                           ` Vladislav Shpilevoy
  2019-03-27 13:44                             ` n.pettik
  0 siblings, 1 reply; 34+ messages in thread
From: Vladislav Shpilevoy @ 2019-03-27 13:29 UTC (permalink / raw)
  To: n.pettik, tarantool-patches



On 27/03/2019 16:00, n.pettik wrote:
> 
> 
>> On 26 Mar 2019, at 21:06, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
>>
>> Thanks for the fixes! This commit LGTM.
>> Lets proceed to the next patches, and start
>> with a rebase, which is going to be hard.
> 
> Ok. Then I would like to clarify some details to avoid wasting time.
> In previous patch version, I used next (reworked) grammar to add
> FK constraints using ALTER:
> 
> cmd ::= alter_table_start alter_table_action .
> 
> alter_table_start ::= ALTER TABLE fullname(Z) . (1)
> 
> alter_table_action ::= add_constraint_def.
> alter_table_action ::= drop_constraint_def.
> alter_table_action ::= rename.
> 
> add_constraint_def ::= add_constraint_start constraint_def.
> 
> add_constraint_start(N) ::= ADD CONSTRAINT nm(Z). (2)
> constraint_def ::= foreign_key_def.
> 
> foreign_key_def ::= FOREIGN KEY LP eidlist(FA) RP REFERENCES nm(T)
>                        eidlist_opt(TA) matcharg(M) refargs(R) defer_subclause_opt(D).
> 
> Now obviously I can’t use it since foreign_key_def should call
> create_fk_def_init() which in turn requires table name and name
> of constraint defined in rules (1) and (2).
> 
> Why I want to use grammar mentioned above: it allows to remove
> code duplication. Rules to parse constraints are defined three times:
> 
> 1. ccons rule - that is part of column definition: …, a INT REFERENCES t1);
> 2. tcons rule - that is part of CREATE TABLE: …, CONSTRAINT c FOREIGN KEY …);
> 3. ALTER TABLE statement
> 
> All of them use the same grammar to parse statement starting from
> REFERENCES keyword. The same applies to UNIQUE and CHECK
> constraints. 
> 
> IDK how to avoid using alter_entity_def_init() and create_constraint_def_init()
> and at the same time divide constraint definition into several stages.
> 
> Ofc, we can still use simple approach like:
> 
> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  FOREIGN KEY
>              LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) matcharg(M)
>              refargs(R) defer_subclause_opt(D)
> 
> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  UNIQUE
>              LP sortlist(X) RP
> 
> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  PRIMARY KEY
>              LP sortlist(X) RP
> 
> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z) CHECK …
> 
> cmd ::= ALTER TABLE fullname(Z) RENAME TO nm(N) .
> 
> Is this OK?
> 

Obviously, it is not. Why can't you define this?

alter_table_start(T) ::= ALTER TABLE fullname(T)
alter_add_constraint(T, N) ::= alter_table_start(T) ADD CONSTRAINT nm(N).

cmd ::= alter_add_constraint(T, N) FOREIGN KEY ...
cmd ::= alter_add_constraint(T, N) UNIQUE LP sortlist(X) RP
cmd ::= alter_add_constraint(T, N) PRIMARY KEY LP sortlist(X) RP
cmd ::= alter_add_constraint(T, N) CHECK ...
cmd ::= alter_table_start RENAME TO nm(N) .

Then inside each cmd you have both table and constraint names.

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-03-27 13:29                           ` Vladislav Shpilevoy
@ 2019-03-27 13:44                             ` n.pettik
  2019-03-27 14:03                               ` Vladislav Shpilevoy
  0 siblings, 1 reply; 34+ messages in thread
From: n.pettik @ 2019-03-27 13:44 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy


> On 27 Mar 2019, at 16:29, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> On 27/03/2019 16:00, n.pettik wrote:
>> 
>>> On 26 Mar 2019, at 21:06, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
>>> 
>>> Thanks for the fixes! This commit LGTM.
>>> Lets proceed to the next patches, and start
>>> with a rebase, which is going to be hard.
>> 
>> Ok. Then I would like to clarify some details to avoid wasting time.
>> In previous patch version, I used next (reworked) grammar to add
>> FK constraints using ALTER:
>> 
>> cmd ::= alter_table_start alter_table_action .
>> 
>> alter_table_start ::= ALTER TABLE fullname(Z) . (1)
>> 
>> alter_table_action ::= add_constraint_def.
>> alter_table_action ::= drop_constraint_def.
>> alter_table_action ::= rename.
>> 
>> add_constraint_def ::= add_constraint_start constraint_def.
>> 
>> add_constraint_start(N) ::= ADD CONSTRAINT nm(Z). (2)
>> constraint_def ::= foreign_key_def.
>> 
>> foreign_key_def ::= FOREIGN KEY LP eidlist(FA) RP REFERENCES nm(T)
>>                       eidlist_opt(TA) matcharg(M) refargs(R) defer_subclause_opt(D).
>> 
>> Now obviously I can’t use it since foreign_key_def should call
>> create_fk_def_init() which in turn requires table name and name
>> of constraint defined in rules (1) and (2).
>> 
>> Why I want to use grammar mentioned above: it allows to remove
>> code duplication. Rules to parse constraints are defined three times:
>> 
>> 1. ccons rule - that is part of column definition: …, a INT REFERENCES t1);
>> 2. tcons rule - that is part of CREATE TABLE: …, CONSTRAINT c FOREIGN KEY …);
>> 3. ALTER TABLE statement
>> 
>> All of them use the same grammar to parse statement starting from
>> REFERENCES keyword. The same applies to UNIQUE and CHECK
>> constraints. 
>> 
>> IDK how to avoid using alter_entity_def_init() and create_constraint_def_init()
>> and at the same time divide constraint definition into several stages.
>> 
>> Ofc, we can still use simple approach like:
>> 
>> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  FOREIGN KEY
>>             LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) matcharg(M)
>>             refargs(R) defer_subclause_opt(D)
>> 
>> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  UNIQUE
>>             LP sortlist(X) RP
>> 
>> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  PRIMARY KEY
>>             LP sortlist(X) RP
>> 
>> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z) CHECK …
>> 
>> cmd ::= ALTER TABLE fullname(Z) RENAME TO nm(N) .
>> 
>> Is this OK?
>> 
> 
> Obviously, it is not. Why can't you define this?
> 
> alter_table_start(T) ::= ALTER TABLE fullname(T)
> alter_add_constraint(T, N) ::= alter_table_start(T) ADD CONSTRAINT nm(N).

Lemon can’t use two aliases as rule parameters at the same time.
Instead we can introduce *another one* local struct to hold these names.
Anyway my initial worry was not about duplication of ALTER TABLE CREATE CONSTRAINT,
but rather of constraints grammar (i.e. starting from FOREIGN KEY…).

> cmd ::= alter_add_constraint(T, N) FOREIGN KEY ...
> cmd ::= alter_add_constraint(T, N) UNIQUE LP sortlist(X) RP
> cmd ::= alter_add_constraint(T, N) PRIMARY KEY LP sortlist(X) RP
> cmd ::= alter_add_constraint(T, N) CHECK ...
> cmd ::= alter_table_start RENAME TO nm(N) .
> 
> Then inside each cmd you have both table and constraint names.

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-03-27 13:44                             ` n.pettik
@ 2019-03-27 14:03                               ` Vladislav Shpilevoy
  2019-03-27 14:11                                 ` n.pettik
  0 siblings, 1 reply; 34+ messages in thread
From: Vladislav Shpilevoy @ 2019-03-27 14:03 UTC (permalink / raw)
  To: n.pettik, tarantool-patches



On 27/03/2019 16:44, n.pettik wrote:
> 
>> On 27 Mar 2019, at 16:29, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
>> On 27/03/2019 16:00, n.pettik wrote:
>>>
>>>> On 26 Mar 2019, at 21:06, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
>>>>
>>>> Thanks for the fixes! This commit LGTM.
>>>> Lets proceed to the next patches, and start
>>>> with a rebase, which is going to be hard.
>>>
>>> Ok. Then I would like to clarify some details to avoid wasting time.
>>> In previous patch version, I used next (reworked) grammar to add
>>> FK constraints using ALTER:
>>>
>>> cmd ::= alter_table_start alter_table_action .
>>>
>>> alter_table_start ::= ALTER TABLE fullname(Z) . (1)
>>>
>>> alter_table_action ::= add_constraint_def.
>>> alter_table_action ::= drop_constraint_def.
>>> alter_table_action ::= rename.
>>>
>>> add_constraint_def ::= add_constraint_start constraint_def.
>>>
>>> add_constraint_start(N) ::= ADD CONSTRAINT nm(Z). (2)
>>> constraint_def ::= foreign_key_def.
>>>
>>> foreign_key_def ::= FOREIGN KEY LP eidlist(FA) RP REFERENCES nm(T)
>>>                       eidlist_opt(TA) matcharg(M) refargs(R) defer_subclause_opt(D).
>>>
>>> Now obviously I can’t use it since foreign_key_def should call
>>> create_fk_def_init() which in turn requires table name and name
>>> of constraint defined in rules (1) and (2).
>>>
>>> Why I want to use grammar mentioned above: it allows to remove
>>> code duplication. Rules to parse constraints are defined three times:
>>>
>>> 1. ccons rule - that is part of column definition: …, a INT REFERENCES t1);
>>> 2. tcons rule - that is part of CREATE TABLE: …, CONSTRAINT c FOREIGN KEY …);
>>> 3. ALTER TABLE statement
>>>
>>> All of them use the same grammar to parse statement starting from
>>> REFERENCES keyword. The same applies to UNIQUE and CHECK
>>> constraints. 
>>>
>>> IDK how to avoid using alter_entity_def_init() and create_constraint_def_init()
>>> and at the same time divide constraint definition into several stages.
>>>
>>> Ofc, we can still use simple approach like:
>>>
>>> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  FOREIGN KEY
>>>             LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) matcharg(M)
>>>             refargs(R) defer_subclause_opt(D)
>>>
>>> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  UNIQUE
>>>             LP sortlist(X) RP
>>>
>>> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  PRIMARY KEY
>>>             LP sortlist(X) RP
>>>
>>> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z) CHECK …
>>>
>>> cmd ::= ALTER TABLE fullname(Z) RENAME TO nm(N) .
>>>
>>> Is this OK?
>>>
>>
>> Obviously, it is not. Why can't you define this?
>>
>> alter_table_start(T) ::= ALTER TABLE fullname(T)
>> alter_add_constraint(T, N) ::= alter_table_start(T) ADD CONSTRAINT nm(N).
> 
> Lemon can’t use two aliases as rule parameters at the same time.
> Instead we can introduce *another one* local struct to hold these names.

Yes, you can define a structure in parse.y to store these two parameters,
and unpack it back inside the concrete rules. It means, that such a
helper struct will never be stored anywhere out of parse.y.

> Anyway my initial worry was not about duplication of ALTER TABLE CREATE CONSTRAINT,
> but rather of constraints grammar (i.e. starting from FOREIGN KEY…).

For constraints grammar you can consult the standard. I do not remember
how it defines FOREIGN KEY rules, if it does at all. Personally for me
it looks ok.

> 
>> cmd ::= alter_add_constraint(T, N) FOREIGN KEY ...
>> cmd ::= alter_add_constraint(T, N) UNIQUE LP sortlist(X) RP
>> cmd ::= alter_add_constraint(T, N) PRIMARY KEY LP sortlist(X) RP
>> cmd ::= alter_add_constraint(T, N) CHECK ...
>> cmd ::= alter_table_start RENAME TO nm(N) .
>>
>> Then inside each cmd you have both table and constraint names.
> 

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

* [tarantool-patches] Re: [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing
  2019-03-27 14:03                               ` Vladislav Shpilevoy
@ 2019-03-27 14:11                                 ` n.pettik
  0 siblings, 0 replies; 34+ messages in thread
From: n.pettik @ 2019-03-27 14:11 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy

[-- Attachment #1: Type: text/plain, Size: 4235 bytes --]


> On 27 Mar 2019, at 17:03, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> On 27/03/2019 16:44, n.pettik wrote:
>> 
>>> On 27 Mar 2019, at 16:29, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
>>> On 27/03/2019 16:00, n.pettik wrote:
>>>> 
>>>>> On 26 Mar 2019, at 21:06, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
>>>>> 
>>>>> Thanks for the fixes! This commit LGTM.
>>>>> Lets proceed to the next patches, and start
>>>>> with a rebase, which is going to be hard.
>>>> 
>>>> Ok. Then I would like to clarify some details to avoid wasting time.
>>>> In previous patch version, I used next (reworked) grammar to add
>>>> FK constraints using ALTER:
>>>> 
>>>> cmd ::= alter_table_start alter_table_action .
>>>> 
>>>> alter_table_start ::= ALTER TABLE fullname(Z) . (1)
>>>> 
>>>> alter_table_action ::= add_constraint_def.
>>>> alter_table_action ::= drop_constraint_def.
>>>> alter_table_action ::= rename.
>>>> 
>>>> add_constraint_def ::= add_constraint_start constraint_def.
>>>> 
>>>> add_constraint_start(N) ::= ADD CONSTRAINT nm(Z). (2)
>>>> constraint_def ::= foreign_key_def.
>>>> 
>>>> foreign_key_def ::= FOREIGN KEY LP eidlist(FA) RP REFERENCES nm(T)
>>>>                      eidlist_opt(TA) matcharg(M) refargs(R) defer_subclause_opt(D).
>>>> 
>>>> Now obviously I can’t use it since foreign_key_def should call
>>>> create_fk_def_init() which in turn requires table name and name
>>>> of constraint defined in rules (1) and (2).
>>>> 
>>>> Why I want to use grammar mentioned above: it allows to remove
>>>> code duplication. Rules to parse constraints are defined three times:
>>>> 
>>>> 1. ccons rule - that is part of column definition: …, a INT REFERENCES t1);
>>>> 2. tcons rule - that is part of CREATE TABLE: …, CONSTRAINT c FOREIGN KEY …);
>>>> 3. ALTER TABLE statement
>>>> 
>>>> All of them use the same grammar to parse statement starting from
>>>> REFERENCES keyword. The same applies to UNIQUE and CHECK
>>>> constraints. 
>>>> 
>>>> IDK how to avoid using alter_entity_def_init() and create_constraint_def_init()
>>>> and at the same time divide constraint definition into several stages.
>>>> 
>>>> Ofc, we can still use simple approach like:
>>>> 
>>>> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  FOREIGN KEY
>>>>            LP eidlist(FA) RP REFERENCES nm(T) eidlist_opt(TA) matcharg(M)
>>>>            refargs(R) defer_subclause_opt(D)
>>>> 
>>>> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  UNIQUE
>>>>            LP sortlist(X) RP
>>>> 
>>>> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z)  PRIMARY KEY
>>>>            LP sortlist(X) RP
>>>> 
>>>> cmd ::= ALTER TABLE fullname(Z) ADD CONSTRAINT nm(Z) CHECK …
>>>> 
>>>> cmd ::= ALTER TABLE fullname(Z) RENAME TO nm(N) .
>>>> 
>>>> Is this OK?
>>>> 
>>> 
>>> Obviously, it is not. Why can't you define this?
>>> 
>>> alter_table_start(T) ::= ALTER TABLE fullname(T)
>>> alter_add_constraint(T, N) ::= alter_table_start(T) ADD CONSTRAINT nm(N).
>> 
>> Lemon can’t use two aliases as rule parameters at the same time.
>> Instead we can introduce *another one* local struct to hold these names.
> 
> Yes, you can define a structure in parse.y to store these two parameters,
> and unpack it back inside the concrete rules. It means, that such a
> helper struct will never be stored anywhere out of parse.y.
> 
>> Anyway my initial worry was not about duplication of ALTER TABLE CREATE CONSTRAINT,
>> but rather of constraints grammar (i.e. starting from FOREIGN KEY…).
> 
> For constraints grammar you can consult the standard. I do not remember
> how it defines FOREIGN KEY rules, if it does at all. Personally for me
> it looks ok.

Thank you for your feedback. I’m going to send rebased version of
top-most patches soon.

>>> cmd ::= alter_add_constraint(T, N) FOREIGN KEY ...
>>> cmd ::= alter_add_constraint(T, N) UNIQUE LP sortlist(X) RP
>>> cmd ::= alter_add_constraint(T, N) PRIMARY KEY LP sortlist(X) RP
>>> cmd ::= alter_add_constraint(T, N) CHECK ...
>>> cmd ::= alter_table_start RENAME TO nm(N) .
>>> 
>>> Then inside each cmd you have both table and constraint names.


[-- Attachment #2: Type: text/html, Size: 12475 bytes --]

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

end of thread, other threads:[~2019-03-27 14:11 UTC | newest]

Thread overview: 34+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-01-23 17:56 [tarantool-patches] [PATCH v2 0/5] Introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PK Nikita Pettik
2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 1/5] sql: introduce structs assembling DDL arguments during parsing Nikita Pettik
2019-01-24  8:36   ` [tarantool-patches] " Konstantin Osipov
2019-01-24 10:47     ` n.pettik
2019-01-24 12:30       ` Konstantin Osipov
2019-01-29 19:03         ` n.pettik
2019-01-29 19:29   ` Vladislav Shpilevoy
2019-01-29 20:04     ` n.pettik
2019-01-29 20:20       ` Vladislav Shpilevoy
2019-01-29 21:25         ` n.pettik
2019-01-31 19:32     ` n.pettik
2019-02-04 15:25       ` Vladislav Shpilevoy
2019-02-08 14:25         ` n.pettik
2019-02-15 20:13           ` Vladislav Shpilevoy
2019-02-27 22:56             ` n.pettik
2019-03-12 12:50               ` Vladislav Shpilevoy
2019-03-14 18:13                 ` n.pettik
2019-03-25 11:25                   ` Vladislav Shpilevoy
2019-03-26 18:01                     ` n.pettik
2019-03-26 18:06                       ` Vladislav Shpilevoy
2019-03-27 13:00                         ` n.pettik
2019-03-27 13:29                           ` Vladislav Shpilevoy
2019-03-27 13:44                             ` n.pettik
2019-03-27 14:03                               ` Vladislav Shpilevoy
2019-03-27 14:11                                 ` n.pettik
2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 2/5] sql: rework ALTER TABLE grammar Nikita Pettik
2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 3/5] sql: refactor getNewIid() function Nikita Pettik
2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 4/5] sql: fix error message for improperly created index Nikita Pettik
2019-02-08 17:14   ` [tarantool-patches] " Konstantin Osipov
2019-01-23 17:56 ` [tarantool-patches] [PATCH v2 5/5] sql: introduce ALTER TABLE ADD CONSTRAINT UNIQUE/PRIMARY KEY Nikita Pettik
2019-01-24  8:31   ` [tarantool-patches] " Konstantin Osipov
2019-01-29 19:29   ` Vladislav Shpilevoy
2019-02-08 17:16   ` Konstantin Osipov
2019-02-08 17:36     ` n.pettik

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