Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v2 0/2] Support column addition with <ALTER TABLE>
@ 2020-04-03 15:27 Roman Khabibov
  2020-04-03 15:27 ` [Tarantool-patches] [PATCH v2 1/2] sql: rename TK_COLUMN to TK_COLUMN_NAME Roman Khabibov
                   ` (3 more replies)
  0 siblings, 4 replies; 14+ messages in thread
From: Roman Khabibov @ 2020-04-03 15:27 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

According to the SQL standard, column definition in a
<CREATE TABLE> and a <ALTER TABLE ADD COLUMN> is the same. So, I
decided to support Tarantool features like AUTOINCREMENT in the
alter column description.

Roman Khabibov (2):
  sql: rename TK_COLUMN to TK_COLUMN_NAME
  sql: support column addition

 extra/addopcodes.sh          |   2 +-
 extra/mkkeywordhash.c        |   5 +-
 src/box/errcode.h            |   2 +
 src/box/schema_def.h         |   1 +
 src/box/sql/alter.c          |   1 +
 src/box/sql/build.c          | 596 +++++++++++++++++++++++------------
 src/box/sql/expr.c           |  42 +--
 src/box/sql/fk_constraint.c  |   2 +-
 src/box/sql/parse.y          |  45 ++-
 src/box/sql/parse_def.h      |  62 ++--
 src/box/sql/prepare.c        |   8 +-
 src/box/sql/resolve.c        |  10 +-
 src/box/sql/select.c         |  10 +-
 src/box/sql/sqlInt.h         |  58 +++-
 src/box/sql/treeview.c       |   2 +-
 src/box/sql/where.c          |  18 +-
 src/box/sql/whereexpr.c      |  12 +-
 test/box/error.result        |   2 +
 test/sql/add-column.result   | 231 ++++++++++++++
 test/sql/add-column.test.lua |  87 +++++
 20 files changed, 892 insertions(+), 304 deletions(-)
 create mode 100644 test/sql/add-column.result
 create mode 100644 test/sql/add-column.test.lua

-- 
2.21.0 (Apple Git-122)

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

* [Tarantool-patches] [PATCH v2 1/2] sql: rename TK_COLUMN to TK_COLUMN_NAME
  2020-04-03 15:27 [Tarantool-patches] [PATCH v2 0/2] Support column addition with <ALTER TABLE> Roman Khabibov
@ 2020-04-03 15:27 ` Roman Khabibov
  2020-04-24 22:55   ` Vladislav Shpilevoy
  2020-04-03 15:27 ` [Tarantool-patches] [PATCH v2 2/2] sql: support column addition Roman Khabibov
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 14+ messages in thread
From: Roman Khabibov @ 2020-04-03 15:27 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

Rename TK_COLUMN used for tokens treated as a column name to
TK_COLUMN_NAME. It is needed to allow the typing of COLUMN
keyword in <ALTER TABLE ADD COLUMN> statement.

Needed for #3075
---
 extra/addopcodes.sh         |  2 +-
 extra/mkkeywordhash.c       |  5 +----
 src/box/sql/build.c         |  2 +-
 src/box/sql/expr.c          | 42 ++++++++++++++++++-------------------
 src/box/sql/fk_constraint.c |  2 +-
 src/box/sql/resolve.c       | 10 ++++-----
 src/box/sql/select.c        | 10 ++++-----
 src/box/sql/sqlInt.h        |  8 +++----
 src/box/sql/treeview.c      |  2 +-
 src/box/sql/where.c         | 18 +++++++++-------
 src/box/sql/whereexpr.c     | 12 +++++------
 11 files changed, 56 insertions(+), 57 deletions(-)

diff --git a/extra/addopcodes.sh b/extra/addopcodes.sh
index cb6c84725..986c62683 100755
--- a/extra/addopcodes.sh
+++ b/extra/addopcodes.sh
@@ -39,7 +39,7 @@ extras="            \
     END_OF_FILE     \
     UNCLOSED_STRING \
     FUNCTION        \
-    COLUMN          \
+    COLUMN_NAME     \
     AGG_FUNCTION    \
     AGG_COLUMN      \
     UMINUS          \
diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index 006285622..b3e42bf2a 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -75,10 +75,7 @@ static Keyword aKeywordTable[] = {
   { "CAST",                   "TK_CAST",        false },
   { "CHECK",                  "TK_CHECK",       true  },
   { "COLLATE",                "TK_COLLATE",     true  },
-  /* gh-3075: Reserved until ALTER ADD COLUMN is implemeneted.
-   * Move it back to ALTER when done.
-   */
-  /* { "COLUMN",              "TK_COLUMNKW",    true  }, */
+  { "COLUMN_NAME",            "TK_COLUMN_NAME", true  },
   { "COLUMN",                 "TK_STANDARD",    true  },
   { "COMMIT",                 "TK_COMMIT",      true  },
   { "CONFLICT",               "TK_CONFLICT",    false },
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 7511fad37..4552f10ab 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -2274,7 +2274,7 @@ index_fill_def(struct Parse *parse, struct index *index,
 			goto cleanup;
 
 		struct Expr *column_expr = sqlExprSkipCollate(expr);
-		if (column_expr->op != TK_COLUMN) {
+		if (column_expr->op != TK_COLUMN_NAME) {
 			diag_set(ClientError, ER_UNSUPPORTED, "Tarantool",
 				 "functional indexes");
 			goto tnt_error;
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index bc2182446..bf2585735 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -62,7 +62,7 @@ sql_expr_type(struct Expr *pExpr)
 		assert(!ExprHasProperty(pExpr, EP_IntValue));
 		return pExpr->type;
 	case TK_AGG_COLUMN:
-	case TK_COLUMN:
+	case TK_COLUMN_NAME:
 	case TK_TRIGGER:
 		assert(pExpr->iColumn >= 0);
 		return pExpr->space_def->fields[pExpr->iColumn].type;
@@ -262,13 +262,13 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
 			*is_explicit_coll = true;
 			break;
 		}
-		if ((op == TK_AGG_COLUMN || op == TK_COLUMN ||
+		if ((op == TK_AGG_COLUMN || op == TK_COLUMN_NAME ||
 		     op == TK_REGISTER || op == TK_TRIGGER) &&
 		    p->space_def != NULL) {
 			/*
 			 * op==TK_REGISTER && p->space_def!=0
 			 * happens when pExpr was originally
-			 * a TK_COLUMN but was previously
+			 * a TK_COLUMN_NAME but was previously
 			 * evaluated and cached in a register.
 			 */
 			int j = p->iColumn;
@@ -2061,11 +2061,11 @@ exprNodeIsConstant(Walker * pWalker, Expr * pExpr)
 			return WRC_Abort;
 		}
 	case TK_ID:
-	case TK_COLUMN:
+	case TK_COLUMN_NAME:
 	case TK_AGG_FUNCTION:
 	case TK_AGG_COLUMN:
 		testcase(pExpr->op == TK_ID);
-		testcase(pExpr->op == TK_COLUMN);
+		testcase(pExpr->op == TK_COLUMN_NAME);
 		testcase(pExpr->op == TK_AGG_FUNCTION);
 		testcase(pExpr->op == TK_AGG_COLUMN);
 		if (pWalker->eCode == 3 && pExpr->iTable == pWalker->u.iCur) {
@@ -2236,7 +2236,7 @@ sqlExprCanBeNull(const Expr * p)
 	case TK_FLOAT:
 	case TK_BLOB:
 		return 0;
-	case TK_COLUMN:
+	case TK_COLUMN_NAME:
 		assert(p->space_def != 0);
 		return ExprHasProperty(p, EP_CanBeNull) ||
 		       (p->iColumn >= 0
@@ -2267,7 +2267,7 @@ sql_expr_needs_no_type_change(const struct Expr *p, enum field_type type)
 		return type == FIELD_TYPE_STRING;
 	case TK_BLOB:
 		return type == FIELD_TYPE_VARBINARY;
-	case TK_COLUMN:
+	case TK_COLUMN_NAME:
 		/* p cannot be part of a CHECK constraint. */
 		assert(p->iTable >= 0);
 		return p->iColumn < 0 && sql_type_is_numeric(type);
@@ -2324,7 +2324,7 @@ isCandidateForInOpt(Expr * pX)
 	/* All SELECT results must be columns. */
 	for (i = 0; i < pEList->nExpr; i++) {
 		Expr *pRes = pEList->a[i].pExpr;
-		if (pRes->op != TK_COLUMN)
+		if (pRes->op != TK_COLUMN_NAME)
 			return 0;
 		assert(pRes->iTable == pSrc->a[0].iCursor);	/* Not a correlated subquery */
 	}
@@ -3707,10 +3707,10 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				}
 				return target;
 			}
-			/* Otherwise, fall thru into the TK_COLUMN case */
+			/* Otherwise, fall thru into the TK_COLUMN_NAME case */
 			FALLTHROUGH;
 		}
-	case TK_COLUMN:{
+	case TK_COLUMN_NAME:{
 			int iTab = pExpr->iTable;
 			int col = pExpr->iColumn;
 			if (iTab < 0) {
@@ -4102,7 +4102,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 					assert(nFarg == 1);
 					assert(pFarg->a[0].pExpr != 0);
 					exprOp = pFarg->a[0].pExpr->op;
-					if (exprOp == TK_COLUMN
+					if (exprOp == TK_COLUMN_NAME
 					    || exprOp == TK_AGG_COLUMN) {
 						assert(SQL_FUNC_LENGTH ==
 						       OPFLAG_LENGTHARG);
@@ -4319,7 +4319,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			endLabel = sqlVdbeMakeLabel(v);
 			if ((pX = pExpr->pLeft) != 0) {
 				tempX = *pX;
-				testcase(pX->op == TK_COLUMN);
+				testcase(pX->op == TK_COLUMN_NAME);
 				exprToRegister(&tempX,
 					       exprCodeVector(pParse, &tempX,
 							      &regFree1));
@@ -4344,11 +4344,11 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 					pTest = aListelem[i].pExpr;
 				}
 				nextCase = sqlVdbeMakeLabel(v);
-				testcase(pTest->op == TK_COLUMN);
+				testcase(pTest->op == TK_COLUMN_NAME);
 				sqlExprIfFalse(pParse, pTest, nextCase,
 						   SQL_JUMPIFNULL);
 				testcase(aListelem[i + 1].pExpr->op ==
-					 TK_COLUMN);
+					 TK_COLUMN_NAME);
 				sqlExprCode(pParse, aListelem[i + 1].pExpr,
 						target);
 				sqlVdbeGoto(v, endLabel);
@@ -5081,7 +5081,7 @@ sqlExprCompare(Expr * pA, Expr * pB, int iTab)
 		}
 		return 2;
 	}
-	if (pA->op != TK_COLUMN && pA->op != TK_AGG_COLUMN && pA->u.zToken) {
+	if (pA->op != TK_COLUMN_NAME && pA->op != TK_AGG_COLUMN && pA->u.zToken) {
 		if (pA->op == TK_FUNCTION) {
 			if (sqlStrICmp(pA->u.zToken, pB->u.zToken) != 0)
 				return 2;
@@ -5161,7 +5161,7 @@ sqlExprListCompare(ExprList * pA, ExprList * pB, int iTab)
  *     pE1: x IS NULL  pE2: x IS NOT NULL    Result: false
  *     pE1: x IS ?2    pE2: x IS NOT NULL    Reuslt: false
  *
- * When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has
+ * When comparing TK_COLUMN_NAME nodes between pE1 and pE2, if pE2 has
  * Expr.iTable<0 then assume a table number given by iTab.
  *
  * When in doubt, return false.  Returning true might give a performance
@@ -5209,11 +5209,11 @@ exprSrcCount(Walker * pWalker, Expr * pExpr)
 {
 	/* The NEVER() on the second term is because sqlFunctionUsesThisSrc()
 	 * is always called before sqlExprAnalyzeAggregates() and so the
-	 * TK_COLUMNs have not yet been converted into TK_AGG_COLUMN.  If
+	 * TK_COLUMN_NAMEs have not yet been converted into TK_AGG_COLUMN.  If
 	 * sqlFunctionUsesThisSrc() is used differently in the future, the
 	 * NEVER() will need to be removed.
 	 */
-	if (pExpr->op == TK_COLUMN || NEVER(pExpr->op == TK_AGG_COLUMN)) {
+	if (pExpr->op == TK_COLUMN_NAME || NEVER(pExpr->op == TK_AGG_COLUMN)) {
 		int i;
 		struct SrcCount *p = pWalker->u.pSrcCount;
 		SrcList *pSrc = p->pSrc;
@@ -5299,9 +5299,9 @@ analyzeAggregate(Walker * pWalker, Expr * pExpr)
 
 	switch (pExpr->op) {
 	case TK_AGG_COLUMN:
-	case TK_COLUMN:{
+	case TK_COLUMN_NAME:{
 			testcase(pExpr->op == TK_AGG_COLUMN);
-			testcase(pExpr->op == TK_COLUMN);
+			testcase(pExpr->op == TK_COLUMN_NAME);
 			/* Check to see if the column is in one of the tables in the FROM
 			 * clause of the aggregate query
 			 */
@@ -5370,7 +5370,7 @@ analyzeAggregate(Walker * pWalker, Expr * pExpr)
 									if (pE->
 									    op
 									    ==
-									    TK_COLUMN
+									    TK_COLUMN_NAME
 									    &&
 									    pE->
 									    iTable
diff --git a/src/box/sql/fk_constraint.c b/src/box/sql/fk_constraint.c
index 482220a95..3f3625ad5 100644
--- a/src/box/sql/fk_constraint.c
+++ b/src/box/sql/fk_constraint.c
@@ -338,7 +338,7 @@ static struct Expr *
 sql_expr_new_column_by_cursor(struct sql *db, struct space_def *def,
 			      int cursor, int column)
 {
-	struct Expr *expr = sql_expr_new_anon(db, TK_COLUMN);
+	struct Expr *expr = sql_expr_new_anon(db, TK_COLUMN_NAME);
 	if (expr == NULL)
 		return NULL;
 	expr->space_def = def;
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 6f625dc18..5fe8ee3f6 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -189,7 +189,7 @@ sqlMatchSpanName(const char *zSpan,
  *    pExpr->space_def     Points to the space_def structure of X.Y
  *                         (even if X and/or Y are implied.)
  *    pExpr->iColumn       Set to the column number within the table.
- *    pExpr->op            Set to TK_COLUMN.
+ *    pExpr->op            Set to TK_COLUMN_NAME.
  *    pExpr->pLeft         Any expression this points to is deleted
  *    pExpr->pRight        Any expression this points to is deleted.
  *
@@ -461,7 +461,7 @@ lookupName(Parse * pParse,	/* The parsing context */
 	pExpr->pLeft = 0;
 	sql_expr_delete(db, pExpr->pRight, false);
 	pExpr->pRight = 0;
-	pExpr->op = (isTrigger ? TK_TRIGGER : TK_COLUMN);
+	pExpr->op = (isTrigger ? TK_TRIGGER : TK_COLUMN_NAME);
  lookupname_end:
 	if (cnt == 1) {
 		assert(pNC != 0);
@@ -485,7 +485,7 @@ struct Expr *
 sql_expr_new_column(struct sql *db, struct SrcList *src_list, int src_idx,
 		    int column)
 {
-	struct Expr *expr = sql_expr_new_anon(db, TK_COLUMN);
+	struct Expr *expr = sql_expr_new_anon(db, TK_COLUMN_NAME);
 	if (expr == NULL)
 		return NULL;
 	struct SrcList_item *item = &src_list->a[src_idx];
@@ -518,7 +518,7 @@ exprProbability(Expr * p)
 /*
  * This routine is callback for sqlWalkExpr().
  *
- * Resolve symbolic names into TK_COLUMN operators for the current
+ * Resolve symbolic names into TK_COLUMN_NAME operators for the current
  * node in the expression tree.  Return 0 to continue the search down
  * the tree or 2 to abort the tree walk.
  *
@@ -1451,7 +1451,7 @@ resolveSelectStep(Walker * pWalker, Select * p)
  *
  * The node at the root of the subtree is modified as follows:
  *
- *    Expr.op        Changed to TK_COLUMN
+ *    Expr.op        Changed to TK_COLUMN_NAME
  *    Expr.pTab      Points to the Table object for X.Y
  *    Expr.iColumn   The column index in X.Y.  -1 for the rowid.
  *    Expr.iTable    The VDBE cursor number for X.Y
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 65e41f219..f602c65e7 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1813,7 +1813,7 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
 		vdbe_metadata_set_col_nullability(v, i, -1);
 		const char *colname = pEList->a[i].zName;
 		const char *span = pEList->a[i].zSpan;
-		if (p->op == TK_COLUMN || p->op == TK_AGG_COLUMN) {
+		if (p->op == TK_COLUMN_NAME || p->op == TK_AGG_COLUMN) {
 			char *zCol;
 			int iCol = p->iColumn;
 			for (j = 0; ALWAYS(j < pTabList->nSrc); j++) {
@@ -1936,7 +1936,7 @@ sqlColumnsFromExprList(Parse * parse, ExprList * expr_list,
 				pColExpr = pColExpr->pRight;
 				assert(pColExpr != 0);
 			}
-			if (pColExpr->op == TK_COLUMN
+			if (pColExpr->op == TK_COLUMN_NAME
 			    && ALWAYS(pColExpr->space_def != NULL)) {
 				/* For columns use the column name name */
 				int iCol = pColExpr->iColumn;
@@ -3650,7 +3650,7 @@ substExpr(Parse * pParse,	/* Report errors here */
 	sql *db = pParse->db;
 	if (pExpr == 0)
 		return 0;
-	if (pExpr->op == TK_COLUMN && pExpr->iTable == iTable) {
+	if (pExpr->op == TK_COLUMN_NAME && pExpr->iTable == iTable) {
 		if (pExpr->iColumn < 0) {
 			pExpr->op = TK_NULL;
 		} else {
@@ -6015,7 +6015,7 @@ sqlSelect(Parse * pParse,		/* The parser context */
 		/* Create a label to jump to when we want to abort the query */
 		addrEnd = sqlVdbeMakeLabel(v);
 
-		/* Convert TK_COLUMN nodes into TK_AGG_COLUMN and make entries in
+		/* Convert TK_COLUMN_NAME nodes into TK_AGG_COLUMN and make entries in
 		 * sAggInfo for all TK_AGG_FUNCTION nodes in expressions of the
 		 * SELECT statement.
 		 */
@@ -6387,7 +6387,7 @@ sqlSelect(Parse * pParse,		/* The parser context */
 						    flag !=
 						    WHERE_ORDERBY_MIN ? 1 : 0;
 						pMinMax->a[0].pExpr->op =
-						    TK_COLUMN;
+						    TK_COLUMN_NAME;
 					}
 				}
 
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 1579cc92e..0479ebc21 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -1513,7 +1513,7 @@ typedef int ynVar;
  * valid.
  *
  * An expression of the form ID or ID.ID refers to a column in a table.
- * For such expressions, Expr.op is set to TK_COLUMN and Expr.iTable is
+ * For such expressions, Expr.op is set to TK_COLUMN_NAME and Expr.iTable is
  * the integer cursor number of a VDBE cursor pointing to that table and
  * Expr.iColumn is the column number for the specific column.  If the
  * expression is used as a result in an aggregate SELECT, then the
@@ -1583,20 +1583,20 @@ struct Expr {
 #if SQL_MAX_EXPR_DEPTH>0
 	int nHeight;		/* Height of the tree headed by this node */
 #endif
-	int iTable;		/* TK_COLUMN: cursor number of table holding column
+	int iTable;		/* TK_COLUMN_NAME: cursor number of table holding column
 				 * TK_REGISTER: register number
 				 * TK_TRIGGER: 1 -> new, 0 -> old
 				 * EP_Unlikely:  134217728 times likelihood
 				 * TK_SELECT: 1st register of result vector
 				 */
-	ynVar iColumn;		/* TK_COLUMN: column index.
+	ynVar iColumn;		/* TK_COLUMN_NAME: column index.
 				 * TK_VARIABLE: variable number (always >= 1).
 				 * TK_SELECT_COLUMN: column of the result vector
 				 */
 	i16 iAgg;		/* Which entry in pAggInfo->aCol[] or ->aFunc[] */
 	i16 iRightJoinTable;	/* If EP_FromJoin, the right table of the join */
 	u8 op2;			/* TK_REGISTER: original value of Expr.op
-				 * TK_COLUMN: the value of p5 for OP_Column
+				 * TK_COLUMN_NAME: the value of p5 for OP_Column
 				 * TK_AGG_FUNCTION: nesting depth
 				 */
 	AggInfo *pAggInfo;	/* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */
diff --git a/src/box/sql/treeview.c b/src/box/sql/treeview.c
index a04597979..ea26fcf6d 100644
--- a/src/box/sql/treeview.c
+++ b/src/box/sql/treeview.c
@@ -327,7 +327,7 @@ sqlTreeViewExpr(TreeView * pView, const Expr * pExpr, u8 moreToFollow)
 					    zFlgs);
 			break;
 		}
-	case TK_COLUMN:{
+	case TK_COLUMN_NAME:{
 			if (pExpr->iTable < 0) {
 				/* This only happens when coding check constraints */
 				sqlTreeViewLine(pView, "COLUMN(%d)%s",
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index 7ec43e184..2bef0f698 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -276,7 +276,7 @@ whereScanNext(WhereScan * pScan)
 						sqlExprSkipCollate(pTerm->
 								       pExpr->
 								       pRight))->
-					    op == TK_COLUMN) {
+					    op == TK_COLUMN_NAME) {
 						int j;
 						for (j = 0; j < pScan->nEquiv; j++) {
 							if (pScan->aiCur[j] == pX->iTable
@@ -319,7 +319,7 @@ whereScanNext(WhereScan * pScan)
 							}
 						}
 						if ((pTerm->eOperator & WO_EQ) != 0
-						    && (pX = pTerm->pExpr->pRight)->op == TK_COLUMN
+						    && (pX = pTerm->pExpr->pRight)->op == TK_COLUMN_NAME
 						    && pX->iTable == pScan->aiCur[0]
 						    && pX->iColumn == pScan->aiColumn[0]) {
 							continue;
@@ -555,7 +555,7 @@ findIndexCol(Parse * pParse,	/* Parse context */
 	struct key_part *part_to_match = &idx_def->key_def->parts[iCol];
 	for (int i = 0; i < pList->nExpr; i++) {
 		Expr *p = sqlExprSkipCollate(pList->a[i].pExpr);
-		if (p->op == TK_COLUMN && p->iTable == iBase &&
+		if (p->op == TK_COLUMN_NAME && p->iTable == iBase &&
 		    p->iColumn == (int) part_to_match->fieldno) {
 			bool is_found;
 			uint32_t id;
@@ -601,7 +601,8 @@ isDistinctRedundant(Parse * pParse,		/* Parsing context */
 	 */
 	for (int i = 0; i < pDistinct->nExpr; i++) {
 		Expr *p = sqlExprSkipCollate(pDistinct->a[i].pExpr);
-		if (p->op == TK_COLUMN && p->iTable == iBase && p->iColumn < 0)
+		if (p->op == TK_COLUMN_NAME && p->iTable == iBase &&
+		    p->iColumn < 0)
 			return 1;
 	}
 	if (space == NULL)
@@ -2245,7 +2246,7 @@ whereRangeVectorLen(Parse * pParse,	/* Parsing context */
 		 * leftmost index column.
 		 */
 		struct key_part *parts = idx_def->key_def->parts;
-		if (pLhs->op != TK_COLUMN || pLhs->iTable != iCur ||
+		if (pLhs->op != TK_COLUMN_NAME || pLhs->iTable != iCur ||
 		    pLhs->iColumn != (int)parts[i + nEq].fieldno ||
 		    parts[i + nEq].sort_order != parts[nEq].sort_order)
 			break;
@@ -2677,7 +2678,7 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
 		return 0;
 	for (ii = 0; ii < pOB->nExpr; ii++) {
 		Expr *pExpr = sqlExprSkipCollate(pOB->a[ii].pExpr);
-		if (pExpr->op == TK_COLUMN && pExpr->iTable == iCursor) {
+		if (pExpr->op == TK_COLUMN_NAME && pExpr->iTable == iCursor) {
 			if (pExpr->iColumn < 0)
 				return 1;
 			for (jj = 0; jj < part_count; jj++) {
@@ -3213,7 +3214,7 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 			if (MASKBIT(i) & obSat)
 				continue;
 			pOBExpr = sqlExprSkipCollate(pOrderBy->a[i].pExpr);
-			if (pOBExpr->op != TK_COLUMN)
+			if (pOBExpr->op != TK_COLUMN_NAME)
 				continue;
 			if (pOBExpr->iTable != iCur)
 				continue;
@@ -3361,7 +3362,8 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 					if ((wctrlFlags & (WHERE_GROUPBY | WHERE_DISTINCTBY)) == 0)
 						bOnce = 0;
 					if (iColumn >= (-1)) {
-						if (pOBExpr->op != TK_COLUMN)
+						if (pOBExpr->op !=
+						    TK_COLUMN_NAME)
 							continue;
 						if (pOBExpr->iTable != iCur)
 							continue;
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index d9b5c78f5..c2ce4339f 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -279,7 +279,7 @@ like_optimization_is_valid(Parse *pParse, Expr *pExpr, Expr **ppPrefix,
 	pList = pExpr->x.pList;
 	pLeft = pList->a[1].pExpr;
 	/* Value might be numeric */
-	if (pLeft->op != TK_COLUMN ||
+	if (pLeft->op != TK_COLUMN_NAME ||
 	    sql_expr_type(pLeft) != FIELD_TYPE_STRING) {
 		/* IMP: R-02065-49465 The left-hand side of the
 		 * LIKE operator must be the name of an indexed
@@ -928,7 +928,7 @@ exprSelectUsage(WhereMaskSet * pMaskSet, Select * pS)
  * number of the table that is indexed and *piColumn to the column number
  * of the column that is indexed.
  *
- * If pExpr is a TK_COLUMN column reference, then this routine always returns
+ * If pExpr is a TK_COLUMN_NAME column reference, then this routine always returns
  * true even if that particular column is not indexed, because the column
  * might be added to an automatic index later.
  */
@@ -950,7 +950,7 @@ exprMightBeIndexed(int op,	/* The specific comparison operator */
 		pExpr = pExpr->x.pList->a[0].pExpr;
 	}
 
-	if (pExpr->op == TK_COLUMN) {
+	if (pExpr->op == TK_COLUMN_NAME) {
 		*piCur = pExpr->iTable;
 		*piColumn = pExpr->iColumn;
 		return 1;
@@ -1272,7 +1272,7 @@ exprAnalyze(SrcList * pSrc,	/* the FROM clause */
 	 * Note that the virtual term must be tagged with TERM_VNULL.
 	 */
 	if (pExpr->op == TK_NOTNULL
-	    && pExpr->pLeft->op == TK_COLUMN
+	    && pExpr->pLeft->op == TK_COLUMN_NAME
 	    && pExpr->pLeft->iColumn >= 0) {
 		Expr *pNewExpr;
 		Expr *pLeft = pExpr->pLeft;
@@ -1397,7 +1397,7 @@ sqlWhereExprUsage(WhereMaskSet * pMaskSet, Expr * p)
 	Bitmask mask;
 	if (p == 0)
 		return 0;
-	if (p->op == TK_COLUMN) {
+	if (p->op == TK_COLUMN_NAME) {
 		mask = sqlWhereGetMask(pMaskSet, p->iTable);
 		return mask;
 	}
@@ -1475,7 +1475,7 @@ sqlWhereTabFuncArgs(Parse * pParse,	/* Parsing context */
 		 * unused.
 		 */
 		assert(k < (int)space_def->field_count);
-		pColRef = sql_expr_new_anon(pParse->db, TK_COLUMN);
+		pColRef = sql_expr_new_anon(pParse->db, TK_COLUMN_NAME);
 		if (pColRef == NULL) {
 			pParse->is_aborted = true;
 			return;
-- 
2.21.0 (Apple Git-122)

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

* [Tarantool-patches] [PATCH v2 2/2] sql: support column addition
  2020-04-03 15:27 [Tarantool-patches] [PATCH v2 0/2] Support column addition with <ALTER TABLE> Roman Khabibov
  2020-04-03 15:27 ` [Tarantool-patches] [PATCH v2 1/2] sql: rename TK_COLUMN to TK_COLUMN_NAME Roman Khabibov
@ 2020-04-03 15:27 ` Roman Khabibov
  2020-04-24 22:56   ` Vladislav Shpilevoy
  2020-04-03 17:40 ` [Tarantool-patches] [PATCH v2 0/2] Support column addition with <ALTER TABLE> Roman Khabibov
  2020-11-18 17:23 ` Alexander V. Tikhonov
  3 siblings, 1 reply; 14+ messages in thread
From: Roman Khabibov @ 2020-04-03 15:27 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

Enable to add column to existing space with
<ALTER TABLE ADD [COLUMN]> statement. Column definition can be
supplemented with the four types of constraints, <DEFAULT>,
<COLLATE> clauses and <[NOT] NULL>, AUTOINCREMENT.

Closes #2349, #3075

@TarantoolBot document
Title: Add columns to existing tables in SQL
Now, it is possible to add columns to existing empty spaces using
<ALTER TABLE table_name ADD [COLUMN] column_name column_type ...>
statement. The column definition is the same as in <CREATE TABLE>
statement, except that table constraints (PRIMARY KEY, UNIQUE,
REFERENCES, CHECK) cannot be specified yet.

For example:

tarantool> box.execute([[CREATE TABLE test (
                           a INTEGER PRIMARY KEY
                      );]])
---
- row_count: 1
...

tarantool> box.execute([[ALTER TABLE test ADD COLUMN
                           b TEXT
                           NOT NULL
                           DEFAULT ('a')
                           COLLATE "unicode_ci"
                       ;]])
---
- row_count: 0
...
---
---
 src/box/errcode.h            |   2 +
 src/box/schema_def.h         |   1 +
 src/box/sql/alter.c          |   1 +
 src/box/sql/build.c          | 594 ++++++++++++++++++++++++-----------
 src/box/sql/parse.y          |  45 ++-
 src/box/sql/parse_def.h      |  62 ++--
 src/box/sql/prepare.c        |   8 +-
 src/box/sql/sqlInt.h         |  50 ++-
 test/box/error.result        |   2 +
 test/sql/add-column.result   | 231 ++++++++++++++
 test/sql/add-column.test.lua |  87 +++++
 11 files changed, 836 insertions(+), 247 deletions(-)
 create mode 100644 test/sql/add-column.result
 create mode 100644 test/sql/add-column.test.lua

diff --git a/src/box/errcode.h b/src/box/errcode.h
index 444171778..2409b07b9 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -265,6 +265,8 @@ struct errcode_record {
 	/*210 */_(ER_SQL_PREPARE,		"Failed to prepare SQL statement: %s") \
 	/*211 */_(ER_WRONG_QUERY_ID,		"Prepared statement with id %u does not exist") \
 	/*212 */_(ER_SEQUENCE_NOT_STARTED,		"Sequence '%s' is not started") \
+	/*213 */_(ER_SQL_CANT_ADD_COLUMN_TO_VIEW,	"Can't add column '%s'. '%s' is a view") \
+	/*214 */_(ER_SQL_CANT_ADD_AUTOINC,	"Can't add AUTOINCREMENT: the space '%s' already has one auto-incremented field") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/schema_def.h b/src/box/schema_def.h
index f86cd42f1..f2af9e23f 100644
--- a/src/box/schema_def.h
+++ b/src/box/schema_def.h
@@ -131,6 +131,7 @@ enum {
 	BOX_SPACE_FIELD_FIELD_COUNT = 4,
 	BOX_SPACE_FIELD_OPTS = 5,
 	BOX_SPACE_FIELD_FORMAT = 6,
+	box_space_field_MAX = 7
 };
 
 /** _index fields. */
diff --git a/src/box/sql/alter.c b/src/box/sql/alter.c
index 14f6c1a97..e2bf2b7e9 100644
--- a/src/box/sql/alter.c
+++ b/src/box/sql/alter.c
@@ -36,6 +36,7 @@
 #include "sqlInt.h"
 #include "box/box.h"
 #include "box/schema.h"
+#include "tarantoolInt.h"
 
 void
 sql_alter_table_rename(struct Parse *parse)
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 4552f10ab..2c68af659 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -285,48 +285,112 @@ sql_field_retrieve(Parse *parser, struct space_def *space_def, uint32_t id)
 	return field;
 }
 
-/*
- * Add a new column to the table currently being constructed.
+/**
+ * Make shallow copy of @a space on region.
  *
- * The parser calls this routine once for each column declaration
- * in a CREATE TABLE statement.  sqlStartTable() gets called
- * first to get things going.  Then this routine is called for each
- * column.
+ * Function is used to add a new column to the existing space with
+ * <ALTER TABLE ADD COLUMN>. Copy info about indexes and
+ * definition to create constraints appeared in the statement.
  */
+static struct space *
+sql_shallow_space_copy(struct Parse *parse, struct space *space)
+{
+	assert(space->def != NULL);
+	struct space *ret = sql_ephemeral_space_new(parse, space->def->name);
+	if (ret == NULL)
+		return NULL;
+	ret->index_count = space->index_count;
+	ret->index_id_max = space->index_id_max;
+	uint32_t indexes_sz = sizeof(struct index *) * (ret->index_count);
+	ret->index = (struct index **) malloc(indexes_sz);
+	if (ret->index == NULL) {
+		diag_set(OutOfMemory, indexes_sz, "realloc", "ret->index");
+		return NULL;
+	}
+	for (uint32_t i = 0; i < ret->index_count; i++)
+		ret->index[i] = space->index[i];
+	memcpy(ret->def, space->def, sizeof(struct space_def));
+	ret->def->opts.is_temporary = true;
+	ret->def->opts.is_ephemeral = true;
+	uint32_t fields_size = sizeof(struct field_def) * ret->def->field_count;
+	ret->def->fields = region_alloc(&parse->region, fields_size);
+	if (ret->def->fields == NULL) {
+		diag_set(OutOfMemory, fields_size, "region_alloc",
+			 "ret->def->fields");
+		return NULL;
+	}
+	memcpy(ret->def->fields, space->def->fields, fields_size);
+
+	return ret;
+}
+
 void
-sqlAddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
+sql_create_column_start(struct Parse *parse)
 {
-	assert(type_def != NULL);
-	char *z;
-	sql *db = pParse->db;
-	if (pParse->create_table_def.new_space == NULL)
-		return;
-	struct space_def *def = pParse->create_table_def.new_space->def;
+	struct create_column_def *create_column_def = &parse->create_column_def;
+	struct alter_entity_def *alter_entity_def =
+		&create_column_def->base.base;
+	assert(alter_entity_def->entity_type == ENTITY_TYPE_COLUMN);
+	assert(alter_entity_def->alter_action == ALTER_ACTION_CREATE);
+	struct space *space = parse->create_table_def.new_space;
+	bool is_alter = space == NULL;
+	struct sql *db = parse->db;
+	if (is_alter) {
+		const char *space_name =
+			alter_entity_def->entity_name->a[0].zName;
+		space = space_by_name(space_name);
+		if (space == NULL) {
+			diag_set(ClientError, ER_NO_SUCH_SPACE, space_name);
+			goto tnt_error;
+		}
+		space = sql_shallow_space_copy(parse, space);
+		if (space == NULL)
+			goto tnt_error;
+	}
+	create_column_def->space = space;
+	struct space_def *def = space->def;
+	assert(def->opts.is_ephemeral);
 
 #if SQL_MAX_COLUMN
 	if ((int)def->field_count + 1 > db->aLimit[SQL_LIMIT_COLUMN]) {
 		diag_set(ClientError, ER_SQL_COLUMN_COUNT_MAX, def->name,
 			 def->field_count + 1, db->aLimit[SQL_LIMIT_COLUMN]);
-		pParse->is_aborted = true;
-		return;
+		goto tnt_error;
 	}
 #endif
+
+	struct region *region = &parse->region;
+	struct Token *name = &create_column_def->base.name;
+	char *column_name =
+		sql_normalized_name_region_new(region, name->z, name->n);
+	if (column_name == NULL)
+		goto tnt_error;
+
+	if (parse->create_table_def.new_space == NULL && def->opts.is_view) {
+		diag_set(ClientError, ER_SQL_CANT_ADD_COLUMN_TO_VIEW,
+			 column_name, def->name);
+		goto tnt_error;
+	}
+
 	/*
-	 * As sql_field_retrieve will allocate memory on region
-	 * ensure that def is also temporal and would be dropped.
+	 * Format can be set in Lua, then exact_field_count can be
+	 * zero, but field_count is not.
 	 */
-	assert(def->opts.is_ephemeral);
-	if (sql_field_retrieve(pParse, def, def->field_count) == NULL)
-		return;
-	struct region *region = &pParse->region;
-	z = sql_normalized_name_region_new(region, pName->z, pName->n);
-	if (z == NULL) {
-		pParse->is_aborted = true;
+	if (def->exact_field_count == 0)
+		def->exact_field_count = def->field_count;
+	if (sql_field_retrieve(parse, def, def->field_count) == NULL)
 		return;
+
+	for (uint32_t i = 0; i < def->field_count; i++) {
+		if (strcmp(column_name, def->fields[i].name) == 0) {
+			diag_set(ClientError, ER_SPACE_FIELD_IS_DUPLICATE,
+				 column_name);
+			goto tnt_error;
+		}
 	}
 	struct field_def *column_def = &def->fields[def->field_count];
-	memcpy(column_def, &field_def_default, sizeof(field_def_default));
-	column_def->name = z;
+	memcpy(column_def, &field_def_default, sizeof(struct field_def));
+	column_def->name = column_name;
 	/*
 	 * Marker ON_CONFLICT_ACTION_DEFAULT is used to detect
 	 * attempts to define NULL multiple time or to detect
@@ -334,19 +398,101 @@ sqlAddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
 	 */
 	column_def->nullable_action = ON_CONFLICT_ACTION_DEFAULT;
 	column_def->is_nullable = true;
-	column_def->type = type_def->type;
+	column_def->type = create_column_def->type_def->type;
 	def->field_count++;
+
+exit_sql_create_column_start:
+	sqlSrcListDelete(db, alter_entity_def->entity_name);
+	return;
+tnt_error:
+	parse->is_aborted = true;
+	goto exit_sql_create_column_start;
+}
+
+static void
+sql_vdbe_create_constraints(struct Parse *parse, int reg_space_id);
+
+void
+sql_create_column_end(struct Parse *parse)
+{
+	struct create_column_def *create_column_def = &parse->create_column_def;
+	struct space *space = parse->create_table_def.new_space;
+	bool is_alter = space == NULL;
+	space = create_column_def->space;
+	struct space_def *def = space->def;
+	if (is_alter) {
+		struct field_def *field = &def->fields[def->field_count - 1];
+		if (field->nullable_action == ON_CONFLICT_ACTION_DEFAULT) {
+			if (create_column_def->is_pk) {
+				field->nullable_action =
+					ON_CONFLICT_ACTION_ABORT;
+				field->is_nullable = false;
+			} else {
+				field->nullable_action =
+					ON_CONFLICT_ACTION_NONE;
+				field->is_nullable = true;
+			}
+		}
+		/*
+		 * Encode the format array and emit code to update _space.
+		 */
+		uint32_t table_stmt_sz = 0;
+		struct region *region = &parse->region;
+		char *table_stmt = sql_encode_table(region, def,
+						    &table_stmt_sz);
+		char *raw = sqlDbMallocRaw(parse->db, table_stmt_sz);
+		if (table_stmt == NULL || raw == NULL) {
+			parse->is_aborted = true;
+			return;
+		}
+		memcpy(raw, table_stmt, table_stmt_sz);
+
+		struct Vdbe *v = sqlGetVdbe(parse);
+		assert(v != NULL);
+
+		struct space *system_space = space_by_id(BOX_SPACE_ID);
+		assert(system_space != NULL);
+		int cursor = parse->nTab++;
+		vdbe_emit_open_cursor(parse, cursor, 0, system_space);
+		sqlVdbeChangeP5(v, OPFLAG_SYSTEMSP);
+
+		int key_reg = ++parse->nMem;
+		sqlVdbeAddOp2(v, OP_Integer, def->id, key_reg);
+		int addr = sqlVdbeAddOp4Int(v, OP_Found, cursor, 0, key_reg, 1);
+		sqlVdbeAddOp2(v, OP_Halt, -1, ON_CONFLICT_ACTION_ABORT);
+		sqlVdbeJumpHere(v, addr);
+
+		int tuple_reg = sqlGetTempRange(parse, box_space_field_MAX + 1);
+		for (int i = 0; i < box_space_field_MAX - 1; ++i)
+			sqlVdbeAddOp3(v, OP_Column, cursor, i, tuple_reg + i);
+		sqlVdbeAddOp1(v, OP_Close, cursor);
+
+		sqlVdbeAddOp2(v, OP_Integer, def->field_count, tuple_reg + 4);
+		sqlVdbeAddOp4(v, OP_Blob, table_stmt_sz, tuple_reg + 6,
+			      SQL_SUBTYPE_MSGPACK, raw, P4_DYNAMIC);
+		sqlVdbeAddOp3(v, OP_MakeRecord, tuple_reg, box_space_field_MAX,
+			      tuple_reg + box_space_field_MAX);
+		sqlVdbeAddOp4(v, OP_IdxReplace, tuple_reg + box_space_field_MAX,
+			      0, 0, (char *) system_space, P4_SPACEPTR);
+		sql_vdbe_create_constraints(parse, key_reg);
+	}
+
+	memset(create_column_def, 0, sizeof(struct create_column_def));
+	create_column_def->nullable_action = ON_CONFLICT_ACTION_DEFAULT;
 }
 
 void
 sql_column_add_nullable_action(struct Parse *parser,
 			       enum on_conflict_action nullable_action)
 {
-	struct space *space = parser->create_table_def.new_space;
-	if (space == NULL || NEVER(space->def->field_count < 1))
+	struct space_def *def = NULL;
+	struct field_def *field = NULL;
+	struct space *space = parser->create_column_def.space;
+	assert (space != NULL);
+	def = space->def;
+	if (NEVER(def->field_count < 1))
 		return;
-	struct space_def *def = space->def;
-	struct field_def *field = &def->fields[def->field_count - 1];
+	field = &def->fields[def->field_count - 1];
 	if (field->nullable_action != ON_CONFLICT_ACTION_DEFAULT &&
 	    nullable_action != field->nullable_action) {
 		/* Prevent defining nullable_action many times. */
@@ -364,51 +510,46 @@ sql_column_add_nullable_action(struct Parse *parser,
 }
 
 /*
- * The expression is the default value for the most recently added column
- * of the table currently under construction.
+ * The expression is the default value for the most recently added
+ * column.
  *
  * Default value expressions must be constant.  Raise an exception if this
  * is not the case.
  *
  * This routine is called by the parser while in the middle of
- * parsing a CREATE TABLE statement.
+ * parsing a <CREATE TABLE> or a <ALTER TABLE ADD COLUMN>
+ * statement.
  */
 void
 sqlAddDefaultValue(Parse * pParse, ExprSpan * pSpan)
 {
 	sql *db = pParse->db;
-	struct space *p = pParse->create_table_def.new_space;
-	if (p != NULL) {
-		assert(p->def->opts.is_ephemeral);
-		struct space_def *def = p->def;
-		if (!sqlExprIsConstantOrFunction
-		    (pSpan->pExpr, db->init.busy)) {
-			const char *column_name =
-				def->fields[def->field_count - 1].name;
-			diag_set(ClientError, ER_CREATE_SPACE, def->name,
-				 tt_sprintf("default value of column '%s' is "\
-					    "not constant", column_name));
+	struct space_def *def = NULL;
+	struct field_def *field = NULL;
+	struct space *space = pParse->create_column_def.space;
+	assert (space != NULL);
+	def = space->def;
+	field = &def->fields[def->field_count - 1];
+	if (!sqlExprIsConstantOrFunction(pSpan->pExpr, db->init.busy)) {
+		diag_set(ClientError, ER_CREATE_SPACE, def->name,
+			 tt_sprintf("default value of column '%s' is not "
+				    "constant", field->name));
+		pParse->is_aborted = true;
+	} else {
+		assert(def != NULL);
+		struct region *region = &pParse->region;
+		uint32_t default_length = (int)(pSpan->zEnd - pSpan->zStart);
+		field->default_value = region_alloc(region, default_length + 1);
+		if (field->default_value == NULL) {
+			diag_set(OutOfMemory, default_length + 1,
+				 "region_alloc", "field->default_value");
 			pParse->is_aborted = true;
-		} else {
-			assert(def != NULL);
-			struct field_def *field =
-				&def->fields[def->field_count - 1];
-			struct region *region = &pParse->region;
-			uint32_t default_length = (int)(pSpan->zEnd - pSpan->zStart);
-			field->default_value = region_alloc(region,
-							    default_length + 1);
-			if (field->default_value == NULL) {
-				diag_set(OutOfMemory, default_length + 1,
-					 "region_alloc",
-					 "field->default_value");
-				pParse->is_aborted = true;
-				return;
-			}
-			strncpy(field->default_value, pSpan->zStart,
-				default_length);
-			field->default_value[default_length] = '\0';
+			goto add_default_value_exit;
 		}
+		strncpy(field->default_value, pSpan->zStart, default_length);
+		field->default_value[default_length] = '\0';
 	}
+add_default_value_exit:
 	sql_expr_delete(db, pSpan->pExpr, false);
 }
 
@@ -447,6 +588,8 @@ sqlAddPrimaryKey(struct Parse *pParse)
 	int nTerm;
 	struct ExprList *pList = pParse->create_index_def.cols;
 	struct space *space = pParse->create_table_def.new_space;
+	if (space == NULL)
+		space = pParse->create_column_def.space;
 	if (space == NULL)
 		goto primary_key_exit;
 	if (sql_space_primary_key(space) != NULL) {
@@ -574,8 +717,10 @@ sql_create_check_contraint(struct Parse *parser)
 		(struct alter_entity_def *) create_ck_def;
 	assert(alter_def->entity_type == ENTITY_TYPE_CK);
 	(void) alter_def;
-	struct space *space = parser->create_table_def.new_space;
-	bool is_alter = space == NULL;
+	struct space *space = parser->create_column_def.space;
+	if (space == NULL)
+		space = parser->create_table_def.new_space;
+	bool is_alter_add_constr = space == NULL;
 
 	/* Prepare payload for ck constraint definition. */
 	struct region *region = &parser->region;
@@ -589,9 +734,18 @@ sql_create_check_contraint(struct Parse *parser)
 			return;
 		}
 	} else {
-		assert(! is_alter);
-		uint32_t ck_idx = ++parser->create_table_def.check_count;
-		name = tt_sprintf("ck_unnamed_%s_%d", space->def->name, ck_idx);
+		assert(!is_alter_add_constr);
+		uint32_t idx = ++parser->check_count;
+		if (parser->create_table_def.new_space == NULL) {
+			struct space *original_space =
+				space_by_name(space->def->name);
+			assert(original_space != NULL);
+			struct rlist *checks = &original_space->ck_constraint;
+			struct ck_constraint *ck;
+			rlist_foreach_entry(ck, checks, link)
+				idx++;
+		}
+		name = tt_sprintf("ck_unnamed_%s_%d", space->def->name, idx);
 	}
 	size_t name_len = strlen(name);
 
@@ -629,9 +783,9 @@ sql_create_check_contraint(struct Parse *parser)
 	trim_space_snprintf(ck_def->expr_str, expr_str, expr_str_len);
 	memcpy(ck_def->name, name, name_len);
 	ck_def->name[name_len] = '\0';
-	if (is_alter) {
+	if (is_alter_add_constr) {
 		const char *space_name = alter_def->entity_name->a[0].zName;
-		struct space *space = space_by_name(space_name);
+		space = space_by_name(space_name);
 		if (space == NULL) {
 			diag_set(ClientError, ER_NO_SUCH_SPACE, space_name);
 			parser->is_aborted = true;
@@ -647,8 +801,7 @@ sql_create_check_contraint(struct Parse *parser)
 		sqlVdbeCountChanges(v);
 		sqlVdbeChangeP5(v, OPFLAG_NCHANGE);
 	} else {
-		rlist_add_entry(&parser->create_table_def.new_check, ck_parse,
-				link);
+		rlist_add_entry(&parser->checks, ck_parse, link);
 	}
 }
 
@@ -659,18 +812,19 @@ sql_create_check_contraint(struct Parse *parser)
 void
 sqlAddCollateType(Parse * pParse, Token * pToken)
 {
-	struct space *space = pParse->create_table_def.new_space;
-	if (space == NULL)
-		return;
+	struct space *space = pParse->create_column_def.space;
+	uint32_t *coll_id = NULL;
+	assert(space != NULL);
 	uint32_t i = space->def->field_count - 1;
+	coll_id = &space->def->fields[i].coll_id;
 	sql *db = pParse->db;
 	char *coll_name = sql_name_from_token(db, pToken);
 	if (coll_name == NULL) {
 		pParse->is_aborted = true;
 		return;
 	}
-	uint32_t *coll_id = &space->def->fields[i].coll_id;
-	if (sql_get_coll_seq(pParse, coll_name, coll_id) != NULL) {
+	if (sql_get_coll_seq(pParse, coll_name, coll_id) != NULL &&
+	    space != NULL) {
 		/* If the column is declared as "<name> PRIMARY KEY COLLATE <type>",
 		 * then an index may have been created on this column before the
 		 * collation type was added. Correct this if it is the case.
@@ -687,6 +841,7 @@ sqlAddCollateType(Parse * pParse, Token * pToken)
 	sqlDbFree(db, coll_name);
 }
 
+
 struct coll *
 sql_column_collation(struct space_def *def, uint32_t column, uint32_t *coll_id)
 {
@@ -700,8 +855,7 @@ sql_column_collation(struct space_def *def, uint32_t column, uint32_t *coll_id)
 	 *
 	 * In cases mentioned above collation is fetched by id.
 	 */
-	if (space == NULL) {
-		assert(def->opts.is_ephemeral);
+	if (def->opts.is_ephemeral) {
 		assert(column < (uint32_t)def->field_count);
 		*coll_id = def->fields[column].coll_id;
 		struct coll_id *collation = coll_by_id(*coll_id);
@@ -781,7 +935,8 @@ vdbe_emit_create_index(struct Parse *parse, struct space_def *def,
 	memcpy(raw, index_parts, index_parts_sz);
 	index_parts = raw;
 
-	if (parse->create_table_def.new_space != NULL) {
+	if (parse->create_table_def.new_space != NULL ||
+	    parse->create_column_def.space != NULL) {
 		sqlVdbeAddOp2(v, OP_SCopy, space_id_reg, entry_reg);
 		sqlVdbeAddOp2(v, OP_Integer, idx_def->iid, entry_reg + 1);
 	} else {
@@ -916,7 +1071,7 @@ emitNewSysSequenceRecord(Parse *pParse, int reg_seq_id, const char *seq_name)
 static int
 emitNewSysSpaceSequenceRecord(Parse *pParse, int reg_space_id, int reg_seq_id)
 {
-	uint32_t fieldno = pParse->create_table_def.autoinc_fieldno;
+	uint32_t fieldno = pParse->autoinc_fieldno;
 
 	Vdbe *v = sqlGetVdbe(pParse);
 	int first_col = pParse->nMem + 1;
@@ -1019,18 +1174,21 @@ vdbe_emit_fk_constraint_create(struct Parse *parse_context,
 			  P4_DYNAMIC);
 	/*
 	 * In case we are adding FK constraints during execution
-	 * of <CREATE TABLE ...> statement, we don't have child
-	 * id, but we know register where it will be stored.
+	 * of <CREATE TABLE ...> or <ALER TABLE ADD COLUMN ...>
+	 * statement, we don't have child id, but we know register
+	 * where it will be stored.
 	 */
-	if (parse_context->create_table_def.new_space != NULL) {
+	bool is_alter_add_constr =
+		parse_context->create_table_def.new_space == NULL &&
+		parse_context->create_column_def.space == NULL;
+	if (!is_alter_add_constr) {
 		sqlVdbeAddOp2(vdbe, OP_SCopy, fk->child_id,
 				  constr_tuple_reg + 1);
 	} else {
 		sqlVdbeAddOp2(vdbe, OP_Integer, fk->child_id,
 				  constr_tuple_reg + 1);
 	}
-	if (parse_context->create_table_def.new_space != NULL &&
-	    fk_constraint_is_self_referenced(fk)) {
+	if (!is_alter_add_constr && fk_constraint_is_self_referenced(fk)) {
 		sqlVdbeAddOp2(vdbe, OP_SCopy, fk->parent_id,
 				  constr_tuple_reg + 2);
 	} else {
@@ -1094,7 +1252,7 @@ vdbe_emit_fk_constraint_create(struct Parse *parse_context,
 			  constr_tuple_reg + 9);
 	sqlVdbeAddOp2(vdbe, OP_SInsert, BOX_FK_CONSTRAINT_ID,
 		      constr_tuple_reg + 9);
-	if (parse_context->create_table_def.new_space == NULL) {
+	if (is_alter_add_constr) {
 		sqlVdbeCountChanges(vdbe);
 		sqlVdbeChangeP5(vdbe, OPFLAG_NCHANGE);
 	}
@@ -1133,6 +1291,105 @@ resolve_link(struct Parse *parse_context, const struct space_def *def,
 	return -1;
 }
 
+/**
+ * Emit code to create sequences, indexes, check and foreign
+ * constraints appeared in <CREATE TABLE> or
+ * <ALTER TABLE ADD COLUMN> statement.
+ */
+static void
+sql_vdbe_create_constraints(struct Parse *parse, int reg_space_id)
+{
+	assert(reg_space_id != 0);
+	struct space *space = parse->create_table_def.new_space;
+	bool is_alter = space == NULL;
+	uint32_t i = 0;
+	if (is_alter) {
+		space = parse->create_column_def.space;
+		i = space_by_name(space->def->name)->index_count;
+	}
+	assert(space != NULL);
+	for (; i < space->index_count; ++i) {
+		struct index *idx = space->index[i];
+		vdbe_emit_create_index(parse, space->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.
+	 */
+	if (parse->has_autoinc) {
+		/* Do an insertion into _sequence. */
+		int reg_seq_id = ++parse->nMem;
+		struct Vdbe *v = sqlGetVdbe(parse);
+		assert(v != NULL);
+		sqlVdbeAddOp2(v, OP_NextSequenceId, 0, reg_seq_id);
+		int reg_seq_record =
+			emitNewSysSequenceRecord(parse, reg_seq_id,
+						 space->def->name);
+		const char *error_msg =
+			tt_sprintf(tnt_errcode_desc(ER_SQL_CANT_ADD_AUTOINC),
+				   space->def->name);
+		if (vdbe_emit_halt_with_presence_test(parse,
+						      BOX_SEQUENCE_ID, 2,
+						      reg_seq_record + 3, 1,
+						      ER_SQL_CANT_ADD_AUTOINC,
+						      error_msg, false,
+						      OP_NoConflict) != 0)
+			return;
+		sqlVdbeAddOp2(v, OP_SInsert, BOX_SEQUENCE_ID, reg_seq_record);
+		/* Do an insertion into _space_sequence. */
+		int reg_space_seq_record =
+			emitNewSysSpaceSequenceRecord(parse, reg_space_id,
+						      reg_seq_id);
+		sqlVdbeAddOp2(v, OP_SInsert, BOX_SPACE_SEQUENCE_ID,
+			      reg_space_seq_record);
+	}
+
+	/* Code creation of FK constraints, if any. */
+	struct fk_constraint_parse *fk_parse;
+	rlist_foreach_entry(fk_parse, &parse->fkeys, link) {
+		struct fk_constraint_def *fk_def = fk_parse->fk_def;
+		if (fk_parse->selfref_cols != NULL) {
+			struct ExprList *cols = fk_parse->selfref_cols;
+			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
+				if (resolve_link(parse, space->def,
+						 cols->a[i].zName,
+						 &fk_def->links[i].parent_field,
+						 fk_def->name) != 0)
+					return;
+			}
+			fk_def->parent_id = reg_space_id;
+		} else if (fk_parse->is_self_referenced) {
+			struct key_def *pk_key_def =
+				sql_space_primary_key(space)->def->key_def;
+			if (pk_key_def->part_count != fk_def->field_count) {
+				diag_set(ClientError, ER_CREATE_FK_CONSTRAINT,
+					 fk_def->name, "number of columns in "\
+					 "foreign key does not match the "\
+					 "number of columns in the primary "\
+					 "index of referenced table");
+				parse->is_aborted = true;
+				return;
+			}
+			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
+				fk_def->links[i].parent_field =
+					pk_key_def->parts[i].fieldno;
+			}
+			fk_def->parent_id = reg_space_id;
+		}
+		fk_def->child_id = reg_space_id;
+		vdbe_emit_fk_constraint_create(parse, fk_def, space->def->name);
+	}
+
+	/* Code creation of CK constraints, if any. */
+	struct ck_constraint_parse *ck_parse;
+	rlist_foreach_entry(ck_parse, &parse->checks, link) {
+		vdbe_emit_ck_constraint_create(parse, ck_parse->ck_def,
+					       reg_space_id, space->def->name);
+	}
+}
+
 /*
  * This routine is called to report the final ")" that terminates
  * a CREATE TABLE statement.
@@ -1199,73 +1456,7 @@ sqlEndTable(struct Parse *pParse)
 
 	int reg_space_id = getNewSpaceId(pParse);
 	vdbe_emit_space_create(pParse, reg_space_id, name_reg, new_space);
-	for (uint32_t i = 0; i < new_space->index_count; ++i) {
-		struct index *idx = new_space->index[i];
-		vdbe_emit_create_index(pParse, new_space->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.
-	 */
-	if (pParse->create_table_def.has_autoinc) {
-		assert(reg_space_id != 0);
-		/* Do an insertion into _sequence. */
-		int reg_seq_id = ++pParse->nMem;
-		sqlVdbeAddOp2(v, OP_NextSequenceId, 0, reg_seq_id);
-		int reg_seq_record =
-			emitNewSysSequenceRecord(pParse, reg_seq_id,
-						 new_space->def->name);
-		sqlVdbeAddOp2(v, OP_SInsert, BOX_SEQUENCE_ID, reg_seq_record);
-		/* Do an insertion into _space_sequence. */
-		int reg_space_seq_record =
-			emitNewSysSpaceSequenceRecord(pParse, reg_space_id,
-						      reg_seq_id);
-		sqlVdbeAddOp2(v, OP_SInsert, BOX_SPACE_SEQUENCE_ID,
-			      reg_space_seq_record);
-	}
-	/* Code creation of FK constraints, if any. */
-	struct fk_constraint_parse *fk_parse;
-	rlist_foreach_entry(fk_parse, &pParse->create_table_def.new_fkey,
-			    link) {
-		struct fk_constraint_def *fk_def = fk_parse->fk_def;
-		if (fk_parse->selfref_cols != NULL) {
-			struct ExprList *cols = fk_parse->selfref_cols;
-			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
-				if (resolve_link(pParse, new_space->def,
-						 cols->a[i].zName,
-						 &fk_def->links[i].parent_field,
-						 fk_def->name) != 0)
-					return;
-			}
-			fk_def->parent_id = reg_space_id;
-		} else if (fk_parse->is_self_referenced) {
-			struct index *pk = sql_space_primary_key(new_space);
-			if (pk->def->key_def->part_count != fk_def->field_count) {
-				diag_set(ClientError, ER_CREATE_FK_CONSTRAINT,
-					 fk_def->name, "number of columns in "\
-					 "foreign key does not match the "\
-					 "number of columns in the primary "\
-					 "index of referenced table");
-				pParse->is_aborted = true;
-				return;
-			}
-			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
-				fk_def->links[i].parent_field =
-					pk->def->key_def->parts[i].fieldno;
-			}
-			fk_def->parent_id = reg_space_id;
-		}
-		fk_def->child_id = reg_space_id;
-		vdbe_emit_fk_constraint_create(pParse, fk_def, space_name_copy);
-	}
-	struct ck_constraint_parse *ck_parse;
-	rlist_foreach_entry(ck_parse, &pParse->create_table_def.new_check,
-			    link) {
-		vdbe_emit_ck_constraint_create(pParse, ck_parse->ck_def,
-					       reg_space_id, space_name_copy);
-	}
+	sql_vdbe_create_constraints(pParse, reg_space_id);
 }
 
 void
@@ -1844,24 +2035,28 @@ sql_create_foreign_key(struct Parse *parse_context)
 	char *parent_name = NULL;
 	char *constraint_name = NULL;
 	bool is_self_referenced = false;
+	struct space *space = parse_context->create_column_def.space;
 	struct create_table_def *table_def = &parse_context->create_table_def;
-	struct space *space = table_def->new_space;
+	if (space == NULL)
+		space = table_def->new_space;
 	/*
-	 * Space under construction during CREATE TABLE
-	 * processing. NULL for ALTER TABLE statement handling.
+	 * Space under construction during <CREATE TABLE>
+	 * processing or shallow copy of space during <ALTER TABLE
+	 * ... ADD COLUMN>. NULL for <ALTER TABLE ... ADD
+	 * CONSTRAINT> statement handling.
 	 */
-	bool is_alter = space == NULL;
+	bool is_alter_add_constr = space == NULL;
 	uint32_t child_cols_count;
 	struct ExprList *child_cols = create_fk_def->child_cols;
 	if (child_cols == NULL) {
-		assert(!is_alter);
+		assert(!is_alter_add_constr);
 		child_cols_count = 1;
 	} else {
 		child_cols_count = child_cols->nExpr;
 	}
 	struct ExprList *parent_cols = create_fk_def->parent_cols;
 	struct space *child_space = NULL;
-	if (is_alter) {
+	if (is_alter_add_constr) {
 		const char *child_name = alter_def->entity_name->a[0].zName;
 		child_space = space_by_name(child_name);
 		if (child_space == NULL) {
@@ -1877,7 +2072,9 @@ sql_create_foreign_key(struct Parse *parse_context)
 			goto tnt_error;
 		}
 		memset(fk_parse, 0, sizeof(*fk_parse));
-		rlist_add_entry(&table_def->new_fkey, fk_parse, link);
+		if (parse_context->create_column_def.space != NULL)
+			child_space = space;
+		rlist_add_entry(&parse_context->fkeys, fk_parse, link);
 	}
 	struct Token *parent = create_fk_def->parent_name;
 	assert(parent != NULL);
@@ -1889,13 +2086,13 @@ sql_create_foreign_key(struct Parse *parse_context)
 	 * self-referenced, but in this case parent (which is
 	 * also child) table will definitely exist.
 	 */
-	is_self_referenced = !is_alter &&
+	is_self_referenced = !is_alter_add_constr &&
 			     strcmp(parent_name, space->def->name) == 0;
 	struct space *parent_space = space_by_name(parent_name);
 	if (parent_space == NULL) {
-		if (is_self_referenced) {
+		if (is_self_referenced && table_def->new_space != NULL) {
 			struct fk_constraint_parse *fk =
-				rlist_first_entry(&table_def->new_fkey,
+				rlist_first_entry(&parse_context->fkeys,
 						  struct fk_constraint_parse,
 						  link);
 			fk->selfref_cols = parent_cols;
@@ -1905,12 +2102,24 @@ sql_create_foreign_key(struct Parse *parse_context)
 			goto tnt_error;
 		}
 	}
-	if (!is_alter) {
+	if (!is_alter_add_constr) {
 		if (create_def->name.n == 0) {
-			constraint_name =
-				sqlMPrintf(db, "fk_unnamed_%s_%d",
-					   space->def->name,
-					   ++table_def->fkey_count);
+			uint32_t idx = ++parse_context->fkey_count;
+			if (table_def->new_space == NULL) {
+				struct space *original_space =
+					space_by_name(space->def->name);
+				assert(original_space != NULL);
+				struct rlist *child_fk =
+					&original_space->child_fk_constraint;
+				if (!rlist_empty(child_fk)) {
+					struct fk_constraint *fk;
+					rlist_foreach_entry(fk, child_fk,
+							    in_child_space)
+						idx++;
+				}
+			}
+			constraint_name = sqlMPrintf(db, "fk_unnamed_%s_%d",
+						     space->def->name, idx);
 		} else {
 			constraint_name =
 				sql_name_from_token(db, &create_def->name);
@@ -1967,7 +2176,8 @@ sql_create_foreign_key(struct Parse *parse_context)
 	}
 	int actions = create_fk_def->actions;
 	fk_def->field_count = child_cols_count;
-	fk_def->child_id = child_space != NULL ? child_space->def->id : 0;
+	fk_def->child_id = table_def->new_space == NULL ?
+		child_space->def->id : 0;
 	fk_def->parent_id = parent_space != NULL ? parent_space->def->id : 0;
 	fk_def->is_deferred = create_constr_def->is_deferred;
 	fk_def->match = (enum fk_constraint_match) (create_fk_def->match);
@@ -1987,7 +2197,7 @@ sql_create_foreign_key(struct Parse *parse_context)
 					    constraint_name) != 0) {
 			goto exit_create_fk;
 		}
-		if (!is_alter) {
+		if (!is_alter_add_constr) {
 			if (child_cols == NULL) {
 				assert(i == 0);
 				/*
@@ -2016,14 +2226,15 @@ sql_create_foreign_key(struct Parse *parse_context)
 	memcpy(fk_def->name, constraint_name, name_len);
 	fk_def->name[name_len] = '\0';
 	/*
-	 * In case of CREATE TABLE processing, all foreign keys
-	 * constraints must be created after space itself, so
-	 * lets delay it until sqlEndTable() call and simply
+	 * In case of <CREATE TABLE> or <ALTER TABLE ... ADD
+	 * COLUMN> processing, all foreign keys  constraints  must
+	 * be created after space itself, so lets delay it until
+	 * sqlEndTable() or sql_add_column_end() call and simply
 	 * maintain list of all FK constraints inside parser.
 	 */
-	if (!is_alter) {
+	if (!is_alter_add_constr) {
 		struct fk_constraint_parse *fk_parse =
-			rlist_first_entry(&table_def->new_fkey,
+			rlist_first_entry(&parse_context->fkeys,
 					  struct fk_constraint_parse, link);
 		fk_parse->fk_def = fk_def;
 	} else {
@@ -2046,12 +2257,10 @@ tnt_error:
 void
 fk_constraint_change_defer_mode(struct Parse *parse_context, bool is_deferred)
 {
-	if (parse_context->db->init.busy ||
-	    rlist_empty(&parse_context->create_table_def.new_fkey))
+	if (parse_context->db->init.busy || rlist_empty(&parse_context->fkeys))
 		return;
-	rlist_first_entry(&parse_context->create_table_def.new_fkey,
-			  struct fk_constraint_parse, link)->fk_def->is_deferred =
-		is_deferred;
+	rlist_first_entry(&parse_context->fkeys, struct fk_constraint_parse,
+			  link)->fk_def->is_deferred = is_deferred;
 }
 
 /**
@@ -2373,7 +2582,10 @@ sql_create_index(struct Parse *parse) {
 	 * Find the table that is to be indexed.
 	 * Return early if not found.
 	 */
-	struct space *space = NULL;
+	struct space *space = parse->create_table_def.new_space;
+	if (space == NULL)
+		space = parse->create_column_def.space;
+	bool is_create_table_or_add_col = space != NULL;
 	struct Token token = create_entity_def->name;
 	if (tbl_name != NULL) {
 		assert(token.n > 0 && token.z != NULL);
@@ -2386,10 +2598,8 @@ sql_create_index(struct Parse *parse) {
 			}
 			goto exit_create_index;
 		}
-	} else {
-		if (parse->create_table_def.new_space == NULL)
-			goto exit_create_index;
-		space = parse->create_table_def.new_space;
+	} else if (space == NULL) {
+		goto exit_create_index;
 	}
 	struct space_def *def = space->def;
 
@@ -2424,7 +2634,7 @@ sql_create_index(struct Parse *parse) {
 	 * 2) UNIQUE constraint is non-named and standard
 	 *    auto-index name will be generated.
 	 */
-	if (parse->create_table_def.new_space == NULL) {
+	if (!is_create_table_or_add_col) {
 		assert(token.z != NULL);
 		name = sql_name_from_token(db, &token);
 		if (name == NULL) {
@@ -2590,7 +2800,7 @@ sql_create_index(struct Parse *parse) {
 	 * constraint, but has different onError (behavior on
 	 * constraint violation), then an error is raised.
 	 */
-	if (parse->create_table_def.new_space != NULL) {
+	if (is_create_table_or_add_col) {
 		for (uint32_t i = 0; i < space->index_count; ++i) {
 			struct index *existing_idx = space->index[i];
 			uint32_t iid = existing_idx->def->iid;
@@ -2678,7 +2888,7 @@ sql_create_index(struct Parse *parse) {
 		sqlVdbeAddOp0(vdbe, OP_Expire);
 	}
 
-	if (tbl_name != NULL)
+	if (!is_create_table_or_add_col)
 		goto exit_create_index;
 	table_add_index(space, index);
 	index = NULL;
@@ -3285,15 +3495,15 @@ vdbe_emit_halt_with_presence_test(struct Parse *parser, int space_id,
 int
 sql_add_autoincrement(struct Parse *parse_context, uint32_t fieldno)
 {
-	if (parse_context->create_table_def.has_autoinc) {
+	if (parse_context->has_autoinc) {
 		diag_set(ClientError, ER_SQL_SYNTAX_WITH_POS,
 			 parse_context->line_count, parse_context->line_pos,
 			 "table must feature at most one AUTOINCREMENT field");
 		parse_context->is_aborted = true;
 		return -1;
 	}
-	parse_context->create_table_def.has_autoinc = true;
-	parse_context->create_table_def.autoinc_fieldno = fieldno;
+	parse_context->has_autoinc = true;
+	parse_context->autoinc_fieldno = fieldno;
 	return 0;
 }
 
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 1a0e89703..6ada8cd76 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -226,19 +226,23 @@ create_table_end ::= . { sqlEndTable(pParse); }
  */
 
 columnlist ::= columnlist COMMA tcons.
-columnlist ::= columnlist COMMA columnname carglist autoinc(I). {
-  uint32_t fieldno = pParse->create_table_def.new_space->def->field_count - 1;
-  if (I == 1 && sql_add_autoincrement(pParse, fieldno) != 0)
-    return;
+columnlist ::= columnlist COMMA column_def create_column_end.
+columnlist ::= column_def create_column_end.
+columnlist ::= tcons.
+
+column_def ::= column_name_and_type carglist.
+
+column_name_and_type ::= nm(A) typedef(Y). {
+  create_column_def_init(&pParse->create_column_def, NULL, &A, &Y);
+  sql_create_column_start(pParse);
 }
 
-columnlist ::= columnname carglist autoinc(I). {
-  uint32_t fieldno = pParse->create_table_def.new_space->def->field_count - 1;
+create_column_end ::= autoinc(I). {
+  uint32_t fieldno = pParse->create_column_def.space->def->field_count - 1;
   if (I == 1 && sql_add_autoincrement(pParse, fieldno) != 0)
     return;
+  sql_create_column_end(pParse);
 }
-columnlist ::= tcons.
-columnname(A) ::= nm(A) typedef(Y). {sqlAddColumn(pParse,&A,&Y);}
 
 // An IDENTIFIER can be a generic identifier, or one of several
 // keywords.  Any non-standard keyword can also be an identifier.
@@ -281,9 +285,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" is a list of additional constraints and clauses that
+ * come after the column name and column type in a <CREATE TABLE>
+ * or <ALTER TABLE ADD COLUMN> statement.
+ */
 carglist ::= carglist ccons.
 carglist ::= .
 %type cconsname { struct Token }
@@ -314,6 +320,7 @@ ccons ::= cconsname(N) PRIMARY KEY sortorder(Z). {
   create_index_def_init(&pParse->create_index_def, NULL, &N, NULL,
                         SQL_INDEX_TYPE_CONSTRAINT_PK, Z, false);
   sqlAddPrimaryKey(pParse);
+  pParse->create_column_def.is_pk = true;
 }
 ccons ::= cconsname(N) UNIQUE. {
   create_index_def_init(&pParse->create_index_def, NULL, &N, NULL,
@@ -1729,11 +1736,27 @@ alter_table_start(A) ::= ALTER TABLE fullname(T) . { A = T; }
 
 %type alter_add_constraint {struct alter_args}
 alter_add_constraint(A) ::= alter_table_start(T) ADD CONSTRAINT nm(N). {
+   A.table_name = T;
+   A.name = N;
+   pParse->initiateTTrans = true;
+ }
+
+%type alter_add_column {struct alter_args}
+alter_add_column(A) ::= alter_table_start(T) ADD column_name(N). {
   A.table_name = T;
   A.name = N;
   pParse->initiateTTrans = true;
 }
 
+column_name(N) ::= nm(A). { N = A; }
+
+cmd ::= alter_column_def carglist create_column_end.
+
+alter_column_def ::= alter_add_column(N) typedef(Y). {
+  create_column_def_init(&pParse->create_column_def, N.table_name, &N.name, &Y);
+  sql_create_column_start(pParse);
+}
+
 cmd ::= alter_add_constraint(N) FOREIGN KEY LP eidlist(FA) RP REFERENCES
         nm(T) eidlist_opt(TA) matcharg(M) refargs(R) defer_subclause_opt(D). {
   create_fk_def_init(&pParse->create_fk_def, N.table_name, &N.name, FA, &T, TA,
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index cb0ecd2fc..91f9affa2 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -35,6 +35,7 @@
 #include "box/fk_constraint.h"
 #include "box/key_def.h"
 #include "box/sql.h"
+#include "box/constraint_id.h"
 
 /**
  * This file contains auxiliary structures and functions which
@@ -154,6 +155,7 @@ enum sql_index_type {
 
 enum entity_type {
 	ENTITY_TYPE_TABLE = 0,
+	ENTITY_TYPE_COLUMN,
 	ENTITY_TYPE_VIEW,
 	ENTITY_TYPE_INDEX,
 	ENTITY_TYPE_TRIGGER,
@@ -205,26 +207,22 @@ struct create_entity_def {
 struct create_table_def {
 	struct create_entity_def base;
 	struct space *new_space;
-	/**
-	 * 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;
-	/**
-	 * Number of CK constraints declared within
-	 * CREATE TABLE statement.
-	 */
-	uint32_t check_count;
-	/** Check constraint appeared in CREATE TABLE stmt. */
-	struct rlist new_check;
-	/** True, if table to be created has AUTOINCREMENT PK. */
-	bool has_autoinc;
-	/** Id of field with AUTOINCREMENT. */
-	uint32_t autoinc_fieldno;
+};
+
+struct create_column_def {
+	struct create_entity_def base;
+	/** Shallow space_def copy. */
+	struct space *space;
+	/* True if this column has <PRIMARY KEY> constraint. */
+	bool is_pk;
+	/** Column type. */
+	struct type_def *type_def;
+	/** Token from <COLLATE> clause. */
+	struct Token *collate;
+	/** Action to perform if NULL constraint failed. */
+	enum on_conflict_action nullable_action;
+	/** String from <DEFAULT> clause. */
+	struct ExprSpan *default_clause;
 };
 
 struct create_view_def {
@@ -482,9 +480,17 @@ create_table_def_init(struct create_table_def *table_def, struct Token *name,
 {
 	create_entity_def_init(&table_def->base, ENTITY_TYPE_TABLE, NULL, name,
 			       if_not_exists);
-	rlist_create(&table_def->new_fkey);
-	rlist_create(&table_def->new_check);
-	table_def->autoinc_fieldno = 0;
+}
+
+static inline void
+create_column_def_init(struct create_column_def *column_def,
+		       struct SrcList *table_name, struct Token *name,
+		       struct type_def *type_def)
+{
+	create_entity_def_init(&column_def->base, ENTITY_TYPE_COLUMN,
+			       table_name, name, false);
+	column_def->type_def = type_def;
+	column_def->is_pk = false;
 }
 
 static inline void
@@ -499,14 +505,4 @@ create_view_def_init(struct create_view_def *view_def, struct Token *name,
 	view_def->aliases = aliases;
 }
 
-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);
-}
-
 #endif /* TARANTOOL_BOX_SQL_PARSE_DEF_H_INCLUDED */
diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index a5a258805..b31bac437 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -197,9 +197,13 @@ sql_parser_create(struct Parse *parser, struct sql *db, uint32_t sql_flags)
 {
 	memset(parser, 0, sizeof(struct Parse));
 	parser->db = db;
+	parser->create_column_def.nullable_action = ON_CONFLICT_ACTION_DEFAULT;
 	parser->sql_flags = sql_flags;
 	parser->line_count = 1;
 	parser->line_pos = 1;
+	rlist_create(&parser->fkeys);
+	rlist_create(&parser->checks);
+	parser->has_autoinc = false;
 	region_create(&parser->region, &cord()->slabc);
 }
 
@@ -211,7 +215,9 @@ 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);
+	struct fk_constraint_parse *fk;
+	rlist_foreach_entry(fk, &parser->fkeys, link)
+		sql_expr_list_delete(sql_get(), fk->selfref_cols);
 	if (db != NULL) {
 		assert(db->lookaside.bDisable >=
 		       parser->disableLookaside);
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 0479ebc21..308966dd2 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -2245,12 +2245,26 @@ struct Parse {
 		struct enable_entity_def enable_entity_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).
+	 * Table def or column def is not part of union since
+	 * information being held must survive till the end of
+	 * parsing of whole <CREATE TABLE> or
+	 * <ALTER TABLE ADD COLUMN> statement (to pass it to
+	 * sqlEndTable() sql_create_column_end() function).
 	 */
 	struct create_table_def create_table_def;
+	struct create_column_def create_column_def;
+	/**
+	 * FK and CK constraints appeared in a <CREATE TABLE> or
+	 * a <ALTER TABLE ADD COLUMN> statement.
+	 */
+	struct rlist fkeys;
+	struct rlist checks;
+	uint32_t fkey_count;
+	uint32_t check_count;
+	/** True, if column to be created has <AUTOINCREMENT>. */
+	bool has_autoinc;
+	/** Id of field with <AUTOINCREMENT>. */
+	uint32_t autoinc_fieldno;
 	bool initiateTTrans;	/* Initiate Tarantool transaction */
 	/** If set - do not emit byte code at all, just parse.  */
 	bool parse_only;
@@ -2840,15 +2854,31 @@ struct space *sqlResultSetOfSelect(Parse *, Select *);
 
 struct space *
 sqlStartTable(Parse *, Token *);
-void sqlAddColumn(Parse *, Token *, struct type_def *);
+
+/**
+ * Add new field to the format of ephemeral space in
+ * create_table_def. If it is <ALTER TABLE> create shallow copy of
+ * the existing space and add field to its format.
+ */
+void
+sql_create_column_start(struct Parse *parse);
+
+/**
+ * Nullify create_column_def. If it is <ALTER TABLE> emit code to
+ * update entry in _space and code to create constraints (entries
+ * in _index, _ck_constraint, _fk_constraint) described with this
+ * column.
+ */
+void
+sql_create_column_end(struct Parse *parse);
 
 /**
  * This routine is called by the parser while in the middle of
- * parsing a CREATE TABLE statement.  A "NOT NULL" constraint has
- * been seen on a column.  This routine sets the is_nullable flag
- * on the column currently under construction.
- * If nullable_action has been already set, this function raises
- * an error.
+ * parsing a <CREATE TABLE> or a <ALTER TABLE ADD COLUMN>
+ * statement. A "NOT NULL" constraint has been seen on a column.
+ * This routine sets the is_nullable flag on the column currently
+ * under construction. If nullable_action has been already set,
+ * this function raises an error.
  *
  * @param parser SQL Parser object.
  * @param nullable_action on_conflict_action value.
diff --git a/test/box/error.result b/test/box/error.result
index 0a7ec6553..074875353 100644
--- a/test/box/error.result
+++ b/test/box/error.result
@@ -430,6 +430,8 @@ t;
  |   210: box.error.SQL_PREPARE
  |   211: box.error.WRONG_QUERY_ID
  |   212: box.error.SEQUENCE_NOT_STARTED
+ |   213: box.error.SQL_CANT_ADD_COLUMN_TO_VIEW
+ |   214: box.error.SQL_CANT_ADD_AUTOINC
  | ...
 
 test_run:cmd("setopt delimiter ''");
diff --git a/test/sql/add-column.result b/test/sql/add-column.result
new file mode 100644
index 000000000..90457a7cf
--- /dev/null
+++ b/test/sql/add-column.result
@@ -0,0 +1,231 @@
+-- test-run result file version 2
+test_run = require('test_run').new()
+ | ---
+ | ...
+engine = test_run:get_cfg('engine')
+ | ---
+ | ...
+_ = box.space._session_settings:update('sql_default_engine', {{'=', 2, engine}})
+ | ---
+ | ...
+
+--
+-- gh-3075: Check <ALTER TABLE table ADD COLUMN column> statement.
+--
+box.execute('CREATE TABLE t1 (a INTEGER PRIMARY KEY);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t1 ADD b INTEGER;')
+ | ---
+ | - row_count: 0
+ | ...
+
+--
+-- Can't add column to a view.
+--
+box.execute('CREATE VIEW v AS SELECT * FROM t1;')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE v ADD b INTEGER;')
+ | ---
+ | - null
+ | - Can't add column 'B'. 'V' is a view
+ | ...
+box.execute('DROP VIEW v;')
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check column constraints typing and work.
+--
+box.execute('CREATE TABLE t2 (a INTEGER CONSTRAINT pk_constr PRIMARY KEY);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 DROP CONSTRAINT pk_constr')
+ | ---
+ | - row_count: 1
+ | ...
+test_run:cmd("setopt delimiter ';'");
+ | ---
+ | - true
+ | ...
+box.execute([[ALTER TABLE t2 ADD b INTEGER CONSTRAINT pk_constr PRIMARY KEY
+                                   CHECK (b > 0)
+                                   REFERENCES t1(a)
+                                   CONSTRAINT u_constr UNIQUE]])
+test_run:cmd("setopt delimiter ''");
+ | ---
+ | ...
+box.execute('INSERT INTO t1 VALUES (1, 1);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('INSERT INTO t2 VALUES (1, 1);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('INSERT INTO t2 VALUES (1, 1);')
+ | ---
+ | - null
+ | - Duplicate key exists in unique index 'PK_CONSTR' in space 'T2'
+ | ...
+
+box.execute('INSERT INTO t1 VALUES (0, 1);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('INSERT INTO t2 VALUES (2, 0);')
+ | ---
+ | - null
+ | - 'Check constraint failed ''ck_unnamed_T2_1'': b > 0'
+ | ...
+
+box.execute('INSERT INTO t2 VALUES (2, 3);')
+ | ---
+ | - null
+ | - 'Failed to execute SQL statement: FOREIGN KEY constraint failed'
+ | ...
+
+box.execute('DROP TABLE t2;')
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check AUTOINCREMENT work.
+--
+box.execute("CREATE TABLE t2(a INTEGER CONSTRAINT pk PRIMARY KEY);")
+ | ---
+ | - row_count: 1
+ | ...
+box.execute("ALTER TABLE t2 DROP CONSTRAINT pk;")
+ | ---
+ | - row_count: 1
+ | ...
+box.execute("ALTER TABLE t2 ADD b INTEGER PRIMARY KEY AUTOINCREMENT;")
+ | ---
+ | - row_count: 0
+ | ...
+box.execute("ALTER TABLE t2 ADD c INTEGER AUTOINCREMENT;")
+ | ---
+ | - null
+ | - 'Can''t add AUTOINCREMENT: the space ''T2'' already has one auto-incremented field'
+ | ...
+
+box.execute('DROP TABLE t2;')
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check clauses after column typing and work.
+--
+box.execute('CREATE TABLE t2 (a INTEGER PRIMARY KEY, b INTEGER);')
+ | ---
+ | - row_count: 1
+ | ...
+test_run:cmd("setopt delimiter ';'");
+ | ---
+ | - true
+ | ...
+box.execute([[ALTER TABLE t2 ADD c TEXT NOT NULL DEFAULT ('a')
+                                   COLLATE "unicode_ci";]]);
+ | ---
+ | - row_count: 0
+ | ...
+test_run:cmd("setopt delimiter ''");
+ | ---
+ | - true
+ | ...
+box.execute('INSERT INTO t2(a, b) VALUES (1, 1);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('SELECT * FROM t2;')
+ | ---
+ | - metadata:
+ |   - name: A
+ |     type: integer
+ |   - name: B
+ |     type: integer
+ |   - name: C
+ |     type: string
+ |   rows:
+ |   - [1, 1, 'a']
+ | ...
+box.execute('INSERT INTO t2 VALUES (2, 2, NULL);')
+ | ---
+ | - null
+ | - 'Failed to execute SQL statement: NOT NULL constraint failed: T2.C'
+ | ...
+box.execute('SELECT * FROM t2 WHERE c LIKE \'A\';')
+ | ---
+ | - metadata:
+ |   - name: A
+ |     type: integer
+ |   - name: B
+ |     type: integer
+ |   - name: C
+ |     type: string
+ |   rows:
+ |   - [1, 1, 'a']
+ | ...
+
+--
+-- Try to add to a non-empty space a [non-]nullable field.
+--
+box.execute('ALTER TABLE t2 ADD d INTEGER;')
+ | ---
+ | - null
+ | - Tuple field count 3 does not match space field count 4
+ | ...
+box.execute('ALTER TABLE t2 ADD d TEXT NOT NULL');
+ | ---
+ | - null
+ | - Tuple field count 3 does not match space field count 4
+ | ...
+box.execute('ALTER TABLE t2 ADD e TEXT NULL');
+ | ---
+ | - null
+ | - Tuple field count 3 does not match space field count 4
+ | ...
+
+--
+-- Add to a space with no-SQL adjusted or without format.
+--
+_ = box.schema.space.create('WITHOUT')
+ | ---
+ | ...
+box.execute("ALTER TABLE WITHOUT ADD a INTEGER;")
+ | ---
+ | - row_count: 0
+ | ...
+box.execute("DROP TABLE WITHOUT;")
+ | ---
+ | - row_count: 1
+ | ...
+
+s = box.schema.space.create('NOSQL')
+ | ---
+ | ...
+s:format{{name = 'A', type = 'unsigned'}}
+ | ---
+ | ...
+box.execute("ALTER TABLE NOSQL ADD b INTEGER")
+ | ---
+ | - row_count: 0
+ | ...
+
+--
+-- Add multiple columns inside a transaction.
+--
+box.begin()                                                                     \
+box.execute('ALTER TABLE t2 ADD f INTEGER;')                                    \
+box.execute('ALTER TABLE t2 ADD g INTEGER;')                                    \
+box.commit()
+ | ---
+ | ...
diff --git a/test/sql/add-column.test.lua b/test/sql/add-column.test.lua
new file mode 100644
index 000000000..f3b9b5440
--- /dev/null
+++ b/test/sql/add-column.test.lua
@@ -0,0 +1,87 @@
+test_run = require('test_run').new()
+engine = test_run:get_cfg('engine')
+_ = box.space._session_settings:update('sql_default_engine', {{'=', 2, engine}})
+
+--
+-- gh-3075: Check <ALTER TABLE table ADD COLUMN column> statement.
+--
+box.execute('CREATE TABLE t1 (a INTEGER PRIMARY KEY);')
+box.execute('ALTER TABLE t1 ADD b INTEGER;')
+
+--
+-- Can't add column to a view.
+--
+box.execute('CREATE VIEW v AS SELECT * FROM t1;')
+box.execute('ALTER TABLE v ADD b INTEGER;')
+box.execute('DROP VIEW v;')
+
+--
+-- Check column constraints typing and work.
+--
+box.execute('CREATE TABLE t2 (a INTEGER CONSTRAINT pk_constr PRIMARY KEY);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT pk_constr')
+test_run:cmd("setopt delimiter ';'");
+box.execute([[ALTER TABLE t2 ADD b INTEGER CONSTRAINT pk_constr PRIMARY KEY
+                                   CHECK (b > 0)
+                                   REFERENCES t1(a)
+                                   CONSTRAINT u_constr UNIQUE]])
+test_run:cmd("setopt delimiter ''");
+box.execute('INSERT INTO t1 VALUES (1, 1);')
+box.execute('INSERT INTO t2 VALUES (1, 1);')
+box.execute('INSERT INTO t2 VALUES (1, 1);')
+
+box.execute('INSERT INTO t1 VALUES (0, 1);')
+box.execute('INSERT INTO t2 VALUES (2, 0);')
+
+box.execute('INSERT INTO t2 VALUES (2, 3);')
+
+box.execute('DROP TABLE t2;')
+
+--
+-- Check AUTOINCREMENT work.
+--
+box.execute("CREATE TABLE t2(a INTEGER CONSTRAINT pk PRIMARY KEY);")
+box.execute("ALTER TABLE t2 DROP CONSTRAINT pk;")
+box.execute("ALTER TABLE t2 ADD b INTEGER PRIMARY KEY AUTOINCREMENT;")
+box.execute("ALTER TABLE t2 ADD c INTEGER AUTOINCREMENT;")
+
+box.execute('DROP TABLE t2;')
+
+--
+-- Check clauses after column typing and work.
+--
+box.execute('CREATE TABLE t2 (a INTEGER PRIMARY KEY, b INTEGER);')
+test_run:cmd("setopt delimiter ';'");
+box.execute([[ALTER TABLE t2 ADD c TEXT NOT NULL DEFAULT ('a')
+                                   COLLATE "unicode_ci";]]);
+test_run:cmd("setopt delimiter ''");
+box.execute('INSERT INTO t2(a, b) VALUES (1, 1);')
+box.execute('SELECT * FROM t2;')
+box.execute('INSERT INTO t2 VALUES (2, 2, NULL);')
+box.execute('SELECT * FROM t2 WHERE c LIKE \'A\';')
+
+--
+-- Try to add to a non-empty space a [non-]nullable field.
+--
+box.execute('ALTER TABLE t2 ADD d INTEGER;')
+box.execute('ALTER TABLE t2 ADD d TEXT NOT NULL');
+box.execute('ALTER TABLE t2 ADD e TEXT NULL');
+
+--
+-- Add to a space with no-SQL adjusted or without format.
+--
+_ = box.schema.space.create('WITHOUT')
+box.execute("ALTER TABLE WITHOUT ADD a INTEGER;")
+box.execute("DROP TABLE WITHOUT;")
+
+s = box.schema.space.create('NOSQL')
+s:format{{name = 'A', type = 'unsigned'}}
+box.execute("ALTER TABLE NOSQL ADD b INTEGER")
+
+--
+-- Add multiple columns inside a transaction.
+--
+box.begin()                                                                     \
+box.execute('ALTER TABLE t2 ADD f INTEGER;')                                    \
+box.execute('ALTER TABLE t2 ADD g INTEGER;')                                    \
+box.commit()
-- 
2.21.0 (Apple Git-122)

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

* Re: [Tarantool-patches] [PATCH v2 0/2] Support column addition with <ALTER TABLE>
  2020-04-03 15:27 [Tarantool-patches] [PATCH v2 0/2] Support column addition with <ALTER TABLE> Roman Khabibov
  2020-04-03 15:27 ` [Tarantool-patches] [PATCH v2 1/2] sql: rename TK_COLUMN to TK_COLUMN_NAME Roman Khabibov
  2020-04-03 15:27 ` [Tarantool-patches] [PATCH v2 2/2] sql: support column addition Roman Khabibov
@ 2020-04-03 17:40 ` Roman Khabibov
  2020-11-18 17:23 ` Alexander V. Tikhonov
  3 siblings, 0 replies; 14+ messages in thread
From: Roman Khabibov @ 2020-04-03 17:40 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

Oops. I hurry up.  Done, now I have created a template document.
This is the last time.

@Changelog
- Add ability to add a column to a space with <ALTER TABLE ADD COLUMN>.

Branch: https://github.com/tarantool/tarantool/tree/romanhabibov/gh-3075-add-column-v2
Issue: https://github.com/tarantool/tarantool/issues/3075,
https://github.com/tarantool/tarantool/issues/2349

> On Apr 3, 2020, at 18:27, Roman Khabibov <roman.habibov@tarantool.org> wrote:
> 
> According to the SQL standard, column definition in a
> <CREATE TABLE> and a <ALTER TABLE ADD COLUMN> is the same. So, I
> decided to support Tarantool features like AUTOINCREMENT in the
> alter column description.
> 
> Roman Khabibov (2):
>  sql: rename TK_COLUMN to TK_COLUMN_NAME
>  sql: support column addition
> 
> extra/addopcodes.sh          |   2 +-
> extra/mkkeywordhash.c        |   5 +-
> src/box/errcode.h            |   2 +
> src/box/schema_def.h         |   1 +
> src/box/sql/alter.c          |   1 +
> src/box/sql/build.c          | 596 +++++++++++++++++++++++------------
> src/box/sql/expr.c           |  42 +--
> src/box/sql/fk_constraint.c  |   2 +-
> src/box/sql/parse.y          |  45 ++-
> src/box/sql/parse_def.h      |  62 ++--
> src/box/sql/prepare.c        |   8 +-
> src/box/sql/resolve.c        |  10 +-
> src/box/sql/select.c         |  10 +-
> src/box/sql/sqlInt.h         |  58 +++-
> src/box/sql/treeview.c       |   2 +-
> src/box/sql/where.c          |  18 +-
> src/box/sql/whereexpr.c      |  12 +-
> test/box/error.result        |   2 +
> test/sql/add-column.result   | 231 ++++++++++++++
> test/sql/add-column.test.lua |  87 +++++
> 20 files changed, 892 insertions(+), 304 deletions(-)
> create mode 100644 test/sql/add-column.result
> create mode 100644 test/sql/add-column.test.lua
> 
> -- 
> 2.21.0 (Apple Git-122)
> 

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

* Re: [Tarantool-patches] [PATCH v2 1/2] sql: rename TK_COLUMN to TK_COLUMN_NAME
  2020-04-03 15:27 ` [Tarantool-patches] [PATCH v2 1/2] sql: rename TK_COLUMN to TK_COLUMN_NAME Roman Khabibov
@ 2020-04-24 22:55   ` Vladislav Shpilevoy
  2020-08-11  0:34     ` Roman Khabibov
  0 siblings, 1 reply; 14+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-24 22:55 UTC (permalink / raw)
  To: Roman Khabibov, tarantool-patches

Hi! Thanks for the patch!

> @@ -5081,7 +5081,7 @@ sqlExprCompare(Expr * pA, Expr * pB, int iTab)
>  		}
>  		return 2;
>  	}
> -	if (pA->op != TK_COLUMN && pA->op != TK_AGG_COLUMN && pA->u.zToken) {
> +	if (pA->op != TK_COLUMN_NAME && pA->op != TK_AGG_COLUMN && pA->u.zToken) {

1. This is out of 80 symbols. Please, wrap the line.

>  		if (pA->op == TK_FUNCTION) {
>  			if (sqlStrICmp(pA->u.zToken, pB->u.zToken) != 0)
>  				return 2;
> @@ -5161,7 +5161,7 @@ sqlExprListCompare(ExprList * pA, ExprList * pB, int iTab)
>   *     pE1: x IS NULL  pE2: x IS NOT NULL    Result: false
>   *     pE1: x IS ?2    pE2: x IS NOT NULL    Reuslt: false
>   *
> - * When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has
> + * When comparing TK_COLUMN_NAME nodes between pE1 and pE2, if pE2 has
>   * Expr.iTable<0 then assume a table number given by iTab.
>   *
>   * When in doubt, return false.  Returning true might give a performance
> @@ -5209,11 +5209,11 @@ exprSrcCount(Walker * pWalker, Expr * pExpr)
>  {
>  	/* The NEVER() on the second term is because sqlFunctionUsesThisSrc()
>  	 * is always called before sqlExprAnalyzeAggregates() and so the
> -	 * TK_COLUMNs have not yet been converted into TK_AGG_COLUMN.  If
> +	 * TK_COLUMN_NAMEs have not yet been converted into TK_AGG_COLUMN.  If

2. While you are here, please, remove the multiplied whitespace.

>  	 * sqlFunctionUsesThisSrc() is used differently in the future, the
>  	 * NEVER() will need to be removed.
>  	 */
> -	if (pExpr->op == TK_COLUMN || NEVER(pExpr->op == TK_AGG_COLUMN)) {
> +	if (pExpr->op == TK_COLUMN_NAME || NEVER(pExpr->op == TK_AGG_COLUMN)) {
>  		int i;
>  		struct SrcCount *p = pWalker->u.pSrcCount;
>  		SrcList *pSrc = p->pSrc;

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

* Re: [Tarantool-patches] [PATCH v2 2/2] sql: support column addition
  2020-04-03 15:27 ` [Tarantool-patches] [PATCH v2 2/2] sql: support column addition Roman Khabibov
@ 2020-04-24 22:56   ` Vladislav Shpilevoy
  2020-07-06 13:37     ` Roman Khabibov
  0 siblings, 1 reply; 14+ messages in thread
From: Vladislav Shpilevoy @ 2020-04-24 22:56 UTC (permalink / raw)
  To: Roman Khabibov, tarantool-patches

Thanks for the patch!

You didn't respond to my 18 review comments to the previous
version of the patch. Please, do that so as I would know what
was decided on each of them.

See 3 comments below.

On 03/04/2020 17:27, Roman Khabibov wrote:
> Enable to add column to existing space with
> <ALTER TABLE ADD [COLUMN]> statement. Column definition can be
> supplemented with the four types of constraints, <DEFAULT>,
> <COLLATE> clauses and <[NOT] NULL>, AUTOINCREMENT.
> 
> Closes #2349, #3075
> 
> @TarantoolBot document
> Title: Add columns to existing tables in SQL
> Now, it is possible to add columns to existing empty spaces using
> <ALTER TABLE table_name ADD [COLUMN] column_name column_type ...>

1. Why COLUMN keyword is optional? Is it allowed by the standard?

> statement. The column definition is the same as in <CREATE TABLE>
> statement, except that table constraints (PRIMARY KEY, UNIQUE,
> REFERENCES, CHECK) cannot be specified yet.

2. Issue 2349 is about ability to add a UNIQUE column. Here you said
you 'Closes' it, and yet it can't be done, according to what you say
above. Why?

I tried that example:

    tarantool> box.execute("ALTER TABLE te9 ADD s2 INT UNIQUE;")
    ---
    - row_count: 0
    ...

So it is not forbidden.

Also I tried to follow the template and add explicit COLUMN:

    tarantool> box.execute("ALTER TABLE te9 ADD COLUMN s2 INT UNIQUE;")
    ---
    - null
    - 'At line 1 at or near position 21: keyword ''COLUMN'' is reserved. Please use double
      quotes if ''COLUMN'' is an identifier.'
    ...

Why doesn't it work?

> 
> For example:
> 
> tarantool> box.execute([[CREATE TABLE test (
>                            a INTEGER PRIMARY KEY
>                       );]])
> ---
> - row_count: 1
> ...
> 
> tarantool> box.execute([[ALTER TABLE test ADD COLUMN
>                            b TEXT
>                            NOT NULL
>                            DEFAULT ('a')
>                            COLLATE "unicode_ci"
>                        ;]])

3. This example does not work. Gives the same error as the example above.

> ---
> - row_count: 0
> ...

I will continue the review once the comments above and the old 18 comments
are resolved

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

* Re: [Tarantool-patches] [PATCH v2 2/2] sql: support column addition
  2020-04-24 22:56   ` Vladislav Shpilevoy
@ 2020-07-06 13:37     ` Roman Khabibov
  2020-07-12 16:45       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 14+ messages in thread
From: Roman Khabibov @ 2020-07-06 13:37 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Thanks for the review.

> On Apr 25, 2020, at 01:56, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Thanks for the patch!
> 
> You didn't respond to my 18 review comments to the previous
> version of the patch. Please, do that so as I would know what
> was decided on each of them.
> 
> See 3 comments below.
> 
> On 03/04/2020 17:27, Roman Khabibov wrote:
>> Enable to add column to existing space with
>> <ALTER TABLE ADD [COLUMN]> statement. Column definition can be
>> supplemented with the four types of constraints, <DEFAULT>,
>> <COLLATE> clauses and <[NOT] NULL>, AUTOINCREMENT.
>> 
>> Closes #2349, #3075
>> 
>> @TarantoolBot document
>> Title: Add columns to existing tables in SQL
>> Now, it is possible to add columns to existing empty spaces using
>> <ALTER TABLE table_name ADD [COLUMN] column_name column_type ...>
> 
> 1. Why COLUMN keyword is optional? Is it allowed by the standard?
COLUMN is optional by the standard.

>> statement. The column definition is the same as in <CREATE TABLE>
>> statement, except that table constraints (PRIMARY KEY, UNIQUE,
>> REFERENCES, CHECK) cannot be specified yet.
> 
> 2. Issue 2349 is about ability to add a UNIQUE column. Here you said
> you 'Closes' it, and yet it can't be done, according to what you say
> above. Why?
Removed.

> I tried that example:
> 
>    tarantool> box.execute("ALTER TABLE te9 ADD s2 INT UNIQUE;")
>    ---
>    - row_count: 0
>    ...
> 
> So it is not forbidden.
> 
> Also I tried to follow the template and add explicit COLUMN:
> 
>    tarantool> box.execute("ALTER TABLE te9 ADD COLUMN s2 INT UNIQUE;")
>    ---
>    - null
>    - 'At line 1 at or near position 21: keyword ''COLUMN'' is reserved. Please use double
>      quotes if ''COLUMN'' is an identifier.'
>    ...
> 
> Why doesn't it work?
> 
>> 
>> For example:
>> 
>> tarantool> box.execute([[CREATE TABLE test (
>>                           a INTEGER PRIMARY KEY
>>                      );]])
>> ---
>> - row_count: 1
>> ...
>> 
>> tarantool> box.execute([[ALTER TABLE test ADD COLUMN
>>                           b TEXT
>>                           NOT NULL
>>                           DEFAULT ('a')
>>                           COLLATE "unicode_ci"
>>                       ;]])
> 
> 3. This example does not work. Gives the same error as the example above.
My bad. There was something wrong with the first patch. Now, <COLUMN> is
working.

>> ---
>> - row_count: 0
>> ...
> 
> I will continue the review once the comments above and the old 18 comments
> are resolved

commit 82c448dc66f6233faeb40dda353652c2fd5a3d29
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Thu Jan 2 19:06:14 2020 +0300

    sql: support column addition
    
    Enable to add column to existing space with
    <ALTER TABLE ADD [COLUMN]> statement. Column definition can be
    supplemented with the four types of constraints, <DEFAULT>,
    <COLLATE> clauses and <[NOT] NULL>, AUTOINCREMENT.
    
    Closes #2349, #3075
    
    @TarantoolBot document
    Title: Add columns to existing tables in SQL
    Now, it is possible to add columns to existing empty spaces using
    <ALTER TABLE table_name ADD [COLUMN] column_name column_type ...>
    statement. The column definition is the same as in <CREATE TABLE>
    statement.
    
    For example:
    
    tarantool> box.execute([[CREATE TABLE test (
                               a INTEGER PRIMARY KEY
                          );]])
    ---
    - row_count: 1
    ...
    
    tarantool> box.execute([[ALTER TABLE test ADD COLUMN
                               b TEXT
                               NOT NULL
                               DEFAULT ('a')
                               COLLATE "unicode_ci"
                           ;]])
    ---
    - row_count: 0
    ...
    ---

diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index 486b6b30d..dea047241 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -76,7 +76,7 @@ static Keyword aKeywordTable[] = {
   { "CHECK",                  "TK_CHECK",       true  },
   { "COLLATE",                "TK_COLLATE",     true  },
   { "COLUMN_NAME",            "TK_COLUMN_NAME", true  },
-  { "COLUMN",                 "TK_STANDARD",    true  },
+  { "COLUMN",                 "TK_COLUMN",      true  },
   { "COMMIT",                 "TK_COMMIT",      true  },
   { "CONFLICT",               "TK_CONFLICT",    false },
   { "CONSTRAINT",             "TK_CONSTRAINT",  true  },
diff --git a/src/box/errcode.h b/src/box/errcode.h
index d1e4d02a9..3e94bee7a 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -266,6 +266,8 @@ struct errcode_record {
 	/*211 */_(ER_WRONG_QUERY_ID,		"Prepared statement with id %u does not exist") \
 	/*212 */_(ER_SEQUENCE_NOT_STARTED,		"Sequence '%s' is not started") \
 	/*213 */_(ER_NO_SUCH_SESSION_SETTING,	"Session setting %s doesn't exist") \
+	/*214 */_(ER_SQL_CANT_ADD_COLUMN_TO_VIEW,	"Can't add column '%s'. '%s' is a view") \
+	/*215 */_(ER_SQL_CANT_ADD_AUTOINC,	"Can't add AUTOINCREMENT: the space '%s' already has one auto-incremented field") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/schema_def.h b/src/box/schema_def.h
index f86cd42f1..f2af9e23f 100644
--- a/src/box/schema_def.h
+++ b/src/box/schema_def.h
@@ -131,6 +131,7 @@ enum {
 	BOX_SPACE_FIELD_FIELD_COUNT = 4,
 	BOX_SPACE_FIELD_OPTS = 5,
 	BOX_SPACE_FIELD_FORMAT = 6,
+	box_space_field_MAX = 7
 };
 
 /** _index fields. */
diff --git a/src/box/sql/alter.c b/src/box/sql/alter.c
index 14f6c1a97..e2bf2b7e9 100644
--- a/src/box/sql/alter.c
+++ b/src/box/sql/alter.c
@@ -36,6 +36,7 @@
 #include "sqlInt.h"
 #include "box/box.h"
 #include "box/schema.h"
+#include "tarantoolInt.h"
 
 void
 sql_alter_table_rename(struct Parse *parse)
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index ac42fe842..45fb90d38 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -285,48 +285,112 @@ sql_field_retrieve(Parse *parser, struct space_def *space_def, uint32_t id)
 	return field;
 }
 
-/*
- * Add a new column to the table currently being constructed.
+/**
+ * Make shallow copy of @a space on region.
  *
- * The parser calls this routine once for each column declaration
- * in a CREATE TABLE statement.  sqlStartTable() gets called
- * first to get things going.  Then this routine is called for each
- * column.
+ * Function is used to add a new column to the existing space with
+ * <ALTER TABLE ADD COLUMN>. Copy info about indexes and
+ * definition to create constraints appeared in the statement.
  */
+static struct space *
+sql_shallow_space_copy(struct Parse *parse, struct space *space)
+{
+	assert(space->def != NULL);
+	struct space *ret = sql_ephemeral_space_new(parse, space->def->name);
+	if (ret == NULL)
+		return NULL;
+	ret->index_count = space->index_count;
+	ret->index_id_max = space->index_id_max;
+	uint32_t indexes_sz = sizeof(struct index *) * (ret->index_count);
+	ret->index = (struct index **) malloc(indexes_sz);
+	if (ret->index == NULL) {
+		diag_set(OutOfMemory, indexes_sz, "realloc", "ret->index");
+		return NULL;
+	}
+	for (uint32_t i = 0; i < ret->index_count; i++)
+		ret->index[i] = space->index[i];
+	memcpy(ret->def, space->def, sizeof(struct space_def));
+	ret->def->opts.is_temporary = true;
+	ret->def->opts.is_ephemeral = true;
+	uint32_t fields_size = sizeof(struct field_def) * ret->def->field_count;
+	ret->def->fields = region_alloc(&parse->region, fields_size);
+	if (ret->def->fields == NULL) {
+		diag_set(OutOfMemory, fields_size, "region_alloc",
+			 "ret->def->fields");
+		return NULL;
+	}
+	memcpy(ret->def->fields, space->def->fields, fields_size);
+
+	return ret;
+}
+
 void
-sqlAddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
+sql_create_column_start(struct Parse *parse)
 {
-	assert(type_def != NULL);
-	char *z;
-	sql *db = pParse->db;
-	if (pParse->create_table_def.new_space == NULL)
-		return;
-	struct space_def *def = pParse->create_table_def.new_space->def;
+	struct create_column_def *create_column_def = &parse->create_column_def;
+	struct alter_entity_def *alter_entity_def =
+		&create_column_def->base.base;
+	assert(alter_entity_def->entity_type == ENTITY_TYPE_COLUMN);
+	assert(alter_entity_def->alter_action == ALTER_ACTION_CREATE);
+	struct space *space = parse->create_table_def.new_space;
+	bool is_alter = space == NULL;
+	struct sql *db = parse->db;
+	if (is_alter) {
+		const char *space_name =
+			alter_entity_def->entity_name->a[0].zName;
+		space = space_by_name(space_name);
+		if (space == NULL) {
+			diag_set(ClientError, ER_NO_SUCH_SPACE, space_name);
+			goto tnt_error;
+		}
+		space = sql_shallow_space_copy(parse, space);
+		if (space == NULL)
+			goto tnt_error;
+	}
+	create_column_def->space = space;
+	struct space_def *def = space->def;
+	assert(def->opts.is_ephemeral);
 
 #if SQL_MAX_COLUMN
 	if ((int)def->field_count + 1 > db->aLimit[SQL_LIMIT_COLUMN]) {
 		diag_set(ClientError, ER_SQL_COLUMN_COUNT_MAX, def->name,
 			 def->field_count + 1, db->aLimit[SQL_LIMIT_COLUMN]);
-		pParse->is_aborted = true;
-		return;
+		goto tnt_error;
 	}
 #endif
+
+	struct region *region = &parse->region;
+	struct Token *name = &create_column_def->base.name;
+	char *column_name =
+		sql_normalized_name_region_new(region, name->z, name->n);
+	if (column_name == NULL)
+		goto tnt_error;
+
+	if (parse->create_table_def.new_space == NULL && def->opts.is_view) {
+		diag_set(ClientError, ER_SQL_CANT_ADD_COLUMN_TO_VIEW,
+			 column_name, def->name);
+		goto tnt_error;
+	}
+
 	/*
-	 * As sql_field_retrieve will allocate memory on region
-	 * ensure that def is also temporal and would be dropped.
+	 * Format can be set in Lua, then exact_field_count can be
+	 * zero, but field_count is not.
 	 */
-	assert(def->opts.is_ephemeral);
-	if (sql_field_retrieve(pParse, def, def->field_count) == NULL)
-		return;
-	struct region *region = &pParse->region;
-	z = sql_normalized_name_region_new(region, pName->z, pName->n);
-	if (z == NULL) {
-		pParse->is_aborted = true;
+	if (def->exact_field_count == 0)
+		def->exact_field_count = def->field_count;
+	if (sql_field_retrieve(parse, def, def->field_count) == NULL)
 		return;
+
+	for (uint32_t i = 0; i < def->field_count; i++) {
+		if (strcmp(column_name, def->fields[i].name) == 0) {
+			diag_set(ClientError, ER_SPACE_FIELD_IS_DUPLICATE,
+				 column_name);
+			goto tnt_error;
+		}
 	}
 	struct field_def *column_def = &def->fields[def->field_count];
-	memcpy(column_def, &field_def_default, sizeof(field_def_default));
-	column_def->name = z;
+	memcpy(column_def, &field_def_default, sizeof(struct field_def));
+	column_def->name = column_name;
 	/*
 	 * Marker ON_CONFLICT_ACTION_DEFAULT is used to detect
 	 * attempts to define NULL multiple time or to detect
@@ -334,19 +398,100 @@ sqlAddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
 	 */
 	column_def->nullable_action = ON_CONFLICT_ACTION_DEFAULT;
 	column_def->is_nullable = true;
-	column_def->type = type_def->type;
+	column_def->type = create_column_def->type_def->type;
 	def->field_count++;
+
+	sqlSrcListDelete(db, alter_entity_def->entity_name);
+	return;
+tnt_error:
+	parse->is_aborted = true;
+	sqlSrcListDelete(db, alter_entity_def->entity_name);
+}
+
+static void
+sql_vdbe_create_constraints(struct Parse *parse, int reg_space_id);
+
+void
+sql_create_column_end(struct Parse *parse)
+{
+	struct create_column_def *create_column_def = &parse->create_column_def;
+	struct space *space = parse->create_table_def.new_space;
+	bool is_alter = space == NULL;
+	space = create_column_def->space;
+	struct space_def *def = space->def;
+	if (is_alter) {
+		struct field_def *field = &def->fields[def->field_count - 1];
+		if (field->nullable_action == ON_CONFLICT_ACTION_DEFAULT) {
+			if (create_column_def->is_pk) {
+				field->nullable_action =
+					ON_CONFLICT_ACTION_ABORT;
+				field->is_nullable = false;
+			} else {
+				field->nullable_action =
+					ON_CONFLICT_ACTION_NONE;
+				field->is_nullable = true;
+			}
+		}
+		/*
+		 * Encode the format array and emit code to update _space.
+		 */
+		uint32_t table_stmt_sz = 0;
+		struct region *region = &parse->region;
+		char *table_stmt = sql_encode_table(region, def,
+						    &table_stmt_sz);
+		char *raw = sqlDbMallocRaw(parse->db, table_stmt_sz);
+		if (table_stmt == NULL || raw == NULL) {
+			parse->is_aborted = true;
+			return;
+		}
+		memcpy(raw, table_stmt, table_stmt_sz);
+
+		struct Vdbe *v = sqlGetVdbe(parse);
+		assert(v != NULL);
+
+		struct space *system_space = space_by_id(BOX_SPACE_ID);
+		assert(system_space != NULL);
+		int cursor = parse->nTab++;
+		vdbe_emit_open_cursor(parse, cursor, 0, system_space);
+		sqlVdbeChangeP5(v, OPFLAG_SYSTEMSP);
+
+		int key_reg = ++parse->nMem;
+		sqlVdbeAddOp2(v, OP_Integer, def->id, key_reg);
+		int addr = sqlVdbeAddOp4Int(v, OP_Found, cursor, 0, key_reg, 1);
+		sqlVdbeAddOp2(v, OP_Halt, -1, ON_CONFLICT_ACTION_ABORT);
+		sqlVdbeJumpHere(v, addr);
+
+		int tuple_reg = sqlGetTempRange(parse, box_space_field_MAX + 1);
+		for (int i = 0; i < box_space_field_MAX - 1; ++i)
+			sqlVdbeAddOp3(v, OP_Column, cursor, i, tuple_reg + i);
+		sqlVdbeAddOp1(v, OP_Close, cursor);
+
+		sqlVdbeAddOp2(v, OP_Integer, def->field_count, tuple_reg + 4);
+		sqlVdbeAddOp4(v, OP_Blob, table_stmt_sz, tuple_reg + 6,
+			      SQL_SUBTYPE_MSGPACK, raw, P4_DYNAMIC);
+		sqlVdbeAddOp3(v, OP_MakeRecord, tuple_reg, box_space_field_MAX,
+			      tuple_reg + box_space_field_MAX);
+		sqlVdbeAddOp4(v, OP_IdxReplace, tuple_reg + box_space_field_MAX,
+			      0, 0, (char *) system_space, P4_SPACEPTR);
+		sql_vdbe_create_constraints(parse, key_reg);
+	}
+
+	memset(create_column_def, 0, sizeof(struct create_column_def));
+	create_column_def->nullable_action = ON_CONFLICT_ACTION_DEFAULT;
 }
 
 void
 sql_column_add_nullable_action(struct Parse *parser,
 			       enum on_conflict_action nullable_action)
 {
-	struct space *space = parser->create_table_def.new_space;
-	if (space == NULL || NEVER(space->def->field_count < 1))
+	struct space_def *def = NULL;
+	struct field_def *field = NULL;
+	struct space *space = parser->create_column_def.space;
+	assert (space != NULL);
+	def = space->def;
+	if (NEVER(def->field_count < 1))
 		return;
-	struct space_def *def = space->def;
-	struct field_def *field = &def->fields[def->field_count - 1];
+	field = &def->fields[def->field_count - 1];
 	if (field->nullable_action != ON_CONFLICT_ACTION_DEFAULT &&
 	    nullable_action != field->nullable_action) {
 		/* Prevent defining nullable_action many times. */
@@ -364,51 +509,46 @@ sql_column_add_nullable_action(struct Parse *parser,
 }
 
 /*
- * The expression is the default value for the most recently added column
- * of the table currently under construction.
+ * The expression is the default value for the most recently added
+ * column.
  *
  * Default value expressions must be constant.  Raise an exception if this
  * is not the case.
  *
  * This routine is called by the parser while in the middle of
- * parsing a CREATE TABLE statement.
+ * parsing a <CREATE TABLE> or a <ALTER TABLE ADD COLUMN>
+ * statement.
  */
 void
 sqlAddDefaultValue(Parse * pParse, ExprSpan * pSpan)
 {
 	sql *db = pParse->db;
-	struct space *p = pParse->create_table_def.new_space;
-	if (p != NULL) {
-		assert(p->def->opts.is_ephemeral);
-		struct space_def *def = p->def;
-		if (!sqlExprIsConstantOrFunction
-		    (pSpan->pExpr, db->init.busy)) {
-			const char *column_name =
-				def->fields[def->field_count - 1].name;
-			diag_set(ClientError, ER_CREATE_SPACE, def->name,
-				 tt_sprintf("default value of column '%s' is "\
-					    "not constant", column_name));
+	struct space_def *def = NULL;
+	struct field_def *field = NULL;
+	struct space *space = pParse->create_column_def.space;
+	assert (space != NULL);
+	def = space->def;
+	field = &def->fields[def->field_count - 1];
+	if (!sqlExprIsConstantOrFunction(pSpan->pExpr, db->init.busy)) {
+		diag_set(ClientError, ER_CREATE_SPACE, def->name,
+			 tt_sprintf("default value of column '%s' is not "
+				    "constant", field->name));
+		pParse->is_aborted = true;
+	} else {
+		assert(def != NULL);
+		struct region *region = &pParse->region;
+		uint32_t default_length = (int)(pSpan->zEnd - pSpan->zStart);
+		field->default_value = region_alloc(region, default_length + 1);
+		if (field->default_value == NULL) {
+			diag_set(OutOfMemory, default_length + 1,
+				 "region_alloc", "field->default_value");
 			pParse->is_aborted = true;
-		} else {
-			assert(def != NULL);
-			struct field_def *field =
-				&def->fields[def->field_count - 1];
-			struct region *region = &pParse->region;
-			uint32_t default_length = (int)(pSpan->zEnd - pSpan->zStart);
-			field->default_value = region_alloc(region,
-							    default_length + 1);
-			if (field->default_value == NULL) {
-				diag_set(OutOfMemory, default_length + 1,
-					 "region_alloc",
-					 "field->default_value");
-				pParse->is_aborted = true;
-				return;
-			}
-			strncpy(field->default_value, pSpan->zStart,
-				default_length);
-			field->default_value[default_length] = '\0';
+			goto add_default_value_exit;
 		}
+		strncpy(field->default_value, pSpan->zStart, default_length);
+		field->default_value[default_length] = '\0';
 	}
+add_default_value_exit:
 	sql_expr_delete(db, pSpan->pExpr, false);
 }
 
@@ -447,6 +587,8 @@ sqlAddPrimaryKey(struct Parse *pParse)
 	int nTerm;
 	struct ExprList *pList = pParse->create_index_def.cols;
 	struct space *space = pParse->create_table_def.new_space;
+	if (space == NULL)
+		space = pParse->create_column_def.space;
 	if (space == NULL)
 		goto primary_key_exit;
 	if (sql_space_primary_key(space) != NULL) {
@@ -574,8 +716,10 @@ sql_create_check_contraint(struct Parse *parser)
 		(struct alter_entity_def *) create_ck_def;
 	assert(alter_def->entity_type == ENTITY_TYPE_CK);
 	(void) alter_def;
-	struct space *space = parser->create_table_def.new_space;
-	bool is_alter = space == NULL;
+	struct space *space = parser->create_column_def.space;
+	if (space == NULL)
+		space = parser->create_table_def.new_space;
+	bool is_alter_add_constr = space == NULL;
 
 	/* Prepare payload for ck constraint definition. */
 	struct region *region = &parser->region;
@@ -589,9 +733,18 @@ sql_create_check_contraint(struct Parse *parser)
 			return;
 		}
 	} else {
-		assert(! is_alter);
-		uint32_t ck_idx = ++parser->create_table_def.check_count;
-		name = tt_sprintf("ck_unnamed_%s_%d", space->def->name, ck_idx);
+		assert(!is_alter_add_constr);
+		uint32_t idx = ++parser->check_count;
+		if (parser->create_table_def.new_space == NULL) {
+			struct space *original_space =
+				space_by_name(space->def->name);
+			assert(original_space != NULL);
+			struct rlist *checks = &original_space->ck_constraint;
+			struct ck_constraint *ck;
+			rlist_foreach_entry(ck, checks, link)
+				idx++;
+		}
+		name = tt_sprintf("ck_unnamed_%s_%d", space->def->name, idx);
 	}
 	size_t name_len = strlen(name);
 
@@ -634,9 +787,9 @@ sql_create_check_contraint(struct Parse *parser)
 	trim_space_snprintf(ck_def->expr_str, expr_str, expr_str_len);
 	memcpy(ck_def->name, name, name_len);
 	ck_def->name[name_len] = '\0';
-	if (is_alter) {
+	if (is_alter_add_constr) {
 		const char *space_name = alter_def->entity_name->a[0].zName;
-		struct space *space = space_by_name(space_name);
+		space = space_by_name(space_name);
 		if (space == NULL) {
 			diag_set(ClientError, ER_NO_SUCH_SPACE, space_name);
 			parser->is_aborted = true;
@@ -652,8 +805,7 @@ sql_create_check_contraint(struct Parse *parser)
 		sqlVdbeCountChanges(v);
 		sqlVdbeChangeP5(v, OPFLAG_NCHANGE);
 	} else {
-		rlist_add_entry(&parser->create_table_def.new_check, ck_parse,
-				link);
+		rlist_add_entry(&parser->checks, ck_parse, link);
 	}
 }
 
@@ -664,18 +816,19 @@ sql_create_check_contraint(struct Parse *parser)
 void
 sqlAddCollateType(Parse * pParse, Token * pToken)
 {
-	struct space *space = pParse->create_table_def.new_space;
-	if (space == NULL)
-		return;
+	struct space *space = pParse->create_column_def.space;
+	uint32_t *coll_id = NULL;
+	assert(space != NULL);
 	uint32_t i = space->def->field_count - 1;
+	coll_id = &space->def->fields[i].coll_id;
 	sql *db = pParse->db;
 	char *coll_name = sql_name_from_token(db, pToken);
 	if (coll_name == NULL) {
 		pParse->is_aborted = true;
 		return;
 	}
-	uint32_t *coll_id = &space->def->fields[i].coll_id;
-	if (sql_get_coll_seq(pParse, coll_name, coll_id) != NULL) {
+	if (sql_get_coll_seq(pParse, coll_name, coll_id) != NULL &&
+	    space != NULL) {
 		/* If the column is declared as "<name> PRIMARY KEY COLLATE <type>",
 		 * then an index may have been created on this column before the
 		 * collation type was added. Correct this if it is the case.
@@ -692,6 +845,7 @@ sqlAddCollateType(Parse * pParse, Token * pToken)
 	sqlDbFree(db, coll_name);
 }
 
+
 struct coll *
 sql_column_collation(struct space_def *def, uint32_t column, uint32_t *coll_id)
 {
@@ -705,8 +859,7 @@ sql_column_collation(struct space_def *def, uint32_t column, uint32_t *coll_id)
 	 *
 	 * In cases mentioned above collation is fetched by id.
 	 */
-	if (space == NULL) {
-		assert(def->opts.is_ephemeral);
+	if (def->opts.is_ephemeral) {
 		assert(column < (uint32_t)def->field_count);
 		*coll_id = def->fields[column].coll_id;
 		struct coll_id *collation = coll_by_id(*coll_id);
@@ -795,7 +948,8 @@ vdbe_emit_create_index(struct Parse *parse, struct space_def *def,
 	memcpy(raw, index_parts, index_parts_sz);
 	index_parts = raw;
 
-	if (parse->create_table_def.new_space != NULL) {
+	if (parse->create_table_def.new_space != NULL ||
+	    parse->create_column_def.space != NULL) {
 		sqlVdbeAddOp2(v, OP_SCopy, space_id_reg, entry_reg);
 		sqlVdbeAddOp2(v, OP_Integer, idx_def->iid, entry_reg + 1);
 	} else {
@@ -930,7 +1084,7 @@ emitNewSysSequenceRecord(Parse *pParse, int reg_seq_id, const char *seq_name)
 static int
 emitNewSysSpaceSequenceRecord(Parse *pParse, int reg_space_id, int reg_seq_id)
 {
-	uint32_t fieldno = pParse->create_table_def.autoinc_fieldno;
+	uint32_t fieldno = pParse->autoinc_fieldno;
 
 	Vdbe *v = sqlGetVdbe(pParse);
 	int first_col = pParse->nMem + 1;
@@ -1033,18 +1187,21 @@ vdbe_emit_fk_constraint_create(struct Parse *parse_context,
 			  P4_DYNAMIC);
 	/*
 	 * In case we are adding FK constraints during execution
-	 * of <CREATE TABLE ...> statement, we don't have child
-	 * id, but we know register where it will be stored.
+	 * of <CREATE TABLE ...> or <ALER TABLE ADD COLUMN ...>
+	 * statement, we don't have child id, but we know register
+	 * where it will be stored.
 	 */
-	if (parse_context->create_table_def.new_space != NULL) {
+	bool is_alter_add_constr =
+		parse_context->create_table_def.new_space == NULL &&
+		parse_context->create_column_def.space == NULL;
+	if (!is_alter_add_constr) {
 		sqlVdbeAddOp2(vdbe, OP_SCopy, fk->child_id,
 				  constr_tuple_reg + 1);
 	} else {
 		sqlVdbeAddOp2(vdbe, OP_Integer, fk->child_id,
 				  constr_tuple_reg + 1);
 	}
-	if (parse_context->create_table_def.new_space != NULL &&
-	    fk_constraint_is_self_referenced(fk)) {
+	if (!is_alter_add_constr && fk_constraint_is_self_referenced(fk)) {
 		sqlVdbeAddOp2(vdbe, OP_SCopy, fk->parent_id,
 				  constr_tuple_reg + 2);
 	} else {
@@ -1108,7 +1265,7 @@ vdbe_emit_fk_constraint_create(struct Parse *parse_context,
 			  constr_tuple_reg + 9);
 	sqlVdbeAddOp2(vdbe, OP_SInsert, BOX_FK_CONSTRAINT_ID,
 		      constr_tuple_reg + 9);
-	if (parse_context->create_table_def.new_space == NULL) {
+	if (is_alter_add_constr) {
 		sqlVdbeCountChanges(vdbe);
 		sqlVdbeChangeP5(vdbe, OPFLAG_NCHANGE);
 	}
@@ -1147,6 +1304,105 @@ resolve_link(struct Parse *parse_context, const struct space_def *def,
 	return -1;
 }
 
+/**
+ * Emit code to create sequences, indexes, check and foreign
+ * constraints appeared in <CREATE TABLE> or
+ * <ALTER TABLE ADD COLUMN> statement.
+ */
+static void
+sql_vdbe_create_constraints(struct Parse *parse, int reg_space_id)
+{
+	assert(reg_space_id != 0);
+	struct space *space = parse->create_table_def.new_space;
+	bool is_alter = space == NULL;
+	uint32_t i = 0;
+	if (is_alter) {
+		space = parse->create_column_def.space;
+		i = space_by_name(space->def->name)->index_count;
+	}
+	assert(space != NULL);
+	for (; i < space->index_count; ++i) {
+		struct index *idx = space->index[i];
+		vdbe_emit_create_index(parse, space->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.
+	 */
+	if (parse->has_autoinc) {
+		/* Do an insertion into _sequence. */
+		int reg_seq_id = ++parse->nMem;
+		struct Vdbe *v = sqlGetVdbe(parse);
+		assert(v != NULL);
+		sqlVdbeAddOp2(v, OP_NextSequenceId, 0, reg_seq_id);
+		int reg_seq_record =
+			emitNewSysSequenceRecord(parse, reg_seq_id,
+						 space->def->name);
+		const char *error_msg =
+			tt_sprintf(tnt_errcode_desc(ER_SQL_CANT_ADD_AUTOINC),
+				   space->def->name);
+		if (vdbe_emit_halt_with_presence_test(parse,
+						      BOX_SEQUENCE_ID, 2,
+						      reg_seq_record + 3, 1,
+						      ER_SQL_CANT_ADD_AUTOINC,
+						      error_msg, false,
+						      OP_NoConflict) != 0)
+			return;
+		sqlVdbeAddOp2(v, OP_SInsert, BOX_SEQUENCE_ID, reg_seq_record);
+		/* Do an insertion into _space_sequence. */
+		int reg_space_seq_record =
+			emitNewSysSpaceSequenceRecord(parse, reg_space_id,
+						      reg_seq_id);
+		sqlVdbeAddOp2(v, OP_SInsert, BOX_SPACE_SEQUENCE_ID,
+			      reg_space_seq_record);
+	}
+
+	/* Code creation of FK constraints, if any. */
+	struct fk_constraint_parse *fk_parse;
+	rlist_foreach_entry(fk_parse, &parse->fkeys, link) {
+		struct fk_constraint_def *fk_def = fk_parse->fk_def;
+		if (fk_parse->selfref_cols != NULL) {
+			struct ExprList *cols = fk_parse->selfref_cols;
+			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
+				if (resolve_link(parse, space->def,
+						 cols->a[i].zName,
+						 &fk_def->links[i].parent_field,
+						 fk_def->name) != 0)
+					return;
+			}
+			fk_def->parent_id = reg_space_id;
+		} else if (fk_parse->is_self_referenced) {
+			struct key_def *pk_key_def =
+				sql_space_primary_key(space)->def->key_def;
+			if (pk_key_def->part_count != fk_def->field_count) {
+				diag_set(ClientError, ER_CREATE_FK_CONSTRAINT,
+					 fk_def->name, "number of columns in "\
+					 "foreign key does not match the "\
+					 "number of columns in the primary "\
+					 "index of referenced table");
+				parse->is_aborted = true;
+				return;
+			}
+			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
+				fk_def->links[i].parent_field =
+					pk_key_def->parts[i].fieldno;
+			}
+			fk_def->parent_id = reg_space_id;
+		}
+		fk_def->child_id = reg_space_id;
+		vdbe_emit_fk_constraint_create(parse, fk_def, space->def->name);
+	}
+
+	/* Code creation of CK constraints, if any. */
+	struct ck_constraint_parse *ck_parse;
+	rlist_foreach_entry(ck_parse, &parse->checks, link) {
+		vdbe_emit_ck_constraint_create(parse, ck_parse->ck_def,
+					       reg_space_id, space->def->name);
+	}
+}
+
 /*
  * This routine is called to report the final ")" that terminates
  * a CREATE TABLE statement.
@@ -1213,73 +1469,7 @@ sqlEndTable(struct Parse *pParse)
 
 	int reg_space_id = getNewSpaceId(pParse);
 	vdbe_emit_space_create(pParse, reg_space_id, name_reg, new_space);
-	for (uint32_t i = 0; i < new_space->index_count; ++i) {
-		struct index *idx = new_space->index[i];
-		vdbe_emit_create_index(pParse, new_space->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.
-	 */
-	if (pParse->create_table_def.has_autoinc) {
-		assert(reg_space_id != 0);
-		/* Do an insertion into _sequence. */
-		int reg_seq_id = ++pParse->nMem;
-		sqlVdbeAddOp2(v, OP_NextSequenceId, 0, reg_seq_id);
-		int reg_seq_record =
-			emitNewSysSequenceRecord(pParse, reg_seq_id,
-						 new_space->def->name);
-		sqlVdbeAddOp2(v, OP_SInsert, BOX_SEQUENCE_ID, reg_seq_record);
-		/* Do an insertion into _space_sequence. */
-		int reg_space_seq_record =
-			emitNewSysSpaceSequenceRecord(pParse, reg_space_id,
-						      reg_seq_id);
-		sqlVdbeAddOp2(v, OP_SInsert, BOX_SPACE_SEQUENCE_ID,
-			      reg_space_seq_record);
-	}
-	/* Code creation of FK constraints, if any. */
-	struct fk_constraint_parse *fk_parse;
-	rlist_foreach_entry(fk_parse, &pParse->create_table_def.new_fkey,
-			    link) {
-		struct fk_constraint_def *fk_def = fk_parse->fk_def;
-		if (fk_parse->selfref_cols != NULL) {
-			struct ExprList *cols = fk_parse->selfref_cols;
-			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
-				if (resolve_link(pParse, new_space->def,
-						 cols->a[i].zName,
-						 &fk_def->links[i].parent_field,
-						 fk_def->name) != 0)
-					return;
-			}
-			fk_def->parent_id = reg_space_id;
-		} else if (fk_parse->is_self_referenced) {
-			struct index *pk = sql_space_primary_key(new_space);
-			if (pk->def->key_def->part_count != fk_def->field_count) {
-				diag_set(ClientError, ER_CREATE_FK_CONSTRAINT,
-					 fk_def->name, "number of columns in "\
-					 "foreign key does not match the "\
-					 "number of columns in the primary "\
-					 "index of referenced table");
-				pParse->is_aborted = true;
-				return;
-			}
-			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
-				fk_def->links[i].parent_field =
-					pk->def->key_def->parts[i].fieldno;
-			}
-			fk_def->parent_id = reg_space_id;
-		}
-		fk_def->child_id = reg_space_id;
-		vdbe_emit_fk_constraint_create(pParse, fk_def, space_name_copy);
-	}
-	struct ck_constraint_parse *ck_parse;
-	rlist_foreach_entry(ck_parse, &pParse->create_table_def.new_check,
-			    link) {
-		vdbe_emit_ck_constraint_create(pParse, ck_parse->ck_def,
-					       reg_space_id, space_name_copy);
-	}
+	sql_vdbe_create_constraints(pParse, reg_space_id);
 }
 
 void
@@ -1858,24 +2048,28 @@ sql_create_foreign_key(struct Parse *parse_context)
 	char *parent_name = NULL;
 	char *constraint_name = NULL;
 	bool is_self_referenced = false;
+	struct space *space = parse_context->create_column_def.space;
 	struct create_table_def *table_def = &parse_context->create_table_def;
-	struct space *space = table_def->new_space;
+	if (space == NULL)
+		space = table_def->new_space;
 	/*
-	 * Space under construction during CREATE TABLE
-	 * processing. NULL for ALTER TABLE statement handling.
+	 * Space under construction during <CREATE TABLE>
+	 * processing or shallow copy of space during <ALTER TABLE
+	 * ... ADD COLUMN>. NULL for <ALTER TABLE ... ADD
+	 * CONSTRAINT> statement handling.
 	 */
-	bool is_alter = space == NULL;
+	bool is_alter_add_constr = space == NULL;
 	uint32_t child_cols_count;
 	struct ExprList *child_cols = create_fk_def->child_cols;
 	if (child_cols == NULL) {
-		assert(!is_alter);
+		assert(!is_alter_add_constr);
 		child_cols_count = 1;
 	} else {
 		child_cols_count = child_cols->nExpr;
 	}
 	struct ExprList *parent_cols = create_fk_def->parent_cols;
 	struct space *child_space = NULL;
-	if (is_alter) {
+	if (is_alter_add_constr) {
 		const char *child_name = alter_def->entity_name->a[0].zName;
 		child_space = space_by_name(child_name);
 		if (child_space == NULL) {
@@ -1893,7 +2087,9 @@ sql_create_foreign_key(struct Parse *parse_context)
 			goto tnt_error;
 		}
 		memset(fk_parse, 0, sizeof(*fk_parse));
-		rlist_add_entry(&table_def->new_fkey, fk_parse, link);
+		if (parse_context->create_column_def.space != NULL)
+			child_space = space;
+		rlist_add_entry(&parse_context->fkeys, fk_parse, link);
 	}
 	struct Token *parent = create_fk_def->parent_name;
 	assert(parent != NULL);
@@ -1905,28 +2101,39 @@ sql_create_foreign_key(struct Parse *parse_context)
 	 * self-referenced, but in this case parent (which is
 	 * also child) table will definitely exist.
 	 */
-	is_self_referenced = !is_alter &&
+	is_self_referenced = !is_alter_add_constr &&
 			     strcmp(parent_name, space->def->name) == 0;
 	struct space *parent_space = space_by_name(parent_name);
-	if (parent_space == NULL) {
-		if (is_self_referenced) {
-			struct fk_constraint_parse *fk =
-				rlist_first_entry(&table_def->new_fkey,
-						  struct fk_constraint_parse,
-						  link);
-			fk->selfref_cols = parent_cols;
-			fk->is_self_referenced = true;
-		} else {
-			diag_set(ClientError, ER_NO_SUCH_SPACE, parent_name);;
-			goto tnt_error;
-		}
+	if (parent_space == NULL && !is_self_referenced) {
+		diag_set(ClientError, ER_NO_SUCH_SPACE, parent_name);;
+		goto tnt_error;
+	}
+	if (is_self_referenced) {
+		struct fk_constraint_parse *fk =
+			rlist_first_entry(&parse_context->fkeys,
+					  struct fk_constraint_parse,
+					  link);
+		fk->selfref_cols = parent_cols;
+		fk->is_self_referenced = true;
 	}
-	if (!is_alter) {
+	if (!is_alter_add_constr) {
 		if (create_def->name.n == 0) {
-			constraint_name =
-				sqlMPrintf(db, "fk_unnamed_%s_%d",
-					   space->def->name,
-					   ++table_def->fkey_count);
+			uint32_t idx = ++parse_context->fkey_count;
+			if (table_def->new_space == NULL) {
+				struct space *original_space =
+					space_by_name(space->def->name);
+				assert(original_space != NULL);
+				struct rlist *child_fk =
+					&original_space->child_fk_constraint;
+				if (!rlist_empty(child_fk)) {
+					struct fk_constraint *fk;
+					rlist_foreach_entry(fk, child_fk,
+							    in_child_space)
+						idx++;
+				}
+			}
+			constraint_name = sqlMPrintf(db, "fk_unnamed_%s_%d",
+						     space->def->name, idx);
 		} else {
 			constraint_name =
 				sql_name_from_token(db, &create_def->name);
@@ -1986,7 +2193,8 @@ sql_create_foreign_key(struct Parse *parse_context)
 	}
 	int actions = create_fk_def->actions;
 	fk_def->field_count = child_cols_count;
-	fk_def->child_id = child_space != NULL ? child_space->def->id : 0;
+	fk_def->child_id = table_def->new_space == NULL ?
+		child_space->def->id : 0;
 	fk_def->parent_id = parent_space != NULL ? parent_space->def->id : 0;
 	fk_def->is_deferred = create_constr_def->is_deferred;
 	fk_def->match = (enum fk_constraint_match) (create_fk_def->match);
@@ -2006,7 +2214,7 @@ sql_create_foreign_key(struct Parse *parse_context)
 					    constraint_name) != 0) {
 			goto exit_create_fk;
 		}
-		if (!is_alter) {
+		if (!is_alter_add_constr) {
 			if (child_cols == NULL) {
 				assert(i == 0);
 				/*
@@ -2035,14 +2243,15 @@ sql_create_foreign_key(struct Parse *parse_context)
 	memcpy(fk_def->name, constraint_name, name_len);
 	fk_def->name[name_len] = '\0';
 	/*
-	 * In case of CREATE TABLE processing, all foreign keys
-	 * constraints must be created after space itself, so
-	 * lets delay it until sqlEndTable() call and simply
+	 * In case of <CREATE TABLE> or <ALTER TABLE ... ADD
+	 * COLUMN> processing, all foreign keys  constraints  must
+	 * be created after space itself, so lets delay it until
+	 * sqlEndTable() or sql_add_column_end() call and simply
 	 * maintain list of all FK constraints inside parser.
 	 */
-	if (!is_alter) {
+	if (!is_alter_add_constr) {
 		struct fk_constraint_parse *fk_parse =
-			rlist_first_entry(&table_def->new_fkey,
+			rlist_first_entry(&parse_context->fkeys,
 					  struct fk_constraint_parse, link);
 		fk_parse->fk_def = fk_def;
 	} else {
@@ -2065,12 +2274,10 @@ tnt_error:
 void
 fk_constraint_change_defer_mode(struct Parse *parse_context, bool is_deferred)
 {
-	if (parse_context->db->init.busy ||
-	    rlist_empty(&parse_context->create_table_def.new_fkey))
+	if (parse_context->db->init.busy || rlist_empty(&parse_context->fkeys))
 		return;
-	rlist_first_entry(&parse_context->create_table_def.new_fkey,
-			  struct fk_constraint_parse, link)->fk_def->is_deferred =
-		is_deferred;
+	rlist_first_entry(&parse_context->fkeys, struct fk_constraint_parse,
+			  link)->fk_def->is_deferred = is_deferred;
 }
 
 /**
@@ -2394,7 +2601,10 @@ sql_create_index(struct Parse *parse) {
 	 * Find the table that is to be indexed.
 	 * Return early if not found.
 	 */
-	struct space *space = NULL;
+	struct space *space = parse->create_table_def.new_space;
+	if (space == NULL)
+		space = parse->create_column_def.space;
+	bool is_create_table_or_add_col = space != NULL;
 	struct Token token = create_entity_def->name;
 	if (tbl_name != NULL) {
 		assert(token.n > 0 && token.z != NULL);
@@ -2407,10 +2617,8 @@ sql_create_index(struct Parse *parse) {
 			}
 			goto exit_create_index;
 		}
-	} else {
-		if (parse->create_table_def.new_space == NULL)
-			goto exit_create_index;
-		space = parse->create_table_def.new_space;
+	} else if (space == NULL) {
+		goto exit_create_index;
 	}
 	struct space_def *def = space->def;
 
@@ -2445,7 +2653,7 @@ sql_create_index(struct Parse *parse) {
 	 * 2) UNIQUE constraint is non-named and standard
 	 *    auto-index name will be generated.
 	 */
-	if (parse->create_table_def.new_space == NULL) {
+	if (!is_create_table_or_add_col) {
 		assert(token.z != NULL);
 		name = sql_name_from_token(db, &token);
 		if (name == NULL) {
@@ -2611,7 +2819,7 @@ sql_create_index(struct Parse *parse) {
 	 * constraint, but has different onError (behavior on
 	 * constraint violation), then an error is raised.
 	 */
-	if (parse->create_table_def.new_space != NULL) {
+	if (is_create_table_or_add_col) {
 		for (uint32_t i = 0; i < space->index_count; ++i) {
 			struct index *existing_idx = space->index[i];
 			uint32_t iid = existing_idx->def->iid;
@@ -2699,7 +2907,7 @@ sql_create_index(struct Parse *parse) {
 		sqlVdbeAddOp0(vdbe, OP_Expire);
 	}
 
-	if (tbl_name != NULL)
+	if (!is_create_table_or_add_col)
 		goto exit_create_index;
 	table_add_index(space, index);
 	index = NULL;
@@ -3306,15 +3514,15 @@ vdbe_emit_halt_with_presence_test(struct Parse *parser, int space_id,
 int
 sql_add_autoincrement(struct Parse *parse_context, uint32_t fieldno)
 {
-	if (parse_context->create_table_def.has_autoinc) {
+	if (parse_context->has_autoinc) {
 		diag_set(ClientError, ER_SQL_SYNTAX_WITH_POS,
 			 parse_context->line_count, parse_context->line_pos,
 			 "table must feature at most one AUTOINCREMENT field");
 		parse_context->is_aborted = true;
 		return -1;
 	}
-	parse_context->create_table_def.has_autoinc = true;
-	parse_context->create_table_def.autoinc_fieldno = fieldno;
+	parse_context->has_autoinc = true;
+	parse_context->autoinc_fieldno = fieldno;
 	return 0;
 }
 
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 995875566..5904414a3 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -226,19 +226,23 @@ create_table_end ::= . { sqlEndTable(pParse); }
  */
 
 columnlist ::= columnlist COMMA tcons.
-columnlist ::= columnlist COMMA columnname carglist autoinc(I). {
-  uint32_t fieldno = pParse->create_table_def.new_space->def->field_count - 1;
-  if (I == 1 && sql_add_autoincrement(pParse, fieldno) != 0)
-    return;
+columnlist ::= columnlist COMMA column_def create_column_end.
+columnlist ::= column_def create_column_end.
+columnlist ::= tcons.
+
+column_def ::= column_name_and_type carglist.
+
+column_name_and_type ::= nm(A) typedef(Y). {
+  create_column_def_init(&pParse->create_column_def, NULL, &A, &Y);
+  sql_create_column_start(pParse);
 }
 
-columnlist ::= columnname carglist autoinc(I). {
-  uint32_t fieldno = pParse->create_table_def.new_space->def->field_count - 1;
+create_column_end ::= autoinc(I). {
+  uint32_t fieldno = pParse->create_column_def.space->def->field_count - 1;
   if (I == 1 && sql_add_autoincrement(pParse, fieldno) != 0)
     return;
+  sql_create_column_end(pParse);
 }
-columnlist ::= tcons.
-columnname(A) ::= nm(A) typedef(Y). {sqlAddColumn(pParse,&A,&Y);}
 
 // An IDENTIFIER can be a generic identifier, or one of several
 // keywords.  Any non-standard keyword can also be an identifier.
@@ -281,9 +285,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" is a list of additional constraints and clauses that
+ * come after the column name and column type in a <CREATE TABLE>
+ * or <ALTER TABLE ADD COLUMN> statement.
+ */
 carglist ::= carglist ccons.
 carglist ::= .
 %type cconsname { struct Token }
@@ -314,6 +320,7 @@ ccons ::= cconsname(N) PRIMARY KEY sortorder(Z). {
   create_index_def_init(&pParse->create_index_def, NULL, &N, NULL,
                         SQL_INDEX_TYPE_CONSTRAINT_PK, Z, false);
   sqlAddPrimaryKey(pParse);
+  pParse->create_column_def.is_pk = true;
 }
 ccons ::= cconsname(N) UNIQUE. {
   create_index_def_init(&pParse->create_index_def, NULL, &N, NULL,
@@ -1735,11 +1742,28 @@ alter_table_start(A) ::= ALTER TABLE fullname(T) . { A = T; }
 
 %type alter_add_constraint {struct alter_args}
 alter_add_constraint(A) ::= alter_table_start(T) ADD CONSTRAINT nm(N). {
+   A.table_name = T;
+   A.name = N;
+   pParse->initiateTTrans = true;
+ }
+
+%type alter_add_column {struct alter_args}
+alter_add_column(A) ::= alter_table_start(T) ADD column_name(N). {
   A.table_name = T;
   A.name = N;
   pParse->initiateTTrans = true;
 }
 
+column_name(N) ::= COLUMN nm(A). { N = A; }
+column_name(N) ::= nm(A). { N = A; }
+
+cmd ::= alter_column_def carglist create_column_end.
+
+alter_column_def ::= alter_add_column(N) typedef(Y). {
+  create_column_def_init(&pParse->create_column_def, N.table_name, &N.name, &Y);
+  sql_create_column_start(pParse);
+}
+
 cmd ::= alter_add_constraint(N) FOREIGN KEY LP eidlist(FA) RP REFERENCES
         nm(T) eidlist_opt(TA) matcharg(M) refargs(R) defer_subclause_opt(D). {
   create_fk_def_init(&pParse->create_fk_def, N.table_name, &N.name, FA, &T, TA,
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index cb0ecd2fc..91f9affa2 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -35,6 +35,7 @@
 #include "box/fk_constraint.h"
 #include "box/key_def.h"
 #include "box/sql.h"
+#include "box/constraint_id.h"
 
 /**
  * This file contains auxiliary structures and functions which
@@ -154,6 +155,7 @@ enum sql_index_type {
 
 enum entity_type {
 	ENTITY_TYPE_TABLE = 0,
+	ENTITY_TYPE_COLUMN,
 	ENTITY_TYPE_VIEW,
 	ENTITY_TYPE_INDEX,
 	ENTITY_TYPE_TRIGGER,
@@ -205,26 +207,22 @@ struct create_entity_def {
 struct create_table_def {
 	struct create_entity_def base;
 	struct space *new_space;
-	/**
-	 * 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;
-	/**
-	 * Number of CK constraints declared within
-	 * CREATE TABLE statement.
-	 */
-	uint32_t check_count;
-	/** Check constraint appeared in CREATE TABLE stmt. */
-	struct rlist new_check;
-	/** True, if table to be created has AUTOINCREMENT PK. */
-	bool has_autoinc;
-	/** Id of field with AUTOINCREMENT. */
-	uint32_t autoinc_fieldno;
+};
+
+struct create_column_def {
+	struct create_entity_def base;
+	/** Shallow space_def copy. */
+	struct space *space;
+	/* True if this column has <PRIMARY KEY> constraint. */
+	bool is_pk;
+	/** Column type. */
+	struct type_def *type_def;
+	/** Token from <COLLATE> clause. */
+	struct Token *collate;
+	/** Action to perform if NULL constraint failed. */
+	enum on_conflict_action nullable_action;
+	/** String from <DEFAULT> clause. */
+	struct ExprSpan *default_clause;
 };
 
 struct create_view_def {
@@ -482,9 +480,17 @@ create_table_def_init(struct create_table_def *table_def, struct Token *name,
 {
 	create_entity_def_init(&table_def->base, ENTITY_TYPE_TABLE, NULL, name,
 			       if_not_exists);
-	rlist_create(&table_def->new_fkey);
-	rlist_create(&table_def->new_check);
-	table_def->autoinc_fieldno = 0;
+}
+
+static inline void
+create_column_def_init(struct create_column_def *column_def,
+		       struct SrcList *table_name, struct Token *name,
+		       struct type_def *type_def)
+{
+	create_entity_def_init(&column_def->base, ENTITY_TYPE_COLUMN,
+			       table_name, name, false);
+	column_def->type_def = type_def;
+	column_def->is_pk = false;
 }
 
 static inline void
@@ -499,14 +505,4 @@ create_view_def_init(struct create_view_def *view_def, struct Token *name,
 	view_def->aliases = aliases;
 }
 
-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);
-}
-
 #endif /* TARANTOOL_BOX_SQL_PARSE_DEF_H_INCLUDED */
diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index a5a258805..b31bac437 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -197,9 +197,13 @@ sql_parser_create(struct Parse *parser, struct sql *db, uint32_t sql_flags)
 {
 	memset(parser, 0, sizeof(struct Parse));
 	parser->db = db;
+	parser->create_column_def.nullable_action = ON_CONFLICT_ACTION_DEFAULT;
 	parser->sql_flags = sql_flags;
 	parser->line_count = 1;
 	parser->line_pos = 1;
+	rlist_create(&parser->fkeys);
+	rlist_create(&parser->checks);
+	parser->has_autoinc = false;
 	region_create(&parser->region, &cord()->slabc);
 }
 
@@ -211,7 +215,9 @@ 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);
+	struct fk_constraint_parse *fk;
+	rlist_foreach_entry(fk, &parser->fkeys, link)
+		sql_expr_list_delete(sql_get(), fk->selfref_cols);
 	if (db != NULL) {
 		assert(db->lookaside.bDisable >=
 		       parser->disableLookaside);
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index aa6a470f8..3143ec521 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -2249,12 +2249,26 @@ struct Parse {
 		struct enable_entity_def enable_entity_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).
+	 * Table def or column def is not part of union since
+	 * information being held must survive till the end of
+	 * parsing of whole <CREATE TABLE> or
+	 * <ALTER TABLE ADD COLUMN> statement (to pass it to
+	 * sqlEndTable() sql_create_column_end() function).
 	 */
 	struct create_table_def create_table_def;
+	struct create_column_def create_column_def;
+	/**
+	 * FK and CK constraints appeared in a <CREATE TABLE> or
+	 * a <ALTER TABLE ADD COLUMN> statement.
+	 */
+	struct rlist fkeys;
+	struct rlist checks;
+	uint32_t fkey_count;
+	uint32_t check_count;
+	/** True, if column to be created has <AUTOINCREMENT>. */
+	bool has_autoinc;
+	/** Id of field with <AUTOINCREMENT>. */
+	uint32_t autoinc_fieldno;
 	bool initiateTTrans;	/* Initiate Tarantool transaction */
 	/** If set - do not emit byte code at all, just parse.  */
 	bool parse_only;
@@ -2844,15 +2858,31 @@ struct space *sqlResultSetOfSelect(Parse *, Select *);
 
 struct space *
 sqlStartTable(Parse *, Token *);
-void sqlAddColumn(Parse *, Token *, struct type_def *);
+
+/**
+ * Add new field to the format of ephemeral space in
+ * create_table_def. If it is <ALTER TABLE> create shallow copy of
+ * the existing space and add field to its format.
+ */
+void
+sql_create_column_start(struct Parse *parse);
+
+/**
+ * Nullify create_column_def. If it is <ALTER TABLE> emit code to
+ * update entry in _space and code to create constraints (entries
+ * in _index, _ck_constraint, _fk_constraint) described with this
+ * column.
+ */
+void
+sql_create_column_end(struct Parse *parse);
 
 /**
  * This routine is called by the parser while in the middle of
- * parsing a CREATE TABLE statement.  A "NOT NULL" constraint has
- * been seen on a column.  This routine sets the is_nullable flag
- * on the column currently under construction.
- * If nullable_action has been already set, this function raises
- * an error.
+ * parsing a <CREATE TABLE> or a <ALTER TABLE ADD COLUMN>
+ * statement. A "NOT NULL" constraint has been seen on a column.
+ * This routine sets the is_nullable flag on the column currently
+ * under construction. If nullable_action has been already set,
+ * this function raises an error.
  *
  * @param parser SQL Parser object.
  * @param nullable_action on_conflict_action value.
diff --git a/test/box/error.result b/test/box/error.result
index 2196fa541..eb55f9cdf 100644
--- a/test/box/error.result
+++ b/test/box/error.result
@@ -432,6 +432,8 @@ t;
  |   211: box.error.WRONG_QUERY_ID
  |   212: box.error.SEQUENCE_NOT_STARTED
  |   213: box.error.NO_SUCH_SESSION_SETTING
+ |   214: box.error.SQL_CANT_ADD_COLUMN_TO_VIEW
+ |   215: box.error.SQL_CANT_ADD_AUTOINC
  | ...
 
 test_run:cmd("setopt delimiter ''");
diff --git a/test/sql/add-column.result b/test/sql/add-column.result
new file mode 100644
index 000000000..06c95facb
--- /dev/null
+++ b/test/sql/add-column.result
@@ -0,0 +1,276 @@
+-- test-run result file version 2
+test_run = require('test_run').new()
+ | ---
+ | ...
+engine = test_run:get_cfg('engine')
+ | ---
+ | ...
+_ = box.space._session_settings:update('sql_default_engine', {{'=', 2, engine}})
+ | ---
+ | ...
+
+--
+-- gh-3075: Check <ALTER TABLE table ADD COLUMN column> statement.
+--
+box.execute('CREATE TABLE t1 (a INTEGER PRIMARY KEY);')
+ | ---
+ | - row_count: 1
+ | ...
+--
+-- COLUMN keyword is optional. Check it here, but omit it below.
+--
+box.execute('ALTER TABLE t1 ADD COLUMN b INTEGER;')
+ | ---
+ | - row_count: 0
+ | ...
+
+--
+-- Can't add column to a view.
+--
+box.execute('CREATE VIEW v AS SELECT * FROM t1;')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE v ADD b INTEGER;')
+ | ---
+ | - null
+ | - Can't add column 'B'. 'V' is a view
+ | ...
+box.execute('DROP VIEW v;')
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check column constraints typing and work.
+--
+box.execute('CREATE TABLE t2 (a INTEGER CONSTRAINT pk_constr PRIMARY KEY);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 DROP CONSTRAINT pk_constr')
+ | ---
+ | - row_count: 1
+ | ...
+test_run:cmd("setopt delimiter ';'");
+ | ---
+ | - true
+ | ...
+box.execute([[ALTER TABLE t2 ADD b INTEGER CONSTRAINT pk_constr PRIMARY KEY
+                                   CHECK (b > 0)
+                                   REFERENCES t1(a)
+                                   CONSTRAINT u_constr UNIQUE]])
+test_run:cmd("setopt delimiter ''");
+ | ---
+ | ...
+box.execute('INSERT INTO t1 VALUES (1, 1);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('INSERT INTO t2 VALUES (1, 1);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('INSERT INTO t2 VALUES (1, 1);')
+ | ---
+ | - null
+ | - Duplicate key exists in unique index 'PK_CONSTR' in space 'T2'
+ | ...
+
+box.execute('INSERT INTO t1 VALUES (0, 1);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('INSERT INTO t2 VALUES (2, 0);')
+ | ---
+ | - null
+ | - 'Check constraint failed ''ck_unnamed_T2_1'': b > 0'
+ | ...
+
+box.execute('INSERT INTO t2 VALUES (2, 3);')
+ | ---
+ | - null
+ | - 'Failed to execute SQL statement: FOREIGN KEY constraint failed'
+ | ...
+
+box.execute('DROP TABLE t2;')
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check self-referenced FK creation.
+--
+box.execute('CREATE TABLE t2 (a INTEGER PRIMARY KEY);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 ADD b INT REFERENCES t1')
+ | ---
+ | - row_count: 0
+ | ...
+
+box.execute('DROP TABLE t2;')
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check AUTOINCREMENT work.
+--
+box.execute("CREATE TABLE t2(a INTEGER CONSTRAINT pk PRIMARY KEY);")
+ | ---
+ | - row_count: 1
+ | ...
+box.execute("ALTER TABLE t2 DROP CONSTRAINT pk;")
+ | ---
+ | - row_count: 1
+ | ...
+box.execute("ALTER TABLE t2 ADD b INTEGER PRIMARY KEY AUTOINCREMENT;")
+ | ---
+ | - row_count: 0
+ | ...
+box.execute("ALTER TABLE t2 ADD c INTEGER AUTOINCREMENT;")
+ | ---
+ | - null
+ | - 'Can''t add AUTOINCREMENT: the space ''T2'' already has one auto-incremented field'
+ | ...
+
+box.execute('DROP TABLE t2;')
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check clauses after column typing and work.
+--
+box.execute('CREATE TABLE t2 (a INTEGER PRIMARY KEY, b INTEGER);')
+ | ---
+ | - row_count: 1
+ | ...
+test_run:cmd("setopt delimiter ';'");
+ | ---
+ | - true
+ | ...
+box.execute([[ALTER TABLE t2 ADD c TEXT NOT NULL DEFAULT ('a')
+                                   COLLATE "unicode_ci";]]);
+ | ---
+ | - row_count: 0
+ | ...
+test_run:cmd("setopt delimiter ''");
+ | ---
+ | - true
+ | ...
+box.execute('INSERT INTO t2(a, b) VALUES (1, 1);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('SELECT * FROM t2;')
+ | ---
+ | - metadata:
+ |   - name: A
+ |     type: integer
+ |   - name: B
+ |     type: integer
+ |   - name: C
+ |     type: string
+ |   rows:
+ |   - [1, 1, 'a']
+ | ...
+box.execute('INSERT INTO t2 VALUES (2, 2, NULL);')
+ | ---
+ | - null
+ | - 'Failed to execute SQL statement: NOT NULL constraint failed: T2.C'
+ | ...
+box.execute('SELECT * FROM t2 WHERE c LIKE \'A\';')
+ | ---
+ | - metadata:
+ |   - name: A
+ |     type: integer
+ |   - name: B
+ |     type: integer
+ |   - name: C
+ |     type: string
+ |   rows:
+ |   - [1, 1, 'a']
+ | ...
+
+--
+-- Try to add to a non-empty space a [non-]nullable field.
+--
+box.execute('ALTER TABLE t2 ADD d INTEGER;')
+ | ---
+ | - null
+ | - Tuple field count 3 does not match space field count 4
+ | ...
+box.execute('ALTER TABLE t2 ADD d TEXT NOT NULL');
+ | ---
+ | - null
+ | - Tuple field count 3 does not match space field count 4
+ | ...
+box.execute('ALTER TABLE t2 ADD e TEXT NULL');
+ | ---
+ | - null
+ | - Tuple field count 3 does not match space field count 4
+ | ...
+
+--
+-- Add to a space with no-SQL adjusted or without format.
+--
+_ = box.schema.space.create('WITHOUT')
+ | ---
+ | ...
+box.execute("ALTER TABLE WITHOUT ADD a INTEGER;")
+ | ---
+ | - row_count: 0
+ | ...
+box.execute("DROP TABLE WITHOUT;")
+ | ---
+ | - row_count: 1
+ | ...
+
+s = box.schema.space.create('NOSQL')
+ | ---
+ | ...
+s:format{{name = 'A', type = 'unsigned'}}
+ | ---
+ | ...
+box.execute("ALTER TABLE NOSQL ADD b INTEGER")
+ | ---
+ | - row_count: 0
+ | ...
+
+box.execute('DROP TABLE t2;')
+ | ---
+ | - row_count: 1
+ | ...
+--
+-- Add multiple columns inside a transaction.
+--
+box.execute('CREATE TABLE t2 (a INTEGER PRIMARY KEY)')
+ | ---
+ | - row_count: 1
+ | ...
+box.begin()                                                                     \
+box.execute('ALTER TABLE t2 ADD b INT')                                         \
+box.execute('ALTER TABLE t2 ADD c INT')                                         \
+box.commit()
+ | ---
+ | ...
+
+box.execute('INSERT INTO t2 VALUES (1, 1, 1)')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('SELECT * FROM t2;')
+ | ---
+ | - metadata:
+ |   - name: A
+ |     type: integer
+ |   - name: B
+ |     type: integer
+ |   - name: C
+ |     type: integer
+ |   rows:
+ |   - [1, 1, 1]
+ | ...
diff --git a/test/sql/add-column.test.lua b/test/sql/add-column.test.lua
new file mode 100644
index 000000000..f0ce1c44c
--- /dev/null
+++ b/test/sql/add-column.test.lua
@@ -0,0 +1,103 @@
+test_run = require('test_run').new()
+engine = test_run:get_cfg('engine')
+_ = box.space._session_settings:update('sql_default_engine', {{'=', 2, engine}})
+
+--
+-- gh-3075: Check <ALTER TABLE table ADD COLUMN column> statement.
+--
+box.execute('CREATE TABLE t1 (a INTEGER PRIMARY KEY);')
+--
+-- COLUMN keyword is optional. Check it here, but omit it below.
+--
+box.execute('ALTER TABLE t1 ADD COLUMN b INTEGER;')
+
+--
+-- Can't add column to a view.
+--
+box.execute('CREATE VIEW v AS SELECT * FROM t1;')
+box.execute('ALTER TABLE v ADD b INTEGER;')
+box.execute('DROP VIEW v;')
+
+--
+-- Check column constraints typing and work.
+--
+box.execute('CREATE TABLE t2 (a INTEGER CONSTRAINT pk_constr PRIMARY KEY);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT pk_constr')
+test_run:cmd("setopt delimiter ';'");
+box.execute([[ALTER TABLE t2 ADD b INTEGER CONSTRAINT pk_constr PRIMARY KEY
+                                   CHECK (b > 0)
+                                   REFERENCES t1(a)
+                                   CONSTRAINT u_constr UNIQUE]])
+test_run:cmd("setopt delimiter ''");
+box.execute('INSERT INTO t1 VALUES (1, 1);')
+box.execute('INSERT INTO t2 VALUES (1, 1);')
+box.execute('INSERT INTO t2 VALUES (1, 1);')
+
+box.execute('INSERT INTO t1 VALUES (0, 1);')
+box.execute('INSERT INTO t2 VALUES (2, 0);')
+
+box.execute('INSERT INTO t2 VALUES (2, 3);')
+
+box.execute('DROP TABLE t2;')
+
+--
+-- Check self-referenced FK creation.
+--
+box.execute('CREATE TABLE t2 (a INTEGER PRIMARY KEY);')
+box.execute('ALTER TABLE t2 ADD b INT REFERENCES t1')
+
+box.execute('DROP TABLE t2;')
+
+--
+-- Check AUTOINCREMENT work.
+--
+box.execute("CREATE TABLE t2(a INTEGER CONSTRAINT pk PRIMARY KEY);")
+box.execute("ALTER TABLE t2 DROP CONSTRAINT pk;")
+box.execute("ALTER TABLE t2 ADD b INTEGER PRIMARY KEY AUTOINCREMENT;")
+box.execute("ALTER TABLE t2 ADD c INTEGER AUTOINCREMENT;")
+
+box.execute('DROP TABLE t2;')
+
+--
+-- Check clauses after column typing and work.
+--
+box.execute('CREATE TABLE t2 (a INTEGER PRIMARY KEY, b INTEGER);')
+test_run:cmd("setopt delimiter ';'");
+box.execute([[ALTER TABLE t2 ADD c TEXT NOT NULL DEFAULT ('a')
+                                   COLLATE "unicode_ci";]]);
+test_run:cmd("setopt delimiter ''");
+box.execute('INSERT INTO t2(a, b) VALUES (1, 1);')
+box.execute('SELECT * FROM t2;')
+box.execute('INSERT INTO t2 VALUES (2, 2, NULL);')
+box.execute('SELECT * FROM t2 WHERE c LIKE \'A\';')
+
+--
+-- Try to add to a non-empty space a [non-]nullable field.
+--
+box.execute('ALTER TABLE t2 ADD d INTEGER;')
+box.execute('ALTER TABLE t2 ADD d TEXT NOT NULL');
+box.execute('ALTER TABLE t2 ADD e TEXT NULL');
+
+--
+-- Add to a space with no-SQL adjusted or without format.
+--
+_ = box.schema.space.create('WITHOUT')
+box.execute("ALTER TABLE WITHOUT ADD a INTEGER;")
+box.execute("DROP TABLE WITHOUT;")
+
+s = box.schema.space.create('NOSQL')
+s:format{{name = 'A', type = 'unsigned'}}
+box.execute("ALTER TABLE NOSQL ADD b INTEGER")
+
+box.execute('DROP TABLE t2;')
+--
+-- Add multiple columns inside a transaction.
+--
+box.execute('CREATE TABLE t2 (a INTEGER PRIMARY KEY)')
+box.begin()                                                                     \
+box.execute('ALTER TABLE t2 ADD b INT')                                         \
+box.execute('ALTER TABLE t2 ADD c INT')                                         \
+box.commit()
+
+box.execute('INSERT INTO t2 VALUES (1, 1, 1)')
+box.execute('SELECT * FROM t2;')

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

* Re: [Tarantool-patches] [PATCH v2 2/2] sql: support column addition
  2020-07-06 13:37     ` Roman Khabibov
@ 2020-07-12 16:45       ` Vladislav Shpilevoy
  2020-08-11  0:34         ` Roman Khabibov
  0 siblings, 1 reply; 14+ messages in thread
From: Vladislav Shpilevoy @ 2020-07-12 16:45 UTC (permalink / raw)
  To: Roman Khabibov; +Cc: tarantool-patches

Hi! Thanks for the patch!

See 33 comments below.

> commit 82c448dc66f6233faeb40dda353652c2fd5a3d29
> Author: Roman Khabibov <roman.habibov@tarantool.org>
> Date:   Thu Jan 2 19:06:14 2020 +0300
> 
>     sql: support column addition
>     
>     Enable to add column to existing space with
>     <ALTER TABLE ADD [COLUMN]> statement. Column definition can be
>     supplemented with the four types of constraints, <DEFAULT>,
>     <COLLATE> clauses and <[NOT] NULL>, AUTOINCREMENT.
>     
>     Closes #2349, #3075
>     
>     @TarantoolBot document
>     Title: Add columns to existing tables in SQL
>     Now, it is possible to add columns to existing empty spaces using
>     <ALTER TABLE table_name ADD [COLUMN] column_name column_type ...>
>     statement. The column definition is the same as in <CREATE TABLE>
>     statement.
>     
>     For example:
>     
>     tarantool> box.execute([[CREATE TABLE test (
>                                a INTEGER PRIMARY KEY
>                           );]])
>     ---
>     - row_count: 1
>     ...
>     
>     tarantool> box.execute([[ALTER TABLE test ADD COLUMN
>                                b TEXT
>                                NOT NULL
>                                DEFAULT ('a')
>                                COLLATE "unicode_ci"
>                            ;]])
>     ---
>     - row_count: 0
>     ...
>     ---

1. The commit message is different from what I see on the branch. On the
branch there is no 'Closes #2349'. What is the most actual version?

2. The example from the doc request does not work:

	tarantool> box.execute([[CREATE TABLE test (a INTEGER PRIMARY KEY);]])
	---
	- row_count: 1
	...

	tarantool> box.execute([[ALTER TABLE test ADD COLUMN b TEXT NOT NULL DEFAULT ('a') COLLATE "unicode_ci";]])
	---
	- null
	- 'At line 1 at or near position 22: keyword ''COLUMN'' is reserved. Please use double
	  quotes if ''COLUMN'' is an identifier.'
	...

> diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
> index 486b6b30d..dea047241 100644
> --- a/extra/mkkeywordhash.c
> +++ b/extra/mkkeywordhash.c
> @@ -76,7 +76,7 @@ static Keyword aKeywordTable[] = {
>    { "CHECK",                  "TK_CHECK",       true  },
>    { "COLLATE",                "TK_COLLATE",     true  },
>    { "COLUMN_NAME",            "TK_COLUMN_NAME", true  },
> -  { "COLUMN",                 "TK_STANDARD",    true  },
> +  { "COLUMN",                 "TK_COLUMN",      true  },

3. This is how the same hunk looks on the branch:

====================
diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index 006285622..51c8cbb18 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -75,11 +75,7 @@ static Keyword aKeywordTable[] = {
   { "CAST",                   "TK_CAST",        false },
   { "CHECK",                  "TK_CHECK",       true  },
   { "COLLATE",                "TK_COLLATE",     true  },
-  /* gh-3075: Reserved until ALTER ADD COLUMN is implemeneted.
-   * Move it back to ALTER when done.
-   */
-  /* { "COLUMN",              "TK_COLUMNKW",    true  }, */
-  { "COLUMN",                 "TK_STANDARD",    true  },
+  { "COLUMN",                 "TK_COLUMN",      true  },
   { "COMMIT",                 "TK_COMMIT",      true  },
   { "CONFLICT",               "TK_CONFLICT",    false },
   { "CONSTRAINT",             "TK_CONSTRAINT",  true  },
====================

It is very different. Why?

> diff --git a/src/box/errcode.h b/src/box/errcode.h
> index d1e4d02a9..3e94bee7a 100644
> --- a/src/box/errcode.h
> +++ b/src/box/errcode.h
> @@ -266,6 +266,8 @@ struct errcode_record {
>  	/*211 */_(ER_WRONG_QUERY_ID,		"Prepared statement with id %u does not exist") \
>  	/*212 */_(ER_SEQUENCE_NOT_STARTED,		"Sequence '%s' is not started") \
>  	/*213 */_(ER_NO_SUCH_SESSION_SETTING,	"Session setting %s doesn't exist") \
> +	/*214 */_(ER_SQL_CANT_ADD_COLUMN_TO_VIEW,	"Can't add column '%s'. '%s' is a view") \
> +	/*215 */_(ER_SQL_CANT_ADD_AUTOINC,	"Can't add AUTOINCREMENT: the space '%s' already has one auto-incremented field") \

4. The same here:

====================
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -264,6 +264,7 @@ struct errcode_record {
        /*209 */_(ER_SESSION_SETTING_INVALID_VALUE,     "Session setting %s expected a value of type %s") \
        /*210 */_(ER_SQL_PREPARE,               "Failed to prepare SQL statement: %s") \
        /*211 */_(ER_WRONG_QUERY_ID,            "Prepared statement with id %u does not exist") \
+       /*212 */_(ER_SQL_CANT_ADD_COLUMN_TO_VIEW,       "Can't add column '%s' to the view '%s'") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
====================

From this point I will review the email, not the branch.
Generally the patch still looks very raw. I hope this is mostly
because I couldn't review it properly locally.

> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
> index ac42fe842..45fb90d38 100644
> --- a/src/box/sql/build.c
> +++ b/src/box/sql/build.c
> @@ -285,48 +285,112 @@ sql_field_retrieve(Parse *parser, struct space_def *space_def, uint32_t id)
>  	return field;
>  }
>  
> -/*
> - * Add a new column to the table currently being constructed.
> +/**
> + * Make shallow copy of @a space on region.
>   *
> - * The parser calls this routine once for each column declaration
> - * in a CREATE TABLE statement.  sqlStartTable() gets called
> - * first to get things going.  Then this routine is called for each
> - * column.
> + * Function is used to add a new column to the existing space with
> + * <ALTER TABLE ADD COLUMN>. Copy info about indexes and
> + * definition to create constraints appeared in the statement.

5. I don't think I understood anything from the comment. Why is it needed
to create a copy (I remember why, a bit, but I mostly forgot it)?

>   */
> +static struct space *
> +sql_shallow_space_copy(struct Parse *parse, struct space *space)
> +{
> +	assert(space->def != NULL);
> +	struct space *ret = sql_ephemeral_space_new(parse, space->def->name);
> +	if (ret == NULL)
> +		return NULL;
> +	ret->index_count = space->index_count;
> +	ret->index_id_max = space->index_id_max;
> +	uint32_t indexes_sz = sizeof(struct index *) * (ret->index_count);
> +	ret->index = (struct index **) malloc(indexes_sz);

6. When is this array freed?

> +	if (ret->index == NULL) {
> +		diag_set(OutOfMemory, indexes_sz, "realloc", "ret->index");
> +		return NULL;
> +	}
> +	for (uint32_t i = 0; i < ret->index_count; i++)
> +		ret->index[i] = space->index[i];

7. What is the problem to make memcpy() instead of the cycle?

> +	memcpy(ret->def, space->def, sizeof(struct space_def));
> +	ret->def->opts.is_temporary = true;
> +	ret->def->opts.is_ephemeral = true;
> +	uint32_t fields_size = sizeof(struct field_def) * ret->def->field_count;
> +	ret->def->fields = region_alloc(&parse->region, fields_size);

8. Use region_alloc_array. Otherwise it will crash in ASAN build. It
should be visible in CI.

> +	if (ret->def->fields == NULL) {
> +		diag_set(OutOfMemory, fields_size, "region_alloc",
> +			 "ret->def->fields");

9. index array leaks here.

> +		return NULL;
> +	}
> +	memcpy(ret->def->fields, space->def->fields, fields_size);
> +
> +	return ret;
> +}
> +
>  void
> -sqlAddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
> +sql_create_column_start(struct Parse *parse)
>  {
> -	assert(type_def != NULL);
> -	char *z;
> -	sql *db = pParse->db;
> -	if (pParse->create_table_def.new_space == NULL)
> -		return;
> -	struct space_def *def = pParse->create_table_def.new_space->def;
> +	struct create_column_def *create_column_def = &parse->create_column_def;
> +	struct alter_entity_def *alter_entity_def =
> +		&create_column_def->base.base;
> +	assert(alter_entity_def->entity_type == ENTITY_TYPE_COLUMN);
> +	assert(alter_entity_def->alter_action == ALTER_ACTION_CREATE);
> +	struct space *space = parse->create_table_def.new_space;
> +	bool is_alter = space == NULL;
> +	struct sql *db = parse->db;
> +	if (is_alter) {
> +		const char *space_name =
> +			alter_entity_def->entity_name->a[0].zName;
> +		space = space_by_name(space_name);
> +		if (space == NULL) {
> +			diag_set(ClientError, ER_NO_SUCH_SPACE, space_name);
> +			goto tnt_error;
> +		}
> +		space = sql_shallow_space_copy(parse, space);
> +		if (space == NULL)
> +			goto tnt_error;
> +	}
> +	create_column_def->space = space;
> +	struct space_def *def = space->def;
> +	assert(def->opts.is_ephemeral);
>  
>  #if SQL_MAX_COLUMN
>  	if ((int)def->field_count + 1 > db->aLimit[SQL_LIMIT_COLUMN]) {
>  		diag_set(ClientError, ER_SQL_COLUMN_COUNT_MAX, def->name,
>  			 def->field_count + 1, db->aLimit[SQL_LIMIT_COLUMN]);
> -		pParse->is_aborted = true;
> -		return;
> +		goto tnt_error;

10. Was there a leak before your patch? Because of not
called sqlSrcListDelete(db, alter_entity_def->entity_name);

>  	}
>  #endif
> +
> +	struct region *region = &parse->region;
> +	struct Token *name = &create_column_def->base.name;
> +	char *column_name =
> +		sql_normalized_name_region_new(region, name->z, name->n);
> +	if (column_name == NULL)
> +		goto tnt_error;
> +
> +	if (parse->create_table_def.new_space == NULL && def->opts.is_view) {

11. You have is_alter flag for that.

> +		diag_set(ClientError, ER_SQL_CANT_ADD_COLUMN_TO_VIEW,
> +			 column_name, def->name);
> +		goto tnt_error;
> +	}
> +
>  	/*
> -	 * As sql_field_retrieve will allocate memory on region
> -	 * ensure that def is also temporal and would be dropped.
> +	 * Format can be set in Lua, then exact_field_count can be
> +	 * zero, but field_count is not.
>  	 */
> -	assert(def->opts.is_ephemeral);
> -	if (sql_field_retrieve(pParse, def, def->field_count) == NULL)
> -		return;
> -	struct region *region = &pParse->region;
> -	z = sql_normalized_name_region_new(region, pName->z, pName->n);
> -	if (z == NULL) {
> -		pParse->is_aborted = true;
> +	if (def->exact_field_count == 0)
> +		def->exact_field_count = def->field_count;
> +	if (sql_field_retrieve(parse, def, def->field_count) == NULL)
>  		return;
> +
> +	for (uint32_t i = 0; i < def->field_count; i++) {
> +		if (strcmp(column_name, def->fields[i].name) == 0) {
> +			diag_set(ClientError, ER_SPACE_FIELD_IS_DUPLICATE,
> +				 column_name);
> +			goto tnt_error;
> +		}

12. I remember this code was deliberately removed, because the same check is
already done in box. Why did you return it?

>  	}
>  	struct field_def *column_def = &def->fields[def->field_count];
> -	memcpy(column_def, &field_def_default, sizeof(field_def_default));
> -	column_def->name = z;
> +	memcpy(column_def, &field_def_default, sizeof(struct field_def));

13. Unnecessary change of memcpy().

> +	column_def->name = column_name;
>  	/*
>  	 * Marker ON_CONFLICT_ACTION_DEFAULT is used to detect
>  	 * attempts to define NULL multiple time or to detect
> @@ -334,19 +398,100 @@ sqlAddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
>  	 */
>  	column_def->nullable_action = ON_CONFLICT_ACTION_DEFAULT;
>  	column_def->is_nullable = true;
> -	column_def->type = type_def->type;
> +	column_def->type = create_column_def->type_def->type;
>  	def->field_count++;
> +
> +	sqlSrcListDelete(db, alter_entity_def->entity_name);
> +	return;
> +tnt_error:
> +	parse->is_aborted = true;
> +	sqlSrcListDelete(db, alter_entity_def->entity_name);
> +}
> +
> +static void
> +sql_vdbe_create_constraints(struct Parse *parse, int reg_space_id);
> +
> +void
> +sql_create_column_end(struct Parse *parse)
> +{
> +	struct create_column_def *create_column_def = &parse->create_column_def;
> +	struct space *space = parse->create_table_def.new_space;
> +	bool is_alter = space == NULL;
> +	space = create_column_def->space;
> +	struct space_def *def = space->def;
> +	if (is_alter) {
> +		struct field_def *field = &def->fields[def->field_count - 1];
> +		if (field->nullable_action == ON_CONFLICT_ACTION_DEFAULT) {
> +			if (create_column_def->is_pk) {
> +				field->nullable_action =
> +					ON_CONFLICT_ACTION_ABORT;
> +				field->is_nullable = false;
> +			} else {
> +				field->nullable_action =
> +					ON_CONFLICT_ACTION_NONE;
> +				field->is_nullable = true;
> +			}
> +		}
> +		/*
> +		 * Encode the format array and emit code to update _space.
> +		 */
> +		uint32_t table_stmt_sz = 0;
> +		struct region *region = &parse->region;
> +		char *table_stmt = sql_encode_table(region, def,
> +						    &table_stmt_sz);
> +		char *raw = sqlDbMallocRaw(parse->db, table_stmt_sz);
> +		if (table_stmt == NULL || raw == NULL) {
> +			parse->is_aborted = true;
> +			return;
> +		}
> +		memcpy(raw, table_stmt, table_stmt_sz);
> +
> +		struct Vdbe *v = sqlGetVdbe(parse);
> +		assert(v != NULL);
> +
> +		struct space *system_space = space_by_id(BOX_SPACE_ID);
> +		assert(system_space != NULL);
> +		int cursor = parse->nTab++;
> +		vdbe_emit_open_cursor(parse, cursor, 0, system_space);
> +		sqlVdbeChangeP5(v, OPFLAG_SYSTEMSP);
> +
> +		int key_reg = ++parse->nMem;
> +		sqlVdbeAddOp2(v, OP_Integer, def->id, key_reg);
> +		int addr = sqlVdbeAddOp4Int(v, OP_Found, cursor, 0, key_reg, 1);
> +		sqlVdbeAddOp2(v, OP_Halt, -1, ON_CONFLICT_ACTION_ABORT);
> +		sqlVdbeJumpHere(v, addr);
> +
> +		int tuple_reg = sqlGetTempRange(parse, box_space_field_MAX + 1);
> +		for (int i = 0; i < box_space_field_MAX - 1; ++i)
> +			sqlVdbeAddOp3(v, OP_Column, cursor, i, tuple_reg + i);
> +		sqlVdbeAddOp1(v, OP_Close, cursor);
> +
> +		sqlVdbeAddOp2(v, OP_Integer, def->field_count, tuple_reg + 4);
> +		sqlVdbeAddOp4(v, OP_Blob, table_stmt_sz, tuple_reg + 6,
> +			      SQL_SUBTYPE_MSGPACK, raw, P4_DYNAMIC);
> +		sqlVdbeAddOp3(v, OP_MakeRecord, tuple_reg, box_space_field_MAX,
> +			      tuple_reg + box_space_field_MAX);
> +		sqlVdbeAddOp4(v, OP_IdxReplace, tuple_reg + box_space_field_MAX,
> +			      0, 0, (char *) system_space, P4_SPACEPTR);
> +		sql_vdbe_create_constraints(parse, key_reg);
> +	}
> +
> +	memset(create_column_def, 0, sizeof(struct create_column_def));
> +	create_column_def->nullable_action = ON_CONFLICT_ACTION_DEFAULT;

14. Why do you touch column creation def after its usage?

>  }
>  
>  void
>  sql_column_add_nullable_action(struct Parse *parser,
>  			       enum on_conflict_action nullable_action)
>  {
> -	struct space *space = parser->create_table_def.new_space;
> -	if (space == NULL || NEVER(space->def->field_count < 1))
> +	struct space_def *def = NULL;
> +	struct field_def *field = NULL;
> +	struct space *space = parser->create_column_def.space;
> +	assert (space != NULL);

15. Please, don't put whitespace after macro/function name and its arguments.

> +	def = space->def;
> +	if (NEVER(def->field_count < 1))
>  		return;
> -	struct space_def *def = space->def;
> -	struct field_def *field = &def->fields[def->field_count - 1];
> +	field = &def->fields[def->field_count - 1];

16. Why did you do that change about struct field_def? It seems not to
be needed.

>  	if (field->nullable_action != ON_CONFLICT_ACTION_DEFAULT &&
>  	    nullable_action != field->nullable_action) {
>  		/* Prevent defining nullable_action many times. */
> @@ -364,51 +509,46 @@ sql_column_add_nullable_action(struct Parse *parser,
>  }
>  
>  /*
> - * The expression is the default value for the most recently added column
> - * of the table currently under construction.
> + * The expression is the default value for the most recently added
> + * column.
>   *
>   * Default value expressions must be constant.  Raise an exception if this
>   * is not the case.
>   *
>   * This routine is called by the parser while in the middle of
> - * parsing a CREATE TABLE statement.
> + * parsing a <CREATE TABLE> or a <ALTER TABLE ADD COLUMN>
> + * statement.
>   */
>  void
>  sqlAddDefaultValue(Parse * pParse, ExprSpan * pSpan)
>  {
>  	sql *db = pParse->db;
> -	struct space *p = pParse->create_table_def.new_space;
> -	if (p != NULL) {
> -		assert(p->def->opts.is_ephemeral);
> -		struct space_def *def = p->def;
> -		if (!sqlExprIsConstantOrFunction
> -		    (pSpan->pExpr, db->init.busy)) {
> -			const char *column_name =
> -				def->fields[def->field_count - 1].name;
> -			diag_set(ClientError, ER_CREATE_SPACE, def->name,
> -				 tt_sprintf("default value of column '%s' is "\
> -					    "not constant", column_name));
> +	struct space_def *def = NULL;
> +	struct field_def *field = NULL;
> +	struct space *space = pParse->create_column_def.space;
> +	assert (space != NULL);
> +	def = space->def;

17. Why can't you declare and initialize space_def in one line? The same
for field.

> +	field = &def->fields[def->field_count - 1];
> +	if (!sqlExprIsConstantOrFunction(pSpan->pExpr, db->init.busy)) {
> +		diag_set(ClientError, ER_CREATE_SPACE, def->name,
> +			 tt_sprintf("default value of column '%s' is not "
> +				    "constant", field->name));
> +		pParse->is_aborted = true;
> +	} else {
> +		assert(def != NULL);
> +		struct region *region = &pParse->region;
> +		uint32_t default_length = (int)(pSpan->zEnd - pSpan->zStart);
> +		field->default_value = region_alloc(region, default_length + 1);
> +		if (field->default_value == NULL) {
> +			diag_set(OutOfMemory, default_length + 1,
> +				 "region_alloc", "field->default_value");
>  			pParse->is_aborted = true;
> -		} else {
> -			assert(def != NULL);
> -			struct field_def *field =
> -				&def->fields[def->field_count - 1];
> -			struct region *region = &pParse->region;
> -			uint32_t default_length = (int)(pSpan->zEnd - pSpan->zStart);
> -			field->default_value = region_alloc(region,
> -							    default_length + 1);
> -			if (field->default_value == NULL) {
> -				diag_set(OutOfMemory, default_length + 1,
> -					 "region_alloc",
> -					 "field->default_value");
> -				pParse->is_aborted = true;
> -				return;
> -			}
> -			strncpy(field->default_value, pSpan->zStart,
> -				default_length);
> -			field->default_value[default_length] = '\0';
> +			goto add_default_value_exit;
>  		}
> +		strncpy(field->default_value, pSpan->zStart, default_length);
> +		field->default_value[default_length] = '\0';
>  	}
> +add_default_value_exit:
>  	sql_expr_delete(db, pSpan->pExpr, false);
>  }
>  
> @@ -447,6 +587,8 @@ sqlAddPrimaryKey(struct Parse *pParse)
>  	int nTerm;
>  	struct ExprList *pList = pParse->create_index_def.cols;
>  	struct space *space = pParse->create_table_def.new_space;
> +	if (space == NULL)
> +		space = pParse->create_column_def.space;
>  	if (space == NULL)
>  		goto primary_key_exit;
>  	if (sql_space_primary_key(space) != NULL) {
> @@ -574,8 +716,10 @@ sql_create_check_contraint(struct Parse *parser)
>  		(struct alter_entity_def *) create_ck_def;
>  	assert(alter_def->entity_type == ENTITY_TYPE_CK);
>  	(void) alter_def;
> -	struct space *space = parser->create_table_def.new_space;
> -	bool is_alter = space == NULL;
> +	struct space *space = parser->create_column_def.space;
> +	if (space == NULL)
> +		space = parser->create_table_def.new_space;
> +	bool is_alter_add_constr = space == NULL;

18. Why is it called 'is_alter' in other functions, but 'is_alter_add_constr'
here?

>  
>  	/* Prepare payload for ck constraint definition. */
>  	struct region *region = &parser->region;
> @@ -589,9 +733,18 @@ sql_create_check_contraint(struct Parse *parser)
>  			return;
>  		}
>  	} else {
> -		assert(! is_alter);
> -		uint32_t ck_idx = ++parser->create_table_def.check_count;
> -		name = tt_sprintf("ck_unnamed_%s_%d", space->def->name, ck_idx);
> +		assert(!is_alter_add_constr);
> +		uint32_t idx = ++parser->check_count;
> +		if (parser->create_table_def.new_space == NULL) {
> +			struct space *original_space =
> +				space_by_name(space->def->name);
> +			assert(original_space != NULL);
> +			struct rlist *checks = &original_space->ck_constraint;
> +			struct ck_constraint *ck;
> +			rlist_foreach_entry(ck, checks, link)
> +				idx++;
> +		}
> +		name = tt_sprintf("ck_unnamed_%s_%d", space->def->name, idx);
>  	}
>  	size_t name_len = strlen(name);
>  
> @@ -634,9 +787,9 @@ sql_create_check_contraint(struct Parse *parser)
>  	trim_space_snprintf(ck_def->expr_str, expr_str, expr_str_len);
>  	memcpy(ck_def->name, name, name_len);
>  	ck_def->name[name_len] = '\0';
> -	if (is_alter) {
> +	if (is_alter_add_constr) {
>  		const char *space_name = alter_def->entity_name->a[0].zName;
> -		struct space *space = space_by_name(space_name);
> +		space = space_by_name(space_name);

19. Why this change?

>  		if (space == NULL) {
>  			diag_set(ClientError, ER_NO_SUCH_SPACE, space_name);
>  			parser->is_aborted = true;
> @@ -652,8 +805,7 @@ sql_create_check_contraint(struct Parse *parser)
>  		sqlVdbeCountChanges(v);
>  		sqlVdbeChangeP5(v, OPFLAG_NCHANGE);
>  	} else {
> -		rlist_add_entry(&parser->create_table_def.new_check, ck_parse,
> -				link);
> +		rlist_add_entry(&parser->checks, ck_parse, link);
>  	}
>  }
>  
> @@ -664,18 +816,19 @@ sql_create_check_contraint(struct Parse *parser)
>  void
>  sqlAddCollateType(Parse * pParse, Token * pToken)
>  {
> -	struct space *space = pParse->create_table_def.new_space;
> -	if (space == NULL)
> -		return;
> +	struct space *space = pParse->create_column_def.space;
> +	uint32_t *coll_id = NULL;
> +	assert(space != NULL);
>  	uint32_t i = space->def->field_count - 1;
> +	coll_id = &space->def->fields[i].coll_id;

20. Why not declare and initialize coll_id in one line?

>  	sql *db = pParse->db;
>  	char *coll_name = sql_name_from_token(db, pToken);
>  	if (coll_name == NULL) {
>  		pParse->is_aborted = true;
>  		return;
>  	}
> -	uint32_t *coll_id = &space->def->fields[i].coll_id;
> -	if (sql_get_coll_seq(pParse, coll_name, coll_id) != NULL) {
> +	if (sql_get_coll_seq(pParse, coll_name, coll_id) != NULL &&
> +	    space != NULL) {

21. You have assert(space != NULL) above. Why do you need this check again?

>  		/* If the column is declared as "<name> PRIMARY KEY COLLATE <type>",
>  		 * then an index may have been created on this column before the
>  		 * collation type was added. Correct this if it is the case.
> @@ -692,6 +845,7 @@ sqlAddCollateType(Parse * pParse, Token * pToken)
>  	sqlDbFree(db, coll_name);
>  }
>  
> +

22. ???

>  struct coll *
>  sql_column_collation(struct space_def *def, uint32_t column, uint32_t *coll_id)
>  {
> @@ -1147,6 +1304,105 @@ resolve_link(struct Parse *parse_context, const struct space_def *def,
>  	return -1;
>  }
>  
> +/**
> + * Emit code to create sequences, indexes, check and foreign
> + * constraints appeared in <CREATE TABLE> or
> + * <ALTER TABLE ADD COLUMN> statement.
> + */
> +static void
> +sql_vdbe_create_constraints(struct Parse *parse, int reg_space_id)

23. Such huge code movements better do in a separate commit. I can't
tell whether you changed anything here, or just moved the code.

> +{
> +	assert(reg_space_id != 0);
> +	struct space *space = parse->create_table_def.new_space;
> +	bool is_alter = space == NULL;
> +	uint32_t i = 0;
> +	if (is_alter) {
> +		space = parse->create_column_def.space;
> +		i = space_by_name(space->def->name)->index_count;
> +	}
> +	assert(space != NULL);
> +	for (; i < space->index_count; ++i) {
> +		struct index *idx = space->index[i];
> +		vdbe_emit_create_index(parse, space->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.
> +	 */
> +	if (parse->has_autoinc) {
> +		/* Do an insertion into _sequence. */
> +		int reg_seq_id = ++parse->nMem;
> +		struct Vdbe *v = sqlGetVdbe(parse);
> +		assert(v != NULL);
> +		sqlVdbeAddOp2(v, OP_NextSequenceId, 0, reg_seq_id);
> +		int reg_seq_record =
> +			emitNewSysSequenceRecord(parse, reg_seq_id,
> +						 space->def->name);
> +		const char *error_msg =
> +			tt_sprintf(tnt_errcode_desc(ER_SQL_CANT_ADD_AUTOINC),
> +				   space->def->name);
> +		if (vdbe_emit_halt_with_presence_test(parse,
> +						      BOX_SEQUENCE_ID, 2,
> +						      reg_seq_record + 3, 1,
> +						      ER_SQL_CANT_ADD_AUTOINC,
> +						      error_msg, false,
> +						      OP_NoConflict) != 0)
> +			return;
> +		sqlVdbeAddOp2(v, OP_SInsert, BOX_SEQUENCE_ID, reg_seq_record);
> +		/* Do an insertion into _space_sequence. */
> +		int reg_space_seq_record =
> +			emitNewSysSpaceSequenceRecord(parse, reg_space_id,
> +						      reg_seq_id);
> +		sqlVdbeAddOp2(v, OP_SInsert, BOX_SPACE_SEQUENCE_ID,
> +			      reg_space_seq_record);
> +	}
> +
> +	/* Code creation of FK constraints, if any. */
> +	struct fk_constraint_parse *fk_parse;
> +	rlist_foreach_entry(fk_parse, &parse->fkeys, link) {
> +		struct fk_constraint_def *fk_def = fk_parse->fk_def;
> +		if (fk_parse->selfref_cols != NULL) {
> +			struct ExprList *cols = fk_parse->selfref_cols;
> +			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
> +				if (resolve_link(parse, space->def,
> +						 cols->a[i].zName,
> +						 &fk_def->links[i].parent_field,
> +						 fk_def->name) != 0)
> +					return;
> +			}
> +			fk_def->parent_id = reg_space_id;
> +		} else if (fk_parse->is_self_referenced) {
> +			struct key_def *pk_key_def =
> +				sql_space_primary_key(space)->def->key_def;
> +			if (pk_key_def->part_count != fk_def->field_count) {
> +				diag_set(ClientError, ER_CREATE_FK_CONSTRAINT,
> +					 fk_def->name, "number of columns in "\
> +					 "foreign key does not match the "\
> +					 "number of columns in the primary "\
> +					 "index of referenced table");
> +				parse->is_aborted = true;
> +				return;
> +			}
> +			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
> +				fk_def->links[i].parent_field =
> +					pk_key_def->parts[i].fieldno;
> +			}
> +			fk_def->parent_id = reg_space_id;
> +		}
> +		fk_def->child_id = reg_space_id;
> +		vdbe_emit_fk_constraint_create(parse, fk_def, space->def->name);
> +	}
> +
> +	/* Code creation of CK constraints, if any. */
> +	struct ck_constraint_parse *ck_parse;
> +	rlist_foreach_entry(ck_parse, &parse->checks, link) {
> +		vdbe_emit_ck_constraint_create(parse, ck_parse->ck_def,
> +					       reg_space_id, space->def->name);
> +	}
> +}
> +
>  /*
>   * This routine is called to report the final ")" that terminates
>   * a CREATE TABLE statement.
> @@ -1213,73 +1469,7 @@ sqlEndTable(struct Parse *pParse)
>  
>  	int reg_space_id = getNewSpaceId(pParse);
>  	vdbe_emit_space_create(pParse, reg_space_id, name_reg, new_space);
> -	for (uint32_t i = 0; i < new_space->index_count; ++i) {
> -		struct index *idx = new_space->index[i];
> -		vdbe_emit_create_index(pParse, new_space->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.
> -	 */
> -	if (pParse->create_table_def.has_autoinc) {
> -		assert(reg_space_id != 0);
> -		/* Do an insertion into _sequence. */
> -		int reg_seq_id = ++pParse->nMem;
> -		sqlVdbeAddOp2(v, OP_NextSequenceId, 0, reg_seq_id);
> -		int reg_seq_record =
> -			emitNewSysSequenceRecord(pParse, reg_seq_id,
> -						 new_space->def->name);
> -		sqlVdbeAddOp2(v, OP_SInsert, BOX_SEQUENCE_ID, reg_seq_record);
> -		/* Do an insertion into _space_sequence. */
> -		int reg_space_seq_record =
> -			emitNewSysSpaceSequenceRecord(pParse, reg_space_id,
> -						      reg_seq_id);
> -		sqlVdbeAddOp2(v, OP_SInsert, BOX_SPACE_SEQUENCE_ID,
> -			      reg_space_seq_record);
> -	}
> -	/* Code creation of FK constraints, if any. */
> -	struct fk_constraint_parse *fk_parse;
> -	rlist_foreach_entry(fk_parse, &pParse->create_table_def.new_fkey,
> -			    link) {
> -		struct fk_constraint_def *fk_def = fk_parse->fk_def;
> -		if (fk_parse->selfref_cols != NULL) {
> -			struct ExprList *cols = fk_parse->selfref_cols;
> -			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
> -				if (resolve_link(pParse, new_space->def,
> -						 cols->a[i].zName,
> -						 &fk_def->links[i].parent_field,
> -						 fk_def->name) != 0)
> -					return;
> -			}
> -			fk_def->parent_id = reg_space_id;
> -		} else if (fk_parse->is_self_referenced) {
> -			struct index *pk = sql_space_primary_key(new_space);
> -			if (pk->def->key_def->part_count != fk_def->field_count) {
> -				diag_set(ClientError, ER_CREATE_FK_CONSTRAINT,
> -					 fk_def->name, "number of columns in "\
> -					 "foreign key does not match the "\
> -					 "number of columns in the primary "\
> -					 "index of referenced table");
> -				pParse->is_aborted = true;
> -				return;
> -			}
> -			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
> -				fk_def->links[i].parent_field =
> -					pk->def->key_def->parts[i].fieldno;
> -			}
> -			fk_def->parent_id = reg_space_id;
> -		}
> -		fk_def->child_id = reg_space_id;
> -		vdbe_emit_fk_constraint_create(pParse, fk_def, space_name_copy);
> -	}
> -	struct ck_constraint_parse *ck_parse;
> -	rlist_foreach_entry(ck_parse, &pParse->create_table_def.new_check,
> -			    link) {
> -		vdbe_emit_ck_constraint_create(pParse, ck_parse->ck_def,
> -					       reg_space_id, space_name_copy);
> -	}
> +	sql_vdbe_create_constraints(pParse, reg_space_id);
>  }
> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
> index 995875566..5904414a3 100644
> --- a/src/box/sql/parse.y
> +++ b/src/box/sql/parse.y
> @@ -314,6 +320,7 @@ ccons ::= cconsname(N) PRIMARY KEY sortorder(Z). {
>    create_index_def_init(&pParse->create_index_def, NULL, &N, NULL,
>                          SQL_INDEX_TYPE_CONSTRAINT_PK, Z, false);
>    sqlAddPrimaryKey(pParse);
> +  pParse->create_column_def.is_pk = true;

24. Why can't this be done in sqlAddPrimaryKey(pParse);? What if this
is CREATE TABLE, and there are many columns in PK?

>  }
>  ccons ::= cconsname(N) UNIQUE. {
>    create_index_def_init(&pParse->create_index_def, NULL, &N, NULL,
> diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
> index a5a258805..b31bac437 100644
> --- a/src/box/sql/prepare.c
> +++ b/src/box/sql/prepare.c
> @@ -197,9 +197,13 @@ sql_parser_create(struct Parse *parser, struct sql *db, uint32_t sql_flags)
>  {
>  	memset(parser, 0, sizeof(struct Parse));
>  	parser->db = db;
> +	parser->create_column_def.nullable_action = ON_CONFLICT_ACTION_DEFAULT;

25. Why isn't there a proper constructor for create_column_def structure?

>  	parser->sql_flags = sql_flags;
>  	parser->line_count = 1;
>  	parser->line_pos = 1;
> +	rlist_create(&parser->fkeys);
> +	rlist_create(&parser->checks);

26. Why did you merge these fields into struct Parse? They were specially
moved out of here for the purpose of some kind of isolation, into parse_def.h
structures.

> +	parser->has_autoinc = false;
>  	region_create(&parser->region, &cord()->slabc);
>  }
>  
> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index aa6a470f8..3143ec521 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -2249,12 +2249,26 @@ struct Parse {
>  		struct enable_entity_def enable_entity_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).
> +	 * Table def or column def is not part of union since
> +	 * information being held must survive till the end of
> +	 * parsing of whole <CREATE TABLE> or
> +	 * <ALTER TABLE ADD COLUMN> statement (to pass it to
> +	 * sqlEndTable() sql_create_column_end() function).
>  	 */
>  	struct create_table_def create_table_def;
> +	struct create_column_def create_column_def;
> +	/**
> +	 * FK and CK constraints appeared in a <CREATE TABLE> or
> +	 * a <ALTER TABLE ADD COLUMN> statement.
> +	 */
> +	struct rlist fkeys;
> +	struct rlist checks;
> +	uint32_t fkey_count;
> +	uint32_t check_count;
> +	/** True, if column to be created has <AUTOINCREMENT>. */
> +	bool has_autoinc;

27. What column? This is struct Parse, it is not a column.

> +	/** Id of field with <AUTOINCREMENT>. */
> +	uint32_t autoinc_fieldno;
>  	bool initiateTTrans;	/* Initiate Tarantool transaction */
>  	/** If set - do not emit byte code at all, just parse.  */
>  	bool parse_only;
> @@ -2844,15 +2858,31 @@ struct space *sqlResultSetOfSelect(Parse *, Select *);
>  
>  struct space *
>  sqlStartTable(Parse *, Token *);
> -void sqlAddColumn(Parse *, Token *, struct type_def *);
> +
> +/**
> + * Add new field to the format of ephemeral space in
> + * create_table_def. If it is <ALTER TABLE> create shallow copy of
> + * the existing space and add field to its format.
> + */
> +void
> +sql_create_column_start(struct Parse *parse);
> +
> +/**
> + * Nullify create_column_def. If it is <ALTER TABLE> emit code to
> + * update entry in _space and code to create constraints (entries
> + * in _index, _ck_constraint, _fk_constraint) described with this
> + * column.
> + */
> +void
> +sql_create_column_end(struct Parse *parse);
>  
>  /**
>   * This routine is called by the parser while in the middle of
> - * parsing a CREATE TABLE statement.  A "NOT NULL" constraint has
> - * been seen on a column.  This routine sets the is_nullable flag
> - * on the column currently under construction.
> - * If nullable_action has been already set, this function raises
> - * an error.
> + * parsing a <CREATE TABLE> or a <ALTER TABLE ADD COLUMN>
> + * statement. A "NOT NULL" constraint has been seen on a column.
> + * This routine sets the is_nullable flag on the column currently
> + * under construction. If nullable_action has been already set,
> + * this function raises an error.
>   *
>   * @param parser SQL Parser object.
>   * @param nullable_action on_conflict_action value.
> diff --git a/test/box/error.result b/test/box/error.result
> index 2196fa541..eb55f9cdf 100644
> --- a/test/box/error.result
> +++ b/test/box/error.result
> @@ -432,6 +432,8 @@ t;
>   |   211: box.error.WRONG_QUERY_ID
>   |   212: box.error.SEQUENCE_NOT_STARTED
>   |   213: box.error.NO_SUCH_SESSION_SETTING
> + |   214: box.error.SQL_CANT_ADD_COLUMN_TO_VIEW
> + |   215: box.error.SQL_CANT_ADD_AUTOINC
>   | ...
>  
>  test_run:cmd("setopt delimiter ''");
> diff --git a/test/sql/add-column.result b/test/sql/add-column.result
> new file mode 100644
> index 000000000..06c95facb
> --- /dev/null
> +++ b/test/sql/add-column.result

28. You may want to look at the original SQLite tests. They probably
have some.

> @@ -0,0 +1,276 @@
> +-- test-run result file version 2
> +test_run = require('test_run').new()
> + | ---
> + | ...
> +engine = test_run:get_cfg('engine')
> + | ---
> + | ...
> +_ = box.space._session_settings:update('sql_default_engine', {{'=', 2, engine}})
> + | ---
> + | ...
> +
> +--
> +-- gh-3075: Check <ALTER TABLE table ADD COLUMN column> statement.
> +--
> +box.execute('CREATE TABLE t1 (a INTEGER PRIMARY KEY);')
> + | ---
> + | - row_count: 1
> + | ...
> +--
> +-- COLUMN keyword is optional. Check it here, but omit it below.
> +--
> +box.execute('ALTER TABLE t1 ADD COLUMN b INTEGER;')
> + | ---
> + | - row_count: 0
> + | ...
> +
> +--
> +-- Can't add column to a view.
> +--
> +box.execute('CREATE VIEW v AS SELECT * FROM t1;')
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute('ALTER TABLE v ADD b INTEGER;')
> + | ---
> + | - null
> + | - Can't add column 'B'. 'V' is a view
> + | ...
> +box.execute('DROP VIEW v;')
> + | ---
> + | - row_count: 1
> + | ...
> +
> +--
> +-- Check column constraints typing and work.
> +--
> +box.execute('CREATE TABLE t2 (a INTEGER CONSTRAINT pk_constr PRIMARY KEY);')
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute('ALTER TABLE t2 DROP CONSTRAINT pk_constr')
> + | ---
> + | - row_count: 1
> + | ...
> +test_run:cmd("setopt delimiter ';'");
> + | ---
> + | - true
> + | ...
> +box.execute([[ALTER TABLE t2 ADD b INTEGER CONSTRAINT pk_constr PRIMARY KEY
> +                                   CHECK (b > 0)
> +                                   REFERENCES t1(a)
> +                                   CONSTRAINT u_constr UNIQUE]])
> +test_run:cmd("setopt delimiter ''");
> + | ---
> + | ...
> +box.execute('INSERT INTO t1 VALUES (1, 1);')
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute('INSERT INTO t2 VALUES (1, 1);')
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute('INSERT INTO t2 VALUES (1, 1);')
> + | ---
> + | - null
> + | - Duplicate key exists in unique index 'PK_CONSTR' in space 'T2'
> + | ...
> +
> +box.execute('INSERT INTO t1 VALUES (0, 1);')
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute('INSERT INTO t2 VALUES (2, 0);')
> + | ---
> + | - null
> + | - 'Check constraint failed ''ck_unnamed_T2_1'': b > 0'
> + | ...
> +
> +box.execute('INSERT INTO t2 VALUES (2, 3);')
> + | ---
> + | - null
> + | - 'Failed to execute SQL statement: FOREIGN KEY constraint failed'
> + | ...
> +
> +box.execute('DROP TABLE t2;')
> + | ---
> + | - row_count: 1
> + | ...
> +
> +--
> +-- Check self-referenced FK creation.
> +--
> +box.execute('CREATE TABLE t2 (a INTEGER PRIMARY KEY);')
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute('ALTER TABLE t2 ADD b INT REFERENCES t1')
> + | ---
> + | - row_count: 0

29. Worth checking if it works. Not just created and dropped right away.

> + | ...
> +
> +box.execute('DROP TABLE t2;')
> + | ---
> + | - row_count: 1
> + | ...
> +
> +--
> +-- Check AUTOINCREMENT work.
> +--
> +box.execute("CREATE TABLE t2(a INTEGER CONSTRAINT pk PRIMARY KEY);")
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute("ALTER TABLE t2 DROP CONSTRAINT pk;")
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute("ALTER TABLE t2 ADD b INTEGER PRIMARY KEY AUTOINCREMENT;")
> + | ---
> + | - row_count: 0

30. The same.

> + | ...
> +box.execute("ALTER TABLE t2 ADD c INTEGER AUTOINCREMENT;")
> + | ---
> + | - null
> + | - 'Can''t add AUTOINCREMENT: the space ''T2'' already has one auto-incremented field'
> + | ...
> +
> +box.execute('DROP TABLE t2;')
> + | ---
> + | - row_count: 1
> + | ...
> +
> +--
> +-- Check clauses after column typing and work.
> +--
> +box.execute('CREATE TABLE t2 (a INTEGER PRIMARY KEY, b INTEGER);')
> + | ---
> + | - row_count: 1
> + | ...
> +test_run:cmd("setopt delimiter ';'");
> + | ---
> + | - true
> + | ...
> +box.execute([[ALTER TABLE t2 ADD c TEXT NOT NULL DEFAULT ('a')
> +                                   COLLATE "unicode_ci";]]);
> + | ---
> + | - row_count: 0
> + | ...
> +test_run:cmd("setopt delimiter ''");
> + | ---
> + | - true
> + | ...
> +box.execute('INSERT INTO t2(a, b) VALUES (1, 1);')
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute('SELECT * FROM t2;')
> + | ---
> + | - metadata:
> + |   - name: A
> + |     type: integer
> + |   - name: B
> + |     type: integer
> + |   - name: C
> + |     type: string
> + |   rows:
> + |   - [1, 1, 'a']
> + | ...
> +box.execute('INSERT INTO t2 VALUES (2, 2, NULL);')
> + | ---
> + | - null
> + | - 'Failed to execute SQL statement: NOT NULL constraint failed: T2.C'
> + | ...
> +box.execute('SELECT * FROM t2 WHERE c LIKE \'A\';')
> + | ---
> + | - metadata:
> + |   - name: A
> + |     type: integer
> + |   - name: B
> + |     type: integer
> + |   - name: C
> + |     type: string
> + |   rows:
> + |   - [1, 1, 'a']
> + | ...
> +
> +--
> +-- Try to add to a non-empty space a [non-]nullable field.
> +--
> +box.execute('ALTER TABLE t2 ADD d INTEGER;')
> + | ---
> + | - null
> + | - Tuple field count 3 does not match space field count 4
> + | ...
> +box.execute('ALTER TABLE t2 ADD d TEXT NOT NULL');
> + | ---
> + | - null
> + | - Tuple field count 3 does not match space field count 4
> + | ...
> +box.execute('ALTER TABLE t2 ADD e TEXT NULL');
> + | ---
> + | - null
> + | - Tuple field count 3 does not match space field count 4
> + | ...
> +
> +--
> +-- Add to a space with no-SQL adjusted or without format.
> +--
> +_ = box.schema.space.create('WITHOUT')
> + | ---
> + | ...
> +box.execute("ALTER TABLE WITHOUT ADD a INTEGER;")

31. No need to caps table names in SQL.

32. Need to check if it actually worked. The same below.

> + | ---
> + | - row_count: 0
> + | ...
> +box.execute("DROP TABLE WITHOUT;")
> + | ---
> + | - row_count: 1
> + | ...
> +
> +s = box.schema.space.create('NOSQL')
> + | ---
> + | ...
> +s:format{{name = 'A', type = 'unsigned'}}
> + | ---
> + | ...
> +box.execute("ALTER TABLE NOSQL ADD b INTEGER")
> + | ---
> + | - row_count: 0
> + | ...
> +
> +box.execute('DROP TABLE t2;')
> + | ---
> + | - row_count: 1
> + | ...
> +--
> +-- Add multiple columns inside a transaction.
> +--
> +box.execute('CREATE TABLE t2 (a INTEGER PRIMARY KEY)')
> + | ---
> + | - row_count: 1
> + | ...
> +box.begin()                                                                     \
> +box.execute('ALTER TABLE t2 ADD b INT')                                         \
> +box.execute('ALTER TABLE t2 ADD c INT')                                         \
> +box.commit()
> + | ---
> + | ...
> +
> +box.execute('INSERT INTO t2 VALUES (1, 1, 1)')
> + | ---
> + | - row_count: 1
> + | ...
> +box.execute('SELECT * FROM t2;')
> + | ---
> + | - metadata:
> + |   - name: A
> + |     type: integer
> + |   - name: B
> + |     type: integer
> + |   - name: C
> + |     type: integer
> + |   rows:
> + |   - [1, 1, 1]

33. What if I add a column with UNIQUE constraint?

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

* Re: [Tarantool-patches] [PATCH v2 2/2] sql: support column addition
  2020-07-12 16:45       ` Vladislav Shpilevoy
@ 2020-08-11  0:34         ` Roman Khabibov
  2020-08-19 22:20           ` Vladislav Shpilevoy
  0 siblings, 1 reply; 14+ messages in thread
From: Roman Khabibov @ 2020-08-11  0:34 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Thanks for the review.

> On Jul 12, 2020, at 19:45, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Hi! Thanks for the patch!
> 
> See 33 comments below.
> 
>> commit 82c448dc66f6233faeb40dda353652c2fd5a3d29
>> Author: Roman Khabibov <roman.habibov@tarantool.org>
>> Date:   Thu Jan 2 19:06:14 2020 +0300
>> 
>>    sql: support column addition
>> 
>>    Enable to add column to existing space with
>>    <ALTER TABLE ADD [COLUMN]> statement. Column definition can be
>>    supplemented with the four types of constraints, <DEFAULT>,
>>    <COLLATE> clauses and <[NOT] NULL>, AUTOINCREMENT.
>> 
>>    Closes #2349, #3075
>> 
>>    @TarantoolBot document
>>    Title: Add columns to existing tables in SQL
>>    Now, it is possible to add columns to existing empty spaces using
>>    <ALTER TABLE table_name ADD [COLUMN] column_name column_type ...>
>>    statement. The column definition is the same as in <CREATE TABLE>
>>    statement.
>> 
>>    For example:
>> 
>>    tarantool> box.execute([[CREATE TABLE test (
>>                               a INTEGER PRIMARY KEY
>>                          );]])
>>    ---
>>    - row_count: 1
>>    ...
>> 
>>    tarantool> box.execute([[ALTER TABLE test ADD COLUMN
>>                               b TEXT
>>                               NOT NULL
>>                               DEFAULT ('a')
>>                               COLLATE "unicode_ci"
>>                           ;]])
>>    ---
>>    - row_count: 0
>>    ...
>>    ---
> 
> 1. The commit message is different from what I see on the branch. On the
> branch there is no 'Closes #2349'. What is the most actual version?
> 
> 2. The example from the doc request does not work:
> 
> 	tarantool> box.execute([[CREATE TABLE test (a INTEGER PRIMARY KEY);]])
> 	---
> 	- row_count: 1
> 	...
> 
> 	tarantool> box.execute([[ALTER TABLE test ADD COLUMN b TEXT NOT NULL DEFAULT ('a') COLLATE "unicode_ci";]])
> 	---
> 	- null
> 	- 'At line 1 at or near position 22: keyword ''COLUMN'' is reserved. Please use double
> 	  quotes if ''COLUMN'' is an identifier.'
> 	...
> 
>> diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
>> index 486b6b30d..dea047241 100644
>> --- a/extra/mkkeywordhash.c
>> +++ b/extra/mkkeywordhash.c
>> @@ -76,7 +76,7 @@ static Keyword aKeywordTable[] = {
>>   { "CHECK",                  "TK_CHECK",       true  },
>>   { "COLLATE",                "TK_COLLATE",     true  },
>>   { "COLUMN_NAME",            "TK_COLUMN_NAME", true  },
>> -  { "COLUMN",                 "TK_STANDARD",    true  },
>> +  { "COLUMN",                 "TK_COLUMN",      true  },
> 
> 3. This is how the same hunk looks on the branch:
> 
> ====================
> diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
> index 006285622..51c8cbb18 100644
> --- a/extra/mkkeywordhash.c
> +++ b/extra/mkkeywordhash.c
> @@ -75,11 +75,7 @@ static Keyword aKeywordTable[] = {
>   { "CAST",                   "TK_CAST",        false },
>   { "CHECK",                  "TK_CHECK",       true  },
>   { "COLLATE",                "TK_COLLATE",     true  },
> -  /* gh-3075: Reserved until ALTER ADD COLUMN is implemeneted.
> -   * Move it back to ALTER when done.
> -   */
> -  /* { "COLUMN",              "TK_COLUMNKW",    true  }, */
> -  { "COLUMN",                 "TK_STANDARD",    true  },
> +  { "COLUMN",                 "TK_COLUMN",      true  },
>   { "COMMIT",                 "TK_COMMIT",      true  },
>   { "CONFLICT",               "TK_CONFLICT",    false },
>   { "CONSTRAINT",             "TK_CONSTRAINT",  true  },
> ====================
> 
> It is very different. Why?
> 
>> diff --git a/src/box/errcode.h b/src/box/errcode.h
>> index d1e4d02a9..3e94bee7a 100644
>> --- a/src/box/errcode.h
>> +++ b/src/box/errcode.h
>> @@ -266,6 +266,8 @@ struct errcode_record {
>> 	/*211 */_(ER_WRONG_QUERY_ID,		"Prepared statement with id %u does not exist") \
>> 	/*212 */_(ER_SEQUENCE_NOT_STARTED,		"Sequence '%s' is not started") \
>> 	/*213 */_(ER_NO_SUCH_SESSION_SETTING,	"Session setting %s doesn't exist") \
>> +	/*214 */_(ER_SQL_CANT_ADD_COLUMN_TO_VIEW,	"Can't add column '%s'. '%s' is a view") \
>> +	/*215 */_(ER_SQL_CANT_ADD_AUTOINC,	"Can't add AUTOINCREMENT: the space '%s' already has one auto-incremented field") \
> 
> 4. The same here:
> 
> ====================
> --- a/src/box/errcode.h
> +++ b/src/box/errcode.h
> @@ -264,6 +264,7 @@ struct errcode_record {
>        /*209 */_(ER_SESSION_SETTING_INVALID_VALUE,     "Session setting %s expected a value of type %s") \
>        /*210 */_(ER_SQL_PREPARE,               "Failed to prepare SQL statement: %s") \
>        /*211 */_(ER_WRONG_QUERY_ID,            "Prepared statement with id %u does not exist") \
> +       /*212 */_(ER_SQL_CANT_ADD_COLUMN_TO_VIEW,       "Can't add column '%s' to the view '%s'") \
> 
> /*
>  * !IMPORTANT! Please follow instructions at start of the file
> ====================
> 
> From this point I will review the email, not the branch.
> Generally the patch still looks very raw. I hope this is mostly
> because I couldn't review it properly locally.
There was two branches on GitHub: romanhabibov/gh-3075-add-column and romanhabibov/gh-3075-add-column-v2.
I dropped the first so we don't get confused anymore.

>> diff --git a/src/box/sql/build.c b/src/box/sql/build.c
>> index ac42fe842..45fb90d38 100644
>> --- a/src/box/sql/build.c
>> +++ b/src/box/sql/build.c
>> @@ -285,48 +285,112 @@ sql_field_retrieve(Parse *parser, struct space_def *space_def, uint32_t id)
>> 	return field;
>> }
>> 
>> -/*
>> - * Add a new column to the table currently being constructed.
>> +/**
>> + * Make shallow copy of @a space on region.
>>  *
>> - * The parser calls this routine once for each column declaration
>> - * in a CREATE TABLE statement.  sqlStartTable() gets called
>> - * first to get things going.  Then this routine is called for each
>> - * column.
>> + * Function is used to add a new column to the existing space with
>> + * <ALTER TABLE ADD COLUMN>. Copy info about indexes and
>> + * definition to create constraints appeared in the statement.
> 
> 5. I don't think I understood anything from the comment. Why is it needed
> to create a copy (I remember why, a bit, but I mostly forgot it)?
To reuse the existing code. This is the best way, I think.  Otherwise, we
will have to essentially rewrite the functions for creating constraints.

-/*
- * Add a new column to the table currently being constructed.
+/**
+ * Make shallow copy of @a space on region.
  *
- * The parser calls this routine once for each column declaration
- * in a CREATE TABLE statement.  sqlStartTable() gets called
- * first to get things going.  Then this routine is called for each
- * column.
+ * Function is used to add a new column to an existing space with
+ * <ALTER TABLE ADD COLUMN> statement. Copy space def and index
+ * array to create constraints appeared in the statement. The
+ * index array copy will be modified by adding new elements to it.
+ * It is necessary, because the statement may contain several
+ * index definitions (constraints).
  */
+static struct space *
+sql_shallow_space_copy(struct Parse *parse, struct space *space)

>>  */
>> +static struct space *
>> +sql_shallow_space_copy(struct Parse *parse, struct space *space)
>> +{
>> +	assert(space->def != NULL);
>> +	struct space *ret = sql_ephemeral_space_new(parse, space->def->name);
>> +	if (ret == NULL)
>> +		return NULL;
>> +	ret->index_count = space->index_count;
>> +	ret->index_id_max = space->index_id_max;
>> +	uint32_t indexes_sz = sizeof(struct index *) * (ret->index_count);
>> +	ret->index = (struct index **) malloc(indexes_sz);
> 
> 6. When is this array freed?
In sql_create_column_end(struct Parse *parse)

+	/*
+	 * Clean up array allocated in sql_shallow_space_copy().
+	 */
+	free(space->index);

>> +	if (ret->index == NULL) {
>> +		diag_set(OutOfMemory, indexes_sz, "realloc", "ret->index");
>> +		return NULL;
>> +	}
>> +	for (uint32_t i = 0; i < ret->index_count; i++)
>> +		ret->index[i] = space->index[i];
> 
> 7. What is the problem to make memcpy() instead of the cycle?
No problem. See below.

>> +	memcpy(ret->def, space->def, sizeof(struct space_def));
>> +	ret->def->opts.is_temporary = true;
>> +	ret->def->opts.is_ephemeral = true;
>> +	uint32_t fields_size = sizeof(struct field_def) * ret->def->field_count;
>> +	ret->def->fields = region_alloc(&parse->region, fields_size);
> 
> 8. Use region_alloc_array. Otherwise it will crash in ASAN build. It
> should be visible in CI.
Ok.

>> +	if (ret->def->fields == NULL) {
>> +		diag_set(OutOfMemory, fields_size, "region_alloc",
>> +			 "ret->def->fields");
> 
> 9. index array leaks here.
Fixed.

+static struct space *
+sql_shallow_space_copy(struct Parse *parse, struct space *space)
+{
+	assert(space->def != NULL);
+	struct space *ret = sql_ephemeral_space_new(parse, space->def->name);
+	if (ret == NULL)
+		return NULL;
+	ret->index_count = space->index_count;
+	ret->index_id_max = space->index_id_max;
+	uint32_t indexes_sz = sizeof(struct index *) * (ret->index_count);
+	ret->index = (struct index **) malloc(indexes_sz);
+	if (ret->index == NULL) {
+		diag_set(OutOfMemory, indexes_sz, "realloc", "ret->index");
+		return NULL;
+	}
+	memcpy(ret->index, space->index, indexes_sz);
+	memcpy(ret->def, space->def, sizeof(struct space_def));
+	ret->def->opts.is_temporary = true;
+	ret->def->opts.is_ephemeral = true;
+	if (ret->def->field_count != 0) {
+		uint32_t fields_size = 0;
+		ret->def->fields =
+			region_alloc_array(&parse->region,
+					   typeof(struct field_def),
+					   ret->def->field_count, &fields_size);
+		if (ret->def->fields == NULL) {
+			diag_set(OutOfMemory, fields_size, "region_alloc",
+				 "ret->def->fields");
+			free(ret->index);
+			return NULL;
+		}
+		memcpy(ret->def->fields, space->def->fields, fields_size);
+	}
+
+	return ret;
+}

>> +		return NULL;
>> +	}
>> +	memcpy(ret->def->fields, space->def->fields, fields_size);
>> +
>> +	return ret;
>> +}
>> +
>> void
>> -sqlAddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
>> +sql_create_column_start(struct Parse *parse)
>> {
>> -	assert(type_def != NULL);
>> -	char *z;
>> -	sql *db = pParse->db;
>> -	if (pParse->create_table_def.new_space == NULL)
>> -		return;
>> -	struct space_def *def = pParse->create_table_def.new_space->def;
>> +	struct create_column_def *create_column_def = &parse->create_column_def;
>> +	struct alter_entity_def *alter_entity_def =
>> +		&create_column_def->base.base;
>> +	assert(alter_entity_def->entity_type == ENTITY_TYPE_COLUMN);
>> +	assert(alter_entity_def->alter_action == ALTER_ACTION_CREATE);
>> +	struct space *space = parse->create_table_def.new_space;
>> +	bool is_alter = space == NULL;
>> +	struct sql *db = parse->db;
>> +	if (is_alter) {
>> +		const char *space_name =
>> +			alter_entity_def->entity_name->a[0].zName;
>> +		space = space_by_name(space_name);
>> +		if (space == NULL) {
>> +			diag_set(ClientError, ER_NO_SUCH_SPACE, space_name);
>> +			goto tnt_error;
>> +		}
>> +		space = sql_shallow_space_copy(parse, space);
>> +		if (space == NULL)
>> +			goto tnt_error;
>> +	}
>> +	create_column_def->space = space;
>> +	struct space_def *def = space->def;
>> +	assert(def->opts.is_ephemeral);
>> 
>> #if SQL_MAX_COLUMN
>> 	if ((int)def->field_count + 1 > db->aLimit[SQL_LIMIT_COLUMN]) {
>> 		diag_set(ClientError, ER_SQL_COLUMN_COUNT_MAX, def->name,
>> 			 def->field_count + 1, db->aLimit[SQL_LIMIT_COLUMN]);
>> -		pParse->is_aborted = true;
>> -		return;
>> +		goto tnt_error;
> 
> 10. Was there a leak before your patch? Because of not
> called sqlSrcListDelete(db, alter_entity_def->entity_name);
No, because alter_entity_def is a part of create_column_def.
create_column_def didn’t exist before my patch.

>> 	}
>> #endif
>> +
>> +	struct region *region = &parse->region;
>> +	struct Token *name = &create_column_def->base.name;
>> +	char *column_name =
>> +		sql_normalized_name_region_new(region, name->z, name->n);
>> +	if (column_name == NULL)
>> +		goto tnt_error;
>> +
>> +	if (parse->create_table_def.new_space == NULL && def->opts.is_view) {
> 
> 11. You have is_alter flag for that.
Yes. Fixed.

>> +		diag_set(ClientError, ER_SQL_CANT_ADD_COLUMN_TO_VIEW,
>> +			 column_name, def->name);
>> +		goto tnt_error;
>> +	}
>> +
>> 	/*
>> -	 * As sql_field_retrieve will allocate memory on region
>> -	 * ensure that def is also temporal and would be dropped.
>> +	 * Format can be set in Lua, then exact_field_count can be
>> +	 * zero, but field_count is not.
>> 	 */
>> -	assert(def->opts.is_ephemeral);
>> -	if (sql_field_retrieve(pParse, def, def->field_count) == NULL)
>> -		return;
>> -	struct region *region = &pParse->region;
>> -	z = sql_normalized_name_region_new(region, pName->z, pName->n);
>> -	if (z == NULL) {
>> -		pParse->is_aborted = true;
>> +	if (def->exact_field_count == 0)
>> +		def->exact_field_count = def->field_count;
>> +	if (sql_field_retrieve(parse, def, def->field_count) == NULL)
>> 		return;
>> +
>> +	for (uint32_t i = 0; i < def->field_count; i++) {
>> +		if (strcmp(column_name, def->fields[i].name) == 0) {
>> +			diag_set(ClientError, ER_SPACE_FIELD_IS_DUPLICATE,
>> +				 column_name);
>> +			goto tnt_error;
>> +		}
> 
> 12. I remember this code was deliberately removed, because the same check is
> already done in box. Why did you return it?
Sorry, removed.

>> 	}
>> 	struct field_def *column_def = &def->fields[def->field_count];
>> -	memcpy(column_def, &field_def_default, sizeof(field_def_default));
>> -	column_def->name = z;
>> +	memcpy(column_def, &field_def_default, sizeof(struct field_def));
> 
> 13. Unnecessary change of memcpy().
Yes. Returned old version.

+sql_create_column_start(struct Parse *parse)
 {
-	assert(type_def != NULL);
-	char *z;
-	sql *db = pParse->db;
-	if (pParse->create_table_def.new_space == NULL)
-		return;
-	struct space_def *def = pParse->create_table_def.new_space->def;
+	struct create_column_def *create_column_def = &parse->create_column_def;
+	struct alter_entity_def *alter_entity_def =
+		&create_column_def->base.base;
+	assert(alter_entity_def->entity_type == ENTITY_TYPE_COLUMN);
+	assert(alter_entity_def->alter_action == ALTER_ACTION_CREATE);
+	struct space *space = parse->create_table_def.new_space;
+	bool is_alter = space == NULL;
+	struct sql *db = parse->db;
+	if (is_alter) {
+		const char *space_name =
+			alter_entity_def->entity_name->a[0].zName;
+		space = space_by_name(space_name);
+		if (space == NULL) {
+			diag_set(ClientError, ER_NO_SUCH_SPACE, space_name);
+			goto tnt_error;
+		}
+		space = sql_shallow_space_copy(parse, space);
+		if (space == NULL)
+			goto tnt_error;
+	}
+	create_column_def->space = space;
+	struct space_def *def = space->def;
+	assert(def->opts.is_ephemeral);
 
 #if SQL_MAX_COLUMN
 	if ((int)def->field_count + 1 > db->aLimit[SQL_LIMIT_COLUMN]) {
 		diag_set(ClientError, ER_SQL_COLUMN_COUNT_MAX, def->name,
 			 def->field_count + 1, db->aLimit[SQL_LIMIT_COLUMN]);
-		pParse->is_aborted = true;
-		return;
+		goto tnt_error;
 	}
 #endif
+
+	struct region *region = &parse->region;
+	struct Token *name = &create_column_def->base.name;
+	char *column_name =
+		sql_normalized_name_region_new(region, name->z, name->n);
+	if (column_name == NULL)
+		goto tnt_error;
+
+	if (is_alter && def->opts.is_view) {
+		diag_set(ClientError, ER_SQL_CANT_ADD_COLUMN_TO_VIEW,
+			 column_name, def->name);
+		goto tnt_error;
+	}
+
 	/*
-	 * As sql_field_retrieve will allocate memory on region
-	 * ensure that def is also temporal and would be dropped.
+	 * Format can be set in Lua, then exact_field_count can be
+	 * zero, but field_count is not.
 	 */
-	assert(def->opts.is_ephemeral);
-	if (sql_field_retrieve(pParse, def, def->field_count) == NULL)
+	if (def->exact_field_count == 0)
+		def->exact_field_count = def->field_count;
+	if (sql_field_retrieve(parse, def, def->field_count) == NULL)
 		return;
-	struct region *region = &pParse->region;
-	z = sql_normalized_name_region_new(region, pName->z, pName->n);
-	if (z == NULL) {
-		pParse->is_aborted = true;
-		return;
-	}
+
 	struct field_def *column_def = &def->fields[def->field_count];
 	memcpy(column_def, &field_def_default, sizeof(field_def_default));
-	column_def->name = z;
+	column_def->name = column_name;
 	/*
 	 * Marker ON_CONFLICT_ACTION_DEFAULT is used to detect
 	 * attempts to define NULL multiple time or to detect
@@ -334,18 +399,86 @@ sqlAddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
 	 */
 	column_def->nullable_action = ON_CONFLICT_ACTION_DEFAULT;
 	column_def->is_nullable = true;
-	column_def->type = type_def->type;
+	column_def->type = create_column_def->type_def->type;
 	def->field_count++;
+
+	sqlSrcListDelete(db, alter_entity_def->entity_name);
+	return;
+tnt_error:
+	parse->is_aborted = true;
+	sqlSrcListDelete(db, alter_entity_def->entity_name);
+}
+

>> +	column_def->name = column_name;
>> 	/*
>> 	 * Marker ON_CONFLICT_ACTION_DEFAULT is used to detect
>> 	 * attempts to define NULL multiple time or to detect
>> @@ -334,19 +398,100 @@ sqlAddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
>> 	 */
>> 	column_def->nullable_action = ON_CONFLICT_ACTION_DEFAULT;
>> 	column_def->is_nullable = true;
>> -	column_def->type = type_def->type;
>> +	column_def->type = create_column_def->type_def->type;
>> 	def->field_count++;
>> +
>> +	sqlSrcListDelete(db, alter_entity_def->entity_name);
>> +	return;
>> +tnt_error:
>> +	parse->is_aborted = true;
>> +	sqlSrcListDelete(db, alter_entity_def->entity_name);
>> +}
>> +
>> +static void
>> +sql_vdbe_create_constraints(struct Parse *parse, int reg_space_id);
>> +
>> +void
>> +sql_create_column_end(struct Parse *parse)
>> +{
>> +	struct create_column_def *create_column_def = &parse->create_column_def;
>> +	struct space *space = parse->create_table_def.new_space;
>> +	bool is_alter = space == NULL;
>> +	space = create_column_def->space;
>> +	struct space_def *def = space->def;
>> +	if (is_alter) {
>> +		struct field_def *field = &def->fields[def->field_count - 1];
>> +		if (field->nullable_action == ON_CONFLICT_ACTION_DEFAULT) {
>> +			if (create_column_def->is_pk) {
>> +				field->nullable_action =
>> +					ON_CONFLICT_ACTION_ABORT;
>> +				field->is_nullable = false;
>> +			} else {
>> +				field->nullable_action =
>> +					ON_CONFLICT_ACTION_NONE;
>> +				field->is_nullable = true;
>> +			}
>> +		}
>> +		/*
>> +		 * Encode the format array and emit code to update _space.
>> +		 */
>> +		uint32_t table_stmt_sz = 0;
>> +		struct region *region = &parse->region;
>> +		char *table_stmt = sql_encode_table(region, def,
>> +						    &table_stmt_sz);
>> +		char *raw = sqlDbMallocRaw(parse->db, table_stmt_sz);
>> +		if (table_stmt == NULL || raw == NULL) {
>> +			parse->is_aborted = true;
>> +			return;
>> +		}
>> +		memcpy(raw, table_stmt, table_stmt_sz);
>> +
>> +		struct Vdbe *v = sqlGetVdbe(parse);
>> +		assert(v != NULL);
>> +
>> +		struct space *system_space = space_by_id(BOX_SPACE_ID);
>> +		assert(system_space != NULL);
>> +		int cursor = parse->nTab++;
>> +		vdbe_emit_open_cursor(parse, cursor, 0, system_space);
>> +		sqlVdbeChangeP5(v, OPFLAG_SYSTEMSP);
>> +
>> +		int key_reg = ++parse->nMem;
>> +		sqlVdbeAddOp2(v, OP_Integer, def->id, key_reg);
>> +		int addr = sqlVdbeAddOp4Int(v, OP_Found, cursor, 0, key_reg, 1);
>> +		sqlVdbeAddOp2(v, OP_Halt, -1, ON_CONFLICT_ACTION_ABORT);
>> +		sqlVdbeJumpHere(v, addr);
>> +
>> +		int tuple_reg = sqlGetTempRange(parse, box_space_field_MAX + 1);
>> +		for (int i = 0; i < box_space_field_MAX - 1; ++i)
>> +			sqlVdbeAddOp3(v, OP_Column, cursor, i, tuple_reg + i);
>> +		sqlVdbeAddOp1(v, OP_Close, cursor);
>> +
>> +		sqlVdbeAddOp2(v, OP_Integer, def->field_count, tuple_reg + 4);
>> +		sqlVdbeAddOp4(v, OP_Blob, table_stmt_sz, tuple_reg + 6,
>> +			      SQL_SUBTYPE_MSGPACK, raw, P4_DYNAMIC);
>> +		sqlVdbeAddOp3(v, OP_MakeRecord, tuple_reg, box_space_field_MAX,
>> +			      tuple_reg + box_space_field_MAX);
>> +		sqlVdbeAddOp4(v, OP_IdxReplace, tuple_reg + box_space_field_MAX,
>> +			      0, 0, (char *) system_space, P4_SPACEPTR);
>> +		sql_vdbe_create_constraints(parse, key_reg);
>> +	}
>> +
>> +	memset(create_column_def, 0, sizeof(struct create_column_def));
>> +	create_column_def->nullable_action = ON_CONFLICT_ACTION_DEFAULT;
> 
> 14. Why do you touch column creation def after its usage?
Removed.

+void
+sql_create_column_end(struct Parse *parse)
+{
+	struct space *space = parse->create_column_def.space;
+	assert(space != NULL);
+	struct space_def *def = space->def;
+	struct field_def *field = &def->fields[def->field_count - 1];
+	if (field->nullable_action == ON_CONFLICT_ACTION_DEFAULT) {
+		field->nullable_action = ON_CONFLICT_ACTION_NONE;
+		field->is_nullable = true;
+	}
+	/*
+	 * Encode the format array and emit code to update _space.
+	 */
+	uint32_t table_stmt_sz = 0;
+	struct region *region = &parse->region;
+	char *table_stmt = sql_encode_table(region, def, &table_stmt_sz);
+	char *raw = sqlDbMallocRaw(parse->db, table_stmt_sz);
+	if (table_stmt == NULL || raw == NULL) {
+		parse->is_aborted = true;
+		return;
+	}
+	memcpy(raw, table_stmt, table_stmt_sz);
+
+	struct Vdbe *v = sqlGetVdbe(parse);
+	assert(v != NULL);
+
+	struct space *system_space = space_by_id(BOX_SPACE_ID);
+	assert(system_space != NULL);
+	int cursor = parse->nTab++;
+	vdbe_emit_open_cursor(parse, cursor, 0, system_space);
+	sqlVdbeChangeP5(v, OPFLAG_SYSTEMSP);
+
+	int key_reg = ++parse->nMem;
+	sqlVdbeAddOp2(v, OP_Integer, def->id, key_reg);
+	int addr = sqlVdbeAddOp4Int(v, OP_Found, cursor, 0, key_reg, 1);
+	sqlVdbeAddOp2(v, OP_Halt, -1, ON_CONFLICT_ACTION_ABORT);
+	sqlVdbeJumpHere(v, addr);
+
+	int tuple_reg = sqlGetTempRange(parse, box_space_field_MAX + 1);
+	for (int i = 0; i < box_space_field_MAX - 1; ++i)
+		sqlVdbeAddOp3(v, OP_Column, cursor, i, tuple_reg + i);
+	sqlVdbeAddOp1(v, OP_Close, cursor);
+
+	sqlVdbeAddOp2(v, OP_Integer, def->field_count, tuple_reg + 4);
+	sqlVdbeAddOp4(v, OP_Blob, table_stmt_sz, tuple_reg + 6,
+		      SQL_SUBTYPE_MSGPACK, raw, P4_DYNAMIC);
+	sqlVdbeAddOp3(v, OP_MakeRecord, tuple_reg, box_space_field_MAX,
+		      tuple_reg + box_space_field_MAX);
+	sqlVdbeAddOp4(v, OP_IdxReplace, tuple_reg + box_space_field_MAX, 0, 0,
+		      (char *) system_space, P4_SPACEPTR);
+	sql_vdbe_create_constraints(parse, key_reg);
+
+	/*
+	 * Clean up array allocated in sql_shallow_space_copy().
+	 */
+	free(space->index);
 }

>> }
>> 
>> void
>> sql_column_add_nullable_action(struct Parse *parser,
>> 			       enum on_conflict_action nullable_action)
>> {
>> -	struct space *space = parser->create_table_def.new_space;
>> -	if (space == NULL || NEVER(space->def->field_count < 1))
>> +	struct space_def *def = NULL;
>> +	struct field_def *field = NULL;
>> +	struct space *space = parser->create_column_def.space;
>> +	assert (space != NULL);
> 
> 15. Please, don't put whitespace after macro/function name and its arguments.
Sorry. Fixed.

>> +	def = space->def;
>> +	if (NEVER(def->field_count < 1))
>> 		return;
>> -	struct space_def *def = space->def;
>> -	struct field_def *field = &def->fields[def->field_count - 1];
>> +	field = &def->fields[def->field_count - 1];
> 
> 16. Why did you do that change about struct field_def? It seems not to
> be needed.
Yes.

 void
 sql_column_add_nullable_action(struct Parse *parser,
 			       enum on_conflict_action nullable_action)
 {
-	struct space *space = parser->create_table_def.new_space;
-	if (space == NULL || NEVER(space->def->field_count < 1))
+	assert(parser->create_column_def.space != NULL);
+	struct space_def *def = parser->create_column_def.space->def;
+	if (NEVER(def->field_count < 1))
 		return;
-	struct space_def *def = space->def;
 	struct field_def *field = &def->fields[def->field_count - 1];
 	if (field->nullable_action != ON_CONFLICT_ACTION_DEFAULT &&
 	    nullable_action != field->nullable_action) {
@@ -364,51 +497,42 @@ sql_column_add_nullable_action(struct Parse *parser,
 }

> 
>> 	if (field->nullable_action != ON_CONFLICT_ACTION_DEFAULT &&
>> 	    nullable_action != field->nullable_action) {
>> 		/* Prevent defining nullable_action many times. */
>> @@ -364,51 +509,46 @@ sql_column_add_nullable_action(struct Parse *parser,
>> }
>> 
>> /*
>> - * The expression is the default value for the most recently added column
>> - * of the table currently under construction.
>> + * The expression is the default value for the most recently added
>> + * column.
>>  *
>>  * Default value expressions must be constant.  Raise an exception if this
>>  * is not the case.
>>  *
>>  * This routine is called by the parser while in the middle of
>> - * parsing a CREATE TABLE statement.
>> + * parsing a <CREATE TABLE> or a <ALTER TABLE ADD COLUMN>
>> + * statement.
>>  */
>> void
>> sqlAddDefaultValue(Parse * pParse, ExprSpan * pSpan)
>> {
>> 	sql *db = pParse->db;
>> -	struct space *p = pParse->create_table_def.new_space;
>> -	if (p != NULL) {
>> -		assert(p->def->opts.is_ephemeral);
>> -		struct space_def *def = p->def;
>> -		if (!sqlExprIsConstantOrFunction
>> -		    (pSpan->pExpr, db->init.busy)) {
>> -			const char *column_name =
>> -				def->fields[def->field_count - 1].name;
>> -			diag_set(ClientError, ER_CREATE_SPACE, def->name,
>> -				 tt_sprintf("default value of column '%s' is "\
>> -					    "not constant", column_name));
>> +	struct space_def *def = NULL;
>> +	struct field_def *field = NULL;
>> +	struct space *space = pParse->create_column_def.space;
>> +	assert (space != NULL);
>> +	def = space->def;
> 
> 17. Why can't you declare and initialize space_def in one line? The same
> for field.
Fixed.

 void
 sqlAddDefaultValue(Parse * pParse, ExprSpan * pSpan)
 {
 	sql *db = pParse->db;
-	struct space *p = pParse->create_table_def.new_space;
-	if (p != NULL) {
-		assert(p->def->opts.is_ephemeral);
-		struct space_def *def = p->def;
-		if (!sqlExprIsConstantOrFunction
-		    (pSpan->pExpr, db->init.busy)) {
-			const char *column_name =
-				def->fields[def->field_count - 1].name;
-			diag_set(ClientError, ER_CREATE_SPACE, def->name,
-				 tt_sprintf("default value of column '%s' is "\
-					    "not constant", column_name));
+	assert(pParse->create_column_def.space != NULL);
+	struct space_def *def = pParse->create_column_def.space->def;
+	struct field_def *field = &def->fields[def->field_count - 1];
+	if (!sqlExprIsConstantOrFunction(pSpan->pExpr, db->init.busy)) {
+		diag_set(ClientError, ER_CREATE_SPACE, def->name,
+			 tt_sprintf("default value of column '%s' is not "
+				    "constant", field->name));
+		pParse->is_aborted = true;
+	} else {
+		struct region *region = &pParse->region;
+		uint32_t default_length = (int)(pSpan->zEnd - pSpan->zStart);
+		field->default_value = region_alloc(region, default_length + 1);
+		if (field->default_value == NULL) {
+			diag_set(OutOfMemory, default_length + 1,
+				 "region_alloc", "field->default_value");
 			pParse->is_aborted = true;
-		} else {
-			assert(def != NULL);
-			struct field_def *field =
-				&def->fields[def->field_count - 1];
-			struct region *region = &pParse->region;
-			uint32_t default_length = (int)(pSpan->zEnd - pSpan->zStart);
-			field->default_value = region_alloc(region,
-							    default_length + 1);
-			if (field->default_value == NULL) {
-				diag_set(OutOfMemory, default_length + 1,
-					 "region_alloc",
-					 "field->default_value");
-				pParse->is_aborted = true;
-				return;
-			}
-			strncpy(field->default_value, pSpan->zStart,
-				default_length);
-			field->default_value[default_length] = '\0';
+			goto add_default_value_exit;
 		}
+		strncpy(field->default_value, pSpan->zStart, default_length);
+		field->default_value[default_length] = '\0';
 	}
+add_default_value_exit:
 	sql_expr_delete(db, pSpan->pExpr, false);
 }

>> +	field = &def->fields[def->field_count - 1];
>> +	if (!sqlExprIsConstantOrFunction(pSpan->pExpr, db->init.busy)) {
>> +		diag_set(ClientError, ER_CREATE_SPACE, def->name,
>> +			 tt_sprintf("default value of column '%s' is not "
>> +				    "constant", field->name));
>> +		pParse->is_aborted = true;
>> +	} else {
>> +		assert(def != NULL);
>> +		struct region *region = &pParse->region;
>> +		uint32_t default_length = (int)(pSpan->zEnd - pSpan->zStart);
>> +		field->default_value = region_alloc(region, default_length + 1);
>> +		if (field->default_value == NULL) {
>> +			diag_set(OutOfMemory, default_length + 1,
>> +				 "region_alloc", "field->default_value");
>> 			pParse->is_aborted = true;
>> -		} else {
>> -			assert(def != NULL);
>> -			struct field_def *field =
>> -				&def->fields[def->field_count - 1];
>> -			struct region *region = &pParse->region;
>> -			uint32_t default_length = (int)(pSpan->zEnd - pSpan->zStart);
>> -			field->default_value = region_alloc(region,
>> -							    default_length + 1);
>> -			if (field->default_value == NULL) {
>> -				diag_set(OutOfMemory, default_length + 1,
>> -					 "region_alloc",
>> -					 "field->default_value");
>> -				pParse->is_aborted = true;
>> -				return;
>> -			}
>> -			strncpy(field->default_value, pSpan->zStart,
>> -				default_length);
>> -			field->default_value[default_length] = '\0';
>> +			goto add_default_value_exit;
>> 		}
>> +		strncpy(field->default_value, pSpan->zStart, default_length);
>> +		field->default_value[default_length] = '\0';
>> 	}
>> +add_default_value_exit:
>> 	sql_expr_delete(db, pSpan->pExpr, false);
>> }
>> 
>> @@ -447,6 +587,8 @@ sqlAddPrimaryKey(struct Parse *pParse)
>> 	int nTerm;
>> 	struct ExprList *pList = pParse->create_index_def.cols;
>> 	struct space *space = pParse->create_table_def.new_space;
>> +	if (space == NULL)
>> +		space = pParse->create_column_def.space;
>> 	if (space == NULL)
>> 		goto primary_key_exit;
>> 	if (sql_space_primary_key(space) != NULL) {
>> @@ -574,8 +716,10 @@ sql_create_check_contraint(struct Parse *parser)
>> 		(struct alter_entity_def *) create_ck_def;
>> 	assert(alter_def->entity_type == ENTITY_TYPE_CK);
>> 	(void) alter_def;
>> -	struct space *space = parser->create_table_def.new_space;
>> -	bool is_alter = space == NULL;
>> +	struct space *space = parser->create_column_def.space;
>> +	if (space == NULL)
>> +		space = parser->create_table_def.new_space;
>> +	bool is_alter_add_constr = space == NULL;
> 
> 18. Why is it called 'is_alter' in other functions, but 'is_alter_add_constr'
> here?
If I added ‘is_alter_add_constr’, it means that we should distinguish current
statements: <ALTER TABLE ADD CONSTRAINT> or <ALTER TABLE ADD COLUMN> (maybe <CREATE INDEX>).
It’s important for branching within constraint creation functions (index, ck, fk).

>> 
>> 	/* Prepare payload for ck constraint definition. */
>> 	struct region *region = &parser->region;
>> @@ -589,9 +733,18 @@ sql_create_check_contraint(struct Parse *parser)
>> 			return;
>> 		}
>> 	} else {
>> -		assert(! is_alter);
>> -		uint32_t ck_idx = ++parser->create_table_def.check_count;
>> -		name = tt_sprintf("ck_unnamed_%s_%d", space->def->name, ck_idx);
>> +		assert(!is_alter_add_constr);
>> +		uint32_t idx = ++parser->check_count;
>> +		if (parser->create_table_def.new_space == NULL) {
>> +			struct space *original_space =
>> +				space_by_name(space->def->name);
>> +			assert(original_space != NULL);
>> +			struct rlist *checks = &original_space->ck_constraint;
>> +			struct ck_constraint *ck;
>> +			rlist_foreach_entry(ck, checks, link)
>> +				idx++;
>> +		}
>> +		name = tt_sprintf("ck_unnamed_%s_%d", space->def->name, idx);
>> 	}
>> 	size_t name_len = strlen(name);
>> 
>> @@ -634,9 +787,9 @@ sql_create_check_contraint(struct Parse *parser)
>> 	trim_space_snprintf(ck_def->expr_str, expr_str, expr_str_len);
>> 	memcpy(ck_def->name, name, name_len);
>> 	ck_def->name[name_len] = '\0';
>> -	if (is_alter) {
>> +	if (is_alter_add_constr) {
>> 		const char *space_name = alter_def->entity_name->a[0].zName;
>> -		struct space *space = space_by_name(space_name);
>> +		space = space_by_name(space_name);
> 
> 19. Why this change?
Dropped.

> 
>> 		if (space == NULL) {
>> 			diag_set(ClientError, ER_NO_SUCH_SPACE, space_name);
>> 			parser->is_aborted = true;
>> @@ -652,8 +805,7 @@ sql_create_check_contraint(struct Parse *parser)
>> 		sqlVdbeCountChanges(v);
>> 		sqlVdbeChangeP5(v, OPFLAG_NCHANGE);
>> 	} else {
>> -		rlist_add_entry(&parser->create_table_def.new_check, ck_parse,
>> -				link);
>> +		rlist_add_entry(&parser->checks, ck_parse, link);
>> 	}
>> }
>> 
>> @@ -664,18 +816,19 @@ sql_create_check_contraint(struct Parse *parser)
>> void
>> sqlAddCollateType(Parse * pParse, Token * pToken)
>> {
>> -	struct space *space = pParse->create_table_def.new_space;
>> -	if (space == NULL)
>> -		return;
>> +	struct space *space = pParse->create_column_def.space;
>> +	uint32_t *coll_id = NULL;
>> +	assert(space != NULL);
>> 	uint32_t i = space->def->field_count - 1;
>> +	coll_id = &space->def->fields[i].coll_id;
> 
> 20. Why not declare and initialize coll_id in one line?
Done.

 void
 sqlAddCollateType(Parse * pParse, Token * pToken)
 {
-	struct space *space = pParse->create_table_def.new_space;
-	if (space == NULL)
-		return;
+	struct space *space = pParse->create_column_def.space;
+	assert(space != NULL);
 	uint32_t i = space->def->field_count - 1;
 	sql *db = pParse->db;
 	char *coll_name = sql_name_from_token(db, pToken);

>> 	sql *db = pParse->db;
>> 	char *coll_name = sql_name_from_token(db, pToken);
>> 	if (coll_name == NULL) {
>> 		pParse->is_aborted = true;
>> 		return;
>> 	}
>> -	uint32_t *coll_id = &space->def->fields[i].coll_id;
>> -	if (sql_get_coll_seq(pParse, coll_name, coll_id) != NULL) {
>> +	if (sql_get_coll_seq(pParse, coll_name, coll_id) != NULL &&
>> +	    space != NULL) {
> 
> 21. You have assert(space != NULL) above. Why do you need this check again?
Removed.

>> 		/* If the column is declared as "<name> PRIMARY KEY COLLATE <type>",
>> 		 * then an index may have been created on this column before the
>> 		 * collation type was added. Correct this if it is the case.
>> @@ -692,6 +845,7 @@ sqlAddCollateType(Parse * pParse, Token * pToken)
>> 	sqlDbFree(db, coll_name);
>> }
>> 
>> +
> 
> 22. ???
Oops. Removed.

>> struct coll *
>> sql_column_collation(struct space_def *def, uint32_t column, uint32_t *coll_id)
>> {
>> @@ -1147,6 +1304,105 @@ resolve_link(struct Parse *parse_context, const struct space_def *def,
>> 	return -1;
>> }
>> 
>> +/**
>> + * Emit code to create sequences, indexes, check and foreign
>> + * constraints appeared in <CREATE TABLE> or
>> + * <ALTER TABLE ADD COLUMN> statement.
>> + */
>> +static void
>> +sql_vdbe_create_constraints(struct Parse *parse, int reg_space_id)
> 
> 23. Such huge code movements better do in a separate commit. I can't
> tell whether you changed anything here, or just moved the code.
Done. See commit in the new version of the patch set.

>> +{
>> +	assert(reg_space_id != 0);
>> +	struct space *space = parse->create_table_def.new_space;
>> +	bool is_alter = space == NULL;
>> +	uint32_t i = 0;
>> +	if (is_alter) {
>> +		space = parse->create_column_def.space;
>> +		i = space_by_name(space->def->name)->index_count;
>> +	}
>> +	assert(space != NULL);
>> +	for (; i < space->index_count; ++i) {
>> +		struct index *idx = space->index[i];
>> +		vdbe_emit_create_index(parse, space->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.
>> +	 */
>> +	if (parse->has_autoinc) {
>> +		/* Do an insertion into _sequence. */
>> +		int reg_seq_id = ++parse->nMem;
>> +		struct Vdbe *v = sqlGetVdbe(parse);
>> +		assert(v != NULL);
>> +		sqlVdbeAddOp2(v, OP_NextSequenceId, 0, reg_seq_id);
>> +		int reg_seq_record =
>> +			emitNewSysSequenceRecord(parse, reg_seq_id,
>> +						 space->def->name);
>> +		const char *error_msg =
>> +			tt_sprintf(tnt_errcode_desc(ER_SQL_CANT_ADD_AUTOINC),
>> +				   space->def->name);
>> +		if (vdbe_emit_halt_with_presence_test(parse,
>> +						      BOX_SEQUENCE_ID, 2,
>> +						      reg_seq_record + 3, 1,
>> +						      ER_SQL_CANT_ADD_AUTOINC,
>> +						      error_msg, false,
>> +						      OP_NoConflict) != 0)
>> +			return;
>> +		sqlVdbeAddOp2(v, OP_SInsert, BOX_SEQUENCE_ID, reg_seq_record);
>> +		/* Do an insertion into _space_sequence. */
>> +		int reg_space_seq_record =
>> +			emitNewSysSpaceSequenceRecord(parse, reg_space_id,
>> +						      reg_seq_id);
>> +		sqlVdbeAddOp2(v, OP_SInsert, BOX_SPACE_SEQUENCE_ID,
>> +			      reg_space_seq_record);
>> +	}
>> +
>> +	/* Code creation of FK constraints, if any. */
>> +	struct fk_constraint_parse *fk_parse;
>> +	rlist_foreach_entry(fk_parse, &parse->fkeys, link) {
>> +		struct fk_constraint_def *fk_def = fk_parse->fk_def;
>> +		if (fk_parse->selfref_cols != NULL) {
>> +			struct ExprList *cols = fk_parse->selfref_cols;
>> +			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
>> +				if (resolve_link(parse, space->def,
>> +						 cols->a[i].zName,
>> +						 &fk_def->links[i].parent_field,
>> +						 fk_def->name) != 0)
>> +					return;
>> +			}
>> +			fk_def->parent_id = reg_space_id;
>> +		} else if (fk_parse->is_self_referenced) {
>> +			struct key_def *pk_key_def =
>> +				sql_space_primary_key(space)->def->key_def;
>> +			if (pk_key_def->part_count != fk_def->field_count) {
>> +				diag_set(ClientError, ER_CREATE_FK_CONSTRAINT,
>> +					 fk_def->name, "number of columns in "\
>> +					 "foreign key does not match the "\
>> +					 "number of columns in the primary "\
>> +					 "index of referenced table");
>> +				parse->is_aborted = true;
>> +				return;
>> +			}
>> +			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
>> +				fk_def->links[i].parent_field =
>> +					pk_key_def->parts[i].fieldno;
>> +			}
>> +			fk_def->parent_id = reg_space_id;
>> +		}
>> +		fk_def->child_id = reg_space_id;
>> +		vdbe_emit_fk_constraint_create(parse, fk_def, space->def->name);
>> +	}
>> +
>> +	/* Code creation of CK constraints, if any. */
>> +	struct ck_constraint_parse *ck_parse;
>> +	rlist_foreach_entry(ck_parse, &parse->checks, link) {
>> +		vdbe_emit_ck_constraint_create(parse, ck_parse->ck_def,
>> +					       reg_space_id, space->def->name);
>> +	}
>> +}
>> +
>> /*
>>  * This routine is called to report the final ")" that terminates
>>  * a CREATE TABLE statement.
>> @@ -1213,73 +1469,7 @@ sqlEndTable(struct Parse *pParse)
>> 
>> 	int reg_space_id = getNewSpaceId(pParse);
>> 	vdbe_emit_space_create(pParse, reg_space_id, name_reg, new_space);
>> -	for (uint32_t i = 0; i < new_space->index_count; ++i) {
>> -		struct index *idx = new_space->index[i];
>> -		vdbe_emit_create_index(pParse, new_space->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.
>> -	 */
>> -	if (pParse->create_table_def.has_autoinc) {
>> -		assert(reg_space_id != 0);
>> -		/* Do an insertion into _sequence. */
>> -		int reg_seq_id = ++pParse->nMem;
>> -		sqlVdbeAddOp2(v, OP_NextSequenceId, 0, reg_seq_id);
>> -		int reg_seq_record =
>> -			emitNewSysSequenceRecord(pParse, reg_seq_id,
>> -						 new_space->def->name);
>> -		sqlVdbeAddOp2(v, OP_SInsert, BOX_SEQUENCE_ID, reg_seq_record);
>> -		/* Do an insertion into _space_sequence. */
>> -		int reg_space_seq_record =
>> -			emitNewSysSpaceSequenceRecord(pParse, reg_space_id,
>> -						      reg_seq_id);
>> -		sqlVdbeAddOp2(v, OP_SInsert, BOX_SPACE_SEQUENCE_ID,
>> -			      reg_space_seq_record);
>> -	}
>> -	/* Code creation of FK constraints, if any. */
>> -	struct fk_constraint_parse *fk_parse;
>> -	rlist_foreach_entry(fk_parse, &pParse->create_table_def.new_fkey,
>> -			    link) {
>> -		struct fk_constraint_def *fk_def = fk_parse->fk_def;
>> -		if (fk_parse->selfref_cols != NULL) {
>> -			struct ExprList *cols = fk_parse->selfref_cols;
>> -			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
>> -				if (resolve_link(pParse, new_space->def,
>> -						 cols->a[i].zName,
>> -						 &fk_def->links[i].parent_field,
>> -						 fk_def->name) != 0)
>> -					return;
>> -			}
>> -			fk_def->parent_id = reg_space_id;
>> -		} else if (fk_parse->is_self_referenced) {
>> -			struct index *pk = sql_space_primary_key(new_space);
>> -			if (pk->def->key_def->part_count != fk_def->field_count) {
>> -				diag_set(ClientError, ER_CREATE_FK_CONSTRAINT,
>> -					 fk_def->name, "number of columns in "\
>> -					 "foreign key does not match the "\
>> -					 "number of columns in the primary "\
>> -					 "index of referenced table");
>> -				pParse->is_aborted = true;
>> -				return;
>> -			}
>> -			for (uint32_t i = 0; i < fk_def->field_count; ++i) {
>> -				fk_def->links[i].parent_field =
>> -					pk->def->key_def->parts[i].fieldno;
>> -			}
>> -			fk_def->parent_id = reg_space_id;
>> -		}
>> -		fk_def->child_id = reg_space_id;
>> -		vdbe_emit_fk_constraint_create(pParse, fk_def, space_name_copy);
>> -	}
>> -	struct ck_constraint_parse *ck_parse;
>> -	rlist_foreach_entry(ck_parse, &pParse->create_table_def.new_check,
>> -			    link) {
>> -		vdbe_emit_ck_constraint_create(pParse, ck_parse->ck_def,
>> -					       reg_space_id, space_name_copy);
>> -	}
>> +	sql_vdbe_create_constraints(pParse, reg_space_id);
>> }
>> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
>> index 995875566..5904414a3 100644
>> --- a/src/box/sql/parse.y
>> +++ b/src/box/sql/parse.y
>> @@ -314,6 +320,7 @@ ccons ::= cconsname(N) PRIMARY KEY sortorder(Z). {
>>   create_index_def_init(&pParse->create_index_def, NULL, &N, NULL,
>>                         SQL_INDEX_TYPE_CONSTRAINT_PK, Z, false);
>>   sqlAddPrimaryKey(pParse);
>> +  pParse->create_column_def.is_pk = true;
> 
> 24. Why can't this be done in sqlAddPrimaryKey(pParse);? What if this
> is CREATE TABLE, and there are many columns in PK?
I removed this field from create_column_def. It turned out to be unnecessary.

>> }
>> ccons ::= cconsname(N) UNIQUE. {
>>   create_index_def_init(&pParse->create_index_def, NULL, &N, NULL,
>> diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
>> index a5a258805..b31bac437 100644
>> --- a/src/box/sql/prepare.c
>> +++ b/src/box/sql/prepare.c
>> @@ -197,9 +197,13 @@ sql_parser_create(struct Parse *parser, struct sql *db, uint32_t sql_flags)
>> {
>> 	memset(parser, 0, sizeof(struct Parse));
>> 	parser->db = db;
>> +	parser->create_column_def.nullable_action = ON_CONFLICT_ACTION_DEFAULT;
> 
> 25. Why isn't there a proper constructor for create_column_def structure?
Dropped.

>> 	parser->sql_flags = sql_flags;
>> 	parser->line_count = 1;
>> 	parser->line_pos = 1;
>> +	rlist_create(&parser->fkeys);
>> +	rlist_create(&parser->checks);

> 26. Why did you merge these fields into struct Parse? They were specially
> moved out of here for the purpose of some kind of isolation, into parse_def.h
> structures.
Because these fields are convenient to use right here, so as not to duplicate
the code in build.c. These fields are used by two statements: CREATE TABLE and
ALTER TABLE ADD COLUMN.

>> +	parser->has_autoinc = false;
>> 	region_create(&parser->region, &cord()->slabc);
>> }
>> 
>> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
>> index aa6a470f8..3143ec521 100644
>> --- a/src/box/sql/sqlInt.h
>> +++ b/src/box/sql/sqlInt.h
>> @@ -2249,12 +2249,26 @@ struct Parse {
>> 		struct enable_entity_def enable_entity_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).
>> +	 * Table def or column def is not part of union since
>> +	 * information being held must survive till the end of
>> +	 * parsing of whole <CREATE TABLE> or
>> +	 * <ALTER TABLE ADD COLUMN> statement (to pass it to
>> +	 * sqlEndTable() sql_create_column_end() function).
>> 	 */
>> 	struct create_table_def create_table_def;
>> +	struct create_column_def create_column_def;
>> +	/**
>> +	 * FK and CK constraints appeared in a <CREATE TABLE> or
>> +	 * a <ALTER TABLE ADD COLUMN> statement.
>> +	 */
>> +	struct rlist fkeys;
>> +	struct rlist checks;
>> +	uint32_t fkey_count;
>> +	uint32_t check_count;
>> +	/** True, if column to be created has <AUTOINCREMENT>. */
>> +	bool has_autoinc;
> 
> 27. What column? This is struct Parse, it is not a column.
I know, but I haven't come up with anything better.

diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index fa87e7bd2..32142a871 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -2251,22 +2251,26 @@ struct Parse {
 		struct enable_entity_def enable_entity_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).
+	 * Table def or column def is not part of union since
+	 * information being held must survive till the end of
+	 * parsing of whole <CREATE TABLE> or
+	 * <ALTER TABLE ADD COLUMN> statement (to pass it to
+	 * sqlEndTable() sql_create_column_end() function).
 	 */
 	struct create_table_def create_table_def;
+	struct create_column_def create_column_def;
 	/*
-	 * FK and CK constraints appeared in a <CREATE TABLE>.
+	 * FK and CK constraints appeared in a <CREATE TABLE> or
+	 * an <ALTER TABLE ADD COLUMN> statement.
 	 */
 	struct rlist fkeys;
 	struct rlist checks;
 	uint32_t fkey_count;
 	uint32_t check_count;
 	/*
-	 * True, if column within a <CREATE TABLE> statement to be
-	 * created has <AUTOINCREMENT>.
+	 * True, if column in a <CREATE TABLE> or an
+	 * <ALTER TABLE ADD COLUMN> statement to be created has
+	 * <AUTOINCREMENT>.
 	 */
 	bool has_autoinc;
 	/* Id of field with <AUTOINCREMENT>. */

>> +	/** Id of field with <AUTOINCREMENT>. */
>> +	uint32_t autoinc_fieldno;
>> 	bool initiateTTrans;	/* Initiate Tarantool transaction */
>> 	/** If set - do not emit byte code at all, just parse.  */
>> 	bool parse_only;
>> @@ -2844,15 +2858,31 @@ struct space *sqlResultSetOfSelect(Parse *, Select *);
>> 
>> struct space *
>> sqlStartTable(Parse *, Token *);
>> -void sqlAddColumn(Parse *, Token *, struct type_def *);
>> +
>> +/**
>> + * Add new field to the format of ephemeral space in
>> + * create_table_def. If it is <ALTER TABLE> create shallow copy of
>> + * the existing space and add field to its format.
>> + */
>> +void
>> +sql_create_column_start(struct Parse *parse);
>> +
>> +/**
>> + * Nullify create_column_def. If it is <ALTER TABLE> emit code to
>> + * update entry in _space and code to create constraints (entries
>> + * in _index, _ck_constraint, _fk_constraint) described with this
>> + * column.
>> + */
>> +void
>> +sql_create_column_end(struct Parse *parse);
>> 
>> /**
>>  * This routine is called by the parser while in the middle of
>> - * parsing a CREATE TABLE statement.  A "NOT NULL" constraint has
>> - * been seen on a column.  This routine sets the is_nullable flag
>> - * on the column currently under construction.
>> - * If nullable_action has been already set, this function raises
>> - * an error.
>> + * parsing a <CREATE TABLE> or a <ALTER TABLE ADD COLUMN>
>> + * statement. A "NOT NULL" constraint has been seen on a column.
>> + * This routine sets the is_nullable flag on the column currently
>> + * under construction. If nullable_action has been already set,
>> + * this function raises an error.
>>  *
>>  * @param parser SQL Parser object.
>>  * @param nullable_action on_conflict_action value.
>> diff --git a/test/box/error.result b/test/box/error.result
>> index 2196fa541..eb55f9cdf 100644
>> --- a/test/box/error.result
>> +++ b/test/box/error.result
>> @@ -432,6 +432,8 @@ t;
>>  |   211: box.error.WRONG_QUERY_ID
>>  |   212: box.error.SEQUENCE_NOT_STARTED
>>  |   213: box.error.NO_SUCH_SESSION_SETTING
>> + |   214: box.error.SQL_CANT_ADD_COLUMN_TO_VIEW
>> + |   215: box.error.SQL_CANT_ADD_AUTOINC
>>  | ...
>> 
>> test_run:cmd("setopt delimiter ''");
>> diff --git a/test/sql/add-column.result b/test/sql/add-column.result
>> new file mode 100644
>> index 000000000..06c95facb
>> --- /dev/null
>> +++ b/test/sql/add-column.result
> 
> 28. You may want to look at the original SQLite tests. They probably
> have some.
I looked. I took everything that is relevant for Tarantool from there.

>> @@ -0,0 +1,276 @@
>> +-- test-run result file version 2
>> +test_run = require('test_run').new()
>> + | ---
>> + | ...
>> +engine = test_run:get_cfg('engine')
>> + | ---
>> + | ...
>> +_ = box.space._session_settings:update('sql_default_engine', {{'=', 2, engine}})
>> + | ---
>> + | ...
>> +
>> +--
>> +-- gh-3075: Check <ALTER TABLE table ADD COLUMN column> statement.
>> +--
>> +box.execute('CREATE TABLE t1 (a INTEGER PRIMARY KEY);')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +--
>> +-- COLUMN keyword is optional. Check it here, but omit it below.
>> +--
>> +box.execute('ALTER TABLE t1 ADD COLUMN b INTEGER;')
>> + | ---
>> + | - row_count: 0
>> + | ...
>> +
>> +--
>> +-- Can't add column to a view.
>> +--
>> +box.execute('CREATE VIEW v AS SELECT * FROM t1;')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +box.execute('ALTER TABLE v ADD b INTEGER;')
>> + | ---
>> + | - null
>> + | - Can't add column 'B'. 'V' is a view
>> + | ...
>> +box.execute('DROP VIEW v;')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +
>> +--
>> +-- Check column constraints typing and work.
>> +--
>> +box.execute('CREATE TABLE t2 (a INTEGER CONSTRAINT pk_constr PRIMARY KEY);')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +box.execute('ALTER TABLE t2 DROP CONSTRAINT pk_constr')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +test_run:cmd("setopt delimiter ';'");
>> + | ---
>> + | - true
>> + | ...
>> +box.execute([[ALTER TABLE t2 ADD b INTEGER CONSTRAINT pk_constr PRIMARY KEY
>> +                                   CHECK (b > 0)
>> +                                   REFERENCES t1(a)
>> +                                   CONSTRAINT u_constr UNIQUE]])
>> +test_run:cmd("setopt delimiter ''");
>> + | ---
>> + | ...
>> +box.execute('INSERT INTO t1 VALUES (1, 1);')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +box.execute('INSERT INTO t2 VALUES (1, 1);')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +box.execute('INSERT INTO t2 VALUES (1, 1);')
>> + | ---
>> + | - null
>> + | - Duplicate key exists in unique index 'PK_CONSTR' in space 'T2'
>> + | ...
>> +
>> +box.execute('INSERT INTO t1 VALUES (0, 1);')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +box.execute('INSERT INTO t2 VALUES (2, 0);')
>> + | ---
>> + | - null
>> + | - 'Check constraint failed ''ck_unnamed_T2_1'': b > 0'
>> + | ...
>> +
>> +box.execute('INSERT INTO t2 VALUES (2, 3);')
>> + | ---
>> + | - null
>> + | - 'Failed to execute SQL statement: FOREIGN KEY constraint failed'
>> + | ...
>> +
>> +box.execute('DROP TABLE t2;')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +
>> +--
>> +-- Check self-referenced FK creation.
>> +--
>> +box.execute('CREATE TABLE t2 (a INTEGER PRIMARY KEY);')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +box.execute('ALTER TABLE t2 ADD b INT REFERENCES t1')
>> + | ---
>> + | - row_count: 0
> 
> 29. Worth checking if it works. Not just created and dropped right away.
See the new version of tests in the diff.

>> + | ...
>> +
>> +box.execute('DROP TABLE t2;')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +
>> +--
>> +-- Check AUTOINCREMENT work.
>> +--
>> +box.execute("CREATE TABLE t2(a INTEGER CONSTRAINT pk PRIMARY KEY);")
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +box.execute("ALTER TABLE t2 DROP CONSTRAINT pk;")
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +box.execute("ALTER TABLE t2 ADD b INTEGER PRIMARY KEY AUTOINCREMENT;")
>> + | ---
>> + | - row_count: 0
> 
> 30. The same.
> 
>> + | ...
>> +box.execute("ALTER TABLE t2 ADD c INTEGER AUTOINCREMENT;")
>> + | ---
>> + | - null
>> + | - 'Can''t add AUTOINCREMENT: the space ''T2'' already has one auto-incremented field'
>> + | ...
>> +
>> +box.execute('DROP TABLE t2;')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +
>> +--
>> +-- Check clauses after column typing and work.
>> +--
>> +box.execute('CREATE TABLE t2 (a INTEGER PRIMARY KEY, b INTEGER);')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +test_run:cmd("setopt delimiter ';'");
>> + | ---
>> + | - true
>> + | ...
>> +box.execute([[ALTER TABLE t2 ADD c TEXT NOT NULL DEFAULT ('a')
>> +                                   COLLATE "unicode_ci";]]);
>> + | ---
>> + | - row_count: 0
>> + | ...
>> +test_run:cmd("setopt delimiter ''");
>> + | ---
>> + | - true
>> + | ...
>> +box.execute('INSERT INTO t2(a, b) VALUES (1, 1);')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +box.execute('SELECT * FROM t2;')
>> + | ---
>> + | - metadata:
>> + |   - name: A
>> + |     type: integer
>> + |   - name: B
>> + |     type: integer
>> + |   - name: C
>> + |     type: string
>> + |   rows:
>> + |   - [1, 1, 'a']
>> + | ...
>> +box.execute('INSERT INTO t2 VALUES (2, 2, NULL);')
>> + | ---
>> + | - null
>> + | - 'Failed to execute SQL statement: NOT NULL constraint failed: T2.C'
>> + | ...
>> +box.execute('SELECT * FROM t2 WHERE c LIKE \'A\';')
>> + | ---
>> + | - metadata:
>> + |   - name: A
>> + |     type: integer
>> + |   - name: B
>> + |     type: integer
>> + |   - name: C
>> + |     type: string
>> + |   rows:
>> + |   - [1, 1, 'a']
>> + | ...
>> +
>> +--
>> +-- Try to add to a non-empty space a [non-]nullable field.
>> +--
>> +box.execute('ALTER TABLE t2 ADD d INTEGER;')
>> + | ---
>> + | - null
>> + | - Tuple field count 3 does not match space field count 4
>> + | ...
>> +box.execute('ALTER TABLE t2 ADD d TEXT NOT NULL');
>> + | ---
>> + | - null
>> + | - Tuple field count 3 does not match space field count 4
>> + | ...
>> +box.execute('ALTER TABLE t2 ADD e TEXT NULL');
>> + | ---
>> + | - null
>> + | - Tuple field count 3 does not match space field count 4
>> + | ...
>> +
>> +--
>> +-- Add to a space with no-SQL adjusted or without format.
>> +--
>> +_ = box.schema.space.create('WITHOUT')
>> + | ---
>> + | ...
>> +box.execute("ALTER TABLE WITHOUT ADD a INTEGER;")
> 
> 31. No need to caps table names in SQL.
Fixed.

> 32. Need to check if it actually worked. The same below.
> 
>> + | ---
>> + | - row_count: 0
>> + | ...
>> +box.execute("DROP TABLE WITHOUT;")
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +
>> +s = box.schema.space.create('NOSQL')
>> + | ---
>> + | ...
>> +s:format{{name = 'A', type = 'unsigned'}}
>> + | ---
>> + | ...
>> +box.execute("ALTER TABLE NOSQL ADD b INTEGER")
>> + | ---
>> + | - row_count: 0
>> + | ...
>> +
>> +box.execute('DROP TABLE t2;')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +--
>> +-- Add multiple columns inside a transaction.
>> +--
>> +box.execute('CREATE TABLE t2 (a INTEGER PRIMARY KEY)')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +box.begin()                                                                     \
>> +box.execute('ALTER TABLE t2 ADD b INT')                                         \
>> +box.execute('ALTER TABLE t2 ADD c INT')                                         \
>> +box.commit()
>> + | ---
>> + | ...
>> +
>> +box.execute('INSERT INTO t2 VALUES (1, 1, 1)')
>> + | ---
>> + | - row_count: 1
>> + | ...
>> +box.execute('SELECT * FROM t2;')
>> + | ---
>> + | - metadata:
>> + |   - name: A
>> + |     type: integer
>> + |   - name: B
>> + |     type: integer
>> + |   - name: C
>> + |     type: integer
>> + |   rows:
>> + |   - [1, 1, 1]
> 
> 33. What if I add a column with UNIQUE constraint?
Added.

P.S.
You can see that I added tests in checks.test.lua and foreign-keys.test.lua.
I tested the naming of these constraints. This naming is (maybe isn’t)
temporary, because it allows such a case:

 box.execute("CREATE TABLE t1 (a INT PRIMARY KEY);")
---
- row_count: 1
...
box.execute("CREATE TABLE check_naming (a INT PRIMARY KEY REFERENCES t1(a));")
---
- row_count: 1
...
box.execute("ALTER TABLE check_naming ADD b INT REFERENCES t1(a);")
---
- row_count: 0
...
box.execute("ALTER TABLE check_naming DROP CONSTRAINT \"fk_unnamed_CHECK_NAMING_1\"")
---
- row_count: 1
...
box.execute("ALTER TABLE check_naming ADD c INT REFERENCES t1(a);")
---
- null
- FOREIGN KEY constraint 'fk_unnamed_CHECK_NAMING_2' already exists in space 'CHECK_NAMING'
...

To avoid such collisions, we need to use a hash table of names. Let’s
put it aside for later.

commit d0fb61f0283f64f1253307d38d1c8cfdea4c933b
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Thu Jan 2 19:06:14 2020 +0300

    sql: support column addition
    
    Enable to add column to existing space with
    <ALTER TABLE ADD [COLUMN]> statement. Column definition can be
    supplemented with the four types of constraints, <DEFAULT>,
    <COLLATE> clauses and <[NOT] NULL>, AUTOINCREMENT.
    
    Closes #2349, #3075
    
    @TarantoolBot document
    Title: Add columns to existing tables in SQL
    Now, it is possible to add columns to existing empty spaces using
    <ALTER TABLE table_name ADD [COLUMN] column_name column_type ...>
    statement. The column definition is the same as in <CREATE TABLE>
    statement.
    
    For example:
    
    ```
    tarantool> box.execute("CREATE TABLE test (a INTEGER PRIMARY KEY)")
    ---
    - row_count: 1
    ...
    
    tarantool> box.execute([[ALTER TABLE test ADD COLUMN b TEXT
             >                                           CHECK (LENGTH(b) > 1)
             >                                           NOT NULL
             >                                           DEFAULT ('aa')
             >                                           COLLATE "unicode_ci"
             >             ]])
    ---
    - row_count: 0
    ...
    ```

diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index 486b6b30d..dea047241 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -76,7 +76,7 @@ static Keyword aKeywordTable[] = {
   { "CHECK",                  "TK_CHECK",       true  },
   { "COLLATE",                "TK_COLLATE",     true  },
   { "COLUMN_NAME",            "TK_COLUMN_NAME", true  },
-  { "COLUMN",                 "TK_STANDARD",    true  },
+  { "COLUMN",                 "TK_COLUMN",      true  },
   { "COMMIT",                 "TK_COMMIT",      true  },
   { "CONFLICT",               "TK_CONFLICT",    false },
   { "CONSTRAINT",             "TK_CONSTRAINT",  true  },
diff --git a/src/box/errcode.h b/src/box/errcode.h
index 3c21375f5..cbcffb3a8 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -271,6 +271,8 @@ struct errcode_record {
         /*216 */_(ER_SYNC_QUORUM_TIMEOUT,       "Quorum collection for a synchronous transaction is timed out") \
         /*217 */_(ER_SYNC_ROLLBACK,             "A rollback for a synchronous transaction is received") \
 	/*218 */_(ER_TUPLE_METADATA_IS_TOO_BIG,	"Can't create tuple: metadata size %u is too big") \
+	/*219 */_(ER_SQL_CANT_ADD_COLUMN_TO_VIEW,	"Can't add column '%s'. '%s' is a view") \
+	/*220 */_(ER_SQL_CANT_ADD_AUTOINC,	"Can't add AUTOINCREMENT: the space '%s' already has one auto incremented field") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 9013bc86f..6f3d2747d 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -285,48 +285,113 @@ sql_field_retrieve(Parse *parser, struct space_def *space_def, uint32_t id)
 	return field;
 }
 
-/*
- * Add a new column to the table currently being constructed.
+/**
+ * Make shallow copy of @a space on region.
  *
- * The parser calls this routine once for each column declaration
- * in a CREATE TABLE statement.  sqlStartTable() gets called
- * first to get things going.  Then this routine is called for each
- * column.
+ * Function is used to add a new column to an existing space with
+ * <ALTER TABLE ADD COLUMN> statement. Copy space def and index
+ * array to create constraints appeared in the statement. The
+ * index array copy will be modified by adding new elements to it.
+ * It is necessary, because the statement may contain several
+ * index definitions (constraints).
  */
+static struct space *
+sql_shallow_space_copy(struct Parse *parse, struct space *space)
+{
+	assert(space->def != NULL);
+	struct space *ret = sql_ephemeral_space_new(parse, space->def->name);
+	if (ret == NULL)
+		return NULL;
+	ret->index_count = space->index_count;
+	ret->index_id_max = space->index_id_max;
+	uint32_t indexes_sz = sizeof(struct index *) * (ret->index_count);
+	ret->index = (struct index **) malloc(indexes_sz);
+	if (ret->index == NULL) {
+		diag_set(OutOfMemory, indexes_sz, "realloc", "ret->index");
+		return NULL;
+	}
+	memcpy(ret->index, space->index, indexes_sz);
+	memcpy(ret->def, space->def, sizeof(struct space_def));
+	ret->def->opts.is_temporary = true;
+	ret->def->opts.is_ephemeral = true;
+	if (ret->def->field_count != 0) {
+		uint32_t fields_size = 0;
+		ret->def->fields =
+			region_alloc_array(&parse->region,
+					   typeof(struct field_def),
+					   ret->def->field_count, &fields_size);
+		if (ret->def->fields == NULL) {
+			diag_set(OutOfMemory, fields_size, "region_alloc",
+				 "ret->def->fields");
+			free(ret->index);
+			return NULL;
+		}
+		memcpy(ret->def->fields, space->def->fields, fields_size);
+	}
+
+	return ret;
+}
+
 void
-sqlAddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
+sql_create_column_start(struct Parse *parse)
 {
-	assert(type_def != NULL);
-	char *z;
-	sql *db = pParse->db;
-	if (pParse->create_table_def.new_space == NULL)
-		return;
-	struct space_def *def = pParse->create_table_def.new_space->def;
+	struct create_column_def *create_column_def = &parse->create_column_def;
+	struct alter_entity_def *alter_entity_def =
+		&create_column_def->base.base;
+	assert(alter_entity_def->entity_type == ENTITY_TYPE_COLUMN);
+	assert(alter_entity_def->alter_action == ALTER_ACTION_CREATE);
+	struct space *space = parse->create_table_def.new_space;
+	bool is_alter = space == NULL;
+	struct sql *db = parse->db;
+	if (is_alter) {
+		const char *space_name =
+			alter_entity_def->entity_name->a[0].zName;
+		space = space_by_name(space_name);
+		if (space == NULL) {
+			diag_set(ClientError, ER_NO_SUCH_SPACE, space_name);
+			goto tnt_error;
+		}
+		space = sql_shallow_space_copy(parse, space);
+		if (space == NULL)
+			goto tnt_error;
+	}
+	create_column_def->space = space;
+	struct space_def *def = space->def;
+	assert(def->opts.is_ephemeral);
 
 #if SQL_MAX_COLUMN
 	if ((int)def->field_count + 1 > db->aLimit[SQL_LIMIT_COLUMN]) {
 		diag_set(ClientError, ER_SQL_COLUMN_COUNT_MAX, def->name,
 			 def->field_count + 1, db->aLimit[SQL_LIMIT_COLUMN]);
-		pParse->is_aborted = true;
-		return;
+		goto tnt_error;
 	}
 #endif
+
+	struct region *region = &parse->region;
+	struct Token *name = &create_column_def->base.name;
+	char *column_name =
+		sql_normalized_name_region_new(region, name->z, name->n);
+	if (column_name == NULL)
+		goto tnt_error;
+
+	if (is_alter && def->opts.is_view) {
+		diag_set(ClientError, ER_SQL_CANT_ADD_COLUMN_TO_VIEW,
+			 column_name, def->name);
+		goto tnt_error;
+	}
+
 	/*
-	 * As sql_field_retrieve will allocate memory on region
-	 * ensure that def is also temporal and would be dropped.
+	 * Format can be set in Lua, then exact_field_count can be
+	 * zero, but field_count is not.
 	 */
-	assert(def->opts.is_ephemeral);
-	if (sql_field_retrieve(pParse, def, def->field_count) == NULL)
+	if (def->exact_field_count == 0)
+		def->exact_field_count = def->field_count;
+	if (sql_field_retrieve(parse, def, def->field_count) == NULL)
 		return;
-	struct region *region = &pParse->region;
-	z = sql_normalized_name_region_new(region, pName->z, pName->n);
-	if (z == NULL) {
-		pParse->is_aborted = true;
-		return;
-	}
+
 	struct field_def *column_def = &def->fields[def->field_count];
 	memcpy(column_def, &field_def_default, sizeof(field_def_default));
-	column_def->name = z;
+	column_def->name = column_name;
 	/*
 	 * Marker ON_CONFLICT_ACTION_DEFAULT is used to detect
 	 * attempts to define NULL multiple time or to detect
@@ -334,18 +399,86 @@ sqlAddColumn(Parse * pParse, Token * pName, struct type_def *type_def)
 	 */
 	column_def->nullable_action = ON_CONFLICT_ACTION_DEFAULT;
 	column_def->is_nullable = true;
-	column_def->type = type_def->type;
+	column_def->type = create_column_def->type_def->type;
 	def->field_count++;
+
+	sqlSrcListDelete(db, alter_entity_def->entity_name);
+	return;
+tnt_error:
+	parse->is_aborted = true;
+	sqlSrcListDelete(db, alter_entity_def->entity_name);
+}
+
+static void
+sql_vdbe_create_constraints(struct Parse *parse, int reg_space_id);
+
+void
+sql_create_column_end(struct Parse *parse)
+{
+	struct space *space = parse->create_column_def.space;
+	assert(space != NULL);
+	struct space_def *def = space->def;
+	struct field_def *field = &def->fields[def->field_count - 1];
+	if (field->nullable_action == ON_CONFLICT_ACTION_DEFAULT) {
+		field->nullable_action = ON_CONFLICT_ACTION_NONE;
+		field->is_nullable = true;
+	}
+	/*
+	 * Encode the format array and emit code to update _space.
+	 */
+	uint32_t table_stmt_sz = 0;
+	struct region *region = &parse->region;
+	char *table_stmt = sql_encode_table(region, def, &table_stmt_sz);
+	char *raw = sqlDbMallocRaw(parse->db, table_stmt_sz);
+	if (table_stmt == NULL || raw == NULL) {
+		parse->is_aborted = true;
+		return;
+	}
+	memcpy(raw, table_stmt, table_stmt_sz);
+
+	struct Vdbe *v = sqlGetVdbe(parse);
+	assert(v != NULL);
+
+	struct space *system_space = space_by_id(BOX_SPACE_ID);
+	assert(system_space != NULL);
+	int cursor = parse->nTab++;
+	vdbe_emit_open_cursor(parse, cursor, 0, system_space);
+	sqlVdbeChangeP5(v, OPFLAG_SYSTEMSP);
+
+	int key_reg = ++parse->nMem;
+	sqlVdbeAddOp2(v, OP_Integer, def->id, key_reg);
+	int addr = sqlVdbeAddOp4Int(v, OP_Found, cursor, 0, key_reg, 1);
+	sqlVdbeAddOp2(v, OP_Halt, -1, ON_CONFLICT_ACTION_ABORT);
+	sqlVdbeJumpHere(v, addr);
+
+	int tuple_reg = sqlGetTempRange(parse, box_space_field_MAX + 1);
+	for (int i = 0; i < box_space_field_MAX - 1; ++i)
+		sqlVdbeAddOp3(v, OP_Column, cursor, i, tuple_reg + i);
+	sqlVdbeAddOp1(v, OP_Close, cursor);
+
+	sqlVdbeAddOp2(v, OP_Integer, def->field_count, tuple_reg + 4);
+	sqlVdbeAddOp4(v, OP_Blob, table_stmt_sz, tuple_reg + 6,
+		      SQL_SUBTYPE_MSGPACK, raw, P4_DYNAMIC);
+	sqlVdbeAddOp3(v, OP_MakeRecord, tuple_reg, box_space_field_MAX,
+		      tuple_reg + box_space_field_MAX);
+	sqlVdbeAddOp4(v, OP_IdxReplace, tuple_reg + box_space_field_MAX, 0, 0,
+		      (char *) system_space, P4_SPACEPTR);
+	sql_vdbe_create_constraints(parse, key_reg);
+
+	/*
+	 * Clean up array allocated in sql_shallow_space_copy().
+	 */
+	free(space->index);
 }
 
 void
 sql_column_add_nullable_action(struct Parse *parser,
 			       enum on_conflict_action nullable_action)
 {
-	struct space *space = parser->create_table_def.new_space;
-	if (space == NULL || NEVER(space->def->field_count < 1))
+	assert(parser->create_column_def.space != NULL);
+	struct space_def *def = parser->create_column_def.space->def;
+	if (NEVER(def->field_count < 1))
 		return;
-	struct space_def *def = space->def;
 	struct field_def *field = &def->fields[def->field_count - 1];
 	if (field->nullable_action != ON_CONFLICT_ACTION_DEFAULT &&
 	    nullable_action != field->nullable_action) {
@@ -364,51 +497,42 @@ sql_column_add_nullable_action(struct Parse *parser,
 }
 
 /*
- * The expression is the default value for the most recently added column
- * of the table currently under construction.
+ * The expression is the default value for the most recently added
+ * column.
  *
  * Default value expressions must be constant.  Raise an exception if this
  * is not the case.
  *
  * This routine is called by the parser while in the middle of
- * parsing a CREATE TABLE statement.
+ * parsing a <CREATE TABLE> or a <ALTER TABLE ADD COLUMN>
+ * statement.
  */
 void
 sqlAddDefaultValue(Parse * pParse, ExprSpan * pSpan)
 {
 	sql *db = pParse->db;
-	struct space *p = pParse->create_table_def.new_space;
-	if (p != NULL) {
-		assert(p->def->opts.is_ephemeral);
-		struct space_def *def = p->def;
-		if (!sqlExprIsConstantOrFunction
-		    (pSpan->pExpr, db->init.busy)) {
-			const char *column_name =
-				def->fields[def->field_count - 1].name;
-			diag_set(ClientError, ER_CREATE_SPACE, def->name,
-				 tt_sprintf("default value of column '%s' is "\
-					    "not constant", column_name));
+	assert(pParse->create_column_def.space != NULL);
+	struct space_def *def = pParse->create_column_def.space->def;
+	struct field_def *field = &def->fields[def->field_count - 1];
+	if (!sqlExprIsConstantOrFunction(pSpan->pExpr, db->init.busy)) {
+		diag_set(ClientError, ER_CREATE_SPACE, def->name,
+			 tt_sprintf("default value of column '%s' is not "
+				    "constant", field->name));
+		pParse->is_aborted = true;
+	} else {
+		struct region *region = &pParse->region;
+		uint32_t default_length = (int)(pSpan->zEnd - pSpan->zStart);
+		field->default_value = region_alloc(region, default_length + 1);
+		if (field->default_value == NULL) {
+			diag_set(OutOfMemory, default_length + 1,
+				 "region_alloc", "field->default_value");
 			pParse->is_aborted = true;
-		} else {
-			assert(def != NULL);
-			struct field_def *field =
-				&def->fields[def->field_count - 1];
-			struct region *region = &pParse->region;
-			uint32_t default_length = (int)(pSpan->zEnd - pSpan->zStart);
-			field->default_value = region_alloc(region,
-							    default_length + 1);
-			if (field->default_value == NULL) {
-				diag_set(OutOfMemory, default_length + 1,
-					 "region_alloc",
-					 "field->default_value");
-				pParse->is_aborted = true;
-				return;
-			}
-			strncpy(field->default_value, pSpan->zStart,
-				default_length);
-			field->default_value[default_length] = '\0';
+			goto add_default_value_exit;
 		}
+		strncpy(field->default_value, pSpan->zStart, default_length);
+		field->default_value[default_length] = '\0';
 	}
+add_default_value_exit:
 	sql_expr_delete(db, pSpan->pExpr, false);
 }
 
@@ -447,6 +571,8 @@ sqlAddPrimaryKey(struct Parse *pParse)
 	int nTerm;
 	struct ExprList *pList = pParse->create_index_def.cols;
 	struct space *space = pParse->create_table_def.new_space;
+	if (space == NULL)
+		space = pParse->create_column_def.space;
 	if (space == NULL)
 		goto primary_key_exit;
 	if (sql_space_primary_key(space) != NULL) {
@@ -574,8 +700,10 @@ sql_create_check_contraint(struct Parse *parser)
 		(struct alter_entity_def *) create_ck_def;
 	assert(alter_def->entity_type == ENTITY_TYPE_CK);
 	(void) alter_def;
-	struct space *space = parser->create_table_def.new_space;
-	bool is_alter = space == NULL;
+	struct space *space = parser->create_column_def.space;
+	if (space == NULL)
+		space = parser->create_table_def.new_space;
+	bool is_alter_add_constr = space == NULL;
 
 	/* Prepare payload for ck constraint definition. */
 	struct region *region = &parser->region;
@@ -589,9 +717,23 @@ sql_create_check_contraint(struct Parse *parser)
 			return;
 		}
 	} else {
-		assert(! is_alter);
-		uint32_t ck_idx = ++parser->check_count;
-		name = tt_sprintf("ck_unnamed_%s_%d", space->def->name, ck_idx);
+		assert(!is_alter_add_constr);
+		uint32_t idx = ++parser->check_count;
+		/*
+		 * If it is <ALTER TABLE ADD COLUMN> we should
+		 * count the existing CHECK constraints in the
+		 * space and form a name based on this.
+		 */
+		if (parser->create_table_def.new_space == NULL) {
+			struct space *original_space =
+				space_by_name(space->def->name);
+			assert(original_space != NULL);
+			struct rlist *checks = &original_space->ck_constraint;
+			struct ck_constraint *ck;
+			rlist_foreach_entry(ck, checks, link)
+				idx++;
+		}
+		name = tt_sprintf("ck_unnamed_%s_%d", space->def->name, idx);
 	}
 	size_t name_len = strlen(name);
 
@@ -634,7 +776,7 @@ sql_create_check_contraint(struct Parse *parser)
 	trim_space_snprintf(ck_def->expr_str, expr_str, expr_str_len);
 	memcpy(ck_def->name, name, name_len);
 	ck_def->name[name_len] = '\0';
-	if (is_alter) {
+	if (is_alter_add_constr) {
 		const char *space_name = alter_def->entity_name->a[0].zName;
 		struct space *space = space_by_name(space_name);
 		if (space == NULL) {
@@ -663,9 +805,8 @@ sql_create_check_contraint(struct Parse *parser)
 void
 sqlAddCollateType(Parse * pParse, Token * pToken)
 {
-	struct space *space = pParse->create_table_def.new_space;
-	if (space == NULL)
-		return;
+	struct space *space = pParse->create_column_def.space;
+	assert(space != NULL);
 	uint32_t i = space->def->field_count - 1;
 	sql *db = pParse->db;
 	char *coll_name = sql_name_from_token(db, pToken);
@@ -704,8 +845,7 @@ sql_column_collation(struct space_def *def, uint32_t column, uint32_t *coll_id)
 	 *
 	 * In cases mentioned above collation is fetched by id.
 	 */
-	if (space == NULL) {
-		assert(def->opts.is_ephemeral);
+	if (def->opts.is_ephemeral) {
 		assert(column < (uint32_t)def->field_count);
 		*coll_id = def->fields[column].coll_id;
 		struct coll_id *collation = coll_by_id(*coll_id);
@@ -794,7 +934,8 @@ vdbe_emit_create_index(struct Parse *parse, struct space_def *def,
 	memcpy(raw, index_parts, index_parts_sz);
 	index_parts = raw;
 
-	if (parse->create_table_def.new_space != NULL) {
+	if (parse->create_table_def.new_space != NULL ||
+	    parse->create_column_def.space != NULL) {
 		sqlVdbeAddOp2(v, OP_SCopy, space_id_reg, entry_reg);
 		sqlVdbeAddOp2(v, OP_Integer, idx_def->iid, entry_reg + 1);
 	} else {
@@ -1032,18 +1173,21 @@ vdbe_emit_fk_constraint_create(struct Parse *parse_context,
 			  P4_DYNAMIC);
 	/*
 	 * In case we are adding FK constraints during execution
-	 * of <CREATE TABLE ...> statement, we don't have child
-	 * id, but we know register where it will be stored.
+	 * of <CREATE TABLE ...> or <ALER TABLE ADD COLUMN ...>
+	 * statement, we don't have child id, but we know register
+	 * where it will be stored.
 	 */
-	if (parse_context->create_table_def.new_space != NULL) {
+	bool is_alter_add_constr =
+		parse_context->create_table_def.new_space == NULL &&
+		parse_context->create_column_def.space == NULL;
+	if (!is_alter_add_constr) {
 		sqlVdbeAddOp2(vdbe, OP_SCopy, fk->child_id,
 				  constr_tuple_reg + 1);
 	} else {
 		sqlVdbeAddOp2(vdbe, OP_Integer, fk->child_id,
 				  constr_tuple_reg + 1);
 	}
-	if (parse_context->create_table_def.new_space != NULL &&
-	    fk_constraint_is_self_referenced(fk)) {
+	if (!is_alter_add_constr && fk_constraint_is_self_referenced(fk)) {
 		sqlVdbeAddOp2(vdbe, OP_SCopy, fk->parent_id,
 				  constr_tuple_reg + 2);
 	} else {
@@ -1107,7 +1251,7 @@ vdbe_emit_fk_constraint_create(struct Parse *parse_context,
 			  constr_tuple_reg + 9);
 	sqlVdbeAddOp2(vdbe, OP_SInsert, BOX_FK_CONSTRAINT_ID,
 		      constr_tuple_reg + 9);
-	if (parse_context->create_table_def.new_space == NULL) {
+	if (is_alter_add_constr) {
 		sqlVdbeCountChanges(vdbe);
 		sqlVdbeChangeP5(vdbe, OPFLAG_NCHANGE);
 	}
@@ -1148,15 +1292,21 @@ resolve_link(struct Parse *parse_context, const struct space_def *def,
 
 /**
  * Emit code to create sequences, indexes, check and foreign key
- * constraints appeared in <CREATE TABLE>.
+ * constraints appeared in <CREATE TABLE> or
+ * <ALTER TABLE ADD COLUMN>.
  */
 static void
 sql_vdbe_create_constraints(struct Parse *parse, int reg_space_id)
 {
 	assert(reg_space_id != 0);
 	struct space *space = parse->create_table_def.new_space;
-	assert(space != NULL);
+	bool is_alter = space == NULL;
 	uint32_t i = 0;
+	if (is_alter) {
+		space = parse->create_column_def.space;
+		i = space_by_name(space->def->name)->index_count;
+	}
+	assert(space != NULL);
 	for (; i < space->index_count; ++i) {
 		struct index *idx = space->index[i];
 		vdbe_emit_create_index(parse, space->def, idx->def,
@@ -1175,6 +1325,21 @@ sql_vdbe_create_constraints(struct Parse *parse, int reg_space_id)
 		sqlVdbeAddOp2(v, OP_NextSequenceId, 0, reg_seq_id);
 		int reg_seq_rec = emitNewSysSequenceRecord(parse, reg_seq_id,
 							   space->def->name);
+		if (is_alter) {
+			int errcode = ER_SQL_CANT_ADD_AUTOINC;
+			const char *error_msg =
+				tt_sprintf(tnt_errcode_desc(errcode),
+					   space->def->name);
+			if (vdbe_emit_halt_with_presence_test(parse,
+							      BOX_SEQUENCE_ID,
+							      2,
+							      reg_seq_rec + 3,
+							      1, errcode,
+							      error_msg, false,
+							      OP_NoConflict)
+			    != 0)
+				return;
+		}
 		sqlVdbeAddOp2(v, OP_SInsert, BOX_SEQUENCE_ID, reg_seq_rec);
 		/* Do an insertion into _space_sequence. */
 		int reg_space_seq_record =
@@ -1873,24 +2038,28 @@ sql_create_foreign_key(struct Parse *parse_context)
 	char *parent_name = NULL;
 	char *constraint_name = NULL;
 	bool is_self_referenced = false;
+	struct space *space = parse_context->create_column_def.space;
 	struct create_table_def *table_def = &parse_context->create_table_def;
-	struct space *space = table_def->new_space;
+	if (space == NULL)
+		space = table_def->new_space;
 	/*
-	 * Space under construction during CREATE TABLE
-	 * processing. NULL for ALTER TABLE statement handling.
+	 * Space under construction during <CREATE TABLE>
+	 * processing or shallow copy of space during <ALTER TABLE
+	 * ... ADD COLUMN>. NULL for <ALTER TABLE ... ADD
+	 * CONSTRAINT> statement handling.
 	 */
-	bool is_alter = space == NULL;
+	bool is_alter_add_constr = space == NULL;
 	uint32_t child_cols_count;
 	struct ExprList *child_cols = create_fk_def->child_cols;
 	if (child_cols == NULL) {
-		assert(!is_alter);
+		assert(!is_alter_add_constr);
 		child_cols_count = 1;
 	} else {
 		child_cols_count = child_cols->nExpr;
 	}
 	struct ExprList *parent_cols = create_fk_def->parent_cols;
 	struct space *child_space = NULL;
-	if (is_alter) {
+	if (is_alter_add_constr) {
 		const char *child_name = alter_def->entity_name->a[0].zName;
 		child_space = space_by_name(child_name);
 		if (child_space == NULL) {
@@ -1908,6 +2077,8 @@ sql_create_foreign_key(struct Parse *parse_context)
 			goto tnt_error;
 		}
 		memset(fk_parse, 0, sizeof(*fk_parse));
+		if (parse_context->create_column_def.space != NULL)
+			child_space = space;
 		rlist_add_entry(&parse_context->fkeys, fk_parse, link);
 	}
 	struct Token *parent = create_fk_def->parent_name;
@@ -1920,28 +2091,45 @@ sql_create_foreign_key(struct Parse *parse_context)
 	 * self-referenced, but in this case parent (which is
 	 * also child) table will definitely exist.
 	 */
-	is_self_referenced = !is_alter &&
+	is_self_referenced = !is_alter_add_constr &&
 			     strcmp(parent_name, space->def->name) == 0;
 	struct space *parent_space = space_by_name(parent_name);
-	if (parent_space == NULL) {
-		if (is_self_referenced) {
-			struct fk_constraint_parse *fk =
-				rlist_first_entry(&parse_context->fkeys,
-						  struct fk_constraint_parse,
-						  link);
-			fk->selfref_cols = parent_cols;
-			fk->is_self_referenced = true;
-		} else {
-			diag_set(ClientError, ER_NO_SUCH_SPACE, parent_name);;
-			goto tnt_error;
-		}
+	if (parent_space == NULL && !is_self_referenced) {
+		diag_set(ClientError, ER_NO_SUCH_SPACE, parent_name);
+		goto tnt_error;
+	}
+	if (is_self_referenced) {
+		struct fk_constraint_parse *fk =
+			rlist_first_entry(&parse_context->fkeys,
+					  struct fk_constraint_parse,
+					  link);
+		fk->selfref_cols = parent_cols;
+		fk->is_self_referenced = true;
 	}
-	if (!is_alter) {
+	if (!is_alter_add_constr) {
 		if (create_def->name.n == 0) {
-			constraint_name =
-				sqlMPrintf(db, "fk_unnamed_%s_%d",
-					   space->def->name,
-					   ++parse_context->fkey_count);
+			uint32_t idx = ++parse_context->fkey_count;
+			/*
+			 * If it is <ALTER TABLE ADD COLUMN> we
+			 * should count the existing FK
+			 * constraints in the space and form a
+			 * name based on this.
+			 */
+			if (table_def->new_space == NULL) {
+				struct space *original_space =
+					space_by_name(space->def->name);
+				assert(original_space != NULL);
+				struct rlist *child_fk =
+					&original_space->child_fk_constraint;
+				if (!rlist_empty(child_fk)) {
+					struct fk_constraint *fk;
+					rlist_foreach_entry(fk, child_fk,
+							    in_child_space)
+						idx++;
+				}
+			}
+			constraint_name = sqlMPrintf(db, "fk_unnamed_%s_%d",
+						     space->def->name, idx);
 		} else {
 			constraint_name =
 				sql_name_from_token(db, &create_def->name);
@@ -2001,7 +2189,8 @@ sql_create_foreign_key(struct Parse *parse_context)
 	}
 	int actions = create_fk_def->actions;
 	fk_def->field_count = child_cols_count;
-	fk_def->child_id = child_space != NULL ? child_space->def->id : 0;
+	fk_def->child_id = table_def->new_space == NULL ?
+		child_space->def->id : 0;
 	fk_def->parent_id = parent_space != NULL ? parent_space->def->id : 0;
 	fk_def->is_deferred = create_constr_def->is_deferred;
 	fk_def->match = (enum fk_constraint_match) (create_fk_def->match);
@@ -2021,7 +2210,7 @@ sql_create_foreign_key(struct Parse *parse_context)
 					    constraint_name) != 0) {
 			goto exit_create_fk;
 		}
-		if (!is_alter) {
+		if (!is_alter_add_constr) {
 			if (child_cols == NULL) {
 				assert(i == 0);
 				/*
@@ -2050,12 +2239,13 @@ sql_create_foreign_key(struct Parse *parse_context)
 	memcpy(fk_def->name, constraint_name, name_len);
 	fk_def->name[name_len] = '\0';
 	/*
-	 * In case of CREATE TABLE processing, all foreign keys
-	 * constraints must be created after space itself, so
-	 * lets delay it until sqlEndTable() call and simply
+	 * In case of <CREATE TABLE> or <ALTER TABLE ... ADD
+	 * COLUMN> processing, all foreign keys  constraints  must
+	 * be created after space itself, so lets delay it until
+	 * sqlEndTable() or sql_add_column_end() call and simply
 	 * maintain list of all FK constraints inside parser.
 	 */
-	if (!is_alter) {
+	if (!is_alter_add_constr) {
 		struct fk_constraint_parse *fk_parse =
 			rlist_first_entry(&parse_context->fkeys,
 					  struct fk_constraint_parse, link);
@@ -2407,7 +2597,10 @@ sql_create_index(struct Parse *parse) {
 	 * Find the table that is to be indexed.
 	 * Return early if not found.
 	 */
-	struct space *space = NULL;
+	struct space *space = parse->create_table_def.new_space;
+	if (space == NULL)
+		space = parse->create_column_def.space;
+	bool is_create_table_or_add_col = space != NULL;
 	struct Token token = create_entity_def->name;
 	if (tbl_name != NULL) {
 		assert(token.n > 0 && token.z != NULL);
@@ -2420,10 +2613,8 @@ sql_create_index(struct Parse *parse) {
 			}
 			goto exit_create_index;
 		}
-	} else {
-		if (parse->create_table_def.new_space == NULL)
-			goto exit_create_index;
-		space = parse->create_table_def.new_space;
+	} else if (space == NULL) {
+		goto exit_create_index;
 	}
 	struct space_def *def = space->def;
 
@@ -2458,7 +2649,7 @@ sql_create_index(struct Parse *parse) {
 	 * 2) UNIQUE constraint is non-named and standard
 	 *    auto-index name will be generated.
 	 */
-	if (parse->create_table_def.new_space == NULL) {
+	if (!is_create_table_or_add_col) {
 		assert(token.z != NULL);
 		name = sql_name_from_token(db, &token);
 		if (name == NULL) {
@@ -2624,7 +2815,7 @@ sql_create_index(struct Parse *parse) {
 	 * constraint, but has different onError (behavior on
 	 * constraint violation), then an error is raised.
 	 */
-	if (parse->create_table_def.new_space != NULL) {
+	if (is_create_table_or_add_col) {
 		for (uint32_t i = 0; i < space->index_count; ++i) {
 			struct index *existing_idx = space->index[i];
 			uint32_t iid = existing_idx->def->iid;
@@ -2712,7 +2903,7 @@ sql_create_index(struct Parse *parse) {
 		sqlVdbeAddOp0(vdbe, OP_Expire);
 	}
 
-	if (tbl_name != NULL)
+	if (!is_create_table_or_add_col)
 		goto exit_create_index;
 	table_add_index(space, index);
 	index = NULL;
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 995875566..0c9887851 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -226,19 +226,24 @@ create_table_end ::= . { sqlEndTable(pParse); }
  */
 
 columnlist ::= columnlist COMMA tcons.
-columnlist ::= columnlist COMMA columnname carglist autoinc(I). {
-  uint32_t fieldno = pParse->create_table_def.new_space->def->field_count - 1;
-  if (I == 1 && sql_add_autoincrement(pParse, fieldno) != 0)
-    return;
+columnlist ::= columnlist COMMA column_def create_column_end.
+columnlist ::= column_def create_column_end.
+
+column_def ::= column_name_and_type carglist.
+
+column_name_and_type ::= nm(A) typedef(Y). {
+  create_column_def_init(&pParse->create_column_def, NULL, &A, &Y);
+  sql_create_column_start(pParse);
 }
 
-columnlist ::= columnname carglist autoinc(I). {
-  uint32_t fieldno = pParse->create_table_def.new_space->def->field_count - 1;
+create_column_end ::= autoinc(I). {
+  uint32_t fieldno = pParse->create_column_def.space->def->field_count - 1;
   if (I == 1 && sql_add_autoincrement(pParse, fieldno) != 0)
     return;
+  if (pParse->create_table_def.new_space == NULL)
+    sql_create_column_end(pParse);
 }
 columnlist ::= tcons.
-columnname(A) ::= nm(A) typedef(Y). {sqlAddColumn(pParse,&A,&Y);}
 
 // An IDENTIFIER can be a generic identifier, or one of several
 // keywords.  Any non-standard keyword can also be an identifier.
@@ -281,9 +286,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" is a list of additional constraints and clauses that
+ * come after the column name and column type in a <CREATE TABLE>
+ * or <ALTER TABLE ADD COLUMN> statement.
+ */
 carglist ::= carglist ccons.
 carglist ::= .
 %type cconsname { struct Token }
@@ -1735,11 +1742,28 @@ alter_table_start(A) ::= ALTER TABLE fullname(T) . { A = T; }
 
 %type alter_add_constraint {struct alter_args}
 alter_add_constraint(A) ::= alter_table_start(T) ADD CONSTRAINT nm(N). {
+   A.table_name = T;
+   A.name = N;
+   pParse->initiateTTrans = true;
+ }
+
+%type alter_add_column {struct alter_args}
+alter_add_column(A) ::= alter_table_start(T) ADD column_name(N). {
   A.table_name = T;
   A.name = N;
   pParse->initiateTTrans = true;
 }
 
+column_name(N) ::= COLUMN nm(A). { N = A; }
+column_name(N) ::= nm(A). { N = A; }
+
+cmd ::= alter_column_def carglist create_column_end.
+
+alter_column_def ::= alter_add_column(N) typedef(Y). {
+  create_column_def_init(&pParse->create_column_def, N.table_name, &N.name, &Y);
+  sql_create_column_start(pParse);
+}
+
 cmd ::= alter_add_constraint(N) FOREIGN KEY LP eidlist(FA) RP REFERENCES
         nm(T) eidlist_opt(TA) matcharg(M) refargs(R) defer_subclause_opt(D). {
   create_fk_def_init(&pParse->create_fk_def, N.table_name, &N.name, FA, &T, TA,
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index 1105fda6e..336914c57 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -35,6 +35,7 @@
 #include "box/fk_constraint.h"
 #include "box/key_def.h"
 #include "box/sql.h"
+#include "box/constraint_id.h"
 
 /**
  * This file contains auxiliary structures and functions which
@@ -154,6 +155,7 @@ enum sql_index_type {
 
 enum entity_type {
 	ENTITY_TYPE_TABLE = 0,
+	ENTITY_TYPE_COLUMN,
 	ENTITY_TYPE_VIEW,
 	ENTITY_TYPE_INDEX,
 	ENTITY_TYPE_TRIGGER,
@@ -207,6 +209,14 @@ struct create_table_def {
 	struct space *new_space;
 };
 
+struct create_column_def {
+	struct create_entity_def base;
+	/** Shallow space_def copy. */
+	struct space *space;
+	/** Column type. */
+	struct type_def *type_def;
+};
+
 struct create_view_def {
 	struct create_entity_def base;
 	/**
@@ -464,6 +474,16 @@ create_table_def_init(struct create_table_def *table_def, struct Token *name,
 			       if_not_exists);
 }
 
+static inline void
+create_column_def_init(struct create_column_def *column_def,
+		       struct SrcList *table_name, struct Token *name,
+		       struct type_def *type_def)
+{
+	create_entity_def_init(&column_def->base, ENTITY_TYPE_COLUMN,
+			       table_name, name, false);
+	column_def->type_def = type_def;
+}
+
 static inline void
 create_view_def_init(struct create_view_def *view_def, struct Token *name,
 		     struct Token *create, struct ExprList *aliases,
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index fa87e7bd2..32142a871 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -2251,22 +2251,26 @@ struct Parse {
 		struct enable_entity_def enable_entity_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).
+	 * Table def or column def is not part of union since
+	 * information being held must survive till the end of
+	 * parsing of whole <CREATE TABLE> or
+	 * <ALTER TABLE ADD COLUMN> statement (to pass it to
+	 * sqlEndTable() sql_create_column_end() function).
 	 */
 	struct create_table_def create_table_def;
+	struct create_column_def create_column_def;
 	/*
-	 * FK and CK constraints appeared in a <CREATE TABLE>.
+	 * FK and CK constraints appeared in a <CREATE TABLE> or
+	 * an <ALTER TABLE ADD COLUMN> statement.
 	 */
 	struct rlist fkeys;
 	struct rlist checks;
 	uint32_t fkey_count;
 	uint32_t check_count;
 	/*
-	 * True, if column within a <CREATE TABLE> statement to be
-	 * created has <AUTOINCREMENT>.
+	 * True, if column in a <CREATE TABLE> or an
+	 * <ALTER TABLE ADD COLUMN> statement to be created has
+	 * <AUTOINCREMENT>.
 	 */
 	bool has_autoinc;
 	/* Id of field with <AUTOINCREMENT>. */
@@ -2860,15 +2864,30 @@ struct space *sqlResultSetOfSelect(Parse *, Select *);
 
 struct space *
 sqlStartTable(Parse *, Token *);
-void sqlAddColumn(Parse *, Token *, struct type_def *);
+
+/**
+ * Add new field to the format of ephemeral space in
+ * create_table_def. If it is <ALTER TABLE> create shallow copy of
+ * the existing space and add field to its format.
+ */
+void
+sql_create_column_start(struct Parse *parse);
+
+/**
+ * Emit code to update entry in _space and code to create
+ * constraints (entries in _index, _ck_constraint, _fk_constraint)
+ * described with this column.
+ */
+void
+sql_create_column_end(struct Parse *parse);
 
 /**
  * This routine is called by the parser while in the middle of
- * parsing a CREATE TABLE statement.  A "NOT NULL" constraint has
- * been seen on a column.  This routine sets the is_nullable flag
- * on the column currently under construction.
- * If nullable_action has been already set, this function raises
- * an error.
+ * parsing a <CREATE TABLE> or a <ALTER TABLE ADD COLUMN>
+ * statement. A "NOT NULL" constraint has been seen on a column.
+ * This routine sets the is_nullable flag on the column currently
+ * under construction. If nullable_action has been already set,
+ * this function raises an error.
  *
  * @param parser SQL Parser object.
  * @param nullable_action on_conflict_action value.
diff --git a/test/box/error.result b/test/box/error.result
index cdecdb221..6fc3cb99f 100644
--- a/test/box/error.result
+++ b/test/box/error.result
@@ -437,6 +437,8 @@ t;
  |   216: box.error.SYNC_QUORUM_TIMEOUT
  |   217: box.error.SYNC_ROLLBACK
  |   218: box.error.TUPLE_METADATA_IS_TOO_BIG
+ |   219: box.error.SQL_CANT_ADD_COLUMN_TO_VIEW
+ |   220: box.error.SQL_CANT_ADD_AUTOINC
  | ...
 
 test_run:cmd("setopt delimiter ''");
diff --git a/test/sql/add-column.result b/test/sql/add-column.result
new file mode 100644
index 000000000..f86259105
--- /dev/null
+++ b/test/sql/add-column.result
@@ -0,0 +1,471 @@
+-- test-run result file version 2
+--
+-- gh-3075: Check <ALTER TABLE table ADD COLUMN column> statement.
+--
+CREATE TABLE t1 (a INT PRIMARY KEY);
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- COLUMN keyword is optional. Check it here, but omit it below.
+--
+ALTER TABLE t1 ADD COLUMN b INT;
+ | ---
+ | - row_count: 0
+ | ...
+
+--
+-- A column with the same name already exists.
+--
+ALTER TABLE t1 ADD b SCALAR;
+ | ---
+ | - null
+ | - Space field 'B' is duplicate
+ | ...
+
+--
+-- Can't add column to a view.
+--
+CREATE VIEW v AS SELECT * FROM t1;
+ | ---
+ | - row_count: 1
+ | ...
+ALTER TABLE v ADD b INT;
+ | ---
+ | - null
+ | - Can't add column 'B'. 'V' is a view
+ | ...
+DROP VIEW v;
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check PRIMARY KEY constraint works with an added column.
+--
+CREATE TABLE pk_check (a INT CONSTRAINT pk PRIMARY KEY);
+ | ---
+ | - row_count: 1
+ | ...
+ALTER TABLE pk_check DROP CONSTRAINT pk;
+ | ---
+ | - row_count: 1
+ | ...
+ALTER TABLE pk_check ADD b INT PRIMARY KEY;
+ | ---
+ | - row_count: 0
+ | ...
+INSERT INTO pk_check VALUES (1, 1);
+ | ---
+ | - row_count: 1
+ | ...
+INSERT INTO pk_check VALUES (1, 1);
+ | ---
+ | - null
+ | - Duplicate key exists in unique index 'pk_unnamed_PK_CHECK_1' in space 'PK_CHECK'
+ | ...
+DROP TABLE pk_check;
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check UNIQUE constraint works with an added column.
+--
+CREATE TABLE unique_check (a INT PRIMARY KEY);
+ | ---
+ | - row_count: 1
+ | ...
+ALTER TABLE unique_check ADD b INT UNIQUE;
+ | ---
+ | - row_count: 0
+ | ...
+INSERT INTO unique_check VALUES (1, 1);
+ | ---
+ | - row_count: 1
+ | ...
+INSERT INTO unique_check VALUES (2, 1);
+ | ---
+ | - null
+ | - Duplicate key exists in unique index 'unique_unnamed_UNIQUE_CHECK_2' in space 'UNIQUE_CHECK'
+ | ...
+DROP TABLE unique_check;
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check CHECK constraint works with an added column.
+--
+CREATE TABLE ck_check (a INT PRIMARY KEY);
+ | ---
+ | - row_count: 1
+ | ...
+ALTER TABLE ck_check ADD b INT CHECK (b > 0);
+ | ---
+ | - row_count: 0
+ | ...
+INSERT INTO ck_check VALUES (1, 0);
+ | ---
+ | - null
+ | - 'Check constraint failed ''ck_unnamed_CK_CHECK_1'': b > 0'
+ | ...
+DROP TABLE ck_check;
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check FOREIGN KEY constraint works with an added column.
+--
+CREATE TABLE fk_check (a INT PRIMARY KEY);
+ | ---
+ | - row_count: 1
+ | ...
+ALTER TABLE fk_check ADD b INT REFERENCES t1(a);
+ | ---
+ | - row_count: 0
+ | ...
+INSERT INTO fk_check VALUES (0, 1);
+ | ---
+ | - null
+ | - 'Failed to execute SQL statement: FOREIGN KEY constraint failed'
+ | ...
+INSERT INTO fk_check VALUES (2, 0);
+ | ---
+ | - null
+ | - 'Failed to execute SQL statement: FOREIGN KEY constraint failed'
+ | ...
+INSERT INTO fk_check VALUES (2, 1);
+ | ---
+ | - null
+ | - 'Failed to execute SQL statement: FOREIGN KEY constraint failed'
+ | ...
+DROP TABLE fk_check;
+ | ---
+ | - row_count: 1
+ | ...
+DROP TABLE t1;
+ | ---
+ | - row_count: 1
+ | ...
+--
+-- Check FOREIGN KEY (self-referenced) constraint works with an
+-- added column.
+--
+CREATE TABLE self (id INT PRIMARY KEY AUTOINCREMENT, a INT UNIQUE)
+ | ---
+ | - row_count: 1
+ | ...
+ALTER TABLE self ADD b INT REFERENCES self(a)
+ | ---
+ | - row_count: 0
+ | ...
+INSERT INTO self(a,b) VALUES(1, 1);
+ | ---
+ | - autoincrement_ids:
+ |   - 1
+ |   row_count: 1
+ | ...
+UPDATE self SET a = 2, b = 2;
+ | ---
+ | - row_count: 1
+ | ...
+UPDATE self SET b = 3;
+ | ---
+ | - null
+ | - 'Failed to execute SQL statement: FOREIGN KEY constraint failed'
+ | ...
+UPDATE self SET a = 3;
+ | ---
+ | - null
+ | - 'Failed to execute SQL statement: FOREIGN KEY constraint failed'
+ | ...
+DROP TABLE self;
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check AUTOINCREMENT works with an added column.
+--
+CREATE TABLE autoinc_check (a INT CONSTRAINT pk PRIMARY KEY);
+ | ---
+ | - row_count: 1
+ | ...
+ALTER TABLE autoinc_check DROP CONSTRAINT pk;
+ | ---
+ | - row_count: 1
+ | ...
+ALTER TABLE autoinc_check ADD b INT PRIMARY KEY AUTOINCREMENT;
+ | ---
+ | - row_count: 0
+ | ...
+INSERT INTO autoinc_check(a) VALUES(1);
+ | ---
+ | - autoincrement_ids:
+ |   - 1
+ |   row_count: 1
+ | ...
+INSERT INTO autoinc_check(a) VALUES(1);
+ | ---
+ | - autoincrement_ids:
+ |   - 2
+ |   row_count: 1
+ | ...
+TRUNCATE TABLE autoinc_check;
+ | ---
+ | - row_count: 0
+ | ...
+
+--
+-- Can't add second column with AUTOINCREMENT.
+--
+ALTER TABLE autoinc_check ADD c INT AUTOINCREMENT;
+ | ---
+ | - null
+ | - 'Can''t add AUTOINCREMENT: the space ''AUTOINC_CHECK'' already has one auto incremented
+ |   field'
+ | ...
+DROP TABLE autoinc_check;
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check COLLATE clause works with an added column.
+--
+CREATE TABLE collate_check (a INT PRIMARY KEY);
+ | ---
+ | - row_count: 1
+ | ...
+ALTER TABLE collate_check ADD b TEXT COLLATE "unicode_ci";
+ | ---
+ | - row_count: 0
+ | ...
+INSERT INTO collate_check VALUES (1, 'a');
+ | ---
+ | - row_count: 1
+ | ...
+INSERT INTO collate_check VALUES (2, 'A');
+ | ---
+ | - row_count: 1
+ | ...
+SELECT * FROM collate_check WHERE b LIKE 'a';
+ | ---
+ | - metadata:
+ |   - name: A
+ |     type: integer
+ |   - name: B
+ |     type: string
+ |   rows:
+ |   - [1, 'a']
+ |   - [2, 'A']
+ | ...
+DROP TABLE collate_check;
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check DEFAULT clause works with an added column.
+--
+CREATE TABLE default_check (a INT PRIMARY KEY);
+ | ---
+ | - row_count: 1
+ | ...
+ALTER TABLE default_check ADD b TEXT DEFAULT ('a');
+ | ---
+ | - row_count: 0
+ | ...
+INSERT INTO default_check(a) VALUES (1);
+ | ---
+ | - row_count: 1
+ | ...
+SELECT * FROM default_check;
+ | ---
+ | - metadata:
+ |   - name: A
+ |     type: integer
+ |   - name: B
+ |     type: string
+ |   rows:
+ |   - [1, 'a']
+ | ...
+DROP TABLE default_check;
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check NULL constraint works with an added column.
+--
+CREATE TABLE null_check (a INT PRIMARY KEY);
+ | ---
+ | - row_count: 1
+ | ...
+ALTER TABLE null_check ADD b TEXT NULL;
+ | ---
+ | - row_count: 0
+ | ...
+INSERT INTO null_check(a) VALUES (1);
+ | ---
+ | - row_count: 1
+ | ...
+DROP TABLE null_check;
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Check NOT NULL constraint works with an added column.
+--
+CREATE TABLE notnull_check (a INT PRIMARY KEY);
+ | ---
+ | - row_count: 1
+ | ...
+ALTER TABLE notnull_check ADD b TEXT NOT NULL;
+ | ---
+ | - row_count: 0
+ | ...
+INSERT INTO notnull_check(a) VALUES (1);
+ | ---
+ | - null
+ | - 'Failed to execute SQL statement: NOT NULL constraint failed: NOTNULL_CHECK.B'
+ | ...
+DROP TABLE notnull_check;
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Can't add a column with DEAFULT or NULL to a non-empty space.
+-- This ability isn't implemented yet.
+--
+CREATE TABLE non_empty (a INT PRIMARY KEY);
+ | ---
+ | - row_count: 1
+ | ...
+INSERT INTO non_empty VALUES (1);
+ | ---
+ | - row_count: 1
+ | ...
+ALTER TABLE non_empty ADD b INT NULL;
+ | ---
+ | - null
+ | - Tuple field count 1 does not match space field count 2
+ | ...
+ALTER TABLE non_empty ADD b INT DEFAULT (1);
+ | ---
+ | - null
+ | - Tuple field count 1 does not match space field count 2
+ | ...
+DROP TABLE non_empty;
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Add to a no-SQL adjusted space without format.
+--
+\set language lua
+ | ---
+ | - true
+ | ...
+_ = box.schema.space.create('WITHOUT_FORMAT')
+ | ---
+ | ...
+\set language sql
+ | ---
+ | - true
+ | ...
+ALTER TABLE without_format ADD a INT PRIMARY KEY;
+ | ---
+ | - row_count: 0
+ | ...
+INSERT INTO without_format VALUES (1);
+ | ---
+ | - row_count: 1
+ | ...
+DROP TABLE without_format;
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Add to a no-SQL adjusted space with format.
+--
+\set language lua
+ | ---
+ | - true
+ | ...
+with_format = box.schema.space.create('WITH_FORMAT')
+ | ---
+ | ...
+with_format:format{{name = 'A', type = 'unsigned'}}
+ | ---
+ | ...
+\set language sql
+ | ---
+ | - true
+ | ...
+ALTER TABLE with_format ADD b INT PRIMARY KEY;
+ | ---
+ | - row_count: 0
+ | ...
+INSERT INTO with_format VALUES (1, 1);
+ | ---
+ | - row_count: 1
+ | ...
+DROP TABLE with_format;
+ | ---
+ | - row_count: 1
+ | ...
+
+--
+-- Add multiple columns (with a constraint) inside a transaction.
+--
+CREATE TABLE t2 (a INT PRIMARY KEY)
+ | ---
+ | - row_count: 1
+ | ...
+\set language lua
+ | ---
+ | - true
+ | ...
+box.begin()                                                                     \
+box.execute('ALTER TABLE t2 ADD b INT')                                         \
+box.execute('ALTER TABLE t2 ADD c INT UNIQUE')                                  \
+box.commit()
+ | ---
+ | ...
+\set language sql
+ | ---
+ | - true
+ | ...
+INSERT INTO t2 VALUES (1, 1, 1);
+ | ---
+ | - row_count: 1
+ | ...
+INSERT INTO t2 VALUES (2, 1, 1);
+ | ---
+ | - null
+ | - Duplicate key exists in unique index 'unique_unnamed_T2_2' in space 'T2'
+ | ...
+SELECT * FROM t2;
+ | ---
+ | - metadata:
+ |   - name: A
+ |     type: integer
+ |   - name: B
+ |     type: integer
+ |   - name: C
+ |     type: integer
+ |   rows:
+ |   - [1, 1, 1]
+ | ...
+DROP TABLE t2;
+ | ---
+ | - row_count: 1
+ | ...
diff --git a/test/sql/add-column.test.sql b/test/sql/add-column.test.sql
new file mode 100644
index 000000000..f8ab3f756
--- /dev/null
+++ b/test/sql/add-column.test.sql
@@ -0,0 +1,167 @@
+--
+-- gh-3075: Check <ALTER TABLE table ADD COLUMN column> statement.
+--
+CREATE TABLE t1 (a INT PRIMARY KEY);
+
+--
+-- COLUMN keyword is optional. Check it here, but omit it below.
+--
+ALTER TABLE t1 ADD COLUMN b INT;
+
+--
+-- A column with the same name already exists.
+--
+ALTER TABLE t1 ADD b SCALAR;
+
+--
+-- Can't add column to a view.
+--
+CREATE VIEW v AS SELECT * FROM t1;
+ALTER TABLE v ADD b INT;
+DROP VIEW v;
+
+--
+-- Check PRIMARY KEY constraint works with an added column.
+--
+CREATE TABLE pk_check (a INT CONSTRAINT pk PRIMARY KEY);
+ALTER TABLE pk_check DROP CONSTRAINT pk;
+ALTER TABLE pk_check ADD b INT PRIMARY KEY;
+INSERT INTO pk_check VALUES (1, 1);
+INSERT INTO pk_check VALUES (1, 1);
+DROP TABLE pk_check;
+
+--
+-- Check UNIQUE constraint works with an added column.
+--
+CREATE TABLE unique_check (a INT PRIMARY KEY);
+ALTER TABLE unique_check ADD b INT UNIQUE;
+INSERT INTO unique_check VALUES (1, 1);
+INSERT INTO unique_check VALUES (2, 1);
+DROP TABLE unique_check;
+
+--
+-- Check CHECK constraint works with an added column.
+--
+CREATE TABLE ck_check (a INT PRIMARY KEY);
+ALTER TABLE ck_check ADD b INT CHECK (b > 0);
+INSERT INTO ck_check VALUES (1, 0);
+DROP TABLE ck_check;
+
+--
+-- Check FOREIGN KEY constraint works with an added column.
+--
+CREATE TABLE fk_check (a INT PRIMARY KEY);
+ALTER TABLE fk_check ADD b INT REFERENCES t1(a);
+INSERT INTO fk_check VALUES (0, 1);
+INSERT INTO fk_check VALUES (2, 0);
+INSERT INTO fk_check VALUES (2, 1);
+DROP TABLE fk_check;
+DROP TABLE t1;
+--
+-- Check FOREIGN KEY (self-referenced) constraint works with an
+-- added column.
+--
+CREATE TABLE self (id INT PRIMARY KEY AUTOINCREMENT, a INT UNIQUE)
+ALTER TABLE self ADD b INT REFERENCES self(a)
+INSERT INTO self(a,b) VALUES(1, 1);
+UPDATE self SET a = 2, b = 2;
+UPDATE self SET b = 3;
+UPDATE self SET a = 3;
+DROP TABLE self;
+
+--
+-- Check AUTOINCREMENT works with an added column.
+--
+CREATE TABLE autoinc_check (a INT CONSTRAINT pk PRIMARY KEY);
+ALTER TABLE autoinc_check DROP CONSTRAINT pk;
+ALTER TABLE autoinc_check ADD b INT PRIMARY KEY AUTOINCREMENT;
+INSERT INTO autoinc_check(a) VALUES(1);
+INSERT INTO autoinc_check(a) VALUES(1);
+TRUNCATE TABLE autoinc_check;
+
+--
+-- Can't add second column with AUTOINCREMENT.
+--
+ALTER TABLE autoinc_check ADD c INT AUTOINCREMENT;
+DROP TABLE autoinc_check;
+
+--
+-- Check COLLATE clause works with an added column.
+--
+CREATE TABLE collate_check (a INT PRIMARY KEY);
+ALTER TABLE collate_check ADD b TEXT COLLATE "unicode_ci";
+INSERT INTO collate_check VALUES (1, 'a');
+INSERT INTO collate_check VALUES (2, 'A');
+SELECT * FROM collate_check WHERE b LIKE 'a';
+DROP TABLE collate_check;
+
+--
+-- Check DEFAULT clause works with an added column.
+--
+CREATE TABLE default_check (a INT PRIMARY KEY);
+ALTER TABLE default_check ADD b TEXT DEFAULT ('a');
+INSERT INTO default_check(a) VALUES (1);
+SELECT * FROM default_check;
+DROP TABLE default_check;
+
+--
+-- Check NULL constraint works with an added column.
+--
+CREATE TABLE null_check (a INT PRIMARY KEY);
+ALTER TABLE null_check ADD b TEXT NULL;
+INSERT INTO null_check(a) VALUES (1);
+DROP TABLE null_check;
+
+--
+-- Check NOT NULL constraint works with an added column.
+--
+CREATE TABLE notnull_check (a INT PRIMARY KEY);
+ALTER TABLE notnull_check ADD b TEXT NOT NULL;
+INSERT INTO notnull_check(a) VALUES (1);
+DROP TABLE notnull_check;
+
+--
+-- Can't add a column with DEAFULT or NULL to a non-empty space.
+-- This ability isn't implemented yet.
+--
+CREATE TABLE non_empty (a INT PRIMARY KEY);
+INSERT INTO non_empty VALUES (1);
+ALTER TABLE non_empty ADD b INT NULL;
+ALTER TABLE non_empty ADD b INT DEFAULT (1);
+DROP TABLE non_empty;
+
+--
+-- Add to a no-SQL adjusted space without format.
+--
+\set language lua
+_ = box.schema.space.create('WITHOUT_FORMAT')
+\set language sql
+ALTER TABLE without_format ADD a INT PRIMARY KEY;
+INSERT INTO without_format VALUES (1);
+DROP TABLE without_format;
+
+--
+-- Add to a no-SQL adjusted space with format.
+--
+\set language lua
+with_format = box.schema.space.create('WITH_FORMAT')
+with_format:format{{name = 'A', type = 'unsigned'}}
+\set language sql
+ALTER TABLE with_format ADD b INT PRIMARY KEY;
+INSERT INTO with_format VALUES (1, 1);
+DROP TABLE with_format;
+
+--
+-- Add multiple columns (with a constraint) inside a transaction.
+--
+CREATE TABLE t2 (a INT PRIMARY KEY)
+\set language lua
+box.begin()                                                                     \
+box.execute('ALTER TABLE t2 ADD b INT')                                         \
+box.execute('ALTER TABLE t2 ADD c INT UNIQUE')                                  \
+box.commit()
+\set language sql
+INSERT INTO t2 VALUES (1, 1, 1);
+INSERT INTO t2 VALUES (2, 1, 1);
+SELECT * FROM t2;
+DROP TABLE t2;
diff --git a/test/sql/checks.result b/test/sql/checks.result
index 7b18e5d6b..513ed1b62 100644
--- a/test/sql/checks.result
+++ b/test/sql/checks.result
@@ -856,6 +856,26 @@ box.execute("DROP TABLE t6")
 ---
 - row_count: 1
 ...
+--
+-- gh-3075: Check the auto naming of CHECK constraints in
+-- <ALTER TABLE ADD COLUMN>.
+--
+box.execute("CREATE TABLE check_naming (a INT PRIMARY KEY CHECK (a > 0))")
+---
+- row_count: 1
+...
+box.execute("ALTER TABLE check_naming ADD b INT CHECK (b > 0)")
+---
+- row_count: 0
+...
+box.execute("ALTER TABLE check_naming DROP CONSTRAINT \"ck_unnamed_CHECK_NAMING_2\"")
+---
+- row_count: 1
+...
+box.execute("DROP TABLE check_naming")
+---
+- row_count: 1
+...
 test_run:cmd("clear filter")
 ---
 - true
diff --git a/test/sql/checks.test.lua b/test/sql/checks.test.lua
index 301f8ea69..a79131466 100644
--- a/test/sql/checks.test.lua
+++ b/test/sql/checks.test.lua
@@ -280,4 +280,13 @@ box.func.MYFUNC:drop()
 box.execute("INSERT INTO t6 VALUES(11);");
 box.execute("DROP TABLE t6")
 
+--
+-- gh-3075: Check the auto naming of CHECK constraints in
+-- <ALTER TABLE ADD COLUMN>.
+--
+box.execute("CREATE TABLE check_naming (a INT PRIMARY KEY CHECK (a > 0))")
+box.execute("ALTER TABLE check_naming ADD b INT CHECK (b > 0)")
+box.execute("ALTER TABLE check_naming DROP CONSTRAINT \"ck_unnamed_CHECK_NAMING_2\"")
+box.execute("DROP TABLE check_naming")
+
 test_run:cmd("clear filter")
diff --git a/test/sql/foreign-keys.result b/test/sql/foreign-keys.result
index 33689a06e..de2a0c512 100644
--- a/test/sql/foreign-keys.result
+++ b/test/sql/foreign-keys.result
@@ -499,5 +499,33 @@ box.space.S:drop()
 box.space.T:drop()
 ---
 ...
+--
+-- gh-3075: Check the auto naming of FOREIGN KEY constraints in
+-- <ALTER TABLE ADD COLUMN>.
+--
+box.execute("CREATE TABLE t1 (a INT PRIMARY KEY)")
+---
+- row_count: 1
+...
+box.execute("CREATE TABLE check_naming (a INT PRIMARY KEY REFERENCES t1(a))")
+---
+- row_count: 1
+...
+box.execute("ALTER TABLE check_naming ADD b INT REFERENCES t1(a)")
+---
+- row_count: 0
+...
+box.execute("ALTER TABLE check_naming DROP CONSTRAINT \"fk_unnamed_CHECK_NAMING_2\"")
+---
+- row_count: 1
+...
+box.execute("DROP TABLE check_naming")
+---
+- row_count: 1
+...
+box.execute("DROP TABLE t1")
+---
+- row_count: 1
+...
 --- Clean-up SQL DD hash.
 -test_run:cmd('restart server default with cleanup=1')
diff --git a/test/sql/foreign-keys.test.lua b/test/sql/foreign-keys.test.lua
index d2dd88d28..29918c5d4 100644
--- a/test/sql/foreign-keys.test.lua
+++ b/test/sql/foreign-keys.test.lua
@@ -209,5 +209,16 @@ box.space.T:select()
 box.space.S:drop()
 box.space.T:drop()
 
+--
+-- gh-3075: Check the auto naming of FOREIGN KEY constraints in
+-- <ALTER TABLE ADD COLUMN>.
+--
+box.execute("CREATE TABLE t1 (a INT PRIMARY KEY)")
+box.execute("CREATE TABLE check_naming (a INT PRIMARY KEY REFERENCES t1(a))")
+box.execute("ALTER TABLE check_naming ADD b INT REFERENCES t1(a)")
+box.execute("ALTER TABLE check_naming DROP CONSTRAINT \"fk_unnamed_CHECK_NAMING_2\"")
+box.execute("DROP TABLE check_naming")
+box.execute("DROP TABLE t1")
+
 --- Clean-up SQL DD hash.
 -test_run:cmd('restart server default with cleanup=1')

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

* Re: [Tarantool-patches] [PATCH v2 1/2] sql: rename TK_COLUMN to TK_COLUMN_NAME
  2020-04-24 22:55   ` Vladislav Shpilevoy
@ 2020-08-11  0:34     ` Roman Khabibov
  0 siblings, 0 replies; 14+ messages in thread
From: Roman Khabibov @ 2020-08-11  0:34 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Thanks for the review.

> On Apr 25, 2020, at 01:55, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Hi! Thanks for the patch!
> 
>> @@ -5081,7 +5081,7 @@ sqlExprCompare(Expr * pA, Expr * pB, int iTab)
>> 		}
>> 		return 2;
>> 	}
>> -	if (pA->op != TK_COLUMN && pA->op != TK_AGG_COLUMN && pA->u.zToken) {
>> +	if (pA->op != TK_COLUMN_NAME && pA->op != TK_AGG_COLUMN && pA->u.zToken) {
> 
> 1. This is out of 80 symbols. Please, wrap the line.
Fixed.

>> 		if (pA->op == TK_FUNCTION) {
>> 			if (sqlStrICmp(pA->u.zToken, pB->u.zToken) != 0)
>> 				return 2;
>> @@ -5161,7 +5161,7 @@ sqlExprListCompare(ExprList * pA, ExprList * pB, int iTab)
>>  *     pE1: x IS NULL  pE2: x IS NOT NULL    Result: false
>>  *     pE1: x IS ?2    pE2: x IS NOT NULL    Reuslt: false
>>  *
>> - * When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has
>> + * When comparing TK_COLUMN_NAME nodes between pE1 and pE2, if pE2 has
>>  * Expr.iTable<0 then assume a table number given by iTab.
>>  *
>>  * When in doubt, return false.  Returning true might give a performance
>> @@ -5209,11 +5209,11 @@ exprSrcCount(Walker * pWalker, Expr * pExpr)
>> {
>> 	/* The NEVER() on the second term is because sqlFunctionUsesThisSrc()
>> 	 * is always called before sqlExprAnalyzeAggregates() and so the
>> -	 * TK_COLUMNs have not yet been converted into TK_AGG_COLUMN.  If
>> +	 * TK_COLUMN_NAMEs have not yet been converted into TK_AGG_COLUMN.  If
> 
> 2. While you are here, please, remove the multiplied whitespace.
Done.

>> 	 * sqlFunctionUsesThisSrc() is used differently in the future, the
>> 	 * NEVER() will need to be removed.
>> 	 */
>> -	if (pExpr->op == TK_COLUMN || NEVER(pExpr->op == TK_AGG_COLUMN)) {
>> +	if (pExpr->op == TK_COLUMN_NAME || NEVER(pExpr->op == TK_AGG_COLUMN)) {
>> 		int i;
>> 		struct SrcCount *p = pWalker->u.pSrcCount;
>> 		SrcList *pSrc = p->pSrc;

commit ee578dbfb97b09b911f169826a68aafe37d9300b
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Fri Apr 3 16:27:34 2020 +0300

    sql: rename TK_COLUMN to TK_COLUMN_NAME
    
    Rename TK_COLUMN used for tokens treated as a column name to
    TK_COLUMN_NAME. It is needed to allow the typing of COLUMN
    keyword in <ALTER TABLE ADD COLUMN> statement.
    
    Needed for #3075

diff --git a/extra/addopcodes.sh b/extra/addopcodes.sh
index cb6c84725..986c62683 100755
--- a/extra/addopcodes.sh
+++ b/extra/addopcodes.sh
@@ -39,7 +39,7 @@ extras="            \
     END_OF_FILE     \
     UNCLOSED_STRING \
     FUNCTION        \
-    COLUMN          \
+    COLUMN_NAME     \
     AGG_FUNCTION    \
     AGG_COLUMN      \
     UMINUS          \
diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index dd42c8f5f..486b6b30d 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -75,10 +75,7 @@ static Keyword aKeywordTable[] = {
   { "CAST",                   "TK_CAST",        false },
   { "CHECK",                  "TK_CHECK",       true  },
   { "COLLATE",                "TK_COLLATE",     true  },
-  /* gh-3075: Reserved until ALTER ADD COLUMN is implemeneted.
-   * Move it back to ALTER when done.
-   */
-  /* { "COLUMN",              "TK_COLUMNKW",    true  }, */
+  { "COLUMN_NAME",            "TK_COLUMN_NAME", true  },
   { "COLUMN",                 "TK_STANDARD",    true  },
   { "COMMIT",                 "TK_COMMIT",      true  },
   { "CONFLICT",               "TK_CONFLICT",    false },
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 8f6b403b9..619bbf8e3 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -2295,7 +2295,7 @@ index_fill_def(struct Parse *parse, struct index *index,
 			goto cleanup;
 
 		struct Expr *column_expr = sqlExprSkipCollate(expr);
-		if (column_expr->op != TK_COLUMN) {
+		if (column_expr->op != TK_COLUMN_NAME) {
 			diag_set(ClientError, ER_UNSUPPORTED, "Tarantool",
 				 "functional indexes");
 			goto tnt_error;
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index bc2182446..d389b3daf 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -62,7 +62,7 @@ sql_expr_type(struct Expr *pExpr)
 		assert(!ExprHasProperty(pExpr, EP_IntValue));
 		return pExpr->type;
 	case TK_AGG_COLUMN:
-	case TK_COLUMN:
+	case TK_COLUMN_NAME:
 	case TK_TRIGGER:
 		assert(pExpr->iColumn >= 0);
 		return pExpr->space_def->fields[pExpr->iColumn].type;
@@ -262,13 +262,13 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
 			*is_explicit_coll = true;
 			break;
 		}
-		if ((op == TK_AGG_COLUMN || op == TK_COLUMN ||
+		if ((op == TK_AGG_COLUMN || op == TK_COLUMN_NAME ||
 		     op == TK_REGISTER || op == TK_TRIGGER) &&
 		    p->space_def != NULL) {
 			/*
 			 * op==TK_REGISTER && p->space_def!=0
 			 * happens when pExpr was originally
-			 * a TK_COLUMN but was previously
+			 * a TK_COLUMN_NAME but was previously
 			 * evaluated and cached in a register.
 			 */
 			int j = p->iColumn;
@@ -2061,11 +2061,11 @@ exprNodeIsConstant(Walker * pWalker, Expr * pExpr)
 			return WRC_Abort;
 		}
 	case TK_ID:
-	case TK_COLUMN:
+	case TK_COLUMN_NAME:
 	case TK_AGG_FUNCTION:
 	case TK_AGG_COLUMN:
 		testcase(pExpr->op == TK_ID);
-		testcase(pExpr->op == TK_COLUMN);
+		testcase(pExpr->op == TK_COLUMN_NAME);
 		testcase(pExpr->op == TK_AGG_FUNCTION);
 		testcase(pExpr->op == TK_AGG_COLUMN);
 		if (pWalker->eCode == 3 && pExpr->iTable == pWalker->u.iCur) {
@@ -2236,7 +2236,7 @@ sqlExprCanBeNull(const Expr * p)
 	case TK_FLOAT:
 	case TK_BLOB:
 		return 0;
-	case TK_COLUMN:
+	case TK_COLUMN_NAME:
 		assert(p->space_def != 0);
 		return ExprHasProperty(p, EP_CanBeNull) ||
 		       (p->iColumn >= 0
@@ -2267,7 +2267,7 @@ sql_expr_needs_no_type_change(const struct Expr *p, enum field_type type)
 		return type == FIELD_TYPE_STRING;
 	case TK_BLOB:
 		return type == FIELD_TYPE_VARBINARY;
-	case TK_COLUMN:
+	case TK_COLUMN_NAME:
 		/* p cannot be part of a CHECK constraint. */
 		assert(p->iTable >= 0);
 		return p->iColumn < 0 && sql_type_is_numeric(type);
@@ -2324,7 +2324,7 @@ isCandidateForInOpt(Expr * pX)
 	/* All SELECT results must be columns. */
 	for (i = 0; i < pEList->nExpr; i++) {
 		Expr *pRes = pEList->a[i].pExpr;
-		if (pRes->op != TK_COLUMN)
+		if (pRes->op != TK_COLUMN_NAME)
 			return 0;
 		assert(pRes->iTable == pSrc->a[0].iCursor);	/* Not a correlated subquery */
 	}
@@ -3707,10 +3707,10 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				}
 				return target;
 			}
-			/* Otherwise, fall thru into the TK_COLUMN case */
+			/* Otherwise, fall thru into the TK_COLUMN_NAME case */
 			FALLTHROUGH;
 		}
-	case TK_COLUMN:{
+	case TK_COLUMN_NAME:{
 			int iTab = pExpr->iTable;
 			int col = pExpr->iColumn;
 			if (iTab < 0) {
@@ -4102,7 +4102,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 					assert(nFarg == 1);
 					assert(pFarg->a[0].pExpr != 0);
 					exprOp = pFarg->a[0].pExpr->op;
-					if (exprOp == TK_COLUMN
+					if (exprOp == TK_COLUMN_NAME
 					    || exprOp == TK_AGG_COLUMN) {
 						assert(SQL_FUNC_LENGTH ==
 						       OPFLAG_LENGTHARG);
@@ -4319,7 +4319,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			endLabel = sqlVdbeMakeLabel(v);
 			if ((pX = pExpr->pLeft) != 0) {
 				tempX = *pX;
-				testcase(pX->op == TK_COLUMN);
+				testcase(pX->op == TK_COLUMN_NAME);
 				exprToRegister(&tempX,
 					       exprCodeVector(pParse, &tempX,
 							      &regFree1));
@@ -4344,11 +4344,11 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 					pTest = aListelem[i].pExpr;
 				}
 				nextCase = sqlVdbeMakeLabel(v);
-				testcase(pTest->op == TK_COLUMN);
+				testcase(pTest->op == TK_COLUMN_NAME);
 				sqlExprIfFalse(pParse, pTest, nextCase,
 						   SQL_JUMPIFNULL);
 				testcase(aListelem[i + 1].pExpr->op ==
-					 TK_COLUMN);
+					 TK_COLUMN_NAME);
 				sqlExprCode(pParse, aListelem[i + 1].pExpr,
 						target);
 				sqlVdbeGoto(v, endLabel);
@@ -5081,7 +5081,8 @@ sqlExprCompare(Expr * pA, Expr * pB, int iTab)
 		}
 		return 2;
 	}
-	if (pA->op != TK_COLUMN && pA->op != TK_AGG_COLUMN && pA->u.zToken) {
+	if (pA->op != TK_COLUMN_NAME && pA->op != TK_AGG_COLUMN &&
+	    pA->u.zToken) {
 		if (pA->op == TK_FUNCTION) {
 			if (sqlStrICmp(pA->u.zToken, pB->u.zToken) != 0)
 				return 2;
@@ -5161,8 +5162,8 @@ sqlExprListCompare(ExprList * pA, ExprList * pB, int iTab)
  *     pE1: x IS NULL  pE2: x IS NOT NULL    Result: false
  *     pE1: x IS ?2    pE2: x IS NOT NULL    Reuslt: false
  *
- * When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has
- * Expr.iTable<0 then assume a table number given by iTab.
+ * When comparing TK_COLUMN_NAME nodes between pE1 and pE2, if
+ * pE2 has Expr.iTable<0 then assume a table number given by iTab.
  *
  * When in doubt, return false.  Returning true might give a performance
  * improvement.  Returning false might cause a performance reduction, but
@@ -5209,11 +5210,11 @@ exprSrcCount(Walker * pWalker, Expr * pExpr)
 {
 	/* The NEVER() on the second term is because sqlFunctionUsesThisSrc()
 	 * is always called before sqlExprAnalyzeAggregates() and so the
-	 * TK_COLUMNs have not yet been converted into TK_AGG_COLUMN.  If
+	 * TK_COLUMN_NAMEs have not yet been converted into TK_AGG_COLUMN. If
 	 * sqlFunctionUsesThisSrc() is used differently in the future, the
 	 * NEVER() will need to be removed.
 	 */
-	if (pExpr->op == TK_COLUMN || NEVER(pExpr->op == TK_AGG_COLUMN)) {
+	if (pExpr->op == TK_COLUMN_NAME || NEVER(pExpr->op == TK_AGG_COLUMN)) {
 		int i;
 		struct SrcCount *p = pWalker->u.pSrcCount;
 		SrcList *pSrc = p->pSrc;
@@ -5299,9 +5300,9 @@ analyzeAggregate(Walker * pWalker, Expr * pExpr)
 
 	switch (pExpr->op) {
 	case TK_AGG_COLUMN:
-	case TK_COLUMN:{
+	case TK_COLUMN_NAME:{
 			testcase(pExpr->op == TK_AGG_COLUMN);
-			testcase(pExpr->op == TK_COLUMN);
+			testcase(pExpr->op == TK_COLUMN_NAME);
 			/* Check to see if the column is in one of the tables in the FROM
 			 * clause of the aggregate query
 			 */
@@ -5370,7 +5371,7 @@ analyzeAggregate(Walker * pWalker, Expr * pExpr)
 									if (pE->
 									    op
 									    ==
-									    TK_COLUMN
+									    TK_COLUMN_NAME
 									    &&
 									    pE->
 									    iTable
diff --git a/src/box/sql/fk_constraint.c b/src/box/sql/fk_constraint.c
index 482220a95..3f3625ad5 100644
--- a/src/box/sql/fk_constraint.c
+++ b/src/box/sql/fk_constraint.c
@@ -338,7 +338,7 @@ static struct Expr *
 sql_expr_new_column_by_cursor(struct sql *db, struct space_def *def,
 			      int cursor, int column)
 {
-	struct Expr *expr = sql_expr_new_anon(db, TK_COLUMN);
+	struct Expr *expr = sql_expr_new_anon(db, TK_COLUMN_NAME);
 	if (expr == NULL)
 		return NULL;
 	expr->space_def = def;
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 6f625dc18..5fe8ee3f6 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -189,7 +189,7 @@ sqlMatchSpanName(const char *zSpan,
  *    pExpr->space_def     Points to the space_def structure of X.Y
  *                         (even if X and/or Y are implied.)
  *    pExpr->iColumn       Set to the column number within the table.
- *    pExpr->op            Set to TK_COLUMN.
+ *    pExpr->op            Set to TK_COLUMN_NAME.
  *    pExpr->pLeft         Any expression this points to is deleted
  *    pExpr->pRight        Any expression this points to is deleted.
  *
@@ -461,7 +461,7 @@ lookupName(Parse * pParse,	/* The parsing context */
 	pExpr->pLeft = 0;
 	sql_expr_delete(db, pExpr->pRight, false);
 	pExpr->pRight = 0;
-	pExpr->op = (isTrigger ? TK_TRIGGER : TK_COLUMN);
+	pExpr->op = (isTrigger ? TK_TRIGGER : TK_COLUMN_NAME);
  lookupname_end:
 	if (cnt == 1) {
 		assert(pNC != 0);
@@ -485,7 +485,7 @@ struct Expr *
 sql_expr_new_column(struct sql *db, struct SrcList *src_list, int src_idx,
 		    int column)
 {
-	struct Expr *expr = sql_expr_new_anon(db, TK_COLUMN);
+	struct Expr *expr = sql_expr_new_anon(db, TK_COLUMN_NAME);
 	if (expr == NULL)
 		return NULL;
 	struct SrcList_item *item = &src_list->a[src_idx];
@@ -518,7 +518,7 @@ exprProbability(Expr * p)
 /*
  * This routine is callback for sqlWalkExpr().
  *
- * Resolve symbolic names into TK_COLUMN operators for the current
+ * Resolve symbolic names into TK_COLUMN_NAME operators for the current
  * node in the expression tree.  Return 0 to continue the search down
  * the tree or 2 to abort the tree walk.
  *
@@ -1451,7 +1451,7 @@ resolveSelectStep(Walker * pWalker, Select * p)
  *
  * The node at the root of the subtree is modified as follows:
  *
- *    Expr.op        Changed to TK_COLUMN
+ *    Expr.op        Changed to TK_COLUMN_NAME
  *    Expr.pTab      Points to the Table object for X.Y
  *    Expr.iColumn   The column index in X.Y.  -1 for the rowid.
  *    Expr.iTable    The VDBE cursor number for X.Y
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index b0554a172..80fbf69e0 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1817,7 +1817,7 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
 		vdbe_metadata_set_col_nullability(v, i, -1);
 		const char *colname = pEList->a[i].zName;
 		const char *span = pEList->a[i].zSpan;
-		if (p->op == TK_COLUMN || p->op == TK_AGG_COLUMN) {
+		if (p->op == TK_COLUMN_NAME || p->op == TK_AGG_COLUMN) {
 			char *zCol;
 			int iCol = p->iColumn;
 			for (j = 0; ALWAYS(j < pTabList->nSrc); j++) {
@@ -1939,7 +1939,7 @@ sqlColumnsFromExprList(Parse * parse, ExprList * expr_list,
 				pColExpr = pColExpr->pRight;
 				assert(pColExpr != 0);
 			}
-			if (pColExpr->op == TK_COLUMN
+			if (pColExpr->op == TK_COLUMN_NAME
 			    && ALWAYS(pColExpr->space_def != NULL)) {
 				/* For columns use the column name name */
 				int iCol = pColExpr->iColumn;
@@ -3653,7 +3653,7 @@ substExpr(Parse * pParse,	/* Report errors here */
 	sql *db = pParse->db;
 	if (pExpr == 0)
 		return 0;
-	if (pExpr->op == TK_COLUMN && pExpr->iTable == iTable) {
+	if (pExpr->op == TK_COLUMN_NAME && pExpr->iTable == iTable) {
 		if (pExpr->iColumn < 0) {
 			pExpr->op = TK_NULL;
 		} else {
@@ -6044,7 +6044,7 @@ sqlSelect(Parse * pParse,		/* The parser context */
 		/* Create a label to jump to when we want to abort the query */
 		addrEnd = sqlVdbeMakeLabel(v);
 
-		/* Convert TK_COLUMN nodes into TK_AGG_COLUMN and make entries in
+		/* Convert TK_COLUMN_NAME nodes into TK_AGG_COLUMN and make entries in
 		 * sAggInfo for all TK_AGG_FUNCTION nodes in expressions of the
 		 * SELECT statement.
 		 */
@@ -6416,7 +6416,7 @@ sqlSelect(Parse * pParse,		/* The parser context */
 						    flag !=
 						    WHERE_ORDERBY_MIN ? 1 : 0;
 						pMinMax->a[0].pExpr->op =
-						    TK_COLUMN;
+						    TK_COLUMN_NAME;
 					}
 				}
 
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index adf90d824..beb83ce95 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -1517,7 +1517,7 @@ typedef int ynVar;
  * valid.
  *
  * An expression of the form ID or ID.ID refers to a column in a table.
- * For such expressions, Expr.op is set to TK_COLUMN and Expr.iTable is
+ * For such expressions, Expr.op is set to TK_COLUMN_NAME and Expr.iTable is
  * the integer cursor number of a VDBE cursor pointing to that table and
  * Expr.iColumn is the column number for the specific column.  If the
  * expression is used as a result in an aggregate SELECT, then the
@@ -1587,20 +1587,20 @@ struct Expr {
 #if SQL_MAX_EXPR_DEPTH>0
 	int nHeight;		/* Height of the tree headed by this node */
 #endif
-	int iTable;		/* TK_COLUMN: cursor number of table holding column
+	int iTable;		/* TK_COLUMN_NAME: cursor number of table holding column
 				 * TK_REGISTER: register number
 				 * TK_TRIGGER: 1 -> new, 0 -> old
 				 * EP_Unlikely:  134217728 times likelihood
 				 * TK_SELECT: 1st register of result vector
 				 */
-	ynVar iColumn;		/* TK_COLUMN: column index.
+	ynVar iColumn;		/* TK_COLUMN_NAME: column index.
 				 * TK_VARIABLE: variable number (always >= 1).
 				 * TK_SELECT_COLUMN: column of the result vector
 				 */
 	i16 iAgg;		/* Which entry in pAggInfo->aCol[] or ->aFunc[] */
 	i16 iRightJoinTable;	/* If EP_FromJoin, the right table of the join */
 	u8 op2;			/* TK_REGISTER: original value of Expr.op
-				 * TK_COLUMN: the value of p5 for OP_Column
+				 * TK_COLUMN_NAME: the value of p5 for OP_Column
 				 * TK_AGG_FUNCTION: nesting depth
 				 */
 	AggInfo *pAggInfo;	/* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */
diff --git a/src/box/sql/treeview.c b/src/box/sql/treeview.c
index a04597979..ea26fcf6d 100644
--- a/src/box/sql/treeview.c
+++ b/src/box/sql/treeview.c
@@ -327,7 +327,7 @@ sqlTreeViewExpr(TreeView * pView, const Expr * pExpr, u8 moreToFollow)
 					    zFlgs);
 			break;
 		}
-	case TK_COLUMN:{
+	case TK_COLUMN_NAME:{
 			if (pExpr->iTable < 0) {
 				/* This only happens when coding check constraints */
 				sqlTreeViewLine(pView, "COLUMN(%d)%s",
diff --git a/src/box/sql/where.c b/src/box/sql/where.c
index e9e936856..77f863b0e 100644
--- a/src/box/sql/where.c
+++ b/src/box/sql/where.c
@@ -276,7 +276,7 @@ whereScanNext(WhereScan * pScan)
 						sqlExprSkipCollate(pTerm->
 								       pExpr->
 								       pRight))->
-					    op == TK_COLUMN) {
+					    op == TK_COLUMN_NAME) {
 						int j;
 						for (j = 0; j < pScan->nEquiv; j++) {
 							if (pScan->aiCur[j] == pX->iTable
@@ -319,7 +319,7 @@ whereScanNext(WhereScan * pScan)
 							}
 						}
 						if ((pTerm->eOperator & WO_EQ) != 0
-						    && (pX = pTerm->pExpr->pRight)->op == TK_COLUMN
+						    && (pX = pTerm->pExpr->pRight)->op == TK_COLUMN_NAME
 						    && pX->iTable == pScan->aiCur[0]
 						    && pX->iColumn == pScan->aiColumn[0]) {
 							continue;
@@ -555,7 +555,7 @@ findIndexCol(Parse * pParse,	/* Parse context */
 	struct key_part *part_to_match = &idx_def->key_def->parts[iCol];
 	for (int i = 0; i < pList->nExpr; i++) {
 		Expr *p = sqlExprSkipCollate(pList->a[i].pExpr);
-		if (p->op == TK_COLUMN && p->iTable == iBase &&
+		if (p->op == TK_COLUMN_NAME && p->iTable == iBase &&
 		    p->iColumn == (int) part_to_match->fieldno) {
 			bool is_found;
 			uint32_t id;
@@ -601,7 +601,8 @@ isDistinctRedundant(Parse * pParse,		/* Parsing context */
 	 */
 	for (int i = 0; i < pDistinct->nExpr; i++) {
 		Expr *p = sqlExprSkipCollate(pDistinct->a[i].pExpr);
-		if (p->op == TK_COLUMN && p->iTable == iBase && p->iColumn < 0)
+		if (p->op == TK_COLUMN_NAME && p->iTable == iBase &&
+		    p->iColumn < 0)
 			return 1;
 	}
 	if (space == NULL)
@@ -2245,7 +2246,7 @@ whereRangeVectorLen(Parse * pParse,	/* Parsing context */
 		 * leftmost index column.
 		 */
 		struct key_part *parts = idx_def->key_def->parts;
-		if (pLhs->op != TK_COLUMN || pLhs->iTable != iCur ||
+		if (pLhs->op != TK_COLUMN_NAME || pLhs->iTable != iCur ||
 		    pLhs->iColumn != (int)parts[i + nEq].fieldno ||
 		    parts[i + nEq].sort_order != parts[nEq].sort_order)
 			break;
@@ -2677,7 +2678,7 @@ indexMightHelpWithOrderBy(WhereLoopBuilder * pBuilder,
 		return 0;
 	for (ii = 0; ii < pOB->nExpr; ii++) {
 		Expr *pExpr = sqlExprSkipCollate(pOB->a[ii].pExpr);
-		if (pExpr->op == TK_COLUMN && pExpr->iTable == iCursor) {
+		if (pExpr->op == TK_COLUMN_NAME && pExpr->iTable == iCursor) {
 			if (pExpr->iColumn < 0)
 				return 1;
 			for (jj = 0; jj < part_count; jj++) {
@@ -3213,7 +3214,7 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 			if (MASKBIT(i) & obSat)
 				continue;
 			pOBExpr = sqlExprSkipCollate(pOrderBy->a[i].pExpr);
-			if (pOBExpr->op != TK_COLUMN)
+			if (pOBExpr->op != TK_COLUMN_NAME)
 				continue;
 			if (pOBExpr->iTable != iCur)
 				continue;
@@ -3361,7 +3362,8 @@ wherePathSatisfiesOrderBy(WhereInfo * pWInfo,	/* The WHERE clause */
 					if ((wctrlFlags & (WHERE_GROUPBY | WHERE_DISTINCTBY)) == 0)
 						bOnce = 0;
 					if (iColumn >= (-1)) {
-						if (pOBExpr->op != TK_COLUMN)
+						if (pOBExpr->op !=
+						    TK_COLUMN_NAME)
 							continue;
 						if (pOBExpr->iTable != iCur)
 							continue;
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index d9b5c78f5..c2ce4339f 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -279,7 +279,7 @@ like_optimization_is_valid(Parse *pParse, Expr *pExpr, Expr **ppPrefix,
 	pList = pExpr->x.pList;
 	pLeft = pList->a[1].pExpr;
 	/* Value might be numeric */
-	if (pLeft->op != TK_COLUMN ||
+	if (pLeft->op != TK_COLUMN_NAME ||
 	    sql_expr_type(pLeft) != FIELD_TYPE_STRING) {
 		/* IMP: R-02065-49465 The left-hand side of the
 		 * LIKE operator must be the name of an indexed
@@ -928,7 +928,7 @@ exprSelectUsage(WhereMaskSet * pMaskSet, Select * pS)
  * number of the table that is indexed and *piColumn to the column number
  * of the column that is indexed.
  *
- * If pExpr is a TK_COLUMN column reference, then this routine always returns
+ * If pExpr is a TK_COLUMN_NAME column reference, then this routine always returns
  * true even if that particular column is not indexed, because the column
  * might be added to an automatic index later.
  */
@@ -950,7 +950,7 @@ exprMightBeIndexed(int op,	/* The specific comparison operator */
 		pExpr = pExpr->x.pList->a[0].pExpr;
 	}
 
-	if (pExpr->op == TK_COLUMN) {
+	if (pExpr->op == TK_COLUMN_NAME) {
 		*piCur = pExpr->iTable;
 		*piColumn = pExpr->iColumn;
 		return 1;
@@ -1272,7 +1272,7 @@ exprAnalyze(SrcList * pSrc,	/* the FROM clause */
 	 * Note that the virtual term must be tagged with TERM_VNULL.
 	 */
 	if (pExpr->op == TK_NOTNULL
-	    && pExpr->pLeft->op == TK_COLUMN
+	    && pExpr->pLeft->op == TK_COLUMN_NAME
 	    && pExpr->pLeft->iColumn >= 0) {
 		Expr *pNewExpr;
 		Expr *pLeft = pExpr->pLeft;
@@ -1397,7 +1397,7 @@ sqlWhereExprUsage(WhereMaskSet * pMaskSet, Expr * p)
 	Bitmask mask;
 	if (p == 0)
 		return 0;
-	if (p->op == TK_COLUMN) {
+	if (p->op == TK_COLUMN_NAME) {
 		mask = sqlWhereGetMask(pMaskSet, p->iTable);
 		return mask;
 	}
@@ -1475,7 +1475,7 @@ sqlWhereTabFuncArgs(Parse * pParse,	/* Parsing context */
 		 * unused.
 		 */
 		assert(k < (int)space_def->field_count);
-		pColRef = sql_expr_new_anon(pParse->db, TK_COLUMN);
+		pColRef = sql_expr_new_anon(pParse->db, TK_COLUMN_NAME);
 		if (pColRef == NULL) {
 			pParse->is_aborted = true;
 			return;

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

* Re: [Tarantool-patches] [PATCH v2 2/2] sql: support column addition
  2020-08-11  0:34         ` Roman Khabibov
@ 2020-08-19 22:20           ` Vladislav Shpilevoy
  2020-09-11 21:51             ` Roman Khabibov
  0 siblings, 1 reply; 14+ messages in thread
From: Vladislav Shpilevoy @ 2020-08-19 22:20 UTC (permalink / raw)
  To: Roman Khabibov; +Cc: tarantool-patches

Hi! Thanks for the patch!

>>> +	parser->has_autoinc = false;
>>> 	region_create(&parser->region, &cord()->slabc);
>>> }
>>>
>>> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
>>> index aa6a470f8..3143ec521 100644
>>> --- a/src/box/sql/sqlInt.h
>>> +++ b/src/box/sql/sqlInt.h
>>> @@ -2249,12 +2249,26 @@ struct Parse {
>>> 		struct enable_entity_def enable_entity_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).
>>> +	 * Table def or column def is not part of union since
>>> +	 * information being held must survive till the end of
>>> +	 * parsing of whole <CREATE TABLE> or
>>> +	 * <ALTER TABLE ADD COLUMN> statement (to pass it to
>>> +	 * sqlEndTable() sql_create_column_end() function).
>>> 	 */
>>> 	struct create_table_def create_table_def;
>>> +	struct create_column_def create_column_def;
>>> +	/**
>>> +	 * FK and CK constraints appeared in a <CREATE TABLE> or
>>> +	 * a <ALTER TABLE ADD COLUMN> statement.
>>> +	 */
>>> +	struct rlist fkeys;
>>> +	struct rlist checks;
>>> +	uint32_t fkey_count;
>>> +	uint32_t check_count;
>>> +	/** True, if column to be created has <AUTOINCREMENT>. */
>>> +	bool has_autoinc;
>>
>> 27. What column? This is struct Parse, it is not a column.
> I know, but I haven't come up with anything better.

Why can't autoinc field be moved into create_column_def, since this is
related to only one column?

Can you move struct rlist fkeys and uint32_t fkey_count into a new struct
like 'struct create_fkeys_def;', and the same for checks?

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

* Re: [Tarantool-patches] [PATCH v2 2/2] sql: support column addition
  2020-08-19 22:20           ` Vladislav Shpilevoy
@ 2020-09-11 21:51             ` Roman Khabibov
  0 siblings, 0 replies; 14+ messages in thread
From: Roman Khabibov @ 2020-09-11 21:51 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches


> On Aug 20, 2020, at 01:20, Vladislav Shpilevoy <v.shpilevoy@tarantool.org> wrote:
> 
> Hi! Thanks for the patch!
> 
>>>> +	parser->has_autoinc = false;
>>>> 	region_create(&parser->region, &cord()->slabc);
>>>> }
>>>> 
>>>> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
>>>> index aa6a470f8..3143ec521 100644
>>>> --- a/src/box/sql/sqlInt.h
>>>> +++ b/src/box/sql/sqlInt.h
>>>> @@ -2249,12 +2249,26 @@ struct Parse {
>>>> 		struct enable_entity_def enable_entity_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).
>>>> +	 * Table def or column def is not part of union since
>>>> +	 * information being held must survive till the end of
>>>> +	 * parsing of whole <CREATE TABLE> or
>>>> +	 * <ALTER TABLE ADD COLUMN> statement (to pass it to
>>>> +	 * sqlEndTable() sql_create_column_end() function).
>>>> 	 */
>>>> 	struct create_table_def create_table_def;
>>>> +	struct create_column_def create_column_def;
>>>> +	/**
>>>> +	 * FK and CK constraints appeared in a <CREATE TABLE> or
>>>> +	 * a <ALTER TABLE ADD COLUMN> statement.
>>>> +	 */
>>>> +	struct rlist fkeys;
>>>> +	struct rlist checks;
>>>> +	uint32_t fkey_count;
>>>> +	uint32_t check_count;
>>>> +	/** True, if column to be created has <AUTOINCREMENT>. */
>>>> +	bool has_autoinc;
>>> 
>>> 27. What column? This is struct Parse, it is not a column.
>> I know, but I haven't come up with anything better.
> 
> Why can't autoinc field be moved into create_column_def, since this is
> related to only one column?
Basically to make the current code reusable. The opcode for _sequence is
emitted at the end of table creation. All create_column_def already
worked at this point. We should know number of field with autoincrement.

> Can you move struct rlist fkeys and uint32_t fkey_count into a new struct
> like 'struct create_fkeys_def;', and the same for checks?
Yes.

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

* Re: [Tarantool-patches] [PATCH v2 0/2] Support column addition with <ALTER TABLE>
  2020-04-03 15:27 [Tarantool-patches] [PATCH v2 0/2] Support column addition with <ALTER TABLE> Roman Khabibov
                   ` (2 preceding siblings ...)
  2020-04-03 17:40 ` [Tarantool-patches] [PATCH v2 0/2] Support column addition with <ALTER TABLE> Roman Khabibov
@ 2020-11-18 17:23 ` Alexander V. Tikhonov
  2020-11-18 23:07   ` Nikita Pettik
  3 siblings, 1 reply; 14+ messages in thread
From: Alexander V. Tikhonov @ 2020-11-18 17:23 UTC (permalink / raw)
  To: Roman Khabibov; +Cc: tarantool-patches

Hi Roman, thanks for the patchset, as I see no new degradations found
in gitlab-ci testing commit criteria pipeline [1]. Test jobs based on
opensuse failed because of external issues and already checked at [2],
patch LGTM.

[1] - https://gitlab.com/tarantool/tarantool/-/pipelines/218019328
[2] - https://gitlab.com/tarantool/tarantool/-/pipelines/217893488

On Fri, Apr 03, 2020 at 06:27:50PM +0300, Roman Khabibov wrote:
> According to the SQL standard, column definition in a
> <CREATE TABLE> and a <ALTER TABLE ADD COLUMN> is the same. So, I
> decided to support Tarantool features like AUTOINCREMENT in the
> alter column description.
> 
> Roman Khabibov (2):
>   sql: rename TK_COLUMN to TK_COLUMN_NAME
>   sql: support column addition
> 
>  extra/addopcodes.sh          |   2 +-
>  extra/mkkeywordhash.c        |   5 +-
>  src/box/errcode.h            |   2 +
>  src/box/schema_def.h         |   1 +
>  src/box/sql/alter.c          |   1 +
>  src/box/sql/build.c          | 596 +++++++++++++++++++++++------------
>  src/box/sql/expr.c           |  42 +--
>  src/box/sql/fk_constraint.c  |   2 +-
>  src/box/sql/parse.y          |  45 ++-
>  src/box/sql/parse_def.h      |  62 ++--
>  src/box/sql/prepare.c        |   8 +-
>  src/box/sql/resolve.c        |  10 +-
>  src/box/sql/select.c         |  10 +-
>  src/box/sql/sqlInt.h         |  58 +++-
>  src/box/sql/treeview.c       |   2 +-
>  src/box/sql/where.c          |  18 +-
>  src/box/sql/whereexpr.c      |  12 +-
>  test/box/error.result        |   2 +
>  test/sql/add-column.result   | 231 ++++++++++++++
>  test/sql/add-column.test.lua |  87 +++++
>  20 files changed, 892 insertions(+), 304 deletions(-)
>  create mode 100644 test/sql/add-column.result
>  create mode 100644 test/sql/add-column.test.lua
> 
> -- 
> 2.21.0 (Apple Git-122)
> 

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

* Re: [Tarantool-patches] [PATCH v2 0/2] Support column addition with <ALTER TABLE>
  2020-11-18 17:23 ` Alexander V. Tikhonov
@ 2020-11-18 23:07   ` Nikita Pettik
  0 siblings, 0 replies; 14+ messages in thread
From: Nikita Pettik @ 2020-11-18 23:07 UTC (permalink / raw)
  To: Alexander V. Tikhonov; +Cc: tarantool-patches

On 18 Nov 20:23, Alexander V. Tikhonov wrote:
> Hi Roman, thanks for the patchset, as I see no new degradations found
> in gitlab-ci testing commit criteria pipeline [1]. Test jobs based on
> opensuse failed because of external issues and already checked at [2],
> patch LGTM.

Pushed to master, changelog for 2.7 release is added, branch is dropped.
GJ
 
> [1] - https://gitlab.com/tarantool/tarantool/-/pipelines/218019328
> [2] - https://gitlab.com/tarantool/tarantool/-/pipelines/217893488
> 
> On Fri, Apr 03, 2020 at 06:27:50PM +0300, Roman Khabibov wrote:
> > According to the SQL standard, column definition in a
> > <CREATE TABLE> and a <ALTER TABLE ADD COLUMN> is the same. So, I
> > decided to support Tarantool features like AUTOINCREMENT in the
> > alter column description.
> > 
> > Roman Khabibov (2):
> >   sql: rename TK_COLUMN to TK_COLUMN_NAME
> >   sql: support column addition
> > 
> >  extra/addopcodes.sh          |   2 +-
> >  extra/mkkeywordhash.c        |   5 +-
> >  src/box/errcode.h            |   2 +
> >  src/box/schema_def.h         |   1 +
> >  src/box/sql/alter.c          |   1 +
> >  src/box/sql/build.c          | 596 +++++++++++++++++++++++------------
> >  src/box/sql/expr.c           |  42 +--
> >  src/box/sql/fk_constraint.c  |   2 +-
> >  src/box/sql/parse.y          |  45 ++-
> >  src/box/sql/parse_def.h      |  62 ++--
> >  src/box/sql/prepare.c        |   8 +-
> >  src/box/sql/resolve.c        |  10 +-
> >  src/box/sql/select.c         |  10 +-
> >  src/box/sql/sqlInt.h         |  58 +++-
> >  src/box/sql/treeview.c       |   2 +-
> >  src/box/sql/where.c          |  18 +-
> >  src/box/sql/whereexpr.c      |  12 +-
> >  test/box/error.result        |   2 +
> >  test/sql/add-column.result   | 231 ++++++++++++++
> >  test/sql/add-column.test.lua |  87 +++++
> >  20 files changed, 892 insertions(+), 304 deletions(-)
> >  create mode 100644 test/sql/add-column.result
> >  create mode 100644 test/sql/add-column.test.lua
> > 
> > -- 
> > 2.21.0 (Apple Git-122)
> > 

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

end of thread, other threads:[~2020-11-18 23:07 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-04-03 15:27 [Tarantool-patches] [PATCH v2 0/2] Support column addition with <ALTER TABLE> Roman Khabibov
2020-04-03 15:27 ` [Tarantool-patches] [PATCH v2 1/2] sql: rename TK_COLUMN to TK_COLUMN_NAME Roman Khabibov
2020-04-24 22:55   ` Vladislav Shpilevoy
2020-08-11  0:34     ` Roman Khabibov
2020-04-03 15:27 ` [Tarantool-patches] [PATCH v2 2/2] sql: support column addition Roman Khabibov
2020-04-24 22:56   ` Vladislav Shpilevoy
2020-07-06 13:37     ` Roman Khabibov
2020-07-12 16:45       ` Vladislav Shpilevoy
2020-08-11  0:34         ` Roman Khabibov
2020-08-19 22:20           ` Vladislav Shpilevoy
2020-09-11 21:51             ` Roman Khabibov
2020-04-03 17:40 ` [Tarantool-patches] [PATCH v2 0/2] Support column addition with <ALTER TABLE> Roman Khabibov
2020-11-18 17:23 ` Alexander V. Tikhonov
2020-11-18 23:07   ` Nikita Pettik

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