Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH 0/6] sql: extend response metadata
@ 2019-11-27 12:15 Nikita Pettik
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 1/6] sql: refactor resulting set metadata Nikita Pettik
                   ` (6 more replies)
  0 siblings, 7 replies; 40+ messages in thread
From: Nikita Pettik @ 2019-11-27 12:15 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

Branch: https://github.com/tarantool/tarantool/tree/np/gh-4407-extend-sql-metadata
Issue: https://github.com/tarantool/tarantool/issues/4407

This patch-set extends metadata with collation, nullability,
autoincrement and alias properties of resulting set columns.
Quite straightforward implementation.

Nikita Pettik (6):
  sql: refactor resulting set metadata
  sql: fix possible null dereference in sql_expr_coll()
  sql: extend result set with collation
  sql: extend result set with nullability
  sql: extend result set with autoincrement
  sql: extend result set with alias

 src/box/execute.c                                |  65 +++-
 src/box/iproto_constants.h                       |   4 +
 src/box/lua/execute.c                            |  22 +-
 src/box/lua/net_box.c                            |  33 +-
 src/box/sql/delete.c                             |   6 +-
 src/box/sql/expr.c                               |   3 +-
 src/box/sql/insert.c                             |   5 +-
 src/box/sql/legacy.c                             |   2 +-
 src/box/sql/pragma.c                             |  14 +-
 src/box/sql/prepare.c                            |   9 +-
 src/box/sql/select.c                             |  95 +++--
 src/box/sql/sqlInt.h                             |  12 +
 src/box/sql/update.c                             |   6 +-
 src/box/sql/vdbe.h                               |  41 ++-
 src/box/sql/vdbeInt.h                            |  17 +-
 src/box/sql/vdbeapi.c                            | 103 +++---
 src/box/sql/vdbeaux.c                            | 126 +++++--
 test/box/sql-update-with-nested-select.result    |   5 +-
 test/sql-tap/badutf1.test.lua                    |  46 +--
 test/sql-tap/colname.test.lua                    |  16 +-
 test/sql-tap/lua/sqltester.lua                   |  29 ++
 test/sql-tap/select1.test.lua                    |  18 +-
 test/sql-tap/select4.test.lua                    |   4 +-
 test/sql-tap/view.test.lua                       |   2 +-
 test/sql/bind.result                             |  15 +-
 test/sql/boolean.result                          | 431 ++++++++++++++---------
 test/sql/check-clear-ephemeral.result            |   5 +-
 test/sql/collation.result                        | 241 ++++++++-----
 test/sql/gh-3199-no-mem-leaks.result             | 120 ++++---
 test/sql/gh2141-delete-trigger-drop-table.result |  20 +-
 test/sql/gh2251-multiple-update.result           |  10 +-
 test/sql/iproto.result                           | 110 +++---
 test/sql/misc.result                             |  25 +-
 test/sql/on-conflict.result                      |  20 +-
 test/sql/persistency.result                      | 190 ++++++----
 test/sql/row-count.result                        |  25 +-
 test/sql/sql-debug.result                        |  15 +-
 test/sql/transition.result                       | 190 ++++++----
 test/sql/types.result                            | 105 +++---
 test/sql/update-with-nested-select.result        |   5 +-
 40 files changed, 1376 insertions(+), 834 deletions(-)

-- 
2.15.1

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

* [Tarantool-patches] [PATCH 1/6] sql: refactor resulting set metadata
  2019-11-27 12:15 [Tarantool-patches] [PATCH 0/6] sql: extend response metadata Nikita Pettik
@ 2019-11-27 12:15 ` Nikita Pettik
  2019-11-28 22:41   ` Vladislav Shpilevoy
  2019-12-17 13:23   ` Sergey Ostanevich
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 2/6] sql: fix possible null dereference in sql_expr_coll() Nikita Pettik
                   ` (5 subsequent siblings)
  6 siblings, 2 replies; 40+ messages in thread
From: Nikita Pettik @ 2019-11-27 12:15 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

Move names and types of resulting set to a separate structure. Simplify
their storage by introducing separate members for name and type
(previously names and types were stored in one char * array). It will
allow us to add new metadata properties with ease.

Needed for #4407
---
 src/box/sql/delete.c  |  6 ++--
 src/box/sql/insert.c  |  5 ++--
 src/box/sql/legacy.c  |  2 +-
 src/box/sql/pragma.c  | 14 ++++-----
 src/box/sql/prepare.c |  9 +++---
 src/box/sql/select.c  | 60 ++++++++++++++++++--------------------
 src/box/sql/update.c  |  6 ++--
 src/box/sql/vdbe.h    | 28 ++++++++++--------
 src/box/sql/vdbeInt.h |  8 ++++-
 src/box/sql/vdbeapi.c | 81 +++++++++------------------------------------------
 src/box/sql/vdbeaux.c | 81 +++++++++++++++++++++++++++------------------------
 11 files changed, 124 insertions(+), 176 deletions(-)

diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
index 91c2157ac..31570099b 100644
--- a/src/box/sql/delete.c
+++ b/src/box/sql/delete.c
@@ -418,10 +418,8 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
 	    parse->triggered_space != NULL) {
 		sqlVdbeAddOp2(v, OP_ResultRow, reg_count, 1);
 		sqlVdbeSetNumCols(v, 1);
-		sqlVdbeSetColName(v, 0, COLNAME_NAME, "rows deleted",
-				      SQL_STATIC);
-		sqlVdbeSetColName(v, 0, COLNAME_DECLTYPE, "integer",
-				  SQL_STATIC);
+		vdbe_set_metadata_col_name(v, 0, "rows deleted");
+		vdbe_set_metadata_col_type(v, 0, "integer");
 	}
 
  delete_from_cleanup:
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 70504c800..9be9c191d 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -785,9 +785,8 @@ sqlInsert(Parse * pParse,	/* Parser context */
 			column_name = "rows replaced";
 		else
 			column_name = "rows inserted";
-		sqlVdbeSetColName(v, 0, COLNAME_NAME, column_name, SQL_STATIC);
-		sqlVdbeSetColName(v, 0, COLNAME_DECLTYPE, "integer",
-				  SQL_STATIC);
+		vdbe_set_metadata_col_name(v, 0, column_name);
+		vdbe_set_metadata_col_type(v, 0, "integer");
 	}
 
  insert_cleanup:
diff --git a/src/box/sql/legacy.c b/src/box/sql/legacy.c
index 0b1370f4a..ee58f1eb7 100644
--- a/src/box/sql/legacy.c
+++ b/src/box/sql/legacy.c
@@ -103,7 +103,7 @@ sql_exec(sql * db,	/* The database on which the SQL executes */
 						    (char *)
 						    sql_column_name(pStmt,
 									i);
-						/* sqlVdbeSetColName() installs column names as UTF8
+						/* vdbe_set_metadata_col_name() installs column names as UTF8
 						 * strings so there is no way for sql_column_name() to fail.
 						 */
 						assert(azCols[i] != 0);
diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
index 92bcf4e68..874eb93d2 100644
--- a/src/box/sql/pragma.c
+++ b/src/box/sql/pragma.c
@@ -120,10 +120,8 @@ vdbe_set_pragma_result_columns(struct Vdbe *v, const struct PragmaName *pragma)
 	assert(n > 0);
 	sqlVdbeSetNumCols(v, n);
 	for (int i = 0, j = pragma->iPragCName; i < n; ++i) {
-		sqlVdbeSetColName(v, i, COLNAME_NAME, pragCName[j++],
-				  SQL_STATIC);
-		sqlVdbeSetColName(v, i, COLNAME_DECLTYPE, pragCName[j++],
-				  SQL_STATIC);
+		vdbe_set_metadata_col_name(v, i, pragCName[j++]);
+		vdbe_set_metadata_col_type(v, i, pragCName[j++]);
 	}
 }
 
@@ -168,10 +166,10 @@ vdbe_emit_pragma_status(struct Parse *parse)
 	struct session *user_session = current_session();
 
 	sqlVdbeSetNumCols(v, 2);
-	sqlVdbeSetColName(v, 0, COLNAME_NAME, "pragma_name", SQL_STATIC);
-	sqlVdbeSetColName(v, 0, COLNAME_DECLTYPE, "text", SQL_STATIC);
-	sqlVdbeSetColName(v, 1, COLNAME_NAME, "pragma_value", SQL_STATIC);
-	sqlVdbeSetColName(v, 1, COLNAME_DECLTYPE, "integer", SQL_STATIC);
+	vdbe_set_metadata_col_name(v, 0, "pragma_name");
+	vdbe_set_metadata_col_type(v, 0, "text");
+	vdbe_set_metadata_col_name(v, 1, "pragma_value");
+	vdbe_set_metadata_col_type(v, 1, "integer");
 
 	parse->nMem = 2;
 	for (int i = 0; i < ArraySize(aPragmaName); ++i) {
diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index 0ecc676e2..2d3466cc7 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -146,11 +146,10 @@ sqlPrepare(sql * db,	/* Database handle. */
 		sqlVdbeSetNumCols(sParse.pVdbe, name_count);
 		for (int i = 0; i < name_count; i++) {
 			int name_index = 2 * i + name_first;
-			sqlVdbeSetColName(sParse.pVdbe, i, COLNAME_NAME,
-					  azColName[name_index], SQL_STATIC);
-			sqlVdbeSetColName(sParse.pVdbe, i, COLNAME_DECLTYPE,
-					  azColName[name_index + 1],
-					  SQL_STATIC);
+			vdbe_set_metadata_col_name(sParse.pVdbe, i,
+					  azColName[name_index]);
+			vdbe_set_metadata_col_type(sParse.pVdbe, i,
+					  azColName[name_index + 1]);
 		}
 	}
 
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 8f93edd16..d6b8a158f 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1747,15 +1747,18 @@ generateSortTail(Parse * pParse,	/* Parsing context */
 	sqlVdbeResolveLabel(v, addrBreak);
 }
 
-/*
+/**
  * Generate code that will tell the VDBE the names of columns
- * in the result set.  This information is used to provide the
- * azCol[] values in the callback.
+ * in the result set. This information is used to provide the
+ * metadata during/after statement execution.
+ *
+ * @param pParse Parsing context.
+ * @param pTabList List of tables.
+ * @param pEList Expressions defining the result set.
  */
 static void
-generateColumnNames(Parse * pParse,	/* Parser context */
-		    SrcList * pTabList,	/* List of tables */
-		    ExprList * pEList)	/* Expressions defining the result set */
+generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
+			 struct ExprList *pEList)
 {
 	Vdbe *v = pParse->pVdbe;
 	int i, j;
@@ -1789,12 +1792,11 @@ generateColumnNames(Parse * pParse,	/* Parser context */
 			continue;
 		if (p->op == TK_VARIABLE)
 			var_pos[var_count++] = i;
-		sqlVdbeSetColName(v, i, COLNAME_DECLTYPE,
-				  field_type_strs[sql_expr_type(p)], SQL_TRANSIENT);
+		vdbe_set_metadata_col_type(v, i,
+					   field_type_strs[sql_expr_type(p)]);
 		if (pEList->a[i].zName) {
 			char *zName = pEList->a[i].zName;
-			sqlVdbeSetColName(v, i, COLNAME_NAME, zName,
-					      SQL_TRANSIENT);
+			vdbe_set_metadata_col_name(v, i, zName);
 		} else if (p->op == TK_COLUMN || p->op == TK_AGG_COLUMN) {
 			char *zCol;
 			int iCol = p->iColumn;
@@ -1807,27 +1809,21 @@ generateColumnNames(Parse * pParse,	/* Parser context */
 			assert(iCol >= 0 && iCol < (int)space_def->field_count);
 			zCol = space_def->fields[iCol].name;
 			if (!shortNames && !fullNames) {
-				sqlVdbeSetColName(v, i, COLNAME_NAME,
-						      sqlDbStrDup(db,
-								      pEList->a[i].zSpan),
-						      SQL_DYNAMIC);
+				vdbe_set_metadata_col_name(v, i,
+							   pEList->a[i].zSpan);
 			} else if (fullNames) {
-				char *zName = 0;
-				zName = sqlMPrintf(db, "%s.%s",
-						       space_def->name, zCol);
-				sqlVdbeSetColName(v, i, COLNAME_NAME, zName,
-						      SQL_DYNAMIC);
+				const char *zName = tt_sprintf("%s.%s",
+							       space_def->name,
+							       zCol);
+				vdbe_set_metadata_col_name(v, i, zName);
 			} else {
-				sqlVdbeSetColName(v, i, COLNAME_NAME, zCol,
-						      SQL_TRANSIENT);
+				vdbe_set_metadata_col_name(v, i, zCol);
 			}
 		} else {
 			const char *z = pEList->a[i].zSpan;
-			z = z == 0 ? sqlMPrintf(db, "column%d",
-						    i + 1) : sqlDbStrDup(db,
-									     z);
-			sqlVdbeSetColName(v, i, COLNAME_NAME, z,
-					      SQL_DYNAMIC);
+			if (z == NULL)
+				z = tt_sprintf("column%d", i + 1);
+			vdbe_set_metadata_col_name(v, i, z);
 		}
 	}
 	if (var_count == 0)
@@ -2828,7 +2824,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
 						Select *pFirst = p;
 						while (pFirst->pPrior)
 							pFirst = pFirst->pPrior;
-						generateColumnNames(pParse,
+						generate_column_metadata(pParse,
 								    pFirst->pSrc,
 								    pFirst->pEList);
 					}
@@ -2927,9 +2923,9 @@ multiSelect(Parse * pParse,	/* Parsing context */
 					Select *pFirst = p;
 					while (pFirst->pPrior)
 						pFirst = pFirst->pPrior;
-					generateColumnNames(pParse,
-							    pFirst->pSrc,
-							    pFirst->pEList);
+					generate_column_metadata(pParse,
+								 pFirst->pSrc,
+								 pFirst->pEList);
 				}
 				iBreak = sqlVdbeMakeLabel(v);
 				iCont = sqlVdbeMakeLabel(v);
@@ -3575,7 +3571,7 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
 		Select *pFirst = pPrior;
 		while (pFirst->pPrior)
 			pFirst = pFirst->pPrior;
-		generateColumnNames(pParse, pFirst->pSrc, pFirst->pEList);
+		generate_column_metadata(pParse, pFirst->pSrc, pFirst->pEList);
 	}
 
 	/* Reassembly the compound query so that it will be freed correctly
@@ -6433,7 +6429,7 @@ sqlSelect(Parse * pParse,		/* The parser context */
 	/* Identify column names if results of the SELECT are to be output.
 	 */
 	if (rc == 0 && pDest->eDest == SRT_Output) {
-		generateColumnNames(pParse, pTabList, pEList);
+		generate_column_metadata(pParse, pTabList, pEList);
 	}
 
 	sqlDbFree(db, sAggInfo.aCol);
diff --git a/src/box/sql/update.c b/src/box/sql/update.c
index 6d69b7252..881f87d6f 100644
--- a/src/box/sql/update.c
+++ b/src/box/sql/update.c
@@ -498,10 +498,8 @@ sqlUpdate(Parse * pParse,		/* The parser context */
 	    pParse->triggered_space == NULL) {
 		sqlVdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
 		sqlVdbeSetNumCols(v, 1);
-		sqlVdbeSetColName(v, 0, COLNAME_NAME, "rows updated",
-				      SQL_STATIC);
-		sqlVdbeSetColName(v, 0, COLNAME_DECLTYPE, "integer",
-				  SQL_STATIC);
+		vdbe_set_metadata_col_name(v, 0, "rows updated");
+		vdbe_set_metadata_col_type(v, 0, "integer");
 	}
 
  update_cleanup:
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 582d48a1f..4142fb6ba 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -148,17 +148,6 @@ struct SubProgram {
 #define P5_ConstraintUnique  2
 #define P5_ConstraintFK      4
 
-/*
- * The Vdbe.aColName array contains 5n Mem structures, where n is the
- * number of columns of data returned by the statement.
- */
-#define COLNAME_NAME     0
-#define COLNAME_DECLTYPE 1
-#define COLNAME_DATABASE 2
-#define COLNAME_TABLE    3
-#define COLNAME_COLUMN   4
-#define COLNAME_N        2	/* Store the name and decltype */
-
 /*
  * The following macro converts a relative address in the p2 field
  * of a VdbeOp structure into a negative number.
@@ -238,6 +227,10 @@ sql_vdbe_set_p4_key_def(struct Parse *parse, struct key_def *key_def);
 VdbeOp *sqlVdbeGetOp(Vdbe *, int);
 int sqlVdbeMakeLabel(Vdbe *);
 void sqlVdbeRunOnlyOnce(Vdbe *);
+
+void
+vdbe_metadata_delete(struct Vdbe *v);
+
 void sqlVdbeDelete(Vdbe *);
 void sqlVdbeClearObject(sql *, Vdbe *);
 void sqlVdbeMakeReady(Vdbe *, Parse *);
@@ -248,7 +241,18 @@ void sqlVdbeResetStepResult(Vdbe *);
 void sqlVdbeRewind(Vdbe *);
 int sqlVdbeReset(Vdbe *);
 void sqlVdbeSetNumCols(Vdbe *, int);
-int sqlVdbeSetColName(Vdbe *, int, int, const char *, void (*)(void *));
+
+/**
+ * Set the name of the idx'th column to be returned by the SQL
+ * statement. @name must be a pointer to a nul terminated string.
+ * This call must be made after a call to sqlVdbeSetNumCols().
+ */
+int
+vdbe_set_metadata_col_name(struct Vdbe *v, int col_idx, const char *name);
+
+int
+vdbe_set_metadata_col_type(struct Vdbe *v, int col_idx, const char *type);
+
 void sqlVdbeCountChanges(Vdbe *);
 sql *sqlVdbeDb(Vdbe *);
 void sqlVdbeSetSql(Vdbe *, const char *z, int n, int);
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 0f32b4cd6..9ab3753cb 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -346,6 +346,11 @@ struct ScanStatus {
 	char *zName;		/* Name of table or index */
 };
 
+struct sql_column_metadata {
+	const char *name;
+	const char *type;
+};
+
 /*
  * An instance of the virtual machine.  This structure contains the complete
  * state of the virtual machine.
@@ -394,7 +399,8 @@ struct Vdbe {
 	Op *aOp;		/* Space to hold the virtual machine's program */
 	Mem *aMem;		/* The memory locations */
 	Mem **apArg;		/* Arguments to currently executing user function */
-	Mem *aColName;		/* Column names to return */
+	/** SQL metadata for SELECT queries. */
+	struct sql_column_metadata *metadata;
 	Mem *pResultSet;	/* Pointer to an array of results */
 	VdbeCursor **apCsr;	/* One element of this array for each open cursor */
 	Mem *aVar;		/* Values for the OP_Variable opcode. */
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 685212d91..d746a42f2 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -725,77 +725,24 @@ sql_column_subtype(struct sql_stmt *stmt, int i)
 	return sql_value_subtype(columnMem(stmt, i));
 }
 
-/*
- * Convert the N-th element of pStmt->pColName[] into a string using
- * xFunc() then return that string.  If N is out of range, return 0.
- *
- * There are up to 5 names for each column.  useType determines which
- * name is returned.  Here are the names:
- *
- *    0      The column name as it should be displayed for output
- *    1      The datatype name for the column
- *    2      The name of the database that the column derives from
- *    3      The name of the table that the column derives from
- *    4      The name of the table column that the result column derives from
- *
- * If the result is not a simple column reference (if it is an expression
- * or a constant) then useTypes 2, 3, and 4 return NULL.
- */
-static const void *
-columnName(sql_stmt * pStmt,
-	   int N, const void *(*xFunc) (Mem *), int useType)
-{
-	const void *ret;
-	Vdbe *p;
-	int n;
-	sql *db;
-	ret = 0;
-	p = (Vdbe *) pStmt;
-	db = p->db;
-	assert(db != 0);
-	n = sql_column_count(pStmt);
-	if (N < n && N >= 0) {
-		N += useType * n;
-		assert(db->mallocFailed == 0);
-		ret = xFunc(&p->aColName[N]);
-		/* A malloc may have failed inside of the xFunc() call. If this
-		 * is the case, clear the mallocFailed flag and return NULL.
-		 */
-		if (db->mallocFailed) {
-			sqlOomClear(db);
-			ret = 0;
-		}
-	}
-	return ret;
-}
-
 /*
  * Return the name of the Nth column of the result set returned by SQL
  * statement pStmt.
  */
 const char *
-sql_column_name(sql_stmt * pStmt, int N)
-{
-	return columnName(pStmt, N, (const void *(*)(Mem *))sql_value_text,
-			  COLNAME_NAME);
-}
-
-const char *
-sql_column_datatype(sql_stmt *pStmt, int N)
+sql_column_name(sql_stmt *stmt, int n)
 {
-	return columnName(pStmt, N, (const void *(*)(Mem *))sql_value_text,
-			  COLNAME_DECLTYPE);
+	struct Vdbe *p = (struct Vdbe *) stmt;
+	assert(n < sql_column_count(stmt) && n >= 0);
+	return p->metadata[n].name;
 }
 
-/*
- * Return the column declaration type (if applicable) of the 'i'th column
- * of the result set of SQL statement pStmt.
- */
 const char *
-sql_column_decltype(sql_stmt * pStmt, int N)
+sql_column_datatype(sql_stmt *stmt, int n)
 {
-	return columnName(pStmt, N, (const void *(*)(Mem *))sql_value_text,
-			  COLNAME_DECLTYPE);
+	struct Vdbe *p = (struct Vdbe *) stmt;
+	assert(n < sql_column_count(stmt) && n >= 0);
+	return p->metadata[n].type;
 }
 
 /******************************* sql_bind_  **************************
@@ -853,17 +800,15 @@ sql_bind_type(struct Vdbe *v, uint32_t position, const char *type)
 	if (v->res_var_count < position)
 		return 0;
 	int rc = 0;
-	if (sqlVdbeSetColName(v, v->var_pos[position - 1], COLNAME_DECLTYPE,
-			      type, SQL_TRANSIENT) != 0)
+	if (vdbe_set_metadata_col_type(v, v->var_pos[position - 1], type) != 0)
 		rc = -1;
-	const char *bind_name = v->aColName[position - 1].z;
+	const char *bind_name = v->metadata[position - 1].name;
 	if (strcmp(bind_name, "?") == 0)
 		return rc;
 	for (uint32_t i = position; i < v->res_var_count; ++i) {
-		if (strcmp(bind_name,  v->aColName[i].z) == 0) {
-			if (sqlVdbeSetColName(v, v->var_pos[i],
-					      COLNAME_DECLTYPE, type,
-					      SQL_TRANSIENT) != 0)
+		if (strcmp(bind_name, v->metadata[i].name) == 0) {
+			if (vdbe_set_metadata_col_type(v, v->var_pos[i],
+						       type) != 0)
 				return -1;
 		}
 	}
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index a1d658648..db11fbf33 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1827,6 +1827,18 @@ Cleanup(Vdbe * p)
 	p->pResultSet = 0;
 }
 
+void
+vdbe_metadata_delete(struct Vdbe *v)
+{
+	if (v->metadata != NULL) {
+		for (int i = 0; i < v->nResColumn; ++i) {
+			free((void *)v->metadata[i].name);
+			free((void *)v->metadata[i].type);
+		}
+		free(v->metadata);
+	}
+}
+
 /*
  * Set the number of result columns that will be returned by this SQL
  * statement. This is now set at compile time, rather than during
@@ -1836,50 +1848,44 @@ Cleanup(Vdbe * p)
 void
 sqlVdbeSetNumCols(Vdbe * p, int nResColumn)
 {
-	int n;
-	sql *db = p->db;
-
-	releaseMemArray(p->aColName, p->nResColumn * COLNAME_N);
-	sqlDbFree(db, p->aColName);
-	n = nResColumn * COLNAME_N;
+	vdbe_metadata_delete(p);
 	p->nResColumn = (u16) nResColumn;
-	p->aColName = (Mem *) sqlDbMallocRawNN(db, sizeof(Mem) * n);
-	if (p->aColName == 0)
+	p->metadata = (struct sql_column_metadata *)
+		calloc(nResColumn, sizeof(struct sql_column_metadata));
+	if (p->metadata == NULL) {
+		diag_set(OutOfMemory,
+			 nResColumn * sizeof(struct sql_column_metadata),
+			 "calloc", "metadata");
 		return;
-	initMemArray(p->aColName, n, p->db, MEM_Null);
+	}
 }
 
-/*
- * Set the name of the idx'th column to be returned by the SQL statement.
- * zName must be a pointer to a nul terminated string.
- *
- * This call must be made after a call to sqlVdbeSetNumCols().
- *
- * The final parameter, xDel, must be one of SQL_DYNAMIC, SQL_STATIC
- * or SQL_TRANSIENT. If it is SQL_DYNAMIC, then the buffer pointed
- * to by zName will be freed by sqlDbFree() when the vdbe is destroyed.
- */
 int
-sqlVdbeSetColName(Vdbe * p,			/* Vdbe being configured */
-		      int idx,			/* Index of column zName applies to */
-		      int var,			/* One of the COLNAME_* constants */
-		      const char *zName,	/* Pointer to buffer containing name */
-		      void (*xDel) (void *))	/* Memory management strategy for zName */
-{
-	int rc;
-	Mem *pColName;
+vdbe_set_metadata_col_name(struct Vdbe *p, int idx, const char *name)
+{
 	assert(idx < p->nResColumn);
-	assert(var < COLNAME_N);
-	if (p->db->mallocFailed) {
-		assert(!zName || xDel != SQL_DYNAMIC);
+	if (p->metadata[idx].name != NULL)
+		free((void *)p->metadata[idx].name);
+	p->metadata[idx].name = strdup(name);
+	if (p->metadata[idx].name == NULL) {
+		diag_set(OutOfMemory, strlen(name), "strdup", "name");
 		return -1;
 	}
-	assert(p->aColName != 0);
-	assert(var == COLNAME_NAME || var == COLNAME_DECLTYPE);
-	pColName = &(p->aColName[idx + var * p->nResColumn]);
-	rc = sqlVdbeMemSetStr(pColName, zName, -1, 1, xDel);
-	assert(rc != 0 || !zName || (pColName->flags & MEM_Term) != 0);
-	return rc;
+	return 0;
+}
+
+int
+vdbe_set_metadata_col_type(struct Vdbe *p, int idx, const char *type)
+{
+	assert(idx < p->nResColumn);
+	if (p->metadata[idx].type != NULL)
+		free((void *)p->metadata[idx].type);
+	p->metadata[idx].type = strdup(type);
+	if (p->metadata[idx].type == NULL) {
+		diag_set(OutOfMemory, strlen(type), "strdup", "type");
+		return -1;
+	}
+	return 0;
 }
 
 /*
@@ -2230,7 +2236,7 @@ sqlVdbeClearObject(sql * db, Vdbe * p)
 {
 	SubProgram *pSub, *pNext;
 	assert(p->db == 0 || p->db == db);
-	releaseMemArray(p->aColName, p->nResColumn * COLNAME_N);
+	vdbe_metadata_delete(p);
 	for (pSub = p->pProgram; pSub; pSub = pNext) {
 		pNext = pSub->pNext;
 		vdbeFreeOpArray(db, pSub->aOp, pSub->nOp);
@@ -2242,7 +2248,6 @@ sqlVdbeClearObject(sql * db, Vdbe * p)
 		sqlDbFree(db, p->pFree);
 	}
 	vdbeFreeOpArray(db, p->aOp, p->nOp);
-	sqlDbFree(db, p->aColName);
 	sqlDbFree(db, p->zSql);
 }
 
-- 
2.15.1

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

* [Tarantool-patches] [PATCH 2/6] sql: fix possible null dereference in sql_expr_coll()
  2019-11-27 12:15 [Tarantool-patches] [PATCH 0/6] sql: extend response metadata Nikita Pettik
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 1/6] sql: refactor resulting set metadata Nikita Pettik
@ 2019-11-27 12:15 ` Nikita Pettik
  2019-11-28 22:42   ` Vladislav Shpilevoy
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 3/6] sql: extend result set with collation Nikita Pettik
                   ` (4 subsequent siblings)
  6 siblings, 1 reply; 40+ messages in thread
From: Nikita Pettik @ 2019-11-27 12:15 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

---
 src/box/sql/expr.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 648b7170e..0bdcfe576 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -332,7 +332,8 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
 				sql_func_by_signature(p->u.zToken, arg_count);
 			if (func == NULL)
 				break;
-			if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL)) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL) &&
+			    arg_count > 0) {
 				/*
 				 * Now we use quite straightforward
 				 * approach assuming that resulting
-- 
2.15.1

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

* [Tarantool-patches] [PATCH 3/6] sql: extend result set with collation
  2019-11-27 12:15 [Tarantool-patches] [PATCH 0/6] sql: extend response metadata Nikita Pettik
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 1/6] sql: refactor resulting set metadata Nikita Pettik
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 2/6] sql: fix possible null dereference in sql_expr_coll() Nikita Pettik
@ 2019-11-27 12:15 ` Nikita Pettik
  2019-11-28 22:41   ` Vladislav Shpilevoy
  2019-12-18 11:08   ` Sergey Ostanevich
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 4/6] sql: extend result set with nullability Nikita Pettik
                   ` (3 subsequent siblings)
  6 siblings, 2 replies; 40+ messages in thread
From: Nikita Pettik @ 2019-11-27 12:15 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

If resulting set column is of STRING type and features collation (no
matter explicit or implicit) different from "none", then metadata will
contain its name.

Part of #4407
---
 src/box/execute.c          |  31 +++++++--
 src/box/iproto_constants.h |   1 +
 src/box/lua/execute.c      |   7 +-
 src/box/lua/net_box.c      |  20 +++++-
 src/box/sql/select.c       |  18 ++++++
 src/box/sql/sqlInt.h       |   3 +
 src/box/sql/vdbe.h         |   4 ++
 src/box/sql/vdbeInt.h      |   1 +
 src/box/sql/vdbeapi.c      |   9 +++
 src/box/sql/vdbeaux.c      |  16 +++++
 test/sql/collation.result  | 155 +++++++++++++++++++++++++++------------------
 11 files changed, 195 insertions(+), 70 deletions(-)

diff --git a/src/box/execute.c b/src/box/execute.c
index e8b012e5b..20bfd0957 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -267,6 +267,23 @@ error:
 	region_truncate(region, svp);
 	return -1;
 }
+static size_t
+metadata_map_sizeof(const char *name, const char *type, const char *coll)
+{
+	uint32_t members_count = 2;
+	size_t map_size = 0;
+	if (coll != NULL) {
+		members_count++;
+		map_size += mp_sizeof_uint(IPROTO_FIELD_COLL);
+		map_size += mp_sizeof_str(strlen(coll));
+	}
+	map_size += mp_sizeof_uint(IPROTO_FIELD_NAME);
+	map_size += mp_sizeof_uint(IPROTO_FIELD_TYPE);
+	map_size += mp_sizeof_str(strlen(name));
+	map_size += mp_sizeof_str(strlen(type));
+	map_size += mp_sizeof_map(members_count);
+	return map_size;
+}
 
 /**
  * Serialize a description of the prepared statement.
@@ -291,9 +308,7 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
 	pos = mp_encode_uint(pos, IPROTO_METADATA);
 	pos = mp_encode_array(pos, column_count);
 	for (int i = 0; i < column_count; ++i) {
-		size_t size = mp_sizeof_map(2) +
-			      mp_sizeof_uint(IPROTO_FIELD_NAME) +
-			      mp_sizeof_uint(IPROTO_FIELD_TYPE);
+		const char *coll = sql_column_coll(stmt, i);
 		const char *name = sql_column_name(stmt, i);
 		const char *type = sql_column_datatype(stmt, i);
 		/*
@@ -303,18 +318,22 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
 		 */
 		assert(name != NULL);
 		assert(type != NULL);
-		size += mp_sizeof_str(strlen(name));
-		size += mp_sizeof_str(strlen(type));
+		size = metadata_map_sizeof(name, type, coll);
 		char *pos = (char *) obuf_alloc(out, size);
 		if (pos == NULL) {
 			diag_set(OutOfMemory, size, "obuf_alloc", "pos");
 			return -1;
 		}
-		pos = mp_encode_map(pos, 2);
+		uint32_t map_sz = 2 + (coll != NULL);
+		pos = mp_encode_map(pos, map_sz);
 		pos = mp_encode_uint(pos, IPROTO_FIELD_NAME);
 		pos = mp_encode_str(pos, name, strlen(name));
 		pos = mp_encode_uint(pos, IPROTO_FIELD_TYPE);
 		pos = mp_encode_str(pos, type, strlen(type));
+		if (coll != NULL) {
+			pos = mp_encode_uint(pos, IPROTO_FIELD_COLL);
+			pos = mp_encode_str(pos, coll, strlen(coll));
+		}
 	}
 	return 0;
 }
diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h
index 5e8a7d483..fa9c029a2 100644
--- a/src/box/iproto_constants.h
+++ b/src/box/iproto_constants.h
@@ -131,6 +131,7 @@ enum iproto_key {
 enum iproto_metadata_key {
 	IPROTO_FIELD_NAME = 0,
 	IPROTO_FIELD_TYPE = 1,
+	IPROTO_FIELD_COLL = 2,
 };
 
 enum iproto_ballot_key {
diff --git a/src/box/lua/execute.c b/src/box/lua/execute.c
index 76ecdd541..8a530bfc1 100644
--- a/src/box/lua/execute.c
+++ b/src/box/lua/execute.c
@@ -20,7 +20,8 @@ lua_sql_get_metadata(struct sql_stmt *stmt, struct lua_State *L,
 	assert(column_count > 0);
 	lua_createtable(L, column_count, 0);
 	for (int i = 0; i < column_count; ++i) {
-		lua_createtable(L, 0, 2);
+		const char *coll = sql_column_coll(stmt, i);
+		lua_createtable(L, 0, coll != NULL ? 3 : 2);
 		const char *name = sql_column_name(stmt, i);
 		const char *type = sql_column_datatype(stmt, i);
 		/*
@@ -34,6 +35,10 @@ lua_sql_get_metadata(struct sql_stmt *stmt, struct lua_State *L,
 		lua_setfield(L, -2, "name");
 		lua_pushstring(L, type);
 		lua_setfield(L, -2, "type");
+		if (coll != NULL) {
+			lua_pushstring(L, coll);
+			lua_setfield(L, -2, "collation");
+		}
 		lua_rawseti(L, -2, i + 1);
 	}
 }
diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
index 001af95dc..afbd1e1be 100644
--- a/src/box/lua/net_box.c
+++ b/src/box/lua/net_box.c
@@ -638,6 +638,23 @@ netbox_decode_select(struct lua_State *L)
 	return 2;
 }
 
+/** Decode optional (i.e. may be present in response) metadata fields. */
+static void
+decode_metadata_optional(struct lua_State *L, const char **data,
+			 uint32_t map_size)
+{
+	/* 2 is default metadata map size (field name + field size). */
+	while (map_size-- > 2) {
+		uint32_t key = mp_decode_uint(data);
+		uint32_t len;
+		if (key == IPROTO_FIELD_COLL) {
+			const char *coll = mp_decode_str(data, &len);
+			lua_pushlstring(L, coll, len);
+			lua_setfield(L, -2, "collation");
+		}
+	}
+}
+
 /**
  * Decode IPROTO_METADATA into array of maps.
  * @param L Lua stack to push result on.
@@ -650,7 +667,7 @@ netbox_decode_metadata(struct lua_State *L, const char **data)
 	lua_createtable(L, count, 0);
 	for (uint32_t i = 0; i < count; ++i) {
 		uint32_t map_size = mp_decode_map(data);
-		assert(map_size == 2);
+		assert(map_size == 2 || map_size == 3);
 		(void) map_size;
 		uint32_t key = mp_decode_uint(data);
 		assert(key == IPROTO_FIELD_NAME);
@@ -665,6 +682,7 @@ netbox_decode_metadata(struct lua_State *L, const char **data)
 		const char *type = mp_decode_str(data, &len);
 		lua_pushlstring(L, type, len);
 		lua_setfield(L, -2, "type");
+		decode_metadata_optional(L, data, map_size);
 		lua_rawseti(L, -2, i + 1);
 	}
 }
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index d6b8a158f..66e8c1274 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1794,6 +1794,22 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
 			var_pos[var_count++] = i;
 		vdbe_set_metadata_col_type(v, i,
 					   field_type_strs[sql_expr_type(p)]);
+		if (sql_expr_type(p) == FIELD_TYPE_STRING) {
+			bool unused;
+			uint32_t id;
+			struct coll *coll = NULL;
+			/*
+			 * If sql_expr_coll fails then it fails somewhere
+			 * above the call stack.
+			 */
+			(void) sql_expr_coll(pParse, p, &unused, &id, &coll);
+			if (id != COLL_NONE) {
+				struct coll_id *coll_id = coll_by_id(id);
+				vdbe_set_metadata_col_collation(v, i,
+								coll_id->name,
+								coll_id->name_len);
+			}
+		}
 		if (pEList->a[i].zName) {
 			char *zName = pEList->a[i].zName;
 			vdbe_set_metadata_col_name(v, i, zName);
@@ -1811,6 +1827,7 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
 			if (!shortNames && !fullNames) {
 				vdbe_set_metadata_col_name(v, i,
 							   pEList->a[i].zSpan);
+
 			} else if (fullNames) {
 				const char *zName = tt_sprintf("%s.%s",
 							       space_def->name,
@@ -1819,6 +1836,7 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
 			} else {
 				vdbe_set_metadata_col_name(v, i, zCol);
 			}
+
 		} else {
 			const char *z = pEList->a[i].zSpan;
 			if (z == NULL)
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 2594b73e0..4c2e3ed73 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -576,6 +576,9 @@ sql_column_name(sql_stmt *, int N);
 const char *
 sql_column_datatype(sql_stmt *, int N);
 
+const char *
+sql_column_coll(sql_stmt *stmt, int n);
+
 int
 sql_initialize(void);
 
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 4142fb6ba..5f042d7af 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -253,6 +253,10 @@ vdbe_set_metadata_col_name(struct Vdbe *v, int col_idx, const char *name);
 int
 vdbe_set_metadata_col_type(struct Vdbe *v, int col_idx, const char *type);
 
+int
+vdbe_set_metadata_col_collation(struct Vdbe *p, int idx, const char *coll,
+				size_t coll_len);
+
 void sqlVdbeCountChanges(Vdbe *);
 sql *sqlVdbeDb(Vdbe *);
 void sqlVdbeSetSql(Vdbe *, const char *z, int n, int);
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 9ab3753cb..fba86d664 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -349,6 +349,7 @@ struct ScanStatus {
 struct sql_column_metadata {
 	const char *name;
 	const char *type;
+	const char *collation;
 };
 
 /*
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index d746a42f2..6895d0ad5 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -745,6 +745,15 @@ sql_column_datatype(sql_stmt *stmt, int n)
 	return p->metadata[n].type;
 }
 
+const char *
+sql_column_coll(sql_stmt *stmt, int n)
+{
+	struct Vdbe *p = (struct Vdbe *) stmt;
+	assert(n < sql_column_count(stmt) && n >= 0);
+	return p->metadata[n].collation;
+}
+
+
 /******************************* sql_bind_  **************************
  *
  * Routines used to attach values to wildcards in a compiled SQL statement.
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index db11fbf33..1707df7f0 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1834,6 +1834,7 @@ vdbe_metadata_delete(struct Vdbe *v)
 		for (int i = 0; i < v->nResColumn; ++i) {
 			free((void *)v->metadata[i].name);
 			free((void *)v->metadata[i].type);
+			free((void *)v->metadata[i].collation);
 		}
 		free(v->metadata);
 	}
@@ -1888,6 +1889,21 @@ vdbe_set_metadata_col_type(struct Vdbe *p, int idx, const char *type)
 	return 0;
 }
 
+int
+vdbe_set_metadata_col_collation(struct Vdbe *p, int idx, const char *coll,
+				size_t coll_len)
+{
+	assert(idx < p->nResColumn);
+	if (p->metadata[idx].collation != NULL)
+		free((void *)p->metadata[idx].collation);
+	p->metadata[idx].collation = strndup(coll, coll_len);
+	if (p->metadata[idx].collation == NULL) {
+		diag_set(OutOfMemory, coll_len, "strndup", "collation");
+		return -1;
+	}
+	return 0;
+}
+
 /*
  * This routine checks that the sql.nVdbeActive count variable
  * matches the number of vdbe's in the list sql.pVdbe that are
diff --git a/test/sql/collation.result b/test/sql/collation.result
index 11962ef47..137a38bb4 100644
--- a/test/sql/collation.result
+++ b/test/sql/collation.result
@@ -85,10 +85,12 @@ box.execute([[SELECT descriptor, upper(letter COLLATE "TURKISH") AS upper,lower(
 - metadata:
   - name: DESCRIPTOR
     type: string
-  - name: UPPER
-    type: string
-  - name: LOWER
-    type: string
+  - type: string
+    name: UPPER
+    collation: TURKISH
+  - type: string
+    name: LOWER
+    collation: TURKISH
   rows:
   - ['Latin Capital Letter I U+0049', 'I', 'ı']
   - ['Latin Capital Letter I With Dot Above U+0130', 'İ', 'i']
@@ -125,8 +127,9 @@ box.execute([[SELECT descriptor, upper(letter COLLATE "GERMAN"), letter FROM tu
 - metadata:
   - name: DESCRIPTOR
     type: string
-  - name: upper(letter COLLATE "GERMAN")
-    type: string
+  - type: string
+    name: upper(letter COLLATE "GERMAN")
+    collation: GERMAN
   - name: LETTER
     type: string
   rows:
@@ -237,10 +240,12 @@ box.execute("SELECT * FROM t WHERE a = b;")
     type: integer
   - name: A
     type: string
-  - name: B
-    type: string
-  - name: C
-    type: string
+  - type: string
+    name: B
+    collation: binary
+  - type: string
+    name: C
+    collation: unicode_ci
   rows: []
 ...
 box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = b;")
@@ -250,10 +255,12 @@ box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = b;")
     type: integer
   - name: A
     type: string
-  - name: B
-    type: string
-  - name: C
-    type: string
+  - type: string
+    name: B
+    collation: binary
+  - type: string
+    name: C
+    collation: unicode_ci
   rows: []
 ...
 box.execute("SELECT * FROM t WHERE b = c;")
@@ -268,10 +275,12 @@ box.execute("SELECT * FROM t WHERE b COLLATE \"binary\" = c;")
     type: integer
   - name: A
     type: string
-  - name: B
-    type: string
-  - name: C
-    type: string
+  - type: string
+    name: B
+    collation: binary
+  - type: string
+    name: C
+    collation: unicode_ci
   rows: []
 ...
 box.execute("SELECT * FROM t WHERE a = c;")
@@ -281,10 +290,12 @@ box.execute("SELECT * FROM t WHERE a = c;")
     type: integer
   - name: A
     type: string
-  - name: B
-    type: string
-  - name: C
-    type: string
+  - type: string
+    name: B
+    collation: binary
+  - type: string
+    name: C
+    collation: unicode_ci
   rows: []
 ...
 box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = c COLLATE \"unicode\";")
@@ -313,8 +324,9 @@ box.execute("SELECT c FROM t UNION SELECT b FROM t;")
 box.execute("SELECT b FROM t UNION SELECT a FROM t;")
 ---
 - metadata:
-  - name: B
-    type: string
+  - type: string
+    name: B
+    collation: binary
   rows: []
 ...
 box.execute("SELECT a FROM t UNION SELECT c FROM t;")
@@ -327,15 +339,17 @@ box.execute("SELECT a FROM t UNION SELECT c FROM t;")
 box.execute("SELECT c COLLATE \"binary\" FROM t UNION SELECT a FROM t;")
 ---
 - metadata:
-  - name: c COLLATE "binary"
-    type: string
+  - type: string
+    name: c COLLATE "binary"
+    collation: binary
   rows: []
 ...
 box.execute("SELECT b COLLATE \"unicode\" FROM t UNION SELECT a FROM t;")
 ---
 - metadata:
-  - name: b COLLATE "unicode"
-    type: string
+  - type: string
+    name: b COLLATE "unicode"
+    collation: unicode
   rows: []
 ...
 box.execute("DROP TABLE t;")
@@ -392,8 +406,9 @@ box.execute("UPDATE t0 SET s1 = 'A';")
 box.execute("SELECT s1 FROM t0;")
 ---
 - metadata:
-  - name: S1
-    type: string
+  - type: string
+    name: S1
+    collation: unicode_ci
   rows:
   - ['A']
 ...
@@ -402,8 +417,9 @@ box.execute("SELECT * FROM t1;")
 - metadata:
   - name: S1
     type: integer
-  - name: S0
-    type: string
+  - type: string
+    name: S0
+    collation: unicode_ci
   rows:
   - [1, 'a']
 ...
@@ -586,8 +602,9 @@ box.execute("INSERT INTO t4b VALUES('gHz', 'xxx', 4);")
 box.execute("SELECT a FROM t4b ORDER BY a COLLATE \"unicode_ci\" || ''")
 ---
 - metadata:
-  - name: A
-    type: string
+  - type: string
+    name: A
+    collation: unicode_ci
   rows:
   - ['abc']
   - ['BCD']
@@ -597,8 +614,9 @@ box.execute("SELECT a FROM t4b ORDER BY a COLLATE \"unicode_ci\" || ''")
 box.execute("SELECT a FROM t4b ORDER BY a || b")
 ---
 - metadata:
-  - name: A
-    type: string
+  - type: string
+    name: A
+    collation: unicode_ci
   rows:
   - ['abc']
   - ['BCD']
@@ -662,8 +680,9 @@ box.execute("INSERT INTO t3b VALUES ('A');")
 box.execute("SELECT * FROM t3b;")
 ---
 - metadata:
-  - name: S1
-    type: string
+  - type: string
+    name: S1
+    collation: binary
   rows:
   - ['A']
   - ['a']
@@ -697,8 +716,9 @@ box.execute("INSERT INTO t3b VALUES ('a');")
 box.execute("SELECT * FROM t3b;")
 ---
 - metadata:
-  - name: S1
-    type: string
+  - type: string
+    name: S1
+    collation: binary
   rows:
   - ['A']
   - ['a']
@@ -727,8 +747,9 @@ box.execute("INSERT INTO t3c VALUES ('A');")
 box.execute("SELECT * FROM t3c;")
 ---
 - metadata:
-  - name: S1
-    type: string
+  - type: string
+    name: S1
+    collation: unicode
   rows:
   - ['a']
   - ['A']
@@ -762,8 +783,9 @@ box.execute("INSERT INTO t3c VALUES ('a');")
 box.execute("SELECT * FROM t3c;")
 ---
 - metadata:
-  - name: S1
-    type: string
+  - type: string
+    name: S1
+    collation: unicode
   rows:
   - ['a']
   - ['A']
@@ -792,8 +814,9 @@ box.execute("INSERT INTO t3d VALUES ('A');")
 box.execute("SELECT * FROM t3d;")
 ---
 - metadata:
-  - name: S1
-    type: string
+  - type: string
+    name: S1
+    collation: binary
   rows:
   - ['A']
   - ['a']
@@ -827,8 +850,9 @@ box.execute("INSERT INTO t3d VALUES ('a');")
 box.execute("SELECT * FROM t3d;")
 ---
 - metadata:
-  - name: S1
-    type: string
+  - type: string
+    name: S1
+    collation: binary
   rows:
   - ['A']
   - ['a']
@@ -858,8 +882,9 @@ box.execute("INSERT INTO t3e VALUES ('A');")
 box.execute("SELECT * FROM t3e;")
 ---
 - metadata:
-  - name: S1
-    type: string
+  - type: string
+    name: S1
+    collation: unicode_ci
   rows:
   - ['a']
 ...
@@ -887,8 +912,9 @@ box.execute("INSERT INTO t3f VALUES ('A');")
 box.execute("SELECT * FROM t3f;")
 ---
 - metadata:
-  - name: S1
-    type: string
+  - type: string
+    name: S1
+    collation: unicode
   rows:
   - ['a']
   - ['A']
@@ -922,8 +948,9 @@ box.execute("INSERT INTO t3f VALUES ('a');")
 box.execute("SELECT * FROM t3f;")
 ---
 - metadata:
-  - name: S1
-    type: string
+  - type: string
+    name: S1
+    collation: unicode
   rows:
   - ['a']
   - ['A']
@@ -953,8 +980,9 @@ box.execute("INSERT INTO t3g VALUES ('A');")
 box.execute("SELECT * FROM t3g;")
 ---
 - metadata:
-  - name: S1
-    type: string
+  - type: string
+    name: S1
+    collation: unicode_ci
   rows:
   - ['a']
 ...
@@ -1118,8 +1146,9 @@ box.execute("INSERT INTO jj VALUES (1,'A'), (2,'a')")
 box.execute("SELECT DISTINCT trim(s2) FROM jj;")
 ---
 - metadata:
-  - name: trim(s2)
-    type: string
+  - type: string
+    name: trim(s2)
+    collation: unicode_ci
   rows:
   - ['A']
 ...
@@ -1130,8 +1159,9 @@ box.execute("INSERT INTO jj VALUES (3, 'aS'), (4, 'AS');")
 box.execute("SELECT DISTINCT replace(s2, 'S', 's') FROM jj;")
 ---
 - metadata:
-  - name: replace(s2, 'S', 's')
-    type: string
+  - type: string
+    name: replace(s2, 'S', 's')
+    collation: unicode_ci
   rows:
   - ['A']
   - ['as']
@@ -1139,8 +1169,9 @@ box.execute("SELECT DISTINCT replace(s2, 'S', 's') FROM jj;")
 box.execute("SELECT DISTINCT substr(s2, 1, 1) FROM jj;")
 ---
 - metadata:
-  - name: substr(s2, 1, 1)
-    type: string
+  - type: string
+    name: substr(s2, 1, 1)
+    collation: unicode_ci
   rows:
   - ['A']
 ...
-- 
2.15.1

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

* [Tarantool-patches] [PATCH 4/6] sql: extend result set with nullability
  2019-11-27 12:15 [Tarantool-patches] [PATCH 0/6] sql: extend response metadata Nikita Pettik
                   ` (2 preceding siblings ...)
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 3/6] sql: extend result set with collation Nikita Pettik
@ 2019-11-27 12:15 ` Nikita Pettik
  2019-11-28 22:41   ` Vladislav Shpilevoy
  2019-12-18 13:31   ` Sergey Ostanevich
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 5/6] sql: extend result set with autoincrement Nikita Pettik
                   ` (2 subsequent siblings)
  6 siblings, 2 replies; 40+ messages in thread
From: Nikita Pettik @ 2019-11-27 12:15 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

If member of result set is (solely) column identifier, then metadata
will contain its corresponding field nullability as boolean property.
Note that indicating nullability for other expressions (like x + 1)
may make sense but it requires derived nullability calculation which in
turn seems to be overkill (at least in scope of current patch).

Part of #4407
---
 src/box/execute.c                                |  18 +-
 src/box/iproto_constants.h                       |   1 +
 src/box/lua/execute.c                            |   5 +
 src/box/lua/net_box.c                            |   7 +-
 src/box/sql/select.c                             |   6 +-
 src/box/sql/sqlInt.h                             |   3 +
 src/box/sql/vdbe.h                               |   3 +
 src/box/sql/vdbeInt.h                            |   5 +
 src/box/sql/vdbeapi.c                            |   7 +
 src/box/sql/vdbeaux.c                            |   7 +
 test/box/sql-update-with-nested-select.result    |   5 +-
 test/sql/boolean.result                          | 425 ++++++++++++++---------
 test/sql/check-clear-ephemeral.result            |   5 +-
 test/sql/collation.result                        |  74 ++--
 test/sql/gh-3199-no-mem-leaks.result             | 120 ++++---
 test/sql/gh2141-delete-trigger-drop-table.result |  20 +-
 test/sql/gh2251-multiple-update.result           |  10 +-
 test/sql/iproto.result                           | 105 +++---
 test/sql/misc.result                             |  25 +-
 test/sql/on-conflict.result                      |  20 +-
 test/sql/persistency.result                      | 190 ++++++----
 test/sql/row-count.result                        |  25 +-
 test/sql/sql-debug.result                        |  15 +-
 test/sql/transition.result                       | 190 ++++++----
 test/sql/types.result                            | 105 +++---
 test/sql/update-with-nested-select.result        |   5 +-
 26 files changed, 862 insertions(+), 539 deletions(-)

diff --git a/src/box/execute.c b/src/box/execute.c
index 20bfd0957..98812ae1e 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -267,8 +267,10 @@ error:
 	region_truncate(region, svp);
 	return -1;
 }
+
 static size_t
-metadata_map_sizeof(const char *name, const char *type, const char *coll)
+metadata_map_sizeof(const char *name, const char *type, const char *coll,
+		    int nullable)
 {
 	uint32_t members_count = 2;
 	size_t map_size = 0;
@@ -277,6 +279,11 @@ metadata_map_sizeof(const char *name, const char *type, const char *coll)
 		map_size += mp_sizeof_uint(IPROTO_FIELD_COLL);
 		map_size += mp_sizeof_str(strlen(coll));
 	}
+	if (nullable != -1) {
+		members_count++;
+		map_size += mp_sizeof_uint(IPROTO_FIELD_NULLABLE);
+		map_size += mp_sizeof_bool(nullable);
+	}
 	map_size += mp_sizeof_uint(IPROTO_FIELD_NAME);
 	map_size += mp_sizeof_uint(IPROTO_FIELD_TYPE);
 	map_size += mp_sizeof_str(strlen(name));
@@ -311,6 +318,7 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
 		const char *coll = sql_column_coll(stmt, i);
 		const char *name = sql_column_name(stmt, i);
 		const char *type = sql_column_datatype(stmt, i);
+		int nullable = sql_column_is_nullable(stmt, i);
 		/*
 		 * Can not fail, since all column names and types
 		 * are preallocated during prepare phase and the
@@ -318,13 +326,13 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
 		 */
 		assert(name != NULL);
 		assert(type != NULL);
-		size = metadata_map_sizeof(name, type, coll);
+		size = metadata_map_sizeof(name, type, coll, nullable);
 		char *pos = (char *) obuf_alloc(out, size);
 		if (pos == NULL) {
 			diag_set(OutOfMemory, size, "obuf_alloc", "pos");
 			return -1;
 		}
-		uint32_t map_sz = 2 + (coll != NULL);
+		uint32_t map_sz = 2 + (coll != NULL) + (nullable != -1);
 		pos = mp_encode_map(pos, map_sz);
 		pos = mp_encode_uint(pos, IPROTO_FIELD_NAME);
 		pos = mp_encode_str(pos, name, strlen(name));
@@ -334,6 +342,10 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
 			pos = mp_encode_uint(pos, IPROTO_FIELD_COLL);
 			pos = mp_encode_str(pos, coll, strlen(coll));
 		}
+		if (nullable != -1) {
+			pos = mp_encode_uint(pos, IPROTO_FIELD_NULLABLE);
+			pos = mp_encode_bool(pos, nullable);
+		}
 	}
 	return 0;
 }
diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h
index fa9c029a2..030c25531 100644
--- a/src/box/iproto_constants.h
+++ b/src/box/iproto_constants.h
@@ -132,6 +132,7 @@ enum iproto_metadata_key {
 	IPROTO_FIELD_NAME = 0,
 	IPROTO_FIELD_TYPE = 1,
 	IPROTO_FIELD_COLL = 2,
+	IPROTO_FIELD_NULLABLE = 3
 };
 
 enum iproto_ballot_key {
diff --git a/src/box/lua/execute.c b/src/box/lua/execute.c
index 8a530bfc1..3d7ca710c 100644
--- a/src/box/lua/execute.c
+++ b/src/box/lua/execute.c
@@ -24,6 +24,7 @@ lua_sql_get_metadata(struct sql_stmt *stmt, struct lua_State *L,
 		lua_createtable(L, 0, coll != NULL ? 3 : 2);
 		const char *name = sql_column_name(stmt, i);
 		const char *type = sql_column_datatype(stmt, i);
+		int nullable = sql_column_is_nullable(stmt, i);
 		/*
 		 * Can not fail, since all column names are
 		 * preallocated during prepare phase and the
@@ -39,6 +40,10 @@ lua_sql_get_metadata(struct sql_stmt *stmt, struct lua_State *L,
 			lua_pushstring(L, coll);
 			lua_setfield(L, -2, "collation");
 		}
+		if (nullable != -1) {
+			lua_pushboolean(L, nullable);
+			lua_setfield(L, -2, "is_nullable");
+		}
 		lua_rawseti(L, -2, i + 1);
 	}
 }
diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
index afbd1e1be..3e93cbc75 100644
--- a/src/box/lua/net_box.c
+++ b/src/box/lua/net_box.c
@@ -651,6 +651,11 @@ decode_metadata_optional(struct lua_State *L, const char **data,
 			const char *coll = mp_decode_str(data, &len);
 			lua_pushlstring(L, coll, len);
 			lua_setfield(L, -2, "collation");
+		} else {
+			assert(key == IPROTO_FIELD_NULLABLE);
+			bool is_nullable = mp_decode_bool(data);
+			lua_pushboolean(L, is_nullable);
+			lua_setfield(L, -2, "is_nullable");
 		}
 	}
 }
@@ -667,7 +672,7 @@ netbox_decode_metadata(struct lua_State *L, const char **data)
 	lua_createtable(L, count, 0);
 	for (uint32_t i = 0; i < count; ++i) {
 		uint32_t map_size = mp_decode_map(data);
-		assert(map_size == 2 || map_size == 3);
+		assert(map_size >= 2 && map_size <= 4);
 		(void) map_size;
 		uint32_t key = mp_decode_uint(data);
 		assert(key == IPROTO_FIELD_NAME);
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 66e8c1274..b772bcead 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1810,6 +1810,7 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
 								coll_id->name_len);
 			}
 		}
+		vdbe_set_metadata_col_nullability(v, i, -1);
 		if (pEList->a[i].zName) {
 			char *zName = pEList->a[i].zName;
 			vdbe_set_metadata_col_name(v, i, zName);
@@ -1836,7 +1837,10 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
 			} else {
 				vdbe_set_metadata_col_name(v, i, zCol);
 			}
-
+			bool is_nullable = space_def->fields[iCol].is_nullable;
+			if (p->op == TK_COLUMN)
+				vdbe_set_metadata_col_nullability(v, i,
+								  is_nullable);
 		} else {
 			const char *z = pEList->a[i].zSpan;
 			if (z == NULL)
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 4c2e3ed73..89920b3d1 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -579,6 +579,9 @@ sql_column_datatype(sql_stmt *, int N);
 const char *
 sql_column_coll(sql_stmt *stmt, int n);
 
+int
+sql_column_is_nullable(sql_stmt *stmt, int n);
+
 int
 sql_initialize(void);
 
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 5f042d7af..0f315b660 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -257,6 +257,9 @@ int
 vdbe_set_metadata_col_collation(struct Vdbe *p, int idx, const char *coll,
 				size_t coll_len);
 
+void
+vdbe_set_metadata_col_nullability(struct Vdbe *p, int idx, int nullable);
+
 void sqlVdbeCountChanges(Vdbe *);
 sql *sqlVdbeDb(Vdbe *);
 void sqlVdbeSetSql(Vdbe *, const char *z, int n, int);
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index fba86d664..0e54e42a5 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -350,6 +350,11 @@ struct sql_column_metadata {
 	const char *name;
 	const char *type;
 	const char *collation;
+	/**
+	 * -1 is for any member of result set except for pure
+	 * columns: all other expressions are nullable by default.
+	 */
+	int8_t nullable : 2;
 };
 
 /*
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 6895d0ad5..ea8c7c438 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -753,6 +753,13 @@ sql_column_coll(sql_stmt *stmt, int n)
 	return p->metadata[n].collation;
 }
 
+int
+sql_column_is_nullable(sql_stmt *stmt, int n)
+{
+	struct Vdbe *p = (struct Vdbe *) stmt;
+	assert(n < sql_column_count(stmt) && n >= 0);
+	return p->metadata[n].nullable;
+}
 
 /******************************* sql_bind_  **************************
  *
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 1707df7f0..6c3523ba4 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1904,6 +1904,13 @@ vdbe_set_metadata_col_collation(struct Vdbe *p, int idx, const char *coll,
 	return 0;
 }
 
+void
+vdbe_set_metadata_col_nullability(struct Vdbe *p, int idx, int nullable)
+{
+	assert(idx < p->nResColumn);
+	p->metadata[idx].nullable = nullable;
+}
+
 /*
  * This routine checks that the sql.nVdbeActive count variable
  * matches the number of vdbe's in the list sql.pVdbe that are
diff --git a/test/box/sql-update-with-nested-select.result b/test/box/sql-update-with-nested-select.result
index 4ff090f3a..ea627f769 100644
--- a/test/box/sql-update-with-nested-select.result
+++ b/test/box/sql-update-with-nested-select.result
@@ -27,8 +27,9 @@ box.execute("UPDATE t1 SET e=e+1 WHERE b IN (SELECT b FROM t1);");
 box.execute("SELECT e FROM t1");
 ---
 - metadata:
-  - name: E
-    type: integer
+  - type: integer
+    name: E
+    is_nullable: true
   rows:
   - [7]
   - [8]
diff --git a/test/sql/boolean.result b/test/sql/boolean.result
index 7769d0cb3..339e7d9d0 100644
--- a/test/sql/boolean.result
+++ b/test/sql/boolean.result
@@ -48,16 +48,18 @@ test_run:cmd("setopt delimiter ''");
 SELECT a FROM t WHERE a;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   rows:
  |   - [true]
  | ...
 SELECT a FROM t WHERE a != true;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   rows:
  |   - [false]
  | ...
@@ -123,8 +125,9 @@ INSERT INTO ts SELECT * FROM t0;
 SELECT s FROM ts WHERE s = true;
  | ---
  | - metadata:
- |   - name: S
- |     type: scalar
+ |   - type: scalar
+ |     name: S
+ |     is_nullable: true
  |   rows:
  |   - [true]
  | ...
@@ -148,8 +151,9 @@ SELECT s FROM ts WHERE s < true;
 SELECT s FROM ts WHERE s IN (true, 1, 'abcd');
  | ---
  | - metadata:
- |   - name: S
- |     type: scalar
+ |   - type: scalar
+ |     name: S
+ |     is_nullable: true
  |   rows:
  |   - [true]
  | ...
@@ -202,14 +206,18 @@ SELECT * FROM t LIMIT 1 OFFSET false;
 EXPLAIN QUERY PLAN SELECT a FROM t0 WHERE a = true;
  | ---
  | - metadata:
- |   - name: selectid
- |     type: integer
- |   - name: order
- |     type: integer
- |   - name: from
- |     type: integer
- |   - name: detail
- |     type: text
+ |   - type: integer
+ |     name: selectid
+ |     is_nullable: false
+ |   - type: integer
+ |     name: order
+ |     is_nullable: false
+ |   - type: integer
+ |     name: from
+ |     is_nullable: false
+ |   - type: text
+ |     name: detail
+ |     is_nullable: false
  |   rows:
  |   - [0, 0, 0, 'SEARCH TABLE T0 USING COVERING INDEX I0 (A=?) (~10 rows)']
  | ...
@@ -748,10 +756,12 @@ WITH RECURSIVE cnt(x, y) AS \
 SELECT x, y FROM cnt;
  | ---
  | - metadata:
- |   - name: X
- |     type: integer
- |   - name: Y
- |     type: boolean
+ |   - type: integer
+ |     name: X
+ |     is_nullable: true
+ |   - type: boolean
+ |     name: Y
+ |     is_nullable: true
  |   rows:
  |   - [1, false]
  |   - [2, true]
@@ -849,8 +859,9 @@ END AS a0 \
 FROM t4;
  | ---
  | - metadata:
- |   - name: I
- |     type: integer
+ |   - type: integer
+ |     name: I
+ |     is_nullable: false
  |   - name: A0
  |     type: boolean
  |   rows:
@@ -918,8 +929,9 @@ SELECT NOT false;
 SELECT a, NOT a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: NOT a
  |     type: boolean
  |   rows:
@@ -995,8 +1007,9 @@ SELECT false OR false;
 SELECT a, true AND a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: true AND a
  |     type: boolean
  |   rows:
@@ -1006,8 +1019,9 @@ SELECT a, true AND a FROM t;
 SELECT a, false AND a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: false AND a
  |     type: boolean
  |   rows:
@@ -1017,8 +1031,9 @@ SELECT a, false AND a FROM t;
 SELECT a, true OR a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: true OR a
  |     type: boolean
  |   rows:
@@ -1028,8 +1043,9 @@ SELECT a, true OR a FROM t;
 SELECT a, false OR a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: false OR a
  |     type: boolean
  |   rows:
@@ -1039,8 +1055,9 @@ SELECT a, false OR a FROM t;
 SELECT a, a AND true FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a AND true
  |     type: boolean
  |   rows:
@@ -1050,8 +1067,9 @@ SELECT a, a AND true FROM t;
 SELECT a, a AND false FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a AND false
  |     type: boolean
  |   rows:
@@ -1061,8 +1079,9 @@ SELECT a, a AND false FROM t;
 SELECT a, a OR true FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a OR true
  |     type: boolean
  |   rows:
@@ -1072,8 +1091,9 @@ SELECT a, a OR true FROM t;
 SELECT a, a OR false FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a OR false
  |     type: boolean
  |   rows:
@@ -1084,10 +1104,12 @@ SELECT a, a OR false FROM t;
 SELECT a, a1, a AND a1 FROM t, t6;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
- |   - name: A1
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
+ |   - type: boolean
+ |     name: A1
+ |     is_nullable: false
  |   - name: a AND a1
  |     type: boolean
  |   rows:
@@ -1099,10 +1121,12 @@ SELECT a, a1, a AND a1 FROM t, t6;
 SELECT a, a1, a OR a1 FROM t, t6;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
- |   - name: A1
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
+ |   - type: boolean
+ |     name: A1
+ |     is_nullable: false
  |   - name: a OR a1
  |     type: boolean
  |   rows:
@@ -1682,8 +1706,9 @@ SELECT false < false;
 SELECT a, true > a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: true > a
  |     type: boolean
  |   rows:
@@ -1693,8 +1718,9 @@ SELECT a, true > a FROM t;
 SELECT a, false > a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: false > a
  |     type: boolean
  |   rows:
@@ -1704,8 +1730,9 @@ SELECT a, false > a FROM t;
 SELECT a, true < a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: true < a
  |     type: boolean
  |   rows:
@@ -1715,8 +1742,9 @@ SELECT a, true < a FROM t;
 SELECT a, false < a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: false < a
  |     type: boolean
  |   rows:
@@ -1726,8 +1754,9 @@ SELECT a, false < a FROM t;
 SELECT a, a > true FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a > true
  |     type: boolean
  |   rows:
@@ -1737,8 +1766,9 @@ SELECT a, a > true FROM t;
 SELECT a, a > false FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a > false
  |     type: boolean
  |   rows:
@@ -1748,8 +1778,9 @@ SELECT a, a > false FROM t;
 SELECT a, a < true FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a < true
  |     type: boolean
  |   rows:
@@ -1759,8 +1790,9 @@ SELECT a, a < true FROM t;
 SELECT a, a < false FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a < false
  |     type: boolean
  |   rows:
@@ -1771,10 +1803,12 @@ SELECT a, a < false FROM t;
 SELECT a, a1, a > a1 FROM t, t6;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
- |   - name: A1
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
+ |   - type: boolean
+ |     name: A1
+ |     is_nullable: false
  |   - name: a > a1
  |     type: boolean
  |   rows:
@@ -1786,10 +1820,12 @@ SELECT a, a1, a > a1 FROM t, t6;
 SELECT a, a1, a < a1 FROM t, t6;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
- |   - name: A1
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
+ |   - type: boolean
+ |     name: A1
+ |     is_nullable: false
  |   - name: a < a1
  |     type: boolean
  |   rows:
@@ -1867,8 +1903,9 @@ SELECT false <= false;
 SELECT a, true >= a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: true >= a
  |     type: any
  |   rows:
@@ -1878,8 +1915,9 @@ SELECT a, true >= a FROM t;
 SELECT a, false >= a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: false >= a
  |     type: any
  |   rows:
@@ -1889,8 +1927,9 @@ SELECT a, false >= a FROM t;
 SELECT a, true <= a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: true <= a
  |     type: boolean
  |   rows:
@@ -1900,8 +1939,9 @@ SELECT a, true <= a FROM t;
 SELECT a, false <= a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: false <= a
  |     type: boolean
  |   rows:
@@ -1911,8 +1951,9 @@ SELECT a, false <= a FROM t;
 SELECT a, a >= true FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a >= true
  |     type: any
  |   rows:
@@ -1922,8 +1963,9 @@ SELECT a, a >= true FROM t;
 SELECT a, a >= false FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a >= false
  |     type: any
  |   rows:
@@ -1933,8 +1975,9 @@ SELECT a, a >= false FROM t;
 SELECT a, a <= true FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a <= true
  |     type: boolean
  |   rows:
@@ -1944,8 +1987,9 @@ SELECT a, a <= true FROM t;
 SELECT a, a <= false FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a <= false
  |     type: boolean
  |   rows:
@@ -1956,10 +2000,12 @@ SELECT a, a <= false FROM t;
 SELECT a, a1, a >= a1 FROM t, t6;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
- |   - name: A1
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
+ |   - type: boolean
+ |     name: A1
+ |     is_nullable: false
  |   - name: a >= a1
  |     type: any
  |   rows:
@@ -1971,10 +2017,12 @@ SELECT a, a1, a >= a1 FROM t, t6;
 SELECT a, a1, a <= a1 FROM t, t6;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
- |   - name: A1
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
+ |   - type: boolean
+ |     name: A1
+ |     is_nullable: false
  |   - name: a <= a1
  |     type: boolean
  |   rows:
@@ -2052,8 +2100,9 @@ SELECT false != false;
 SELECT a, true == a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: true == a
  |     type: boolean
  |   rows:
@@ -2063,8 +2112,9 @@ SELECT a, true == a FROM t;
 SELECT a, false == a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: false == a
  |     type: boolean
  |   rows:
@@ -2074,8 +2124,9 @@ SELECT a, false == a FROM t;
 SELECT a, true != a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: true != a
  |     type: boolean
  |   rows:
@@ -2085,8 +2136,9 @@ SELECT a, true != a FROM t;
 SELECT a, false != a FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: false != a
  |     type: boolean
  |   rows:
@@ -2096,8 +2148,9 @@ SELECT a, false != a FROM t;
 SELECT a, a == true FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a == true
  |     type: boolean
  |   rows:
@@ -2107,8 +2160,9 @@ SELECT a, a == true FROM t;
 SELECT a, a == false FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a == false
  |     type: boolean
  |   rows:
@@ -2118,8 +2172,9 @@ SELECT a, a == false FROM t;
 SELECT a, a != true FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a != true
  |     type: boolean
  |   rows:
@@ -2129,8 +2184,9 @@ SELECT a, a != true FROM t;
 SELECT a, a != false FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a != false
  |     type: boolean
  |   rows:
@@ -2141,10 +2197,12 @@ SELECT a, a != false FROM t;
 SELECT a, a1, a == a1 FROM t, t6;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
- |   - name: A1
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
+ |   - type: boolean
+ |     name: A1
+ |     is_nullable: false
  |   - name: a == a1
  |     type: boolean
  |   rows:
@@ -2156,10 +2214,12 @@ SELECT a, a1, a == a1 FROM t, t6;
 SELECT a, a1, a != a1 FROM t, t6;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
- |   - name: A1
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
+ |   - type: boolean
+ |     name: A1
+ |     is_nullable: false
  |   - name: a != a1
  |     type: boolean
  |   rows:
@@ -2269,8 +2329,9 @@ SELECT false IN (1, 1.2, 'true', false);
 SELECT a, a IN (true) FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a IN (true)
  |     type: boolean
  |   rows:
@@ -2280,8 +2341,9 @@ SELECT a, a IN (true) FROM t;
 SELECT a, a IN (false) FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a IN (false)
  |     type: boolean
  |   rows:
@@ -2291,8 +2353,9 @@ SELECT a, a IN (false) FROM t;
 SELECT a, a IN (true, false) FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a IN (true, false)
  |     type: boolean
  |   rows:
@@ -2302,8 +2365,9 @@ SELECT a, a IN (true, false) FROM t;
 SELECT a, a IN (SELECT a1 FROM t6 LIMIT 1) FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a IN (SELECT a1 FROM t6 LIMIT 1)
  |     type: boolean
  |   rows:
@@ -2313,8 +2377,9 @@ SELECT a, a IN (SELECT a1 FROM t6 LIMIT 1) FROM t;
 SELECT a, a IN (SELECT a1 FROM t6) FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a IN (SELECT a1 FROM t6)
  |     type: boolean
  |   rows:
@@ -2324,8 +2389,9 @@ SELECT a, a IN (SELECT a1 FROM t6) FROM t;
 SELECT a, a IN (1, 1.2, 'true', false) FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a IN (1, 1.2, 'true', false)
  |     type: boolean
  |   rows:
@@ -2401,8 +2467,9 @@ SELECT false BETWEEN false AND true;
 SELECT a, a BETWEEN true AND true FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a BETWEEN true AND true
  |     type: boolean
  |   rows:
@@ -2412,8 +2479,9 @@ SELECT a, a BETWEEN true AND true FROM t;
 SELECT a, a BETWEEN false AND false FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a BETWEEN false AND false
  |     type: boolean
  |   rows:
@@ -2423,8 +2491,9 @@ SELECT a, a BETWEEN false AND false FROM t;
 SELECT a, a BETWEEN true AND false FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a BETWEEN true AND false
  |     type: boolean
  |   rows:
@@ -2434,8 +2503,9 @@ SELECT a, a BETWEEN true AND false FROM t;
 SELECT a, a BETWEEN false AND true FROM t;
  | ---
  | - metadata:
- |   - name: A
- |     type: boolean
+ |   - type: boolean
+ |     name: A
+ |     is_nullable: false
  |   - name: a BETWEEN false AND true
  |     type: boolean
  |   rows:
@@ -2549,8 +2619,9 @@ SELECT b, true AND b FROM t7;
 SELECT b, false AND b FROM t7;
  | ---
  | - metadata:
- |   - name: B
- |     type: integer
+ |   - type: integer
+ |     name: B
+ |     is_nullable: false
  |   - name: false AND b
  |     type: boolean
  |   rows:
@@ -2574,8 +2645,9 @@ SELECT b, b AND true FROM t7;
 SELECT b, b AND false FROM t7;
  | ---
  | - metadata:
- |   - name: B
- |     type: integer
+ |   - type: integer
+ |     name: B
+ |     is_nullable: false
  |   - name: b AND false
  |     type: boolean
  |   rows:
@@ -3882,8 +3954,9 @@ SELECT false IN (SELECT b FROM t7);
 SELECT a1, a1 IN (0, 1, 2, 3) FROM t6
  | ---
  | - metadata:
- |   - name: A1
- |     type: boolean
+ |   - type: boolean
+ |     name: A1
+ |     is_nullable: false
  |   - name: a1 IN (0, 1, 2, 3)
  |     type: boolean
  |   rows:
@@ -4018,8 +4091,9 @@ SELECT c, true AND c FROM t8;
 SELECT c, false AND c FROM t8;
  | ---
  | - metadata:
- |   - name: C
- |     type: number
+ |   - type: number
+ |     name: C
+ |     is_nullable: false
  |   - name: false AND c
  |     type: boolean
  |   rows:
@@ -4043,8 +4117,9 @@ SELECT c, c AND true FROM t8;
 SELECT c, c AND false FROM t8;
  | ---
  | - metadata:
- |   - name: C
- |     type: number
+ |   - type: number
+ |     name: C
+ |     is_nullable: false
  |   - name: c AND false
  |     type: boolean
  |   rows:
@@ -5178,8 +5253,9 @@ SELECT d, true AND d FROM t9;
 SELECT d, false AND d FROM t9;
  | ---
  | - metadata:
- |   - name: D
- |     type: string
+ |   - type: string
+ |     name: D
+ |     is_nullable: false
  |   - name: false AND d
  |     type: boolean
  |   rows:
@@ -5203,8 +5279,9 @@ SELECT d, d AND true FROM t9;
 SELECT d, d AND false FROM t9;
  | ---
  | - metadata:
- |   - name: D
- |     type: string
+ |   - type: string
+ |     name: D
+ |     is_nullable: false
  |   - name: d AND false
  |     type: boolean
  |   rows:
@@ -5553,8 +5630,9 @@ SELECT d, true AND d FROM t9 WHERE d = 'TRUE';
 SELECT d, false AND d FROM t9 WHERE d = 'TRUE';
  | ---
  | - metadata:
- |   - name: D
- |     type: string
+ |   - type: string
+ |     name: D
+ |     is_nullable: false
  |   - name: false AND d
  |     type: boolean
  |   rows:
@@ -5578,8 +5656,9 @@ SELECT d, d AND true FROM t9 WHERE d = 'TRUE';
 SELECT d, d AND false FROM t9 WHERE d = 'TRUE';
  | ---
  | - metadata:
- |   - name: D
- |     type: string
+ |   - type: string
+ |     name: D
+ |     is_nullable: false
  |   - name: d AND false
  |     type: boolean
  |   rows:
@@ -5733,8 +5812,9 @@ SELECT d, true AND d FROM t9 WHERE d = 'true';
 SELECT d, false AND d FROM t9 WHERE d = 'true';
  | ---
  | - metadata:
- |   - name: D
- |     type: string
+ |   - type: string
+ |     name: D
+ |     is_nullable: false
  |   - name: false AND d
  |     type: boolean
  |   rows:
@@ -5758,8 +5838,9 @@ SELECT d, d AND true FROM t9 WHERE d = 'true';
 SELECT d, d AND false FROM t9 WHERE d = 'true';
  | ---
  | - metadata:
- |   - name: D
- |     type: string
+ |   - type: string
+ |     name: D
+ |     is_nullable: false
  |   - name: d AND false
  |     type: boolean
  |   rows:
@@ -5913,8 +5994,9 @@ SELECT d, true AND d FROM t9 WHERE d = 'FALSE';
 SELECT d, false AND d FROM t9 WHERE d = 'FALSE';
  | ---
  | - metadata:
- |   - name: D
- |     type: string
+ |   - type: string
+ |     name: D
+ |     is_nullable: false
  |   - name: false AND d
  |     type: boolean
  |   rows:
@@ -5938,8 +6020,9 @@ SELECT d, d AND true FROM t9 WHERE d = 'FALSE';
 SELECT d, d AND false FROM t9 WHERE d = 'FALSE';
  | ---
  | - metadata:
- |   - name: D
- |     type: string
+ |   - type: string
+ |     name: D
+ |     is_nullable: false
  |   - name: d AND false
  |     type: boolean
  |   rows:
@@ -6093,8 +6176,9 @@ SELECT d, true AND d FROM t9 WHERE d = 'false';
 SELECT d, false AND d FROM t9 WHERE d = 'false';
  | ---
  | - metadata:
- |   - name: D
- |     type: string
+ |   - type: string
+ |     name: D
+ |     is_nullable: false
  |   - name: false AND d
  |     type: boolean
  |   rows:
@@ -6118,8 +6202,9 @@ SELECT d, d AND true FROM t9 WHERE d = 'false';
 SELECT d, d AND false FROM t9 WHERE d = 'false';
  | ---
  | - metadata:
- |   - name: D
- |     type: string
+ |   - type: string
+ |     name: D
+ |     is_nullable: false
  |   - name: d AND false
  |     type: boolean
  |   rows:
diff --git a/test/sql/check-clear-ephemeral.result b/test/sql/check-clear-ephemeral.result
index 7d0be5ffb..dd9660220 100644
--- a/test/sql/check-clear-ephemeral.result
+++ b/test/sql/check-clear-ephemeral.result
@@ -25,8 +25,9 @@ box.execute("WITH RECURSIVE cnt(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM cnt W
 box.execute("SELECT a FROM t1 ORDER BY b, a LIMIT 10 OFFSET 20;");
 ---
 - metadata:
-  - name: A
-    type: integer
+  - type: integer
+    name: A
+    is_nullable: true
   rows:
   - [840]
   - [880]
diff --git a/test/sql/collation.result b/test/sql/collation.result
index 137a38bb4..750e9c509 100644
--- a/test/sql/collation.result
+++ b/test/sql/collation.result
@@ -67,8 +67,9 @@ box.execute([[INSERT INTO tu VALUES ('Latin Small Letter Dotless I U+0131','ı')
 box.execute([[SELECT descriptor, upper(letter) AS upper,lower(letter) AS lower FROM tu;]])
 ---
 - metadata:
-  - name: DESCRIPTOR
-    type: string
+  - type: string
+    name: DESCRIPTOR
+    is_nullable: false
   - name: UPPER
     type: string
   - name: LOWER
@@ -83,8 +84,9 @@ box.execute([[SELECT descriptor, upper(letter) AS upper,lower(letter) AS lower F
 box.execute([[SELECT descriptor, upper(letter COLLATE "TURKISH") AS upper,lower(letter COLLATE "TURKISH") AS lower FROM tu;]])
 ---
 - metadata:
-  - name: DESCRIPTOR
-    type: string
+  - type: string
+    name: DESCRIPTOR
+    is_nullable: false
   - type: string
     name: UPPER
     collation: TURKISH
@@ -112,12 +114,14 @@ box.execute([[INSERT INTO tu VALUES ('German Small Letter Sharp S U+00DF','ß');
 box.execute([[SELECT descriptor, upper(letter), letter FROM tu where UPPER(letter) = 'SS';]])
 ---
 - metadata:
-  - name: DESCRIPTOR
-    type: string
+  - type: string
+    name: DESCRIPTOR
+    is_nullable: false
   - name: upper(letter)
     type: string
-  - name: LETTER
-    type: string
+  - type: string
+    name: LETTER
+    is_nullable: true
   rows:
   - ['German Small Letter Sharp S U+00DF', 'SS', 'ß']
 ...
@@ -125,13 +129,15 @@ box.execute([[SELECT descriptor, upper(letter), letter FROM tu where UPPER(lette
 box.execute([[SELECT descriptor, upper(letter COLLATE "GERMAN"), letter FROM tu where UPPER(letter COLLATE "GERMAN") = 'SS';]])
 ---
 - metadata:
-  - name: DESCRIPTOR
-    type: string
+  - type: string
+    name: DESCRIPTOR
+    is_nullable: false
   - type: string
     name: upper(letter COLLATE "GERMAN")
     collation: GERMAN
-  - name: LETTER
-    type: string
+  - type: string
+    name: LETTER
+    is_nullable: true
   rows:
   - ['German Small Letter Sharp S U+00DF', 'SS', 'ß']
 ...
@@ -325,6 +331,7 @@ box.execute("SELECT b FROM t UNION SELECT a FROM t;")
 ---
 - metadata:
   - type: string
+    is_nullable: true
     name: B
     collation: binary
   rows: []
@@ -332,8 +339,9 @@ box.execute("SELECT b FROM t UNION SELECT a FROM t;")
 box.execute("SELECT a FROM t UNION SELECT c FROM t;")
 ---
 - metadata:
-  - name: A
-    type: string
+  - type: string
+    name: A
+    is_nullable: true
   rows: []
 ...
 box.execute("SELECT c COLLATE \"binary\" FROM t UNION SELECT a FROM t;")
@@ -407,6 +415,7 @@ box.execute("SELECT s1 FROM t0;")
 ---
 - metadata:
   - type: string
+    is_nullable: true
     name: S1
     collation: unicode_ci
   rows:
@@ -469,8 +478,9 @@ box.execute("SELECT * FROM t1;")
 box.execute("SELECT s1 FROM t0;")
 ---
 - metadata:
-  - name: S1
-    type: string
+  - type: string
+    name: S1
+    is_nullable: true
   rows:
   - ['a']
 ...
@@ -502,8 +512,9 @@ box.execute("INSERT INTO t4a VALUES('ghi','ghi',3);")
 box.execute("SELECT c FROM t4a WHERE (a||'') = b;")
 ---
 - metadata:
-  - name: C
-    type: integer
+  - type: integer
+    name: C
+    is_nullable: false
   rows:
   - [1]
   - [3]
@@ -514,8 +525,9 @@ box.execute("SELECT c FROM t4a WHERE (a||'') = b;")
 box.execute("SELECT c FROM t4a WHERE (a COLLATE \"binary\"||'') = b;")
 ---
 - metadata:
-  - name: C
-    type: integer
+  - type: integer
+    name: C
+    is_nullable: false
   rows:
   - [3]
 ...
@@ -538,16 +550,18 @@ box.execute("SELECT c FROM t4a WHERE (a COLLATE \"binary\"||'') = b COLLATE \"un
 box.execute("SELECT c FROM t4a WHERE (a||'')=(b||'');")
 ---
 - metadata:
-  - name: C
-    type: integer
+  - type: integer
+    name: C
+    is_nullable: false
   rows:
   - [3]
 ...
 box.execute("SELECT c FROM t4a WHERE (a||b)=(b||a);")
 ---
 - metadata:
-  - name: C
-    type: integer
+  - type: integer
+    name: C
+    is_nullable: false
   rows:
   - [3]
 ...
@@ -568,8 +582,9 @@ box.execute("INSERT INTO t4b VALUES('ghi','ghi',3);")
 box.execute("SELECT c FROM t4a WHERE (a||b)=(b||a);")
 ---
 - metadata:
-  - name: C
-    type: integer
+  - type: integer
+    name: C
+    is_nullable: false
   rows:
   - [3]
 ...
@@ -578,8 +593,9 @@ box.execute("SELECT c FROM t4a WHERE (a||b)=(b||a);")
 box.execute("SELECT c FROM t4a WHERE (a||b COLLATE \"binary\")=(b||a);")
 ---
 - metadata:
-  - name: C
-    type: integer
+  - type: integer
+    name: C
+    is_nullable: false
   rows:
   - [3]
 ...
@@ -603,6 +619,7 @@ box.execute("SELECT a FROM t4b ORDER BY a COLLATE \"unicode_ci\" || ''")
 ---
 - metadata:
   - type: string
+    is_nullable: true
     name: A
     collation: unicode_ci
   rows:
@@ -615,6 +632,7 @@ box.execute("SELECT a FROM t4b ORDER BY a || b")
 ---
 - metadata:
   - type: string
+    is_nullable: true
     name: A
     collation: unicode_ci
   rows:
diff --git a/test/sql/gh-3199-no-mem-leaks.result b/test/sql/gh-3199-no-mem-leaks.result
index e7ba1d29c..6dc5ad212 100644
--- a/test/sql/gh-3199-no-mem-leaks.result
+++ b/test/sql/gh-3199-no-mem-leaks.result
@@ -26,10 +26,12 @@ box.execute('INSERT INTO test VALUES (1, 1, 1), (2, 2, 2)')
 box.execute('SELECT x, y, x + y FROM test ORDER BY y')
 ---
 - metadata:
-  - name: X
-    type: integer
-  - name: Y
-    type: integer
+  - type: integer
+    name: X
+    is_nullable: true
+  - type: integer
+    name: Y
+    is_nullable: true
   - name: x + y
     type: integer
   rows:
@@ -43,10 +45,12 @@ fiber.info()[fiber.self().id()].memory.used
 box.execute('SELECT x, y, x + y FROM test ORDER BY y')
 ---
 - metadata:
-  - name: X
-    type: integer
-  - name: Y
-    type: integer
+  - type: integer
+    name: X
+    is_nullable: true
+  - type: integer
+    name: Y
+    is_nullable: true
   - name: x + y
     type: integer
   rows:
@@ -56,10 +60,12 @@ box.execute('SELECT x, y, x + y FROM test ORDER BY y')
 box.execute('SELECT x, y, x + y FROM test ORDER BY y')
 ---
 - metadata:
-  - name: X
-    type: integer
-  - name: Y
-    type: integer
+  - type: integer
+    name: X
+    is_nullable: true
+  - type: integer
+    name: Y
+    is_nullable: true
   - name: x + y
     type: integer
   rows:
@@ -69,10 +75,12 @@ box.execute('SELECT x, y, x + y FROM test ORDER BY y')
 box.execute('SELECT x, y, x + y FROM test ORDER BY y')
 ---
 - metadata:
-  - name: X
-    type: integer
-  - name: Y
-    type: integer
+  - type: integer
+    name: X
+    is_nullable: true
+  - type: integer
+    name: Y
+    is_nullable: true
   - name: x + y
     type: integer
   rows:
@@ -82,10 +90,12 @@ box.execute('SELECT x, y, x + y FROM test ORDER BY y')
 box.execute('SELECT x, y, x + y FROM test ORDER BY y')
 ---
 - metadata:
-  - name: X
-    type: integer
-  - name: Y
-    type: integer
+  - type: integer
+    name: X
+    is_nullable: true
+  - type: integer
+    name: Y
+    is_nullable: true
   - name: x + y
     type: integer
   rows:
@@ -111,12 +121,14 @@ box.execute('INSERT INTO test2 VALUES (3, \'test\', 3), (4, \'xx\', 4)')
 box.execute('SELECT a, id + 2, b FROM test2 WHERE b < id * 2 ORDER BY a ')
 ---
 - metadata:
-  - name: A
-    type: string
+  - type: string
+    name: A
+    is_nullable: true
   - name: id + 2
     type: integer
-  - name: B
-    type: integer
+  - type: integer
+    name: B
+    is_nullable: true
   rows:
   - ['abc', 3, 1]
   - ['hello', 4, 2]
@@ -130,12 +142,14 @@ fiber.info()[fiber.self().id()].memory.used
 box.execute('SELECT a, id + 2 * b, a FROM test2 WHERE b < id * 2 ORDER BY a ')
 ---
 - metadata:
-  - name: A
-    type: string
+  - type: string
+    name: A
+    is_nullable: true
   - name: id + 2 * b
     type: integer
-  - name: A
-    type: string
+  - type: string
+    name: A
+    is_nullable: true
   rows:
   - ['abc', 3, 'abc']
   - ['hello', 6, 'hello']
@@ -145,12 +159,14 @@ box.execute('SELECT a, id + 2 * b, a FROM test2 WHERE b < id * 2 ORDER BY a ')
 box.execute('SELECT a, id + 2 * b, a FROM test2 WHERE b < id * 2 ORDER BY a ')
 ---
 - metadata:
-  - name: A
-    type: string
+  - type: string
+    name: A
+    is_nullable: true
   - name: id + 2 * b
     type: integer
-  - name: A
-    type: string
+  - type: string
+    name: A
+    is_nullable: true
   rows:
   - ['abc', 3, 'abc']
   - ['hello', 6, 'hello']
@@ -160,12 +176,14 @@ box.execute('SELECT a, id + 2 * b, a FROM test2 WHERE b < id * 2 ORDER BY a ')
 box.execute('SELECT a, id + 2 * b, a FROM test2 WHERE b < id * 2 ORDER BY a ')
 ---
 - metadata:
-  - name: A
-    type: string
+  - type: string
+    name: A
+    is_nullable: true
   - name: id + 2 * b
     type: integer
-  - name: A
-    type: string
+  - type: string
+    name: A
+    is_nullable: true
   rows:
   - ['abc', 3, 'abc']
   - ['hello', 6, 'hello']
@@ -179,12 +197,14 @@ fiber.info()[fiber.self().id()].memory.used
 box.execute('SELECT x, y + 3 * b, b FROM test2, test WHERE b = x')
 ---
 - metadata:
-  - name: X
-    type: integer
+  - type: integer
+    name: X
+    is_nullable: true
   - name: y + 3 * b
     type: integer
-  - name: B
-    type: integer
+  - type: integer
+    name: B
+    is_nullable: true
   rows:
   - [1, 4, 1]
   - [2, 8, 2]
@@ -192,12 +212,14 @@ box.execute('SELECT x, y + 3 * b, b FROM test2, test WHERE b = x')
 box.execute('SELECT x, y + 3 * b, b FROM test2, test WHERE b = x')
 ---
 - metadata:
-  - name: X
-    type: integer
+  - type: integer
+    name: X
+    is_nullable: true
   - name: y + 3 * b
     type: integer
-  - name: B
-    type: integer
+  - type: integer
+    name: B
+    is_nullable: true
   rows:
   - [1, 4, 1]
   - [2, 8, 2]
@@ -205,12 +227,14 @@ box.execute('SELECT x, y + 3 * b, b FROM test2, test WHERE b = x')
 box.execute('SELECT x, y + 3 * b, b FROM test2, test WHERE b = x')
 ---
 - metadata:
-  - name: X
-    type: integer
+  - type: integer
+    name: X
+    is_nullable: true
   - name: y + 3 * b
     type: integer
-  - name: B
-    type: integer
+  - type: integer
+    name: B
+    is_nullable: true
   rows:
   - [1, 4, 1]
   - [2, 8, 2]
diff --git a/test/sql/gh2141-delete-trigger-drop-table.result b/test/sql/gh2141-delete-trigger-drop-table.result
index 1d373f57e..735b932a5 100644
--- a/test/sql/gh2141-delete-trigger-drop-table.result
+++ b/test/sql/gh2141-delete-trigger-drop-table.result
@@ -41,10 +41,12 @@ box.execute("CREATE TRIGGER tt_ad AFTER DELETE ON t FOR EACH ROW BEGIN SELECT 1;
 box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\"")
 ---
 - metadata:
-  - name: name
-    type: string
-  - name: opts
-    type: map
+  - type: string
+    name: name
+    is_nullable: false
+  - type: map
+    name: opts
+    is_nullable: false
   rows:
   - ['TT_AD', {'sql': 'CREATE TRIGGER tt_ad AFTER DELETE ON t FOR EACH ROW BEGIN SELECT
         1; END'}]
@@ -68,9 +70,11 @@ box.execute("DROP TABLE t")
 box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\"")
 ---
 - metadata:
-  - name: name
-    type: string
-  - name: opts
-    type: map
+  - type: string
+    name: name
+    is_nullable: false
+  - type: map
+    name: opts
+    is_nullable: false
   rows: []
 ...
diff --git a/test/sql/gh2251-multiple-update.result b/test/sql/gh2251-multiple-update.result
index 42ebf7f55..a56c4b8b7 100644
--- a/test/sql/gh2251-multiple-update.result
+++ b/test/sql/gh2251-multiple-update.result
@@ -29,8 +29,9 @@ box.execute("UPDATE t1 SET e=e+1 WHERE b IN (SELECT b FROM t1);")
 box.execute("SELECT e FROM t1")
 ---
 - metadata:
-  - name: E
-    type: integer
+  - type: integer
+    name: E
+    is_nullable: true
   rows:
   - [7]
   - [8]
@@ -54,8 +55,9 @@ box.execute("UPDATE t2 SET e=e+1 WHERE b IN (SELECT b FROM t2);")
 box.execute("SELECT e FROM t2")
 ---
 - metadata:
-  - name: E
-    type: integer
+  - type: integer
+    name: E
+    is_nullable: true
   rows:
   - [6]
   - [7]
diff --git a/test/sql/iproto.result b/test/sql/iproto.result
index 67acd0ac1..05c59318c 100644
--- a/test/sql/iproto.result
+++ b/test/sql/iproto.result
@@ -682,18 +682,24 @@ res = cn:execute("PRAGMA table_info(t1)")
 ...
 res.metadata
 ---
-- - name: cid
-    type: integer
-  - name: name
-    type: text
-  - name: type
-    type: text
-  - name: notnull
-    type: integer
-  - name: dflt_value
-    type: text
-  - name: pk
-    type: integer
+- - type: integer
+    name: cid
+    is_nullable: false
+  - type: text
+    name: name
+    is_nullable: false
+  - type: text
+    name: type
+    is_nullable: false
+  - type: integer
+    name: notnull
+    is_nullable: false
+  - type: text
+    name: dflt_value
+    is_nullable: false
+  - type: integer
+    name: pk
+    is_nullable: false
 ...
 -- EXPLAIN
 res = cn:execute("EXPLAIN SELECT 1")
@@ -701,36 +707,48 @@ res = cn:execute("EXPLAIN SELECT 1")
 ...
 res.metadata
 ---
-- - name: addr
-    type: integer
-  - name: opcode
-    type: text
-  - name: p1
-    type: integer
-  - name: p2
-    type: integer
-  - name: p3
-    type: integer
-  - name: p4
-    type: text
-  - name: p5
-    type: text
-  - name: comment
-    type: text
+- - type: integer
+    name: addr
+    is_nullable: false
+  - type: text
+    name: opcode
+    is_nullable: false
+  - type: integer
+    name: p1
+    is_nullable: false
+  - type: integer
+    name: p2
+    is_nullable: false
+  - type: integer
+    name: p3
+    is_nullable: false
+  - type: text
+    name: p4
+    is_nullable: false
+  - type: text
+    name: p5
+    is_nullable: false
+  - type: text
+    name: comment
+    is_nullable: false
 ...
 res = cn:execute("EXPLAIN QUERY PLAN SELECT COUNT(*) FROM t1")
 ---
 ...
 res.metadata
 ---
-- - name: selectid
-    type: integer
-  - name: order
-    type: integer
-  - name: from
-    type: integer
-  - name: detail
-    type: text
+- - type: integer
+    name: selectid
+    is_nullable: false
+  - type: integer
+    name: order
+    is_nullable: false
+  - type: integer
+    name: from
+    is_nullable: false
+  - type: text
+    name: detail
+    is_nullable: false
 ...
 -- When pragma count_changes is on, statements INSERT, REPLACE and
 -- UPDATE returns number of changed columns. Make sure that this
@@ -742,24 +760,27 @@ cn:execute("PRAGMA count_changes = 1;")
 cn:execute("INSERT INTO t1 VALUES (1), (2), (3);")
 ---
 - metadata:
-  - name: rows inserted
-    type: integer
+  - type: integer
+    name: rows inserted
+    is_nullable: false
   rows:
   - [3]
 ...
 cn:execute("REPLACE INTO t1 VALUES (2), (3), (4), (5);")
 ---
 - metadata:
-  - name: rows replaced
-    type: integer
+  - type: integer
+    name: rows replaced
+    is_nullable: false
   rows:
   - [4]
 ...
 cn:execute("UPDATE t1 SET id = id + 100 WHERE id > 10;")
 ---
 - metadata:
-  - name: rows updated
-    type: integer
+  - type: integer
+    name: rows updated
+    is_nullable: false
   rows:
   - [0]
 ...
diff --git a/test/sql/misc.result b/test/sql/misc.result
index a157ddbc1..820cf5119 100644
--- a/test/sql/misc.result
+++ b/test/sql/misc.result
@@ -174,10 +174,12 @@ s:insert(t)
 box.execute('SELECT field70, field64 FROM test')
 ---
 - metadata:
-  - name: FIELD70
-    type: unsigned
-  - name: FIELD64
-    type: unsigned
+  - type: unsigned
+    name: FIELD70
+    is_nullable: false
+  - type: unsigned
+    name: FIELD64
+    is_nullable: false
   rows:
   - [70, 64]
 ...
@@ -188,12 +190,15 @@ pk:alter({parts = {66}})
 box.execute('SELECT field66, field68, field70 FROM test')
 ---
 - metadata:
-  - name: FIELD66
-    type: unsigned
-  - name: FIELD68
-    type: unsigned
-  - name: FIELD70
-    type: unsigned
+  - type: unsigned
+    name: FIELD66
+    is_nullable: false
+  - type: unsigned
+    name: FIELD68
+    is_nullable: false
+  - type: unsigned
+    name: FIELD70
+    is_nullable: false
   rows:
   - [66, 68, 70]
 ...
diff --git a/test/sql/on-conflict.result b/test/sql/on-conflict.result
index 6851e217e..909b68d8f 100644
--- a/test/sql/on-conflict.result
+++ b/test/sql/on-conflict.result
@@ -130,10 +130,12 @@ box.execute("UPDATE OR IGNORE tj SET s1 = s1 + 1;")
 box.execute("SELECT s1, s2 FROM tj;")
 ---
 - metadata:
-  - name: S1
-    type: integer
-  - name: S2
-    type: integer
+  - type: integer
+    name: S1
+    is_nullable: true
+  - type: integer
+    name: S2
+    is_nullable: true
   rows:
   - [1, 2]
   - [3, 3]
@@ -145,10 +147,12 @@ box.execute("UPDATE OR IGNORE tj SET s2 = s2 + 1;")
 box.execute("SELECT s1, s2 FROM tj;")
 ---
 - metadata:
-  - name: S1
-    type: integer
-  - name: S2
-    type: integer
+  - type: integer
+    name: S1
+    is_nullable: true
+  - type: integer
+    name: S2
+    is_nullable: true
   rows:
   - [1, 2]
   - [3, 4]
diff --git a/test/sql/persistency.result b/test/sql/persistency.result
index f8f992c39..90c4b36a6 100644
--- a/test/sql/persistency.result
+++ b/test/sql/persistency.result
@@ -38,10 +38,12 @@ box.execute("INSERT INTO foobar VALUES (1, 'duplicate')")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -54,10 +56,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar LIMIT 2")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -69,10 +73,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar LIMIT 2")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo=2")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -83,10 +89,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo=2")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo>2")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -97,10 +105,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo>2")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo>=2")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -112,10 +122,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo>=2")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo=10000")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -125,10 +137,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo=10000")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo>10000")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -138,10 +152,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo>10000")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<2")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -152,10 +168,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<2")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<2.001")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -167,10 +185,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<2.001")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<=2")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -182,10 +202,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<=2")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<100")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -197,10 +219,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<100")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE bar='foo'")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -227,10 +251,12 @@ box.execute("SELECT count(*) FROM foobar WHERE bar='foo'")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar ORDER BY bar")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -243,10 +269,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar ORDER BY bar")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar ORDER BY bar DESC")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -320,10 +348,12 @@ box.execute("CREATE TRIGGER tfoobar AFTER INSERT ON foobar FOR EACH ROW BEGIN IN
 box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\"");
 ---
 - metadata:
-  - name: name
-    type: string
-  - name: opts
-    type: map
+  - type: string
+    name: name
+    is_nullable: false
+  - type: map
+    name: opts
+    is_nullable: false
   rows:
   - ['TFOOBAR', {'sql': 'CREATE TRIGGER tfoobar AFTER INSERT ON foobar FOR EACH ROW
         BEGIN INSERT INTO barfoo VALUES (''trigger test'', 9999); END'}]
@@ -340,8 +370,9 @@ box.execute("WITH RECURSIVE cnt(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM cnt W
 box.execute("SELECT a FROM t1 ORDER BY b, a LIMIT 10 OFFSET 20;");
 ---
 - metadata:
-  - name: A
-    type: integer
+  - type: integer
+    name: A
+    is_nullable: true
   rows:
   - [840]
   - [880]
@@ -359,10 +390,12 @@ test_run:cmd('restart server default');
 box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\"");
 ---
 - metadata:
-  - name: name
-    type: string
-  - name: opts
-    type: map
+  - type: string
+    name: name
+    is_nullable: false
+  - type: map
+    name: opts
+    is_nullable: false
   rows:
   - ['TFOOBAR', {'sql': 'CREATE TRIGGER tfoobar AFTER INSERT ON foobar FOR EACH ROW
         BEGIN INSERT INTO barfoo VALUES (''trigger test'', 9999); END'}]
@@ -386,10 +419,12 @@ box.execute("SELECT * FROM barfoo WHERE foo = 9999");
 box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\"")
 ---
 - metadata:
-  - name: name
-    type: string
-  - name: opts
-    type: map
+  - type: string
+    name: name
+    is_nullable: false
+  - type: map
+    name: opts
+    is_nullable: false
   rows:
   - ['TFOOBAR', {'sql': 'CREATE TRIGGER tfoobar AFTER INSERT ON foobar FOR EACH ROW
         BEGIN INSERT INTO barfoo VALUES (''trigger test'', 9999); END'}]
@@ -409,10 +444,12 @@ box.execute("DROP TRIGGER tfoobar")
 box.execute("SELECT \"name\", \"opts\" FROM \"_trigger\"")
 ---
 - metadata:
-  - name: name
-    type: string
-  - name: opts
-    type: map
+  - type: string
+    name: name
+    is_nullable: false
+  - type: map
+    name: opts
+    is_nullable: false
   rows: []
 ...
 -- prove barfoo2 still exists
@@ -447,8 +484,9 @@ box.execute("SELECT * FROM foobar");
 box.execute("SELECT a FROM t1 ORDER BY b, a LIMIT 10 OFFSET 20;");
 ---
 - metadata:
-  - name: A
-    type: integer
+  - type: integer
+    name: A
+    is_nullable: true
   rows:
   - [840]
   - [880]
diff --git a/test/sql/row-count.result b/test/sql/row-count.result
index 6bf74ed9b..23af45e39 100644
--- a/test/sql/row-count.result
+++ b/test/sql/row-count.result
@@ -295,14 +295,18 @@ box.execute("SELECT ROW_COUNT();")
 box.execute("EXPLAIN QUERY PLAN INSERT INTO t1 VALUES ('b'), ('c'), ('d');")
 ---
 - metadata:
-  - name: selectid
-    type: integer
-  - name: order
-    type: integer
-  - name: from
-    type: integer
-  - name: detail
-    type: text
+  - type: integer
+    name: selectid
+    is_nullable: false
+  - type: integer
+    name: order
+    is_nullable: false
+  - type: integer
+    name: from
+    is_nullable: false
+  - type: text
+    name: detail
+    is_nullable: false
   rows:
   - [0, 0, 0, 'SCAN TABLE T2 (~262144 rows)']
 ...
@@ -317,8 +321,9 @@ box.execute("SELECT ROW_COUNT();")
 box.execute('PRAGMA recursive_triggers')
 ---
 - metadata:
-  - name: recursive_triggers
-    type: integer
+  - type: integer
+    name: recursive_triggers
+    is_nullable: false
   rows:
   - [1]
 ...
diff --git a/test/sql/sql-debug.result b/test/sql/sql-debug.result
index b19075366..cdaed9a00 100644
--- a/test/sql/sql-debug.result
+++ b/test/sql/sql-debug.result
@@ -18,8 +18,9 @@ box.execute('PRAGMA parser_trace = 1')
 box.execute('PRAGMA parser_trace')
 ---
 - metadata:
-  - name: parser_trace
-    type: integer
+  - type: integer
+    name: parser_trace
+    is_nullable: false
   rows:
   - [1]
 ...
@@ -33,10 +34,12 @@ box.execute('PRAGMA parser_trace = '.. result[1][1])
 box.execute('PRAGMA')
 ---
 - metadata:
-  - name: pragma_name
-    type: text
-  - name: pragma_value
-    type: integer
+  - type: text
+    name: pragma_name
+    is_nullable: false
+  - type: integer
+    name: pragma_value
+    is_nullable: false
   rows:
   - ['count_changes', 0]
   - ['defer_foreign_keys', 0]
diff --git a/test/sql/transition.result b/test/sql/transition.result
index 9738092fe..bba4ddefc 100644
--- a/test/sql/transition.result
+++ b/test/sql/transition.result
@@ -35,10 +35,12 @@ box.execute("INSERT INTO foobar VALUES (1, 'duplicate')")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -51,10 +53,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar LIMIT 2")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -66,10 +70,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar LIMIT 2")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo=2")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -80,10 +86,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo=2")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo>2")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -94,10 +102,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo>2")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo>=2")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -109,10 +119,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo>=2")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo=10000")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -122,10 +134,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo=10000")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo>10000")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -135,10 +149,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo>10000")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<2")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -149,10 +165,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<2")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<2.001")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -164,10 +182,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<2.001")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<=2")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -179,10 +199,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<=2")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<100")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -194,10 +216,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE foo<100")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar WHERE bar='foo'")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -224,10 +248,12 @@ box.execute("SELECT count(*) FROM foobar WHERE bar='foo'")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar ORDER BY bar")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -240,10 +266,12 @@ box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar ORDER BY bar")
 box.execute("SELECT bar, foo, 42, 'awesome' FROM foobar ORDER BY bar DESC")
 ---
 - metadata:
-  - name: BAR
-    type: string
-  - name: FOO
-    type: integer
+  - type: string
+    name: BAR
+    is_nullable: true
+  - type: integer
+    name: FOO
+    is_nullable: false
   - name: '42'
     type: integer
   - name: '''awesome'''
@@ -318,10 +346,12 @@ box.execute("INSERT INTO barfoo VALUES ('xfoo', 1)")
 box.execute("SELECT foo, bar FROM barfoo")
 ---
 - metadata:
-  - name: FOO
-    type: number
-  - name: BAR
-    type: string
+  - type: number
+    name: FOO
+    is_nullable: false
+  - type: string
+    name: BAR
+    is_nullable: true
   rows:
   - [1, 'foo']
   - [2, 'bar']
@@ -330,30 +360,36 @@ box.execute("SELECT foo, bar FROM barfoo")
 box.execute("SELECT foo, bar FROM barfoo WHERE foo==2")
 ---
 - metadata:
-  - name: FOO
-    type: number
-  - name: BAR
-    type: string
+  - type: number
+    name: FOO
+    is_nullable: false
+  - type: string
+    name: BAR
+    is_nullable: true
   rows:
   - [2, 'bar']
 ...
 box.execute("SELECT foo, bar FROM barfoo WHERE bar=='foobar'")
 ---
 - metadata:
-  - name: FOO
-    type: number
-  - name: BAR
-    type: string
+  - type: number
+    name: FOO
+    is_nullable: false
+  - type: string
+    name: BAR
+    is_nullable: true
   rows:
   - [1000, 'foobar']
 ...
 box.execute("SELECT foo, bar FROM barfoo WHERE foo>=2")
 ---
 - metadata:
-  - name: FOO
-    type: number
-  - name: BAR
-    type: string
+  - type: number
+    name: FOO
+    is_nullable: false
+  - type: string
+    name: BAR
+    is_nullable: true
   rows:
   - [2, 'bar']
   - [1000, 'foobar']
@@ -361,10 +397,12 @@ box.execute("SELECT foo, bar FROM barfoo WHERE foo>=2")
 box.execute("SELECT foo, bar FROM barfoo WHERE foo<=2")
 ---
 - metadata:
-  - name: FOO
-    type: number
-  - name: BAR
-    type: string
+  - type: number
+    name: FOO
+    is_nullable: false
+  - type: string
+    name: BAR
+    is_nullable: true
   rows:
   - [1, 'foo']
   - [2, 'bar']
diff --git a/test/sql/types.result b/test/sql/types.result
index 1ad52e8c7..eb1eac989 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -375,37 +375,42 @@ box.execute("INSERT INTO t1 VALUES (1, 1);")
 box.execute("SELECT a FROM t1 WHERE a IN (1.1, 2.1);")
 ---
 - metadata:
-  - name: A
-    type: integer
+  - type: integer
+    name: A
+    is_nullable: true
   rows: []
 ...
 box.execute("SELECT a FROM t1 WHERE a = 1.1;")
 ---
 - metadata:
-  - name: A
-    type: integer
+  - type: integer
+    name: A
+    is_nullable: true
   rows: []
 ...
 box.execute("SELECT a FROM t1 WHERE a = 1.0;")
 ---
 - metadata:
-  - name: A
-    type: integer
+  - type: integer
+    name: A
+    is_nullable: true
   rows:
   - [1]
 ...
 box.execute("SELECT a FROM t1 WHERE a > 1.1;")
 ---
 - metadata:
-  - name: A
-    type: integer
+  - type: integer
+    name: A
+    is_nullable: true
   rows: []
 ...
 box.execute("SELECT a FROM t1 WHERE a < 1.1;")
 ---
 - metadata:
-  - name: A
-    type: integer
+  - type: integer
+    name: A
+    is_nullable: true
   rows:
   - [1]
 ...
@@ -427,23 +432,26 @@ box.execute("INSERT INTO t1 VALUES (1, 1, 1);")
 box.execute("SELECT a FROM t1 WHERE a = 1.0 AND b > 0.5;")
 ---
 - metadata:
-  - name: A
-    type: integer
+  - type: integer
+    name: A
+    is_nullable: true
   rows:
   - [1]
 ...
 box.execute("SELECT a FROM t1 WHERE a = 1.5 AND b IS NULL;")
 ---
 - metadata:
-  - name: A
-    type: integer
+  - type: integer
+    name: A
+    is_nullable: true
   rows: []
 ...
 box.execute("SELECT a FROM t1 WHERE a IS NULL AND b IS NULL;")
 ---
 - metadata:
-  - name: A
-    type: integer
+  - type: integer
+    name: A
+    is_nullable: true
   rows: []
 ...
 box.space.T1:drop()
@@ -474,8 +482,9 @@ s:insert({ 1, 1 })
 box.execute("SELECT a FROM t1 WHERE a IN (1.1, 2.1);")
 ---
 - metadata:
-  - name: A
-    type: unsigned
+  - type: unsigned
+    name: A
+    is_nullable: false
   rows: []
 ...
 s:drop()
@@ -711,8 +720,9 @@ box.execute("INSERT INTO t VALUES (3, 18446744073709551613)")
 box.execute("SELECT i FROM t;")
 ---
 - metadata:
-  - name: I
-    type: integer
+  - type: integer
+    name: I
+    is_nullable: true
   rows:
   - [18446744073709551615]
   - [18446744073709551614]
@@ -721,16 +731,18 @@ box.execute("SELECT i FROM t;")
 box.execute("SELECT i FROM t WHERE i = 18446744073709551615;")
 ---
 - metadata:
-  - name: I
-    type: integer
+  - type: integer
+    name: I
+    is_nullable: true
   rows:
   - [18446744073709551615]
 ...
 box.execute("SELECT i FROM t WHERE i BETWEEN 18446744073709551613 AND 18446744073709551615;")
 ---
 - metadata:
-  - name: I
-    type: integer
+  - type: integer
+    name: I
+    is_nullable: true
   rows:
   - [18446744073709551615]
   - [18446744073709551614]
@@ -739,8 +751,9 @@ box.execute("SELECT i FROM t WHERE i BETWEEN 18446744073709551613 AND 1844674407
 box.execute("SELECT i FROM t ORDER BY i;")
 ---
 - metadata:
-  - name: I
-    type: integer
+  - type: integer
+    name: I
+    is_nullable: true
   rows:
   - [18446744073709551613]
   - [18446744073709551614]
@@ -754,8 +767,9 @@ box.execute("SELECT i FROM t ORDER BY -i;")
 box.execute("SELECT i FROM t ORDER BY i LIMIT 1;")
 ---
 - metadata:
-  - name: I
-    type: integer
+  - type: integer
+    name: I
+    is_nullable: true
   rows:
   - [18446744073709551613]
 ...
@@ -896,16 +910,18 @@ box.execute("CREATE INDEX i ON t(i);")
 box.execute("SELECT i FROM t WHERE i = 18446744073709551613;")
 ---
 - metadata:
-  - name: I
-    type: integer
+  - type: integer
+    name: I
+    is_nullable: true
   rows:
   - [18446744073709551613]
 ...
 box.execute("SELECT i FROM t WHERE i >= 18446744073709551613 ORDER BY i;")
 ---
 - metadata:
-  - name: I
-    type: integer
+  - type: integer
+    name: I
+    is_nullable: true
   rows:
   - [18446744073709551613]
 ...
@@ -916,8 +932,9 @@ box.execute("UPDATE t SET i = 18446744073709551615 WHERE i = 1844674407370955161
 box.execute("SELECT i FROM t;")
 ---
 - metadata:
-  - name: I
-    type: integer
+  - type: integer
+    name: I
+    is_nullable: true
   rows:
   - [18446744073709551615]
 ...
@@ -1072,8 +1089,9 @@ box.execute("INSERT INTO t1 VALUES (-3);")
 box.execute("SELECT id FROM t1;")
 ---
 - metadata:
-  - name: ID
-    type: unsigned
+  - type: unsigned
+    name: ID
+    is_nullable: false
   rows:
   - [0]
   - [1]
@@ -1361,16 +1379,18 @@ box.execute("CREATE INDEX iv ON t(v);")
 box.execute("SELECT v FROM t WHERE v = x'616263';")
 ---
 - metadata:
-  - name: V
-    type: varbinary
+  - type: varbinary
+    name: V
+    is_nullable: true
   rows:
   - ['abc']
 ...
 box.execute("SELECT v FROM t ORDER BY v;")
 ---
 - metadata:
-  - name: V
-    type: varbinary
+  - type: varbinary
+    name: V
+    is_nullable: true
   rows:
   - ['abc']
 ...
@@ -1381,8 +1401,9 @@ box.execute("UPDATE t SET v = x'636261' WHERE v = x'616263';")
 box.execute("SELECT v FROM t;")
 ---
 - metadata:
-  - name: V
-    type: varbinary
+  - type: varbinary
+    name: V
+    is_nullable: true
   rows:
   - ['cba']
 ...
diff --git a/test/sql/update-with-nested-select.result b/test/sql/update-with-nested-select.result
index 31724307f..84b2d79aa 100644
--- a/test/sql/update-with-nested-select.result
+++ b/test/sql/update-with-nested-select.result
@@ -34,8 +34,9 @@ box.execute("UPDATE t1 SET e=e+1 WHERE b IN (SELECT b FROM t1);");
 box.execute("SELECT e FROM t1");
 ---
 - metadata:
-  - name: E
-    type: integer
+  - type: integer
+    name: E
+    is_nullable: true
   rows:
   - [7]
   - [8]
-- 
2.15.1

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

* [Tarantool-patches] [PATCH 5/6] sql: extend result set with autoincrement
  2019-11-27 12:15 [Tarantool-patches] [PATCH 0/6] sql: extend response metadata Nikita Pettik
                   ` (3 preceding siblings ...)
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 4/6] sql: extend result set with nullability Nikita Pettik
@ 2019-11-27 12:15 ` Nikita Pettik
  2019-11-28 22:41   ` Vladislav Shpilevoy
  2019-12-18 15:17   ` Sergey Ostanevich
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 6/6] sql: extend result set with alias Nikita Pettik
  2019-11-28 22:55 ` [Tarantool-patches] [PATCH 0/6] sql: extend response metadata Vladislav Shpilevoy
  6 siblings, 2 replies; 40+ messages in thread
From: Nikita Pettik @ 2019-11-27 12:15 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

If result set contains column which features attached sequence
(AUTOINCREMENT in terms of SQL) then meta-information will contain
corresponding field ('autoicrement' : boolean) in response.

Part of #4407
---
 src/box/execute.c          | 18 +++++++++++++++---
 src/box/iproto_constants.h |  3 ++-
 src/box/lua/execute.c      |  5 +++++
 src/box/lua/net_box.c      | 10 +++++++---
 src/box/sql/select.c       |  6 ++++++
 src/box/sql/sqlInt.h       |  3 +++
 src/box/sql/vdbe.h         |  3 +++
 src/box/sql/vdbeInt.h      |  2 ++
 src/box/sql/vdbeapi.c      |  8 ++++++++
 src/box/sql/vdbeaux.c      |  7 +++++++
 10 files changed, 58 insertions(+), 7 deletions(-)

diff --git a/src/box/execute.c b/src/box/execute.c
index 98812ae1e..8305f6f3b 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -270,7 +270,7 @@ error:
 
 static size_t
 metadata_map_sizeof(const char *name, const char *type, const char *coll,
-		    int nullable)
+		    int nullable, bool is_autoincrement)
 {
 	uint32_t members_count = 2;
 	size_t map_size = 0;
@@ -284,6 +284,11 @@ metadata_map_sizeof(const char *name, const char *type, const char *coll,
 		map_size += mp_sizeof_uint(IPROTO_FIELD_NULLABLE);
 		map_size += mp_sizeof_bool(nullable);
 	}
+	if (is_autoincrement) {
+		members_count++;
+		map_size += mp_sizeof_uint(IPROTO_FIELD_AUTOINCREMENT);
+		map_size += mp_sizeof_bool(true);
+	}
 	map_size += mp_sizeof_uint(IPROTO_FIELD_NAME);
 	map_size += mp_sizeof_uint(IPROTO_FIELD_TYPE);
 	map_size += mp_sizeof_str(strlen(name));
@@ -319,6 +324,7 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
 		const char *name = sql_column_name(stmt, i);
 		const char *type = sql_column_datatype(stmt, i);
 		int nullable = sql_column_is_nullable(stmt, i);
+		bool is_autoincrement = sql_column_is_autoincrement(stmt, i);
 		/*
 		 * Can not fail, since all column names and types
 		 * are preallocated during prepare phase and the
@@ -326,13 +332,15 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
 		 */
 		assert(name != NULL);
 		assert(type != NULL);
-		size = metadata_map_sizeof(name, type, coll, nullable);
+		size = metadata_map_sizeof(name, type, coll, nullable,
+					   is_autoincrement);
 		char *pos = (char *) obuf_alloc(out, size);
 		if (pos == NULL) {
 			diag_set(OutOfMemory, size, "obuf_alloc", "pos");
 			return -1;
 		}
-		uint32_t map_sz = 2 + (coll != NULL) + (nullable != -1);
+		uint32_t map_sz = 2 + (coll != NULL) + (nullable != -1) +
+				  is_autoincrement;
 		pos = mp_encode_map(pos, map_sz);
 		pos = mp_encode_uint(pos, IPROTO_FIELD_NAME);
 		pos = mp_encode_str(pos, name, strlen(name));
@@ -346,6 +354,10 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
 			pos = mp_encode_uint(pos, IPROTO_FIELD_NULLABLE);
 			pos = mp_encode_bool(pos, nullable);
 		}
+		if (is_autoincrement) {
+			pos = mp_encode_uint(pos, IPROTO_FIELD_AUTOINCREMENT);
+			pos = mp_encode_bool(pos, true);
+		}
 	}
 	return 0;
 }
diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h
index 030c25531..4d43583b0 100644
--- a/src/box/iproto_constants.h
+++ b/src/box/iproto_constants.h
@@ -132,7 +132,8 @@ enum iproto_metadata_key {
 	IPROTO_FIELD_NAME = 0,
 	IPROTO_FIELD_TYPE = 1,
 	IPROTO_FIELD_COLL = 2,
-	IPROTO_FIELD_NULLABLE = 3
+	IPROTO_FIELD_NULLABLE = 3,
+	IPROTO_FIELD_AUTOINCREMENT = 4,
 };
 
 enum iproto_ballot_key {
diff --git a/src/box/lua/execute.c b/src/box/lua/execute.c
index 3d7ca710c..d7dd9432f 100644
--- a/src/box/lua/execute.c
+++ b/src/box/lua/execute.c
@@ -25,6 +25,7 @@ lua_sql_get_metadata(struct sql_stmt *stmt, struct lua_State *L,
 		const char *name = sql_column_name(stmt, i);
 		const char *type = sql_column_datatype(stmt, i);
 		int nullable = sql_column_is_nullable(stmt, i);
+		bool is_autoincrement = sql_column_is_autoincrement(stmt, i);
 		/*
 		 * Can not fail, since all column names are
 		 * preallocated during prepare phase and the
@@ -44,6 +45,10 @@ lua_sql_get_metadata(struct sql_stmt *stmt, struct lua_State *L,
 			lua_pushboolean(L, nullable);
 			lua_setfield(L, -2, "is_nullable");
 		}
+		if (is_autoincrement) {
+			lua_pushboolean(L, true);
+			lua_setfield(L, -2, "autoincrement");
+		}
 		lua_rawseti(L, -2, i + 1);
 	}
 }
diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
index 3e93cbc75..3be644785 100644
--- a/src/box/lua/net_box.c
+++ b/src/box/lua/net_box.c
@@ -651,11 +651,15 @@ decode_metadata_optional(struct lua_State *L, const char **data,
 			const char *coll = mp_decode_str(data, &len);
 			lua_pushlstring(L, coll, len);
 			lua_setfield(L, -2, "collation");
-		} else {
-			assert(key == IPROTO_FIELD_NULLABLE);
+		} else if (key == IPROTO_FIELD_NULLABLE) {
 			bool is_nullable = mp_decode_bool(data);
 			lua_pushboolean(L, is_nullable);
 			lua_setfield(L, -2, "is_nullable");
+		} else {
+			assert(key == IPROTO_FIELD_AUTOINCREMENT);
+			bool autoincrement = mp_decode_bool(data);
+			lua_pushboolean(L, autoincrement);
+			lua_setfield(L, -2, "autoincrement");
 		}
 	}
 }
@@ -672,7 +676,7 @@ netbox_decode_metadata(struct lua_State *L, const char **data)
 	lua_createtable(L, count, 0);
 	for (uint32_t i = 0; i < count; ++i) {
 		uint32_t map_size = mp_decode_map(data);
-		assert(map_size >= 2 && map_size <= 4);
+		assert(map_size >= 2 && map_size <= 5);
 		(void) map_size;
 		uint32_t key = mp_decode_uint(data);
 		assert(key == IPROTO_FIELD_NAME);
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index b772bcead..f40178194 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1841,6 +1841,12 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
 			if (p->op == TK_COLUMN)
 				vdbe_set_metadata_col_nullability(v, i,
 								  is_nullable);
+			if (pTabList->a[j].space->sequence != NULL) {
+				int afno =
+					pTabList->a[j].space->sequence_fieldno;
+				if (afno == iCol)
+					vdbe_set_metadata_col_autoincrement(v, i);
+			}
 		} else {
 			const char *z = pEList->a[i].zSpan;
 			if (z == NULL)
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 89920b3d1..590791648 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -582,6 +582,9 @@ sql_column_coll(sql_stmt *stmt, int n);
 int
 sql_column_is_nullable(sql_stmt *stmt, int n);
 
+bool
+sql_column_is_autoincrement(sql_stmt *stmt, int n);
+
 int
 sql_initialize(void);
 
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 0f315b660..4e1a67416 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -260,6 +260,9 @@ vdbe_set_metadata_col_collation(struct Vdbe *p, int idx, const char *coll,
 void
 vdbe_set_metadata_col_nullability(struct Vdbe *p, int idx, int nullable);
 
+void
+vdbe_set_metadata_col_autoincrement(struct Vdbe *p, int idx);
+
 void sqlVdbeCountChanges(Vdbe *);
 sql *sqlVdbeDb(Vdbe *);
 void sqlVdbeSetSql(Vdbe *, const char *z, int n, int);
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 0e54e42a5..63afb8777 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -355,6 +355,8 @@ struct sql_column_metadata {
 	 * columns: all other expressions are nullable by default.
 	 */
 	int8_t nullable : 2;
+	/** True if column features autoincrement property. */
+	int8_t actoincrement : 1;
 };
 
 /*
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index ea8c7c438..ece803262 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -761,6 +761,14 @@ sql_column_is_nullable(sql_stmt *stmt, int n)
 	return p->metadata[n].nullable;
 }
 
+bool
+sql_column_is_autoincrement(sql_stmt *stmt, int n)
+{
+	struct Vdbe *p = (struct Vdbe *) stmt;
+	assert(n < sql_column_count(stmt) && n >= 0);
+	return p->metadata[n].actoincrement;
+}
+
 /******************************* sql_bind_  **************************
  *
  * Routines used to attach values to wildcards in a compiled SQL statement.
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 6c3523ba4..34a0c1267 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1911,6 +1911,13 @@ vdbe_set_metadata_col_nullability(struct Vdbe *p, int idx, int nullable)
 	p->metadata[idx].nullable = nullable;
 }
 
+void
+vdbe_set_metadata_col_autoincrement(struct Vdbe *p, int idx)
+{
+	assert(idx < p->nResColumn);
+	p->metadata[idx].actoincrement = 1;
+}
+
 /*
  * This routine checks that the sql.nVdbeActive count variable
  * matches the number of vdbe's in the list sql.pVdbe that are
-- 
2.15.1

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

* [Tarantool-patches] [PATCH 6/6] sql: extend result set with alias
  2019-11-27 12:15 [Tarantool-patches] [PATCH 0/6] sql: extend response metadata Nikita Pettik
                   ` (4 preceding siblings ...)
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 5/6] sql: extend result set with autoincrement Nikita Pettik
@ 2019-11-27 12:15 ` Nikita Pettik
  2019-11-28 22:41   ` Vladislav Shpilevoy
  2019-12-19 15:17   ` Sergey Ostanevich
  2019-11-28 22:55 ` [Tarantool-patches] [PATCH 0/6] sql: extend response metadata Vladislav Shpilevoy
  6 siblings, 2 replies; 40+ messages in thread
From: Nikita Pettik @ 2019-11-27 12:15 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

Each column of result set can feature its name alias. For instance:

SELECT x + 1 AS add FROM ...;

In this case real name of resulting set column is "x + 1" meanwhile
"add" is its alias. This patch extends metadata with optional metadata
member which corresponds to column's alias.

Closes #4407
---
 src/box/execute.c              | 18 +++++++++++++----
 src/box/iproto_constants.h     |  1 +
 src/box/lua/execute.c          |  5 +++++
 src/box/lua/net_box.c          |  6 +++++-
 src/box/sql/select.c           |  9 +++++++--
 src/box/sql/sqlInt.h           |  3 +++
 src/box/sql/vdbe.h             |  3 +++
 src/box/sql/vdbeInt.h          |  1 +
 src/box/sql/vdbeapi.c          |  8 ++++++++
 src/box/sql/vdbeaux.c          | 15 ++++++++++++++
 test/sql-tap/badutf1.test.lua  | 46 +++++++++++++++++++++---------------------
 test/sql-tap/colname.test.lua  | 16 +++++++--------
 test/sql-tap/lua/sqltester.lua | 29 ++++++++++++++++++++++++++
 test/sql-tap/select1.test.lua  | 18 ++++++++---------
 test/sql-tap/select4.test.lua  |  4 ++--
 test/sql-tap/view.test.lua     |  2 +-
 test/sql/bind.result           | 15 ++++++++------
 test/sql/boolean.result        |  6 ++++--
 test/sql/collation.result      | 16 +++++++++------
 test/sql/iproto.result         |  5 +++--
 20 files changed, 160 insertions(+), 66 deletions(-)

diff --git a/src/box/execute.c b/src/box/execute.c
index 8305f6f3b..967a7a0bf 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -270,7 +270,7 @@ error:
 
 static size_t
 metadata_map_sizeof(const char *name, const char *type, const char *coll,
-		    int nullable, bool is_autoincrement)
+		    const char *alias, int nullable, bool is_autoincrement)
 {
 	uint32_t members_count = 2;
 	size_t map_size = 0;
@@ -279,6 +279,11 @@ metadata_map_sizeof(const char *name, const char *type, const char *coll,
 		map_size += mp_sizeof_uint(IPROTO_FIELD_COLL);
 		map_size += mp_sizeof_str(strlen(coll));
 	}
+	if (alias != NULL) {
+		members_count++;
+		map_size += mp_sizeof_uint(IPROTO_FIELD_ALIAS);
+		map_size += mp_sizeof_str(strlen(alias));
+	}
 	if (nullable != -1) {
 		members_count++;
 		map_size += mp_sizeof_uint(IPROTO_FIELD_NULLABLE);
@@ -323,6 +328,7 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
 		const char *coll = sql_column_coll(stmt, i);
 		const char *name = sql_column_name(stmt, i);
 		const char *type = sql_column_datatype(stmt, i);
+		const char *alias = sql_column_alias(stmt, i);
 		int nullable = sql_column_is_nullable(stmt, i);
 		bool is_autoincrement = sql_column_is_autoincrement(stmt, i);
 		/*
@@ -332,15 +338,15 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
 		 */
 		assert(name != NULL);
 		assert(type != NULL);
-		size = metadata_map_sizeof(name, type, coll, nullable,
+		size = metadata_map_sizeof(name, type, coll, alias, nullable,
 					   is_autoincrement);
 		char *pos = (char *) obuf_alloc(out, size);
 		if (pos == NULL) {
 			diag_set(OutOfMemory, size, "obuf_alloc", "pos");
 			return -1;
 		}
-		uint32_t map_sz = 2 + (coll != NULL) + (nullable != -1) +
-				  is_autoincrement;
+		uint32_t map_sz = 2 + (coll != NULL) + (alias != NULL) +
+				  (nullable != -1) + is_autoincrement;
 		pos = mp_encode_map(pos, map_sz);
 		pos = mp_encode_uint(pos, IPROTO_FIELD_NAME);
 		pos = mp_encode_str(pos, name, strlen(name));
@@ -350,6 +356,10 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
 			pos = mp_encode_uint(pos, IPROTO_FIELD_COLL);
 			pos = mp_encode_str(pos, coll, strlen(coll));
 		}
+		if (alias != NULL) {
+			pos = mp_encode_uint(pos, IPROTO_FIELD_ALIAS);
+			pos = mp_encode_str(pos, alias, strlen(alias));
+		}
 		if (nullable != -1) {
 			pos = mp_encode_uint(pos, IPROTO_FIELD_NULLABLE);
 			pos = mp_encode_bool(pos, nullable);
diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h
index 4d43583b0..c5d053dc4 100644
--- a/src/box/iproto_constants.h
+++ b/src/box/iproto_constants.h
@@ -134,6 +134,7 @@ enum iproto_metadata_key {
 	IPROTO_FIELD_COLL = 2,
 	IPROTO_FIELD_NULLABLE = 3,
 	IPROTO_FIELD_AUTOINCREMENT = 4,
+	IPROTO_FIELD_ALIAS = 5,
 };
 
 enum iproto_ballot_key {
diff --git a/src/box/lua/execute.c b/src/box/lua/execute.c
index d7dd9432f..099d3855a 100644
--- a/src/box/lua/execute.c
+++ b/src/box/lua/execute.c
@@ -24,6 +24,7 @@ lua_sql_get_metadata(struct sql_stmt *stmt, struct lua_State *L,
 		lua_createtable(L, 0, coll != NULL ? 3 : 2);
 		const char *name = sql_column_name(stmt, i);
 		const char *type = sql_column_datatype(stmt, i);
+		const char *alias = sql_column_alias(stmt, i);
 		int nullable = sql_column_is_nullable(stmt, i);
 		bool is_autoincrement = sql_column_is_autoincrement(stmt, i);
 		/*
@@ -41,6 +42,10 @@ lua_sql_get_metadata(struct sql_stmt *stmt, struct lua_State *L,
 			lua_pushstring(L, coll);
 			lua_setfield(L, -2, "collation");
 		}
+		if (alias != NULL) {
+			lua_pushstring(L, alias);
+			lua_setfield(L, -2, "alias");
+		}
 		if (nullable != -1) {
 			lua_pushboolean(L, nullable);
 			lua_setfield(L, -2, "is_nullable");
diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
index 3be644785..dce956932 100644
--- a/src/box/lua/net_box.c
+++ b/src/box/lua/net_box.c
@@ -655,6 +655,10 @@ decode_metadata_optional(struct lua_State *L, const char **data,
 			bool is_nullable = mp_decode_bool(data);
 			lua_pushboolean(L, is_nullable);
 			lua_setfield(L, -2, "is_nullable");
+		} else if (key == IPROTO_FIELD_ALIAS) {
+			const char *alias = mp_decode_str(data, &len);
+			lua_pushlstring(L, alias, len);
+			lua_setfield(L, -2, "alias");
 		} else {
 			assert(key == IPROTO_FIELD_AUTOINCREMENT);
 			bool autoincrement = mp_decode_bool(data);
@@ -676,7 +680,7 @@ netbox_decode_metadata(struct lua_State *L, const char **data)
 	lua_createtable(L, count, 0);
 	for (uint32_t i = 0; i < count; ++i) {
 		uint32_t map_size = mp_decode_map(data);
-		assert(map_size >= 2 && map_size <= 5);
+		assert(map_size >= 2 && map_size <= 6);
 		(void) map_size;
 		uint32_t key = mp_decode_uint(data);
 		assert(key == IPROTO_FIELD_NAME);
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index f40178194..5f01ef515 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1812,8 +1812,13 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
 		}
 		vdbe_set_metadata_col_nullability(v, i, -1);
 		if (pEList->a[i].zName) {
-			char *zName = pEList->a[i].zName;
-			vdbe_set_metadata_col_name(v, i, zName);
+			char *name = pEList->a[i].zSpan != NULL ?
+				     pEList->a[i].zSpan : pEList->a[i].zName;
+			vdbe_set_metadata_col_name(v, i, name);
+			if (pEList->a[i].zSpan != NULL) {
+				vdbe_set_metadata_col_alias(v, i,
+							    pEList->a[i].zName);
+			}
 		} else if (p->op == TK_COLUMN || p->op == TK_AGG_COLUMN) {
 			char *zCol;
 			int iCol = p->iColumn;
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 590791648..d34e7bc52 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -585,6 +585,9 @@ sql_column_is_nullable(sql_stmt *stmt, int n);
 bool
 sql_column_is_autoincrement(sql_stmt *stmt, int n);
 
+const char *
+sql_column_alias(sql_stmt *stmt, int n);
+
 int
 sql_initialize(void);
 
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 4e1a67416..e4fecba6d 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -263,6 +263,9 @@ vdbe_set_metadata_col_nullability(struct Vdbe *p, int idx, int nullable);
 void
 vdbe_set_metadata_col_autoincrement(struct Vdbe *p, int idx);
 
+int
+vdbe_set_metadata_col_alias(struct Vdbe *p, int idx, const char *alias);
+
 void sqlVdbeCountChanges(Vdbe *);
 sql *sqlVdbeDb(Vdbe *);
 void sqlVdbeSetSql(Vdbe *, const char *z, int n, int);
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 63afb8777..f97662673 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -350,6 +350,7 @@ struct sql_column_metadata {
 	const char *name;
 	const char *type;
 	const char *collation;
+	const char *alias;
 	/**
 	 * -1 is for any member of result set except for pure
 	 * columns: all other expressions are nullable by default.
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index ece803262..097e13405 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -769,6 +769,14 @@ sql_column_is_autoincrement(sql_stmt *stmt, int n)
 	return p->metadata[n].actoincrement;
 }
 
+const char *
+sql_column_alias(sql_stmt *stmt, int n)
+{
+	struct Vdbe *p = (struct Vdbe *) stmt;
+	assert(n < sql_column_count(stmt) && n >= 0);
+	return p->metadata[n].alias;
+}
+
 /******************************* sql_bind_  **************************
  *
  * Routines used to attach values to wildcards in a compiled SQL statement.
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 34a0c1267..28f4669e6 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1835,6 +1835,7 @@ vdbe_metadata_delete(struct Vdbe *v)
 			free((void *)v->metadata[i].name);
 			free((void *)v->metadata[i].type);
 			free((void *)v->metadata[i].collation);
+			free((void *)v->metadata[i].alias);
 		}
 		free(v->metadata);
 	}
@@ -1918,6 +1919,20 @@ vdbe_set_metadata_col_autoincrement(struct Vdbe *p, int idx)
 	p->metadata[idx].actoincrement = 1;
 }
 
+int
+vdbe_set_metadata_col_alias(struct Vdbe *p, int idx, const char *alias)
+{
+	assert(idx < p->nResColumn);
+	if (p->metadata[idx].alias != NULL)
+		free((void *)p->metadata[idx].alias);
+	p->metadata[idx].alias = strdup(alias);
+	if (p->metadata[idx].alias == NULL) {
+		diag_set(OutOfMemory, strlen(alias), "strdup", "alias");
+		return -1;
+	}
+	return 0;
+}
+
 /*
  * This routine checks that the sql.nVdbeActive count variable
  * matches the number of vdbe's in the list sql.pVdbe that are
diff --git a/test/sql-tap/badutf1.test.lua b/test/sql-tap/badutf1.test.lua
index 9079dfe25..6623999d0 100755
--- a/test/sql-tap/badutf1.test.lua
+++ b/test/sql-tap/badutf1.test.lua
@@ -25,7 +25,7 @@ test:do_test(
     "badutf-1.1",
     function()
         --test:execsql "PRAGMA encoding='UTF8'"
-        return test:execsql2("SELECT hex('\x80') AS x")
+        return test:execsql_aliases("SELECT hex('\x80') AS x")
     end, {
         -- <badutf-1.1>
         "X", "80"
@@ -35,7 +35,7 @@ test:do_test(
 test:do_test(
     "badutf-1.2",
     function()
-        return test:execsql2("SELECT hex('\x81') AS x")
+        return test:execsql_aliases("SELECT hex('\x81') AS x")
     end, {
         -- <badutf-1.2>
         "X", "81"
@@ -45,7 +45,7 @@ test:do_test(
 test:do_test(
     "badutf-1.3",
     function()
-        return test:execsql2("SELECT hex('\xbf') AS x")
+        return test:execsql_aliases("SELECT hex('\xbf') AS x")
     end, {
         -- <badutf-1.3>
         "X", "BF"
@@ -55,7 +55,7 @@ test:do_test(
 test:do_test(
     "badutf-1.4",
     function()
-        return test:execsql2("SELECT hex('\xc0') AS x")
+        return test:execsql_aliases("SELECT hex('\xc0') AS x")
     end, {
         -- <badutf-1.4>
         "X", "C0"
@@ -65,7 +65,7 @@ test:do_test(
 test:do_test(
     "badutf-1.5",
     function()
-        return test:execsql2("SELECT hex('\xe0') AS x")
+        return test:execsql_aliases("SELECT hex('\xe0') AS x")
     end, {
         -- <badutf-1.5>
         "X", "E0"
@@ -75,7 +75,7 @@ test:do_test(
 test:do_test(
     "badutf-1.6",
     function()
-        return test:execsql2("SELECT hex('\xf0') AS x")
+        return test:execsql_aliases("SELECT hex('\xf0') AS x")
     end, {
         -- <badutf-1.6>
         "X", "F0"
@@ -85,7 +85,7 @@ test:do_test(
 test:do_test(
     "badutf-1.7",
     function()
-        return test:execsql2("SELECT hex('\xff') AS x")
+        return test:execsql_aliases("SELECT hex('\xff') AS x")
     end, {
         -- <badutf-1.7>
         "X", "FF"
@@ -212,7 +212,7 @@ end
 test:do_test(
     "badutf-3.1",
     function()
-        return test:execsql2("SELECT length('\x80') AS x")
+        return test:execsql_aliases("SELECT length('\x80') AS x")
     end, {
         -- <badutf-3.1>
         "X", 1
@@ -222,7 +222,7 @@ test:do_test(
 test:do_test(
     "badutf-3.2",
     function()
-        return test:execsql2("SELECT length('\x61\x62\x63') AS x")
+        return test:execsql_aliases("SELECT length('\x61\x62\x63') AS x")
     end, {
         -- <badutf-3.2>
         "X", 3
@@ -232,7 +232,7 @@ test:do_test(
 test:do_test(
     "badutf-3.3",
     function()
-        return test:execsql2("SELECT length('\x7f\x80\x81') AS x")
+        return test:execsql_aliases("SELECT length('\x7f\x80\x81') AS x")
     end, {
         -- <badutf-3.3>
         "X", 3
@@ -242,7 +242,7 @@ test:do_test(
 test:do_test(
     "badutf-3.4",
     function()
-        return test:execsql2("SELECT length('\x61\xc0') AS x")
+        return test:execsql_aliases("SELECT length('\x61\xc0') AS x")
     end, {
         -- <badutf-3.4>
         "X", 2
@@ -252,7 +252,7 @@ test:do_test(
 test:do_test(
     "badutf-3.5",
     function()
-        return test:execsql2("SELECT length('\x61\xc0\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80') AS x")
+        return test:execsql_aliases("SELECT length('\x61\xc0\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80') AS x")
     end, {
         -- <badutf-3.5>
         "X", 12
@@ -262,7 +262,7 @@ test:do_test(
 test:do_test(
     "badutf-3.6",
     function()
-        return test:execsql2("SELECT length('\xc0\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80') AS x")
+        return test:execsql_aliases("SELECT length('\xc0\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80') AS x")
     end, {
         -- <badutf-3.6>
         "X", 11
@@ -272,7 +272,7 @@ test:do_test(
 test:do_test(
     "badutf-3.7",
     function()
-        return test:execsql2("SELECT length('\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80') AS x")
+        return test:execsql_aliases("SELECT length('\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80') AS x")
     end, {
         -- <badutf-3.7>
         "X", 10
@@ -282,7 +282,7 @@ test:do_test(
 test:do_test(
     "badutf-3.8",
     function()
-        return test:execsql2("SELECT length('\x80\x80\x80\x80\x80\xf0\x80\x80\x80\x80') AS x")
+        return test:execsql_aliases("SELECT length('\x80\x80\x80\x80\x80\xf0\x80\x80\x80\x80') AS x")
     end, {
         -- <badutf-3.8>
         "X", 7
@@ -292,7 +292,7 @@ test:do_test(
 test:do_test(
     "badutf-3.9",
     function()
-        return test:execsql2("SELECT length('\x80\x80\x80\x80\x80\xf0\x80\x80\x80\xff') AS x")
+        return test:execsql_aliases("SELECT length('\x80\x80\x80\x80\x80\xf0\x80\x80\x80\xff') AS x")
     end, {
         -- <badutf-3.9>
         "X", 7
@@ -302,7 +302,7 @@ test:do_test(
 test:do_test(
     "badutf-4.1",
     function()
-        return test:execsql2("SELECT hex(TRIM('\x80\xff' FROM '\x80\x80\x80\xf0\x80\x80\x80\xff')) AS x")
+        return test:execsql_aliases("SELECT hex(TRIM('\x80\xff' FROM '\x80\x80\x80\xf0\x80\x80\x80\xff')) AS x")
     end, {
         -- <badutf-4.1>
         "X", "F0"
@@ -312,7 +312,7 @@ test:do_test(
 test:do_test(
     "badutf-4.2",
     function()
-        return test:execsql2("SELECT hex(TRIM(LEADING '\x80\xff' FROM '\x80\x80\x80\xf0\x80\x80\x80\xff')) AS x")
+        return test:execsql_aliases("SELECT hex(TRIM(LEADING '\x80\xff' FROM '\x80\x80\x80\xf0\x80\x80\x80\xff')) AS x")
     end, {
         -- <badutf-4.2>
         "X", "F0808080FF"
@@ -322,7 +322,7 @@ test:do_test(
 test:do_test(
     "badutf-4.3",
     function()
-        return test:execsql2("SELECT hex(TRIM(TRAILING '\x80\xff' FROM '\x80\x80\x80\xf0\x80\x80\x80\xff')) AS x")
+        return test:execsql_aliases("SELECT hex(TRIM(TRAILING '\x80\xff' FROM '\x80\x80\x80\xf0\x80\x80\x80\xff')) AS x")
     end, {
         -- <badutf-4.3>
         "X", "808080F0"
@@ -332,7 +332,7 @@ test:do_test(
 test:do_test(
     "badutf-4.4",
     function()
-        return test:execsql2("SELECT hex(TRIM('\xff\x80' FROM '\x80\x80\x80\xf0\x80\x80\x80\xff')) AS x")
+        return test:execsql_aliases("SELECT hex(TRIM('\xff\x80' FROM '\x80\x80\x80\xf0\x80\x80\x80\xff')) AS x")
     end, {
         -- <badutf-4.4>
         "X", "808080F0808080FF"
@@ -342,7 +342,7 @@ test:do_test(
 test:do_test(
     "badutf-4.5",
     function()
-        return test:execsql2("SELECT hex(TRIM('\xff\x80' FROM '\xff\x80\x80\xf0\x80\x80\x80\xff')) AS x")
+        return test:execsql_aliases("SELECT hex(TRIM('\xff\x80' FROM '\xff\x80\x80\xf0\x80\x80\x80\xff')) AS x")
     end, {
         -- <badutf-4.5>
         "X", "80F0808080FF"
@@ -352,7 +352,7 @@ test:do_test(
 test:do_test(
     "badutf-4.6",
     function()
-        return test:execsql2("SELECT hex(TRIM('\xff\x80' FROM '\xff\x80\xf0\x80\x80\x80\xff')) AS x")
+        return test:execsql_aliases("SELECT hex(TRIM('\xff\x80' FROM '\xff\x80\xf0\x80\x80\x80\xff')) AS x")
     end, {
         -- <badutf-4.6>
         "X", "F0808080FF"
@@ -362,7 +362,7 @@ test:do_test(
 test:do_test(
     "badutf-4.7",
     function()
-        return test:execsql2("SELECT hex(TRIM('\xff\x80\x80' FROM '\xff\x80\xf0\x80\x80\x80\xff')) AS x")
+        return test:execsql_aliases("SELECT hex(TRIM('\xff\x80\x80' FROM '\xff\x80\xf0\x80\x80\x80\xff')) AS x")
     end, {
         -- <badutf-4.7>
         "X", "FF80F0808080FF"
diff --git a/test/sql-tap/colname.test.lua b/test/sql-tap/colname.test.lua
index 253497cf3..260c4c761 100755
--- a/test/sql-tap/colname.test.lua
+++ b/test/sql-tap/colname.test.lua
@@ -112,7 +112,7 @@ test:do_execsql2_test(
         -- </colname-2.3>
     })
 
-test:do_execsql2_test(
+test:do_execsql_aliases_test(
     "colname-2.4",
     [[
         SELECT +tabc.a AS AAA, -tabc.b AS BBB, tabc.c CCC, * FROM tabc
@@ -213,7 +213,7 @@ test:do_execsql2_test(
         -- </colname-3.3>
     })
 
-test:do_execsql2_test(
+test:do_execsql_aliases_test(
     "colname-3.4",
     [[
         SELECT +tabc.a AS AAA, -tabc.b AS BBB, tabc.c CCC FROM tabc
@@ -334,7 +334,7 @@ test:do_execsql2_test(
         -- </colname-4.3>
     })
 
-test:do_execsql2_test(
+test:do_execsql_aliases_test(
     "colname-4.4",
     [[
         SELECT +tabc.a AS AAA, -tabc.b AS BBB, tabc.c CCC FROM tabc
@@ -480,7 +480,7 @@ test:do_execsql2_test(
         -- </colname-6.3>
     })
 
-test:do_execsql2_test(
+test:do_execsql_aliases_test(
     "colname-6.11",
     [[
         SELECT a, max(a) AS m FROM t6
@@ -490,7 +490,7 @@ test:do_execsql2_test(
         -- </colname-6.11>
     })
 
-test:do_execsql2_test(
+test:do_execsql_aliases_test(
     "colname-6.13",
     [[
         SELECT a, max(a) AS m FROM t6
@@ -500,7 +500,7 @@ test:do_execsql2_test(
         -- </colname-6.13>
     })
 
-test:do_execsql2_test(
+test:do_execsql_aliases_test(
     "colname-6.15",
     [[
         SELECT t6.a, max(a) AS m FROM t6
@@ -510,7 +510,7 @@ test:do_execsql2_test(
         -- </colname-6.15>
     })
 
-test:do_execsql2_test(
+test:do_execsql_aliases_test(
     "colname-6.18",
     [=[
         SELECT "[a]", max("[a]") AS m FROM t6
@@ -520,7 +520,7 @@ test:do_execsql2_test(
         -- </colname-6.18>
     })
 
-test:do_execsql2_test(
+test:do_execsql_aliases_test(
     "colname-6.19",
     [=[
         SELECT "`a`", max("`a`") AS m FROM t6
diff --git a/test/sql-tap/lua/sqltester.lua b/test/sql-tap/lua/sqltester.lua
index 0f3411419..12cb3462e 100644
--- a/test/sql-tap/lua/sqltester.lua
+++ b/test/sql-tap/lua/sqltester.lua
@@ -210,6 +210,11 @@ local function do_execsql2_test(self, label, sql, expect)
 end
 test.do_execsql2_test = do_execsql2_test
 
+local function do_execsql_aliases(self, label, sql, expect)
+    return do_test(self, label, function() return test.execsql_aliases(self, sql) end, expect)
+end
+test.do_execsql_aliases_test = do_execsql_aliases
+
 local function flattern_with_column_names(result, metadata)
     local ret = {}
     for i = 1, #result, 1 do
@@ -221,6 +226,22 @@ local function flattern_with_column_names(result, metadata)
     return ret
 end
 
+local function flattern_with_column_aliases(result, metadata)
+    local ret = {}
+    for i = 1, #result, 1 do
+        for j = 1, #metadata, 1 do
+            if metadata[j].alias ~= nil then
+                table.insert(ret, metadata[j].alias)
+            else
+                table.insert(ret, metadata[j].name)
+            end
+            table.insert(ret, result[i][j])
+        end
+    end
+    return ret
+end
+
+
 function test.do_catchsql_set_test(self, testcases, prefix)
     -- testcases structure:
     -- {
@@ -248,6 +269,14 @@ local function execsql2(self, sql)
 end
 test.execsql2 = execsql2
 
+local function execsql_aliases(self, sql)
+    local result, metadata = execsql_one_by_one(sql)
+    if type(result) ~= 'table' then return end
+    result = flattern_with_column_aliases(result, metadata)
+    return result
+end
+test.execsql_aliases = execsql_aliases
+
 local function sortsql(self, sql)
     local result = execsql(self, sql)
     table.sort(result, function(a,b) return a[2] < b[2] end)
diff --git a/test/sql-tap/select1.test.lua b/test/sql-tap/select1.test.lua
index 4bbfbd67b..9a5f814c9 100755
--- a/test/sql-tap/select1.test.lua
+++ b/test/sql-tap/select1.test.lua
@@ -980,43 +980,43 @@ test:do_catchsql2_test(
         -- </select1-6.1.6>
     })
 
-test:do_catchsql2_test(
+test:do_execsql_aliases_test(
     "select1-6.2",
     [[
         SELECT f1 as xyzzy FROM test1 ORDER BY f2
     ]], {
         -- <select1-6.2>
-        0, {"XYZZY", 11, "XYZZY", 33}
+        "XYZZY", 11, "XYZZY", 33
         -- </select1-6.2>
     })
 
-test:do_catchsql2_test(
+test:do_execsql_aliases_test(
     "select1-6.3",
     [[
         SELECT f1 as "xyzzy" FROM test1 ORDER BY f2
     ]], {
         -- <select1-6.3>
-        0, {"xyzzy", 11, "xyzzy", 33}
+        "xyzzy", 11, "xyzzy", 33
         -- </select1-6.3>
     })
 
-test:do_catchsql2_test(
+test:do_execsql_aliases_test(
     "select1-6.3.1",
     [[
         SELECT f1 as "xyzzy " FROM test1 ORDER BY f2
     ]], {
         -- <select1-6.3.1>
-        0, {"xyzzy ", 11, "xyzzy ", 33}
+        "xyzzy ", 11, "xyzzy ", 33
         -- </select1-6.3.1>
     })
 
-test:do_catchsql2_test(
+test:do_execsql_aliases_test(
     "select1-6.4",
     [[
         SELECT f1+F2 as xyzzy FROM test1 ORDER BY f2
     ]], {
         -- <select1-6.4>
-        0, {"XYZZY", 33, "XYZZY", 77}
+        "XYZZY", 33, "XYZZY", 77
         -- </select1-6.4>
     })
 
@@ -1879,7 +1879,7 @@ test:do_execsql2_test(
         -- </select1-12.2>
     })
 
-test:do_execsql2_test(
+test:do_execsql_aliases_test(
     "select1-12.3",
     [[
         SELECT 1 as "a",'hello' as "b",2 as "c"
diff --git a/test/sql-tap/select4.test.lua b/test/sql-tap/select4.test.lua
index 23cf1bf1b..e2fafd41d 100755
--- a/test/sql-tap/select4.test.lua
+++ b/test/sql-tap/select4.test.lua
@@ -831,7 +831,7 @@ test:do_execsql2_test(
         -- </select4-9.4>
     })
 
-test:do_execsql2_test(
+test:do_execsql_aliases_test(
     "select4-9.5",
     [[
         SELECT 0 AS x, 1 AS y
@@ -894,7 +894,7 @@ test:do_execsql_test(
         -- </select4-9.8>
     })
 
-test:do_execsql2_test(
+test:do_execsql_aliases_test(
     "select4-9.9.1",
     [[
         SELECT 1 AS a, 2 AS b UNION ALL SELECT 3 AS b, 4 AS a
diff --git a/test/sql-tap/view.test.lua b/test/sql-tap/view.test.lua
index 6234f863e..657910006 100755
--- a/test/sql-tap/view.test.lua
+++ b/test/sql-tap/view.test.lua
@@ -912,7 +912,7 @@ test:do_execsql2_test(
         SELECT x, y FROM v15 LIMIT 1
     ]], {
         -- <view-15.2>
-        "X", 2, "Y", 3
+        "x", 2, "y", 3
         -- </view-15.2>
     })
 
diff --git a/test/sql/bind.result b/test/sql/bind.result
index b24094052..d9e917925 100644
--- a/test/sql/bind.result
+++ b/test/sql/bind.result
@@ -192,10 +192,12 @@ execute('SELECT ?, ?, ?, ?, ?', {'abc', -123.456, msgpack.NULL, true, false})
 execute('SELECT ? AS kek, ? AS kek2', {1, 2})
 ---
 - metadata:
-  - name: KEK
-    type: integer
-  - name: KEK2
-    type: integer
+  - type: integer
+    name: '?'
+    alias: KEK
+  - type: integer
+    name: '?'
+    alias: KEK2
   rows:
   - [1, 2]
 ...
@@ -243,8 +245,9 @@ execute(sql, parameters)
 execute('SELECT ? AS big_uint', {0xefffffffffffffff})
 ---
 - metadata:
-  - name: BIG_UINT
-    type: integer
+  - type: integer
+    name: '?'
+    alias: BIG_UINT
   rows:
   - [17293822569102704640]
 ...
diff --git a/test/sql/boolean.result b/test/sql/boolean.result
index 339e7d9d0..6889edc56 100644
--- a/test/sql/boolean.result
+++ b/test/sql/boolean.result
@@ -862,8 +862,10 @@ FROM t4;
  |   - type: integer
  |     name: I
  |     is_nullable: false
- |   - name: A0
- |     type: boolean
+ |   - type: boolean
+ |     name: "CASE  \tWHEN a == true AND i % 2 == 1 THEN false  \tWHEN a == true and
+ |       i % 2 == 0 THEN true  \tWHEN a != true then false  END"
+ |     alias: A0
  |   rows:
  |   - [100, true]
  |   - [111, false]
diff --git a/test/sql/collation.result b/test/sql/collation.result
index 750e9c509..f863784da 100644
--- a/test/sql/collation.result
+++ b/test/sql/collation.result
@@ -70,10 +70,12 @@ box.execute([[SELECT descriptor, upper(letter) AS upper,lower(letter) AS lower F
   - type: string
     name: DESCRIPTOR
     is_nullable: false
-  - name: UPPER
-    type: string
-  - name: LOWER
-    type: string
+  - type: string
+    name: upper(letter)
+    alias: UPPER
+  - type: string
+    name: lower(letter)
+    alias: LOWER
   rows:
   - ['Latin Capital Letter I U+0049', 'I', 'i']
   - ['Latin Capital Letter I With Dot Above U+0130', 'İ', 'i̇']
@@ -88,11 +90,13 @@ box.execute([[SELECT descriptor, upper(letter COLLATE "TURKISH") AS upper,lower(
     name: DESCRIPTOR
     is_nullable: false
   - type: string
-    name: UPPER
     collation: TURKISH
+    name: upper(letter COLLATE "TURKISH")
+    alias: UPPER
   - type: string
-    name: LOWER
     collation: TURKISH
+    name: lower(letter COLLATE "TURKISH")
+    alias: LOWER
   rows:
   - ['Latin Capital Letter I U+0049', 'I', 'ı']
   - ['Latin Capital Letter I With Dot Above U+0130', 'İ', 'i']
diff --git a/test/sql/iproto.result b/test/sql/iproto.result
index 05c59318c..58c0bb4d8 100644
--- a/test/sql/iproto.result
+++ b/test/sql/iproto.result
@@ -112,8 +112,9 @@ cn:execute('insert qwerty gjsdjq  q  qwd qmq;; q;qwd;')
 cn:execute('select id as identifier from test where a = 5;')
 ---
 - metadata:
-  - name: IDENTIFIER
-    type: integer
+  - type: integer
+    name: id
+    alias: IDENTIFIER
   rows: []
 ...
 -- netbox API errors.
-- 
2.15.1

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

* Re: [Tarantool-patches] [PATCH 1/6] sql: refactor resulting set metadata
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 1/6] sql: refactor resulting set metadata Nikita Pettik
@ 2019-11-28 22:41   ` Vladislav Shpilevoy
  2019-12-05 11:39     ` Nikita Pettik
  2019-12-17 13:23   ` Sergey Ostanevich
  1 sibling, 1 reply; 40+ messages in thread
From: Vladislav Shpilevoy @ 2019-11-28 22:41 UTC (permalink / raw)
  To: Nikita Pettik, tarantool-patches

Hi! Thanks for the patch!

How is your life, kids, wife, job? I hope everything
is good! Lets consider what can we do better here.
Below is just my own opinion and some of advice, nothing
obligatory or toxic!

See 6 tiny friendly comments below :) (no dot in the end = no toxic)

On 27/11/2019 13:15, Nikita Pettik wrote:
> Move names and types of resulting set to a separate structure. Simplify
> their storage by introducing separate members for name and type
> (previously names and types were stored in one char * array). It will
> allow us to add new metadata properties with ease.
> 
> Needed for #4407
> ---
>  src/box/sql/delete.c  |  6 ++--
>  src/box/sql/insert.c  |  5 ++--
>  src/box/sql/legacy.c  |  2 +-
>  src/box/sql/pragma.c  | 14 ++++-----
>  src/box/sql/prepare.c |  9 +++---
>  src/box/sql/select.c  | 60 ++++++++++++++++++--------------------
>  src/box/sql/update.c  |  6 ++--
>  src/box/sql/vdbe.h    | 28 ++++++++++--------
>  src/box/sql/vdbeInt.h |  8 ++++-
>  src/box/sql/vdbeapi.c | 81 +++++++++------------------------------------------
>  src/box/sql/vdbeaux.c | 81 +++++++++++++++++++++++++++------------------------
>  11 files changed, 124 insertions(+), 176 deletions(-)
> 
> diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
> index 0ecc676e2..2d3466cc7 100644
> --- a/src/box/sql/prepare.c
> +++ b/src/box/sql/prepare.c
> @@ -146,11 +146,10 @@ sqlPrepare(sql * db,	/* Database handle. */
>  		sqlVdbeSetNumCols(sParse.pVdbe, name_count);
>  		for (int i = 0; i < name_count; i++) {
>  			int name_index = 2 * i + name_first;
> -			sqlVdbeSetColName(sParse.pVdbe, i, COLNAME_NAME,
> -					  azColName[name_index], SQL_STATIC);
> -			sqlVdbeSetColName(sParse.pVdbe, i, COLNAME_DECLTYPE,
> -					  azColName[name_index + 1],
> -					  SQL_STATIC);
> +			vdbe_set_metadata_col_name(sParse.pVdbe, i,
> +					  azColName[name_index]);
> +			vdbe_set_metadata_col_type(sParse.pVdbe, i,
> +					  azColName[name_index + 1]);

1. Lets fix this indentation.

>  		}
>  	}
>  
> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index 8f93edd16..d6b8a158f 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -2828,7 +2824,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
>  						Select *pFirst = p;
>  						while (pFirst->pPrior)
>  							pFirst = pFirst->pPrior;
> -						generateColumnNames(pParse,
> +						generate_column_metadata(pParse,
>  								    pFirst->pSrc,
>  								    pFirst->pEList);

2. And this too.

>  					}
> diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
> index 582d48a1f..4142fb6ba 100644
> --- a/src/box/sql/vdbe.h
> +++ b/src/box/sql/vdbe.h
> @@ -248,7 +241,18 @@ void sqlVdbeResetStepResult(Vdbe *);
>  void sqlVdbeRewind(Vdbe *);
>  int sqlVdbeReset(Vdbe *);
>  void sqlVdbeSetNumCols(Vdbe *, int);
> -int sqlVdbeSetColName(Vdbe *, int, int, const char *, void (*)(void *));
> +
> +/**
> + * Set the name of the idx'th column to be returned by the SQL
> + * statement. @name must be a pointer to a nul terminated string.
> + * This call must be made after a call to sqlVdbeSetNumCols().
> + */
> +int
> +vdbe_set_metadata_col_name(struct Vdbe *v, int col_idx, const char *name);
> +
> +int
> +vdbe_set_metadata_col_type(struct Vdbe *v, int col_idx, const char *type);

3. I think, that we should use one prefix for metadata
methods. You used 'vdbe_metadata_delete()' above, so lets
make it 'vdbe_metadata_*' everywhere. And these functions
will be

    vdbe_metadata_set_col_name/type...

> diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
> index 0f32b4cd6..9ab3753cb 100644
> --- a/src/box/sql/vdbeInt.h
> +++ b/src/box/sql/vdbeInt.h
> @@ -394,7 +399,8 @@ struct Vdbe {
>  	Op *aOp;		/* Space to hold the virtual machine's program */
>  	Mem *aMem;		/* The memory locations */
>  	Mem **apArg;		/* Arguments to currently executing user function */
> -	Mem *aColName;		/* Column names to return */
> +	/** SQL metadata for SELECT queries. */

4. Looks like it can be emitted on DML too. I see
vdbe_set_metadata_col_name(v, 0, "rows updated") in
sqlUpdate(), and vdbe_set_metadata_col_name(v, 0, "rows deleted")
in sql_table_delete_from().

> +	struct sql_column_metadata *metadata;
>  	Mem *pResultSet;	/* Pointer to an array of results */
>  	VdbeCursor **apCsr;	/* One element of this array for each open cursor */
>  	Mem *aVar;		/* Values for the OP_Variable opcode. */
> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> index a1d658648..db11fbf33 100644
> --- a/src/box/sql/vdbeaux.c
> +++ b/src/box/sql/vdbeaux.c
> @@ -1827,6 +1827,18 @@ Cleanup(Vdbe * p)
>  	p->pResultSet = 0;
>  }
>  
> +void
> +vdbe_metadata_delete(struct Vdbe *v)
> +{
> +	if (v->metadata != NULL) {
> +		for (int i = 0; i < v->nResColumn; ++i) {
> +			free((void *)v->metadata[i].name);
> +			free((void *)v->metadata[i].type);

5. The necessity to do that cast basically makes 'const'
in the struct useless. Could you please make them just
char *, not const char *?

> +		}
> +		free(v->metadata);
> +	}
> +}
> +
>  /*
>   * Set the number of result columns that will be returned by this SQL
>   * statement. This is now set at compile time, rather than during
> @@ -1836,50 +1848,44 @@ Cleanup(Vdbe * p)
> +vdbe_set_metadata_col_name(struct Vdbe *p, int idx, const char *name)
> +{
>  	assert(idx < p->nResColumn);
> -	assert(var < COLNAME_N);
> -	if (p->db->mallocFailed) {
> -		assert(!zName || xDel != SQL_DYNAMIC);
> +	if (p->metadata[idx].name != NULL)
> +		free((void *)p->metadata[idx].name);
> +	p->metadata[idx].name = strdup(name);
> +	if (p->metadata[idx].name == NULL) {
> +		diag_set(OutOfMemory, strlen(name), "strdup", "name");

6. Len + 1. Dup allocates terminating zero too. In the next patches
too.

>  		return -1;
>  	}
> -	assert(p->aColName != 0);
> -	assert(var == COLNAME_NAME || var == COLNAME_DECLTYPE);
> -	pColName = &(p->aColName[idx + var * p->nResColumn]);
> -	rc = sqlVdbeMemSetStr(pColName, zName, -1, 1, xDel);
> -	assert(rc != 0 || !zName || (pColName->flags & MEM_Term) != 0);
> -	return rc;
> +	return 0;
> +}
> +

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

* Re: [Tarantool-patches] [PATCH 6/6] sql: extend result set with alias
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 6/6] sql: extend result set with alias Nikita Pettik
@ 2019-11-28 22:41   ` Vladislav Shpilevoy
  2019-12-05 11:51     ` Nikita Pettik
  2019-12-19 15:17   ` Sergey Ostanevich
  1 sibling, 1 reply; 40+ messages in thread
From: Vladislav Shpilevoy @ 2019-11-28 22:41 UTC (permalink / raw)
  To: Nikita Pettik, tarantool-patches

Thanks for the patch!

On 27/11/2019 13:15, Nikita Pettik wrote:
> Each column of result set can feature its name alias. For instance:
> 
> SELECT x + 1 AS add FROM ...;
> 
> In this case real name of resulting set column is "x + 1" meanwhile
> "add" is its alias. This patch extends metadata with optional metadata
> member which corresponds to column's alias.

I was always thinking that the alias should be returned as a
name. And the real name should be returned as meta. And looks
like it is so:

    tarantool> box.execute('SELECT 1 AS kek')
    ---
    - metadata:
      - name: KEK
        type: integer
      rows:
      - [1]
    ...

That makes me think we should not break it. And
meta should return the real name in case there is
an alias. Because otherwise the aliases are useless
in meta.

Btw the example above is executed on this commit. So
now the results are inconsistent. Some queries return
alias in 'name'. Some return a real name in 'name'. I
think we should keep it as was, and return alias in
'name'.

> 
> Closes #4407
> ---
>  src/box/execute.c              | 18 +++++++++++++----
>  src/box/iproto_constants.h     |  1 +
>  src/box/lua/execute.c          |  5 +++++
>  src/box/lua/net_box.c          |  6 +++++-
>  src/box/sql/select.c           |  9 +++++++--
>  src/box/sql/sqlInt.h           |  3 +++
>  src/box/sql/vdbe.h             |  3 +++
>  src/box/sql/vdbeInt.h          |  1 +
>  src/box/sql/vdbeapi.c          |  8 ++++++++
>  src/box/sql/vdbeaux.c          | 15 ++++++++++++++
>  test/sql-tap/badutf1.test.lua  | 46 +++++++++++++++++++++---------------------
>  test/sql-tap/colname.test.lua  | 16 +++++++--------
>  test/sql-tap/lua/sqltester.lua | 29 ++++++++++++++++++++++++++
>  test/sql-tap/select1.test.lua  | 18 ++++++++---------
>  test/sql-tap/select4.test.lua  |  4 ++--
>  test/sql-tap/view.test.lua     |  2 +-
>  test/sql/bind.result           | 15 ++++++++------
>  test/sql/boolean.result        |  6 ++++--
>  test/sql/collation.result      | 16 +++++++++------
>  test/sql/iproto.result         |  5 +++--
>  20 files changed, 160 insertions(+), 66 deletions(-)
> 
> diff --git a/test/sql-tap/badutf1.test.lua b/test/sql-tap/badutf1.test.lua
> index 9079dfe25..6623999d0 100755
> --- a/test/sql-tap/badutf1.test.lua
> +++ b/test/sql-tap/badutf1.test.lua
> @@ -248,6 +269,14 @@ local function execsql2(self, sql)
>  end
>  test.execsql2 = execsql2
>  
> +local function execsql_aliases(self, sql)
> +    local result, metadata = execsql_one_by_one(sql)
> +    if type(result) ~= 'table' then return end
> +    result = flattern_with_column_aliases(result, metadata)
> +    return result
> +end
> +test.execsql_aliases = execsql_aliases

Well, these dancing with aliases is really weird. Seems like
your patch broke the names. AFAIU, as a name you should return
the alias, when it is specified. And the real name is returned
optionally, in meta.

> +
>  local function sortsql(self, sql)
>      local result = execsql(self, sql)
>      table.sort(result, function(a,b) return a[2] < b[2] end)

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

* Re: [Tarantool-patches] [PATCH 5/6] sql: extend result set with autoincrement
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 5/6] sql: extend result set with autoincrement Nikita Pettik
@ 2019-11-28 22:41   ` Vladislav Shpilevoy
  2019-12-05 11:51     ` Nikita Pettik
  2019-12-18 15:17   ` Sergey Ostanevich
  1 sibling, 1 reply; 40+ messages in thread
From: Vladislav Shpilevoy @ 2019-11-28 22:41 UTC (permalink / raw)
  To: Nikita Pettik, tarantool-patches

Thanks for the patch!

On 27/11/2019 13:15, Nikita Pettik wrote:
> If result set contains column which features attached sequence
> (AUTOINCREMENT in terms of SQL) then meta-information will contain
> corresponding field ('autoicrement' : boolean) in response.

1. Please, lets name all booleans values with 'is_' prefix:
is_autoincrement, IPROTO_FIELD_IS_AUTOINCREMENT.

> diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
> index 0e54e42a5..63afb8777 100644
> --- a/src/box/sql/vdbeInt.h
> +++ b/src/box/sql/vdbeInt.h
> @@ -355,6 +355,8 @@ struct sql_column_metadata {
>  	 * columns: all other expressions are nullable by default.
>  	 */
>  	int8_t nullable : 2;
> +	/** True if column features autoincrement property. */
> +	int8_t actoincrement : 1;

2. Lets make it bool, and name is_autoincrement.

>  };
>  
>  /*

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

* Re: [Tarantool-patches] [PATCH 3/6] sql: extend result set with collation
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 3/6] sql: extend result set with collation Nikita Pettik
@ 2019-11-28 22:41   ` Vladislav Shpilevoy
  2019-12-05 11:50     ` Nikita Pettik
  2019-12-18 11:08   ` Sergey Ostanevich
  1 sibling, 1 reply; 40+ messages in thread
From: Vladislav Shpilevoy @ 2019-11-28 22:41 UTC (permalink / raw)
  To: Nikita Pettik, tarantool-patches

Thanks for the patch!

I see, that we calculate nullable and autoinc for
table columns only. And it makes sense. Then maybe
we need collations and alias also for columns only,
not for all strings and result set columns? Could you
please investigate? I think Alexander knows.

See 4 comments below.

On 27/11/2019 13:15, Nikita Pettik wrote:
> If resulting set column is of STRING type and features collation (no
> matter explicit or implicit) different from "none", then metadata will
> contain its name.
> 
> Part of #4407
> ---
>  src/box/execute.c          |  31 +++++++--
>  src/box/iproto_constants.h |   1 +
>  src/box/lua/execute.c      |   7 +-
>  src/box/lua/net_box.c      |  20 +++++-
>  src/box/sql/select.c       |  18 ++++++
>  src/box/sql/sqlInt.h       |   3 +
>  src/box/sql/vdbe.h         |   4 ++
>  src/box/sql/vdbeInt.h      |   1 +
>  src/box/sql/vdbeapi.c      |   9 +++
>  src/box/sql/vdbeaux.c      |  16 +++++
>  test/sql/collation.result  | 155 +++++++++++++++++++++++++++------------------
>  11 files changed, 195 insertions(+), 70 deletions(-)
> 
> diff --git a/src/box/execute.c b/src/box/execute.c
> index e8b012e5b..20bfd0957 100644
> --- a/src/box/execute.c
> +++ b/src/box/execute.c
> @@ -267,6 +267,23 @@ error:
>  	region_truncate(region, svp);
>  	return -1;
>  }
> +static size_t

1. Please, add an empty line between these 2 functions.
(In this commit.)

> +metadata_map_sizeof(const char *name, const char *type, const char *coll)
> +{
> +	uint32_t members_count = 2;
> +	size_t map_size = 0;
> +	if (coll != NULL) {
> +		members_count++;
> +		map_size += mp_sizeof_uint(IPROTO_FIELD_COLL);
> +		map_size += mp_sizeof_str(strlen(coll));
> +	}
> +	map_size += mp_sizeof_uint(IPROTO_FIELD_NAME);
> +	map_size += mp_sizeof_uint(IPROTO_FIELD_TYPE);
> +	map_size += mp_sizeof_str(strlen(name));
> +	map_size += mp_sizeof_str(strlen(type));
> +	map_size += mp_sizeof_map(members_count);
> +	return map_size;
> +}
> diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
> index 001af95dc..afbd1e1be 100644
> --- a/src/box/lua/net_box.c
> +++ b/src/box/lua/net_box.c
> @@ -638,6 +638,23 @@ netbox_decode_select(struct lua_State *L)
>  	return 2;
>  }
>  
> +/** Decode optional (i.e. may be present in response) metadata fields. */
> +static void
> +decode_metadata_optional(struct lua_State *L, const char **data,
> +			 uint32_t map_size)
> +{
> +	/* 2 is default metadata map size (field name + field size). */
> +	while (map_size-- > 2) {
> +		uint32_t key = mp_decode_uint(data);
> +		uint32_t len;
> +		if (key == IPROTO_FIELD_COLL) {
> +			const char *coll = mp_decode_str(data, &len);
> +			lua_pushlstring(L, coll, len);
> +			lua_setfield(L, -2, "collation");
> +		}
> +	}

2. Netbox relies on certain order of fields in iproto
response so as to avoid cycles and checking for already
decoded fields. You can safely use it too. To eliminate
the 'while' cycle you can append to netbox_decode_metadata's
cycle:

    if (map_size == 2)
        continue;
    key = mp_decode_uint(data);
    if (key == IPROTO_FIELD_COLL) {
        /* handle coll ... */
    }
    if (map_size == 3)
        continue;
    key = mp_decode_uint(data);
    if (key == IPROTO_FIELD_NULLABLE) {
        /* handle nullable ... */
    }

> +}
> +
>  /**
>   * Decode IPROTO_METADATA into array of maps.
>   * @param L Lua stack to push result on.
> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index d6b8a158f..66e8c1274 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -1794,6 +1794,22 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
>  			var_pos[var_count++] = i;
>  		vdbe_set_metadata_col_type(v, i,
>  					   field_type_strs[sql_expr_type(p)]);
> +		if (sql_expr_type(p) == FIELD_TYPE_STRING) {
> +			bool unused;
> +			uint32_t id;
> +			struct coll *coll = NULL;
> +			/*
> +			 * If sql_expr_coll fails then it fails somewhere
> +			 * above the call stack.
> +			 */

3. And lets add an assertion for that.

> +			(void) sql_expr_coll(pParse, p, &unused, &id, &coll);
> +			if (id != COLL_NONE) {
> +				struct coll_id *coll_id = coll_by_id(id);
> +				vdbe_set_metadata_col_collation(v, i,
> +								coll_id->name,
> +								coll_id->name_len);
> +			}
> +		}
>  		if (pEList->a[i].zName) {
>  			char *zName = pEList->a[i].zName;
>  			vdbe_set_metadata_col_name(v, i, zName);
> @@ -1819,6 +1836,7 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
>  			} else {
>  				vdbe_set_metadata_col_name(v, i, zCol);
>  			}
> +
>  		} else {

4. How about drop of these two hunks consisting of empty lines?

>  			const char *z = pEList->a[i].zSpan;
>  			if (z == NULL)

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

* Re: [Tarantool-patches] [PATCH 4/6] sql: extend result set with nullability
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 4/6] sql: extend result set with nullability Nikita Pettik
@ 2019-11-28 22:41   ` Vladislav Shpilevoy
  2019-12-05 11:50     ` Nikita Pettik
  2019-12-18 13:31   ` Sergey Ostanevich
  1 sibling, 1 reply; 40+ messages in thread
From: Vladislav Shpilevoy @ 2019-11-28 22:41 UTC (permalink / raw)
  To: Nikita Pettik, tarantool-patches

Thanks for the patch!

You added a leading whitespace to the commit title.

Here is a strange example:

    box.cfg{}
    s = box.schema.create_space('TEST', {
        format = {{'ID', 'unsigned'}, {'COL', 'unsigned', is_nullable = true}}
    })
    pk = s:create_index('pk', {parts = {{'ID'}, {'COL', is_nullable = false}}})

In that example I defined a 'false-nullable' column. It
is nullable in the space format, but is not in the
index format. The strictest rule wins, so it is not nullable
after all. That can be seen in struct tuple_format. SQL says,
that it is nullable. But this is not a real problem. Perhaps
this might be even right. However the next example certainly
misses something:

    box.execute('SELECT * FROM test')
    ---
    - metadata:
      - name: ID
        type: unsigned
      - name: COL
        type: unsigned
      rows: []
    ...

    box.execute('SELECT col FROM test')
    ---
    - metadata:
      - type: unsigned
        name: COL
        is_nullable: true
      rows: []
    ...

As you can see, nullable depends on whether I select a
column explicitly or not. Perhaps the same happens to
autoincrement meta, I didn't check.

See 5 comments below.

On 27/11/2019 13:15, Nikita Pettik wrote:
> If member of result set is (solely) column identifier, then metadata
> will contain its corresponding field nullability as boolean property.
> Note that indicating nullability for other expressions (like x + 1)
> may make sense but it requires derived nullability calculation which in
> turn seems to be overkill (at least in scope of current patch).
> 
> Part of #4407
> ---
>  src/box/execute.c                                |  18 +-
>  src/box/iproto_constants.h                       |   1 +
>  src/box/lua/execute.c                            |   5 +
>  src/box/lua/net_box.c                            |   7 +-
>  src/box/sql/select.c                             |   6 +-
>  src/box/sql/sqlInt.h                             |   3 +
>  src/box/sql/vdbe.h                               |   3 +
>  src/box/sql/vdbeInt.h                            |   5 +
>  src/box/sql/vdbeapi.c                            |   7 +
>  src/box/sql/vdbeaux.c                            |   7 +
>  test/box/sql-update-with-nested-select.result    |   5 +-
>  test/sql/boolean.result                          | 425 ++++++++++++++---------
>  test/sql/check-clear-ephemeral.result            |   5 +-
>  test/sql/collation.result                        |  74 ++--
>  test/sql/gh-3199-no-mem-leaks.result             | 120 ++++---
>  test/sql/gh2141-delete-trigger-drop-table.result |  20 +-
>  test/sql/gh2251-multiple-update.result           |  10 +-
>  test/sql/iproto.result                           | 105 +++---
>  test/sql/misc.result                             |  25 +-
>  test/sql/on-conflict.result                      |  20 +-
>  test/sql/persistency.result                      | 190 ++++++----
>  test/sql/row-count.result                        |  25 +-
>  test/sql/sql-debug.result                        |  15 +-
>  test/sql/transition.result                       | 190 ++++++----
>  test/sql/types.result                            | 105 +++---
>  test/sql/update-with-nested-select.result        |   5 +-
>  26 files changed, 862 insertions(+), 539 deletions(-)
> 
> diff --git a/src/box/execute.c b/src/box/execute.c
> index 20bfd0957..98812ae1e 100644
> --- a/src/box/execute.c
> +++ b/src/box/execute.c
> @@ -267,8 +267,10 @@ error:
>  	region_truncate(region, svp);
>  	return -1;
>  }
> +
>  static size_t
> -metadata_map_sizeof(const char *name, const char *type, const char *coll)
> +metadata_map_sizeof(const char *name, const char *type, const char *coll,
> +		    int nullable)

1. Please, rename to 'is_nullable'. Here and in other places.

>  {
>  	uint32_t members_count = 2;
>  	size_t map_size = 0;
> @@ -277,6 +279,11 @@ metadata_map_sizeof(const char *name, const char *type, const char *coll)
>  		map_size += mp_sizeof_uint(IPROTO_FIELD_COLL);
>  		map_size += mp_sizeof_str(strlen(coll));
>  	}
> +	if (nullable != -1) {
> +		members_count++;
> +		map_size += mp_sizeof_uint(IPROTO_FIELD_NULLABLE);

2. This too. IPROTO_FIELD_IS_NULLABLE.

> +		map_size += mp_sizeof_bool(nullable);
> +	}
>  	map_size += mp_sizeof_uint(IPROTO_FIELD_NAME);
>  	map_size += mp_sizeof_uint(IPROTO_FIELD_TYPE);
>  	map_size += mp_sizeof_str(strlen(name));
> @@ -334,6 +342,10 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
>  			pos = mp_encode_uint(pos, IPROTO_FIELD_COLL);
>  			pos = mp_encode_str(pos, coll, strlen(coll));
>  		}
> +		if (nullable != -1) {
> +			pos = mp_encode_uint(pos, IPROTO_FIELD_NULLABLE);
> +			pos = mp_encode_bool(pos, nullable);
> +		}

3. We send autoincrement only in case it is true.
Then how about sending nullability only in case it
is true? To save a bit of the network.

>  	}
>  	return 0;
>  }
> diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h
> index fa9c029a2..030c25531 100644
> --- a/src/box/iproto_constants.h
> +++ b/src/box/iproto_constants.h
> @@ -132,6 +132,7 @@ enum iproto_metadata_key {
>  	IPROTO_FIELD_NAME = 0,
>  	IPROTO_FIELD_TYPE = 1,
>  	IPROTO_FIELD_COLL = 2,
> +	IPROTO_FIELD_NULLABLE = 3

4. Please, add a comma after 3 so as not to
change this line in the next commit.

>  };
>  
>  enum iproto_ballot_key {
> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index 66e8c1274..b772bcead 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -1836,7 +1837,10 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
>  			} else {
>  				vdbe_set_metadata_col_name(v, i, zCol);
>  			}
> -
> +			bool is_nullable = space_def->fields[iCol].is_nullable;
> +			if (p->op == TK_COLUMN)
> +				vdbe_set_metadata_col_nullability(v, i,
> +								  is_nullable);

5. Please, wrap into {}.

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

* Re: [Tarantool-patches] [PATCH 2/6] sql: fix possible null dereference in sql_expr_coll()
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 2/6] sql: fix possible null dereference in sql_expr_coll() Nikita Pettik
@ 2019-11-28 22:42   ` Vladislav Shpilevoy
  2019-12-05 11:40     ` Nikita Pettik
  0 siblings, 1 reply; 40+ messages in thread
From: Vladislav Shpilevoy @ 2019-11-28 22:42 UTC (permalink / raw)
  To: Nikita Pettik, tarantool-patches

Thanks for the patch!

Is it possible to test this?

On 27/11/2019 13:15, Nikita Pettik wrote:
> ---
>  src/box/sql/expr.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
> 
> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index 648b7170e..0bdcfe576 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -332,7 +332,8 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
>  				sql_func_by_signature(p->u.zToken, arg_count);
>  			if (func == NULL)
>  				break;
> -			if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL)) {
> +			if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL) &&
> +			    arg_count > 0) {
>  				/*
>  				 * Now we use quite straightforward
>  				 * approach assuming that resulting
> 

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

* Re: [Tarantool-patches] [PATCH 0/6] sql: extend response metadata
  2019-11-27 12:15 [Tarantool-patches] [PATCH 0/6] sql: extend response metadata Nikita Pettik
                   ` (5 preceding siblings ...)
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 6/6] sql: extend result set with alias Nikita Pettik
@ 2019-11-28 22:55 ` Vladislav Shpilevoy
  6 siblings, 0 replies; 40+ messages in thread
From: Vladislav Shpilevoy @ 2019-11-28 22:55 UTC (permalink / raw)
  To: Nikita Pettik, tarantool-patches

Please, add a docbot request to the final commit. It
should include binary protocol change description.

On 27/11/2019 13:15, Nikita Pettik wrote:
> Branch: https://github.com/tarantool/tarantool/tree/np/gh-4407-extend-sql-metadata
> Issue: https://github.com/tarantool/tarantool/issues/4407
> 
> This patch-set extends metadata with collation, nullability,
> autoincrement and alias properties of resulting set columns.
> Quite straightforward implementation.
> 
> Nikita Pettik (6):
>   sql: refactor resulting set metadata
>   sql: fix possible null dereference in sql_expr_coll()
>   sql: extend result set with collation
>   sql: extend result set with nullability
>   sql: extend result set with autoincrement
>   sql: extend result set with alias
> 
>  src/box/execute.c                                |  65 +++-
>  src/box/iproto_constants.h                       |   4 +
>  src/box/lua/execute.c                            |  22 +-
>  src/box/lua/net_box.c                            |  33 +-
>  src/box/sql/delete.c                             |   6 +-
>  src/box/sql/expr.c                               |   3 +-
>  src/box/sql/insert.c                             |   5 +-
>  src/box/sql/legacy.c                             |   2 +-
>  src/box/sql/pragma.c                             |  14 +-
>  src/box/sql/prepare.c                            |   9 +-
>  src/box/sql/select.c                             |  95 +++--
>  src/box/sql/sqlInt.h                             |  12 +
>  src/box/sql/update.c                             |   6 +-
>  src/box/sql/vdbe.h                               |  41 ++-
>  src/box/sql/vdbeInt.h                            |  17 +-
>  src/box/sql/vdbeapi.c                            | 103 +++---
>  src/box/sql/vdbeaux.c                            | 126 +++++--
>  test/box/sql-update-with-nested-select.result    |   5 +-
>  test/sql-tap/badutf1.test.lua                    |  46 +--
>  test/sql-tap/colname.test.lua                    |  16 +-
>  test/sql-tap/lua/sqltester.lua                   |  29 ++
>  test/sql-tap/select1.test.lua                    |  18 +-
>  test/sql-tap/select4.test.lua                    |   4 +-
>  test/sql-tap/view.test.lua                       |   2 +-
>  test/sql/bind.result                             |  15 +-
>  test/sql/boolean.result                          | 431 ++++++++++++++---------
>  test/sql/check-clear-ephemeral.result            |   5 +-
>  test/sql/collation.result                        | 241 ++++++++-----
>  test/sql/gh-3199-no-mem-leaks.result             | 120 ++++---
>  test/sql/gh2141-delete-trigger-drop-table.result |  20 +-
>  test/sql/gh2251-multiple-update.result           |  10 +-
>  test/sql/iproto.result                           | 110 +++---
>  test/sql/misc.result                             |  25 +-
>  test/sql/on-conflict.result                      |  20 +-
>  test/sql/persistency.result                      | 190 ++++++----
>  test/sql/row-count.result                        |  25 +-
>  test/sql/sql-debug.result                        |  15 +-
>  test/sql/transition.result                       | 190 ++++++----
>  test/sql/types.result                            | 105 +++---
>  test/sql/update-with-nested-select.result        |   5 +-
>  40 files changed, 1376 insertions(+), 834 deletions(-)
> 

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

* Re: [Tarantool-patches] [PATCH 1/6] sql: refactor resulting set metadata
  2019-11-28 22:41   ` Vladislav Shpilevoy
@ 2019-12-05 11:39     ` Nikita Pettik
  2019-12-05 23:58       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 40+ messages in thread
From: Nikita Pettik @ 2019-12-05 11:39 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

> > diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
> > index 0ecc676e2..2d3466cc7 100644
> > --- a/src/box/sql/prepare.c
> > +++ b/src/box/sql/prepare.c
> > @@ -146,11 +146,10 @@ sqlPrepare(sql * db,	/* Database handle. */
> >  		sqlVdbeSetNumCols(sParse.pVdbe, name_count);
> >  		for (int i = 0; i < name_count; i++) {
> >  			int name_index = 2 * i + name_first;
> > -			sqlVdbeSetColName(sParse.pVdbe, i, COLNAME_NAME,
> > -					  azColName[name_index], SQL_STATIC);
> > -			sqlVdbeSetColName(sParse.pVdbe, i, COLNAME_DECLTYPE,
> > -					  azColName[name_index + 1],
> > -					  SQL_STATIC);
> > +			vdbe_set_metadata_col_name(sParse.pVdbe, i,
> > +					  azColName[name_index]);
> > +			vdbe_set_metadata_col_type(sParse.pVdbe, i,
> > +					  azColName[name_index + 1]);
> 
> 1. Lets fix this indentation.

NP:

diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index 2d3466cc7..9edc59ab5 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -147,9 +147,9 @@ sqlPrepare(sql * db,        /* Database handle. */
                for (int i = 0; i < name_count; i++) {
                        int name_index = 2 * i + name_first;
                        vdbe_set_metadata_col_name(sParse.pVdbe, i,
-                                         azColName[name_index]);
+                                                  azColName[name_index]);
                        vdbe_set_metadata_col_type(sParse.pVdbe, i,
-                                         azColName[name_index + 1]);
+                                                  azColName[name_index + 1]);
                }
        }
 
> > diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> > index 8f93edd16..d6b8a158f 100644
> > --- a/src/box/sql/select.c
> > +++ b/src/box/sql/select.c
> > @@ -2828,7 +2824,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
> >  						Select *pFirst = p;
> >  						while (pFirst->pPrior)
> >  							pFirst = pFirst->pPrior;
> > -						generateColumnNames(pParse,
> > +						generate_column_metadata(pParse,
> >  								    pFirst->pSrc,
> >  								    pFirst->pEList);
> 
> 2. And this too.

diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index d6b8a158f..212f33dd7 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -2825,8 +2825,8 @@ multiSelect(Parse * pParse,       /* Parsing context */
                                                while (pFirst->pPrior)
                                                        pFirst = pFirst->pPrior;
                                                generate_column_metadata(pParse,
-                                                                   pFirst->pSrc,
-                                                                   pFirst->pEList);
+                                                                        pFirst->pSrc,
+                                                                        pFirst->pEList);
                                        }
                                        iBreak = sqlVdbeMakeLabel(v);
                                        iCont = sqlVdbeMakeLabel(v);
 
> >  					}
> > diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
> > index 582d48a1f..4142fb6ba 100644
> > --- a/src/box/sql/vdbe.h
> > +++ b/src/box/sql/vdbe.h
> > @@ -248,7 +241,18 @@ void sqlVdbeResetStepResult(Vdbe *);
> >  void sqlVdbeRewind(Vdbe *);
> >  int sqlVdbeReset(Vdbe *);
> >  void sqlVdbeSetNumCols(Vdbe *, int);
> > -int sqlVdbeSetColName(Vdbe *, int, int, const char *, void (*)(void *));
> > +
> > +/**
> > + * Set the name of the idx'th column to be returned by the SQL
> > + * statement. @name must be a pointer to a nul terminated string.
> > + * This call must be made after a call to sqlVdbeSetNumCols().
> > + */
> > +int
> > +vdbe_set_metadata_col_name(struct Vdbe *v, int col_idx, const char *name);
> > +
> > +int
> > +vdbe_set_metadata_col_type(struct Vdbe *v, int col_idx, const char *type);
> 
> 3. I think, that we should use one prefix for metadata
> methods. You used 'vdbe_metadata_delete()' above, so lets
> make it 'vdbe_metadata_*' everywhere. And these functions
> will be
> 
>     vdbe_metadata_set_col_name/type...

Ok, renamed here and in other patches.

> > diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
> > index 0f32b4cd6..9ab3753cb 100644
> > --- a/src/box/sql/vdbeInt.h
> > +++ b/src/box/sql/vdbeInt.h
> > @@ -394,7 +399,8 @@ struct Vdbe {
> >  	Op *aOp;		/* Space to hold the virtual machine's program */
> >  	Mem *aMem;		/* The memory locations */
> >  	Mem **apArg;		/* Arguments to currently executing user function */
> > -	Mem *aColName;		/* Column names to return */
> > +	/** SQL metadata for SELECT queries. */
> 
> 4. Looks like it can be emitted on DML too. I see
> vdbe_set_metadata_col_name(v, 0, "rows updated") in
> sqlUpdate(), and vdbe_set_metadata_col_name(v, 0, "rows deleted")
> in sql_table_delete_from().

Fair, fixed comment:

diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 9ab3753cb..4fabab9d6 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -399,7 +399,7 @@ struct Vdbe {
        Op *aOp;                /* Space to hold the virtual machine's program */
        Mem *aMem;              /* The memory locations */
        Mem **apArg;            /* Arguments to currently executing user function */
-       /** SQL metadata for SELECT queries. */
+       /** SQL metadata for DML/DQL queries. */
        struct sql_column_metadata *metadata;
        Mem *pResultSet;        /* Pointer to an array of results */
        VdbeCursor **apCsr;     /* One element of this array for each open cursor */
 
> > diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> > index a1d658648..db11fbf33 100644
> > --- a/src/box/sql/vdbeaux.c
> > +++ b/src/box/sql/vdbeaux.c
> > @@ -1827,6 +1827,18 @@ Cleanup(Vdbe * p)
> >  	p->pResultSet = 0;
> >  }
> >  
> > +void
> > +vdbe_metadata_delete(struct Vdbe *v)
> > +{
> > +	if (v->metadata != NULL) {
> > +		for (int i = 0; i < v->nResColumn; ++i) {
> > +			free((void *)v->metadata[i].name);
> > +			free((void *)v->metadata[i].type);
> 
> 5. The necessity to do that cast basically makes 'const'
> in the struct useless. Could you please make them just
> char *, not const char *?

Yep:

diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 9ab3753cb..64250bee2 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -347,8 +347,8 @@ struct ScanStatus {
 };
 
 struct sql_column_metadata {
-       const char *name;
-       const char *type;
+       char *name;
+       char *type;
 };

diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 8cbe6c368..e5528c0e2 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1832,8 +1832,8 @@ vdbe_metadata_delete(struct Vdbe *v)
 {
        if (v->metadata != NULL) {
                for (int i = 0; i < v->nResColumn; ++i) {
-                       free((void *)v->metadata[i].name);
-                       free((void *)v->metadata[i].type);
+                       free(v->metadata[i].name);
+                       free(v->metadata[i].type);
                }
                free(v->metadata);
        }


> 
> > +		}
> > +		free(v->metadata);
> > +	}
> > +}
> > +
> >  /*
> >   * Set the number of result columns that will be returned by this SQL
> >   * statement. This is now set at compile time, rather than during
> > @@ -1836,50 +1848,44 @@ Cleanup(Vdbe * p)
> > +vdbe_set_metadata_col_name(struct Vdbe *p, int idx, const char *name)
> > +{
> >  	assert(idx < p->nResColumn);
> > -	assert(var < COLNAME_N);
> > -	if (p->db->mallocFailed) {
> > -		assert(!zName || xDel != SQL_DYNAMIC);
> > +	if (p->metadata[idx].name != NULL)
> > +		free((void *)p->metadata[idx].name);
> > +	p->metadata[idx].name = strdup(name);
> > +	if (p->metadata[idx].name == NULL) {
> > +		diag_set(OutOfMemory, strlen(name), "strdup", "name");
> 
> 6. Len + 1. Dup allocates terminating zero too. In the next patches
> too.

diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index db11fbf33..8cbe6c368 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1861,28 +1861,28 @@ sqlVdbeSetNumCols(Vdbe * p, int nResColumn)
 }
 
 int
-vdbe_set_metadata_col_name(struct Vdbe *p, int idx, const char *name)
+vdbe_metadata_set_col_name(struct Vdbe *p, int idx, const char *name)
 {
        assert(idx < p->nResColumn);
        if (p->metadata[idx].name != NULL)
                free((void *)p->metadata[idx].name);
        p->metadata[idx].name = strdup(name);
        if (p->metadata[idx].name == NULL) {
-               diag_set(OutOfMemory, strlen(name), "strdup", "name");
+               diag_set(OutOfMemory, strlen(name) + 1, "strdup", "name");
                return -1;
        }
        return 0;
 }
 
 int
-vdbe_set_metadata_col_type(struct Vdbe *p, int idx, const char *type)
+vdbe_metadata_set_col_type(struct Vdbe *p, int idx, const char *type)
 {
        assert(idx < p->nResColumn);
        if (p->metadata[idx].type != NULL)
                free((void *)p->metadata[idx].type);
        p->metadata[idx].type = strdup(type);
        if (p->metadata[idx].type == NULL) {
-               diag_set(OutOfMemory, strlen(type), "strdup", "type");
+               diag_set(OutOfMemory, strlen(type) + 1, "strdup", "type");
                return -1;
        }
        return 0;
 

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

* Re: [Tarantool-patches] [PATCH 2/6] sql: fix possible null dereference in sql_expr_coll()
  2019-11-28 22:42   ` Vladislav Shpilevoy
@ 2019-12-05 11:40     ` Nikita Pettik
  2019-12-05 23:59       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 40+ messages in thread
From: Nikita Pettik @ 2019-12-05 11:40 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

On 28 Nov 23:42, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> Is it possible to test this?

As a pure test case - I've failed to come up with it.
But the next patch (which adds collation to metadata) definitely fails
without this fix (when TRIM() or REPLACE() are called without args).
I can dive into details, but I guess it's not so important here (since
this is obviously buggy place). If you want further investigation, let me
know and I will do it.
 
> On 27/11/2019 13:15, Nikita Pettik wrote:
> > ---
> >  src/box/sql/expr.c | 3 ++-
> >  1 file changed, 2 insertions(+), 1 deletion(-)
> > 
> > diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> > index 648b7170e..0bdcfe576 100644
> > --- a/src/box/sql/expr.c
> > +++ b/src/box/sql/expr.c
> > @@ -332,7 +332,8 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
> >  				sql_func_by_signature(p->u.zToken, arg_count);
> >  			if (func == NULL)
> >  				break;
> > -			if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL)) {
> > +			if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL) &&
> > +			    arg_count > 0) {
> >  				/*
> >  				 * Now we use quite straightforward
> >  				 * approach assuming that resulting
> > 

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

* Re: [Tarantool-patches] [PATCH 3/6] sql: extend result set with collation
  2019-11-28 22:41   ` Vladislav Shpilevoy
@ 2019-12-05 11:50     ` Nikita Pettik
  0 siblings, 0 replies; 40+ messages in thread
From: Nikita Pettik @ 2019-12-05 11:50 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

On 28 Nov 23:41, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> I see, that we calculate nullable and autoinc for
> table columns only. And it makes sense. Then maybe
> we need collations and alias also for columns only,
> not for all strings and result set columns? Could you
> please investigate? I think Alexander knows.

Alexander is guided by Oracle docs:
https://docs.oracle.com/javase/8/docs/api/java/sql/ParameterMetaData.html

Firstly, there's isCaseSensitive() method instead of getCollation().
However, collation includes information about case sensitivity, so
I've decided to include collation to metadata. Secodnly, from this
doc it's unclear whether isCaseSensitive() method applies to columns
which correspond to table columns, or to any column of result set
(and I've failed to find accurate description of result set in ANSI).
On the other hand, setting collation for expressions worth nothing, so
I see no reason (except a few additional bytes in netbox response) to
not include collation for all expressions.
To avoid overhead when user doesn't need extended meta, we can add
setting (pragma or whatever) like sql_extended_meta = true/false.

> On 27/11/2019 13:15, Nikita Pettik wrote:
> > If resulting set column is of STRING type and features collation (no
> > matter explicit or implicit) different from "none", then metadata will
> > contain its name.
> > 
> > Part of #4407
> > ---
> >  src/box/execute.c          |  31 +++++++--
> >  src/box/iproto_constants.h |   1 +
> >  src/box/lua/execute.c      |   7 +-
> >  src/box/lua/net_box.c      |  20 +++++-
> >  src/box/sql/select.c       |  18 ++++++
> >  src/box/sql/sqlInt.h       |   3 +
> >  src/box/sql/vdbe.h         |   4 ++
> >  src/box/sql/vdbeInt.h      |   1 +
> >  src/box/sql/vdbeapi.c      |   9 +++
> >  src/box/sql/vdbeaux.c      |  16 +++++
> >  test/sql/collation.result  | 155 +++++++++++++++++++++++++++------------------
> >  11 files changed, 195 insertions(+), 70 deletions(-)
> > 
> > diff --git a/src/box/execute.c b/src/box/execute.c
> > index e8b012e5b..20bfd0957 100644
> > --- a/src/box/execute.c
> > +++ b/src/box/execute.c
> > @@ -267,6 +267,23 @@ error:
> >  	region_truncate(region, svp);
> >  	return -1;
> >  }
> > +static size_t
> 
> 1. Please, add an empty line between these 2 functions.
> (In this commit.)

Fixed.
 
> > +/** Decode optional (i.e. may be present in response) metadata fields. */
> > +static void
> > +decode_metadata_optional(struct lua_State *L, const char **data,
> > +			 uint32_t map_size)
> > +{
> > +	/* 2 is default metadata map size (field name + field size). */
> > +	while (map_size-- > 2) {
> > +		uint32_t key = mp_decode_uint(data);
> > +		uint32_t len;
> > +		if (key == IPROTO_FIELD_COLL) {
> > +			const char *coll = mp_decode_str(data, &len);
> > +			lua_pushlstring(L, coll, len);
> > +			lua_setfield(L, -2, "collation");
> > +		}
> > +	}
> 
> 2. Netbox relies on certain order of fields in iproto
> response so as to avoid cycles and checking for already
> decoded fields. You can safely use it too. To eliminate
> the 'while' cycle you can append to netbox_decode_metadata's
> cycle:
> 
>     if (map_size == 2)
>         continue;
>     key = mp_decode_uint(data);
>     if (key == IPROTO_FIELD_COLL) {
>         /* handle coll ... */
>     }
>     if (map_size == 3)
>         continue;
>     key = mp_decode_uint(data);
>     if (key == IPROTO_FIELD_NULLABLE) {
>         /* handle nullable ... */
>     }
> 

TBO current implementation with cycle looks cleaner for me.
If you will excuse me, I leave it as it is.

> > +
> >  /**
> >   * Decode IPROTO_METADATA into array of maps.
> >   * @param L Lua stack to push result on.
> > diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> > index d6b8a158f..66e8c1274 100644
> > --- a/src/box/sql/select.c
> > +++ b/src/box/sql/select.c
> > @@ -1794,6 +1794,22 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
> >  			var_pos[var_count++] = i;
> >  		vdbe_set_metadata_col_type(v, i,
> >  					   field_type_strs[sql_expr_type(p)]);
> > +		if (sql_expr_type(p) == FIELD_TYPE_STRING) {
> > +			bool unused;
> > +			uint32_t id;
> > +			struct coll *coll = NULL;
> > +			/*
> > +			 * If sql_expr_coll fails then it fails somewhere
> > +			 * above the call stack.
> > +			 */
> 
> 3. And lets add an assertion for that.

diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index d65644848..962f20d76 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1802,7 +1802,9 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
                         * If sql_expr_coll fails then it fails somewhere
                         * above the call stack.
                         */
-                       (void) sql_expr_coll(pParse, p, &unused, &id, &coll);
+                       int rc =  sql_expr_coll(pParse, p, &unused, &id, &coll);
+                       assert(rc == 0);
+                       (void) rc;
                        if (id != COLL_NONE) {
                                struct coll_id *coll_id = coll_by_id(id);
                                vdbe_set_metadata_col_collation(v, i,
 
> > +			(void) sql_expr_coll(pParse, p, &unused, &id, &coll);
> > +			if (id != COLL_NONE) {
> > +				struct coll_id *coll_id = coll_by_id(id);
> > +				vdbe_set_metadata_col_collation(v, i,
> > +								coll_id->name,
> > +								coll_id->name_len);
> > +			}
> > +		}
> >  		if (pEList->a[i].zName) {
> >  			char *zName = pEList->a[i].zName;
> >  			vdbe_set_metadata_col_name(v, i, zName);
> > @@ -1819,6 +1836,7 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
> >  			} else {
> >  				vdbe_set_metadata_col_name(v, i, zCol);
> >  			}
> > +
> >  		} else {
> 
> 4. How about drop of these two hunks consisting of empty lines?

Sorry, removed.

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

* Re: [Tarantool-patches] [PATCH 4/6] sql: extend result set with nullability
  2019-11-28 22:41   ` Vladislav Shpilevoy
@ 2019-12-05 11:50     ` Nikita Pettik
  2019-12-06  0:00       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 40+ messages in thread
From: Nikita Pettik @ 2019-12-05 11:50 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

On 28 Nov 23:41, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> You added a leading whitespace to the commit title.

Fixed.
 
> Here is a strange example:
> 
>     box.cfg{}
>     s = box.schema.create_space('TEST', {
>         format = {{'ID', 'unsigned'}, {'COL', 'unsigned', is_nullable = true}}
>     })
>     pk = s:create_index('pk', {parts = {{'ID'}, {'COL', is_nullable = false}}})
> 
> In that example I defined a 'false-nullable' column. It
> is nullable in the space format, but is not in the
> index format. The strictest rule wins, so it is not nullable
> after all. That can be seen in struct tuple_format. SQL says,
> that it is nullable. But this is not a real problem. Perhaps
> this might be even right.

If it was up to me, I would disallow such possible inconsistency
between space and index formats. It turns out to be quite
confusing. I believe there's historical reason for that, tho.

>However the next example certainly
> misses something:
> 
>     box.execute('SELECT * FROM test')
>     ---
>     - metadata:
>       - name: ID
>         type: unsigned
>       - name: COL
>         type: unsigned
>       rows: []
>     ...
> 
>     box.execute('SELECT col FROM test')
>     ---
>     - metadata:
>       - type: unsigned
>         name: COL
>         is_nullable: true
>       rows: []
>     ...
> 
> As you can see, nullable depends on whether I select a
> column explicitly or not. Perhaps the same happens to
> autoincrement meta, I didn't check.

I'm glad you found this bug, thanks. To fix that I have to
add a bit more refactoring which I've introduced in the
first patch:

diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index d7975bc7f..e4768121e 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1794,10 +1794,7 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
                        var_pos[var_count++] = i;
                vdbe_metadata_set_col_type(v, i,
                                           field_type_strs[sql_expr_type(p)]);
-               if (pEList->a[i].zName) {
-                       char *zName = pEList->a[i].zName;
-                       vdbe_metadata_set_col_name(v, i, zName);
-               } else if (p->op == TK_COLUMN || p->op == TK_AGG_COLUMN) {
+               if (p->op == TK_COLUMN || p->op == TK_AGG_COLUMN) {
                        char *zCol;
                        int iCol = p->iColumn;
                        for (j = 0; ALWAYS(j < pTabList->nSrc); j++) {
@@ -1808,20 +1805,28 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
                        struct space_def *space_def = pTabList->a[j].space->def;
                        assert(iCol >= 0 && iCol < (int)space_def->field_count);
                        zCol = space_def->fields[iCol].name;
-                       if (!shortNames && !fullNames) {
-                               vdbe_metadata_set_col_name(v, i,
-                                                          pEList->a[i].zSpan);
-                       } else if (fullNames) {
-                               const char *zName = tt_sprintf("%s.%s",
-                                                              space_def->name,
-                                                              zCol);
-                               vdbe_metadata_set_col_name(v, i, zName);
+                       const char *name = NULL;
+                       if (pEList->a[i].zName != NULL) {
+                               name = pEList->a[i].zName;
                        } else {
-                               vdbe_metadata_set_col_name(v, i, zCol);
+                               if (!shortNames && !fullNames) {
+                                       name = pEList->a[i].zSpan;
+                               } else if (fullNames) {
+                                       name = tt_sprintf("%s.%s",
+                                                         space_def->name,
+                                                         zCol);
+                               } else {
+                                       name = zCol;
+                               }
                        }
+                       vdbe_metadata_set_col_name(v, i, name);
                } else {
-                       const char *z = pEList->a[i].zSpan;
-                       if (z == NULL)
+                       const char *z = NULL;
+                       if (pEList->a[i].zName != NULL)
+                               z = pEList->a[i].zName;
+                       else if (pEList->a[i].zSpan != NULL)
+                               z = pEList->a[i].zSpan;
+                       else
                                z = tt_sprintf("column%d", i + 1);
                        vdbe_metadata_set_col_name(v, i, z);
                }

> > diff --git a/src/box/execute.c b/src/box/execute.c
> > index 20bfd0957..98812ae1e 100644
> > --- a/src/box/execute.c
> > +++ b/src/box/execute.c
> > @@ -267,8 +267,10 @@ error:
> >  	region_truncate(region, svp);
> >  	return -1;
> >  }
> > +
> >  static size_t
> > -metadata_map_sizeof(const char *name, const char *type, const char *coll)
> > +metadata_map_sizeof(const char *name, const char *type, const char *coll,
> > +		    int nullable)
> 
> 1. Please, rename to 'is_nullable'. Here and in other places.

IMHO 'is_' prefix implies boolean value, meanwhile here nullable
is in fact three-valued: 0, 1 implies nullable/not nullable, -1
is unknown (for columns of resulting set that are complex expressions).
So that we can avoid sending 'nullable' property in meta for expressions,
but always set it for columns. Hence, I omitted is_ prefix on purpose.

> >  {
> >  	uint32_t members_count = 2;
> >  	size_t map_size = 0;
> > @@ -277,6 +279,11 @@ metadata_map_sizeof(const char *name, const char *type, const char *coll)
> >  		map_size += mp_sizeof_uint(IPROTO_FIELD_COLL);
> >  		map_size += mp_sizeof_str(strlen(coll));
> >  	}
> > +	if (nullable != -1) {
> > +		members_count++;
> > +		map_size += mp_sizeof_uint(IPROTO_FIELD_NULLABLE);
> 
> 2. This too. IPROTO_FIELD_IS_NULLABLE.

Ok, here it makes sense:

diff --git a/src/box/execute.c b/src/box/execute.c
index 98812ae1e..0616638ab 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -281,7 +281,7 @@ metadata_map_sizeof(const char *name, const char *type, const char *coll,
        }
        if (nullable != -1) {
                members_count++;
-               map_size += mp_sizeof_uint(IPROTO_FIELD_NULLABLE);
+               map_size += mp_sizeof_uint(IPROTO_FIELD_IS_NULLABLE);
                map_size += mp_sizeof_bool(nullable);
        }
        map_size += mp_sizeof_uint(IPROTO_FIELD_NAME);
@@ -343,7 +343,7 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
                        pos = mp_encode_str(pos, coll, strlen(coll));
                }
                if (nullable != -1) {
-                       pos = mp_encode_uint(pos, IPROTO_FIELD_NULLABLE);
+                       pos = mp_encode_uint(pos, IPROTO_FIELD_IS_NULLABLE);
                        pos = mp_encode_bool(pos, nullable);
                }
        }
diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h
index 030c25531..53d014b60 100644
--- a/src/box/iproto_constants.h
+++ b/src/box/iproto_constants.h
@@ -132,7 +132,7 @@ enum iproto_metadata_key {
        IPROTO_FIELD_NAME = 0,
        IPROTO_FIELD_TYPE = 1,
        IPROTO_FIELD_COLL = 2,
-       IPROTO_FIELD_NULLABLE = 3
+       IPROTO_FIELD_IS_NULLABLE = 3,
 };
 
 enum iproto_ballot_key {
diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
index 3e93cbc75..9dfcebd23 100644
--- a/src/box/lua/net_box.c
+++ b/src/box/lua/net_box.c
@@ -652,7 +652,7 @@ decode_metadata_optional(struct lua_State *L, const char **data,
                        lua_pushlstring(L, coll, len);
                        lua_setfield(L, -2, "collation");
                } else {
-                       assert(key == IPROTO_FIELD_NULLABLE);
+                       assert(key == IPROTO_FIELD_IS_NULLABLE);
                        bool is_nullable = mp_decode_bool(data);
                        lua_pushboolean(L, is_nullable);
                        lua_setfield(L, -2, "is_nullable");

> > @@ -334,6 +342,10 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
> >  			pos = mp_encode_uint(pos, IPROTO_FIELD_COLL);
> >  			pos = mp_encode_str(pos, coll, strlen(coll));
> >  		}
> > +		if (nullable != -1) {
> > +			pos = mp_encode_uint(pos, IPROTO_FIELD_NULLABLE);
> > +			pos = mp_encode_bool(pos, nullable);
> > +		}
> 
> 3. We send autoincrement only in case it is true.
> Then how about sending nullability only in case it
> is true? To save a bit of the network.

Autoincrement can appear only once in the result set, so it barely
makes sense to send it for each column. On the other hand, we tell
column's false nullability from 'unknown' nullability of expressions.
That's what I want to underline using three-valued nullability status
in meta.

> >  	}
> >  	return 0;
> >  }
> > diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h
> > index fa9c029a2..030c25531 100644
> > --- a/src/box/iproto_constants.h
> > +++ b/src/box/iproto_constants.h
> > @@ -132,6 +132,7 @@ enum iproto_metadata_key {
> >  	IPROTO_FIELD_NAME = 0,
> >  	IPROTO_FIELD_TYPE = 1,
> >  	IPROTO_FIELD_COLL = 2,
> > +	IPROTO_FIELD_NULLABLE = 3
> 
> 4. Please, add a comma after 3 so as not to
> change this line in the next commit.

Fixed.
 
> > index 66e8c1274..b772bcead 100644
> > --- a/src/box/sql/select.c
> > +++ b/src/box/sql/select.c
> > @@ -1836,7 +1837,10 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
> >  			} else {
> >  				vdbe_set_metadata_col_name(v, i, zCol);
> >  			}
> > -
> > +			bool is_nullable = space_def->fields[iCol].is_nullable;
> > +			if (p->op == TK_COLUMN)
> > +				vdbe_set_metadata_col_nullability(v, i,
> > +								  is_nullable);
> 
> 5. Please, wrap into {}.

Skipped (taking into consideration refactoring at the beginning of letter).

New patch (I exluded test changes from diff):

Author: Nikita Pettik <korablev@tarantool.org>
Date:   Mon Nov 25 23:04:58 2019 +0300

    sql: extend result set with nullability
    
    If member of result set is (solely) column identifier, then metadata
    will contain its corresponding field nullability as boolean property.
    Note that indicating nullability for other expressions (like x + 1)
    may make sense but it requires derived nullability calculation which in
    turn seems to be overkill (at least in scope of current patch).
    
    Part of #4407

diff --git a/src/box/execute.c b/src/box/execute.c
index 72300235a..0616638ab 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -269,7 +269,8 @@ error:
 }
 
 static size_t
-metadata_map_sizeof(const char *name, const char *type, const char *coll)
+metadata_map_sizeof(const char *name, const char *type, const char *coll,
+                   int nullable)
 {
        uint32_t members_count = 2;
        size_t map_size = 0;
@@ -278,6 +279,11 @@ metadata_map_sizeof(const char *name, const char *type, const char *coll)
                map_size += mp_sizeof_uint(IPROTO_FIELD_COLL);
                map_size += mp_sizeof_str(strlen(coll));
        }
+       if (nullable != -1) {
+               members_count++;
+               map_size += mp_sizeof_uint(IPROTO_FIELD_IS_NULLABLE);
+               map_size += mp_sizeof_bool(nullable);
+       }
        map_size += mp_sizeof_uint(IPROTO_FIELD_NAME);
        map_size += mp_sizeof_uint(IPROTO_FIELD_TYPE);
        map_size += mp_sizeof_str(strlen(name));
@@ -312,6 +318,7 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
                const char *coll = sql_column_coll(stmt, i);
                const char *name = sql_column_name(stmt, i);
                const char *type = sql_column_datatype(stmt, i);
+               int nullable = sql_column_is_nullable(stmt, i);
                /*
                 * Can not fail, since all column names and types
                 * are preallocated during prepare phase and the
@@ -319,13 +326,13 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
                 */
                assert(name != NULL);
                assert(type != NULL);
-               size = metadata_map_sizeof(name, type, coll);
+               size = metadata_map_sizeof(name, type, coll, nullable);
                char *pos = (char *) obuf_alloc(out, size);
                if (pos == NULL) {
                        diag_set(OutOfMemory, size, "obuf_alloc", "pos");
                        return -1;
                }
-               uint32_t map_sz = 2 + (coll != NULL);
+               uint32_t map_sz = 2 + (coll != NULL) + (nullable != -1);
                pos = mp_encode_map(pos, map_sz);
                pos = mp_encode_uint(pos, IPROTO_FIELD_NAME);
                pos = mp_encode_str(pos, name, strlen(name));
@@ -335,6 +342,10 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
                        pos = mp_encode_uint(pos, IPROTO_FIELD_COLL);
                        pos = mp_encode_str(pos, coll, strlen(coll));
                }
+               if (nullable != -1) {
+                       pos = mp_encode_uint(pos, IPROTO_FIELD_IS_NULLABLE);
+                       pos = mp_encode_bool(pos, nullable);
+               }
        }
        return 0;
 }
diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h
index fa9c029a2..53d014b60 100644
--- a/src/box/iproto_constants.h
+++ b/src/box/iproto_constants.h
@@ -132,6 +132,7 @@ enum iproto_metadata_key {
        IPROTO_FIELD_NAME = 0,
        IPROTO_FIELD_TYPE = 1,
        IPROTO_FIELD_COLL = 2,
+       IPROTO_FIELD_IS_NULLABLE = 3,
 };
 
 enum iproto_ballot_key {
diff --git a/src/box/lua/execute.c b/src/box/lua/execute.c
index 8a530bfc1..3d7ca710c 100644
--- a/src/box/lua/execute.c
+++ b/src/box/lua/execute.c
@@ -24,6 +24,7 @@ lua_sql_get_metadata(struct sql_stmt *stmt, struct lua_State *L,
                lua_createtable(L, 0, coll != NULL ? 3 : 2);
                const char *name = sql_column_name(stmt, i);
                const char *type = sql_column_datatype(stmt, i);
+               int nullable = sql_column_is_nullable(stmt, i);
                /*
                 * Can not fail, since all column names are
                 * preallocated during prepare phase and the
@@ -39,6 +40,10 @@ lua_sql_get_metadata(struct sql_stmt *stmt, struct lua_State *L,
                        lua_pushstring(L, coll);
                        lua_setfield(L, -2, "collation");
                }
+               if (nullable != -1) {
+                       lua_pushboolean(L, nullable);
+                       lua_setfield(L, -2, "is_nullable");
+               }
                lua_rawseti(L, -2, i + 1);
        }
 }
diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
index afbd1e1be..9dfcebd23 100644
--- a/src/box/lua/net_box.c
+++ b/src/box/lua/net_box.c
@@ -651,6 +651,11 @@ decode_metadata_optional(struct lua_State *L, const char **data,
                        const char *coll = mp_decode_str(data, &len);
                        lua_pushlstring(L, coll, len);
                        lua_setfield(L, -2, "collation");
+               } else {
+                       assert(key == IPROTO_FIELD_IS_NULLABLE);
+                       bool is_nullable = mp_decode_bool(data);
+                       lua_pushboolean(L, is_nullable);
+                       lua_setfield(L, -2, "is_nullable");
                }
        }
 }
@@ -667,7 +672,7 @@ netbox_decode_metadata(struct lua_State *L, const char **data)
        lua_createtable(L, count, 0);
        for (uint32_t i = 0; i < count; ++i) {
                uint32_t map_size = mp_decode_map(data);
-               assert(map_size == 2 || map_size == 3);
+               assert(map_size >= 2 && map_size <= 4);
                (void) map_size;
                uint32_t key = mp_decode_uint(data);
                assert(key == IPROTO_FIELD_NAME);
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index c0a1d244c..a0ffbb670 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1838,6 +1838,8 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
                                }
                        }
                        vdbe_metadata_set_col_name(v, i, name);
+                       bool is_nullable = space_def->fields[iCol].is_nullable;
+                       vdbe_metadata_set_col_nullability(v, i, is_nullable);
                } else {
                        const char *z = NULL;
                        if (pEList->a[i].zName != NULL)
@@ -1847,6 +1849,7 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
                        else
                                z = tt_sprintf("column%d", i + 1);
                        vdbe_metadata_set_col_name(v, i, z);
+                       vdbe_metadata_set_col_nullability(v, i, -1);
                }
        }
        if (var_count == 0)
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 4c2e3ed73..89920b3d1 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -579,6 +579,9 @@ sql_column_datatype(sql_stmt *, int N);
 const char *
 sql_column_coll(sql_stmt *stmt, int n);
 
+int
+sql_column_is_nullable(sql_stmt *stmt, int n);
+
 int
 sql_initialize(void);
 
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 22ba0b756..384d7dc0a 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -257,6 +257,9 @@ int
 vdbe_metadata_set_col_collation(struct Vdbe *p, int idx, const char *coll,
                                size_t coll_len);
 
+void
+vdbe_metadata_set_col_nullability(struct Vdbe *p, int idx, int nullable);
+
 void sqlVdbeCountChanges(Vdbe *);
 sql *sqlVdbeDb(Vdbe *);
 void sqlVdbeSetSql(Vdbe *, const char *z, int n, int);
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index d3de5770b..92a50dd7b 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -350,6 +350,11 @@ struct sql_column_metadata {
        char *name;
        char *type;
        char *collation;
+       /**
+        * -1 is for any member of result set except for pure
+        * columns: all other expressions are nullable by default.
+        */
+       int8_t nullable : 2;
 };
 
 /*
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index e57a80334..1d6971223 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -753,6 +753,13 @@ sql_column_coll(sql_stmt *stmt, int n)
        return p->metadata[n].collation;
 }
 
+int
+sql_column_is_nullable(sql_stmt *stmt, int n)
+{
+       struct Vdbe *p = (struct Vdbe *) stmt;
+       assert(n < sql_column_count(stmt) && n >= 0);
+       return p->metadata[n].nullable;
+}
 
 /******************************* sql_bind_  **************************
  *
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 25148a49a..6b467951b 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1904,6 +1904,13 @@ vdbe_metadata_set_col_collation(struct Vdbe *p, int idx, const char *coll,
        return 0;
 }
 
+void
+vdbe_metadata_set_col_nullability(struct Vdbe *p, int idx, int nullable)
+{
+       assert(idx < p->nResColumn);
+       p->metadata[idx].nullable = nullable;
+}
+
 /*
  * This routine checks that the sql.nVdbeActive count variable
  * matches the number of vdbe's in the list sql.pVdbe that are

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

* Re: [Tarantool-patches] [PATCH 5/6] sql: extend result set with autoincrement
  2019-11-28 22:41   ` Vladislav Shpilevoy
@ 2019-12-05 11:51     ` Nikita Pettik
  0 siblings, 0 replies; 40+ messages in thread
From: Nikita Pettik @ 2019-12-05 11:51 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

On 28 Nov 23:41, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> On 27/11/2019 13:15, Nikita Pettik wrote:
> > If result set contains column which features attached sequence
> > (AUTOINCREMENT in terms of SQL) then meta-information will contain
> > corresponding field ('autoicrement' : boolean) in response.
> 
> 1. Please, lets name all booleans values with 'is_' prefix:
> is_autoincrement, IPROTO_FIELD_IS_AUTOINCREMENT.
> 
> > diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
> > index 0e54e42a5..63afb8777 100644
> > --- a/src/box/sql/vdbeInt.h
> > +++ b/src/box/sql/vdbeInt.h
> > @@ -355,6 +355,8 @@ struct sql_column_metadata {
> >  	 * columns: all other expressions are nullable by default.
> >  	 */
> >  	int8_t nullable : 2;
> > +	/** True if column features autoincrement property. */
> > +	int8_t actoincrement : 1;
> 
> 2. Lets make it bool, and name is_autoincrement.

Both points are fixed:

diff --git a/src/box/execute.c b/src/box/execute.c
index 92f0f8ef7..0f18b3e5f 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -286,7 +286,7 @@ metadata_map_sizeof(const char *name, const char *type, const char *coll,
        }
        if (is_autoincrement) {
                members_count++;
-               map_size += mp_sizeof_uint(IPROTO_FIELD_AUTOINCREMENT);
+               map_size += mp_sizeof_uint(IPROTO_FIELD_IS_AUTOINCREMENT);
                map_size += mp_sizeof_bool(true);
        }
        map_size += mp_sizeof_uint(IPROTO_FIELD_NAME);
@@ -355,7 +355,7 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
                        pos = mp_encode_bool(pos, nullable);
                }
                if (is_autoincrement) {
-                       pos = mp_encode_uint(pos, IPROTO_FIELD_AUTOINCREMENT);
+                       pos = mp_encode_uint(pos, IPROTO_FIELD_IS_AUTOINCREMENT);
                        pos = mp_encode_bool(pos, true);
                }
        }
diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h
index abfb279d0..30d1af4cb 100644
--- a/src/box/iproto_constants.h
+++ b/src/box/iproto_constants.h
@@ -133,7 +133,7 @@ enum iproto_metadata_key {
        IPROTO_FIELD_TYPE = 1,
        IPROTO_FIELD_COLL = 2,
        IPROTO_FIELD_IS_NULLABLE = 3,
-       IPROTO_FIELD_AUTOINCREMENT = 4,
+       IPROTO_FIELD_IS_AUTOINCREMENT = 4,
 };
 
 enum iproto_ballot_key {
diff --git a/src/box/lua/execute.c b/src/box/lua/execute.c
index d7dd9432f..86192b728 100644
--- a/src/box/lua/execute.c
+++ b/src/box/lua/execute.c
@@ -47,7 +47,7 @@ lua_sql_get_metadata(struct sql_stmt *stmt, struct lua_State *L,
                }
                if (is_autoincrement) {
                        lua_pushboolean(L, true);
-                       lua_setfield(L, -2, "autoincrement");
+                       lua_setfield(L, -2, "is_autoincrement");
                }
                lua_rawseti(L, -2, i + 1);
        }
diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
index e67c14bed..a312f6cca 100644
--- a/src/box/lua/net_box.c
+++ b/src/box/lua/net_box.c
@@ -656,10 +656,10 @@ decode_metadata_optional(struct lua_State *L, const char **data,
                        lua_pushboolean(L, is_nullable);
                        lua_setfield(L, -2, "is_nullable");
                } else {
-                       assert(key == IPROTO_FIELD_AUTOINCREMENT);
+                       assert(key == IPROTO_FIELD_IS_AUTOINCREMENT);
                        bool autoincrement = mp_decode_bool(data);
                        lua_pushboolean(L, autoincrement);
-                       lua_setfield(L, -2, "autoincrement");
+                       lua_setfield(L, -2, "is_autoincrement");
                }
        }
 }
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 22e7d96a5..57991e1b1 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1844,7 +1844,7 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
                                int afno =
                                        pTabList->a[j].space->sequence_fieldno;
                                if (afno == iCol)
-                                       vdbe_set_metadata_col_autoincrement(v, i);
+                                       vdbe_metadata_set_col_autoincrement(v, i);
                        }
                } else {
                        const char *z = NULL;
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index e19d4cdfa..da9a311b8 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -261,7 +261,7 @@ void
 vdbe_metadata_set_col_nullability(struct Vdbe *p, int idx, int nullable);
 
 void
-vdbe_set_metadata_col_autoincrement(struct Vdbe *p, int idx);
+vdbe_metadata_set_col_autoincrement(struct Vdbe *p, int idx);
 
 void sqlVdbeCountChanges(Vdbe *);
 sql *sqlVdbeDb(Vdbe *);
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index bccb4feb7..d79635b14 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -356,7 +356,7 @@ struct sql_column_metadata {
         */
        int8_t nullable : 2;
        /** True if column features autoincrement property. */
-       int8_t actoincrement : 1;
+       bool is_actoincrement;
 };
 
 /*
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index 34d189e00..8b97f7320 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -766,7 +766,7 @@ sql_column_is_autoincrement(sql_stmt *stmt, int n)
 {
        struct Vdbe *p = (struct Vdbe *) stmt;
        assert(n < sql_column_count(stmt) && n >= 0);
-       return p->metadata[n].actoincrement;
+       return p->metadata[n].is_actoincrement;
 }
 
 /******************************* sql_bind_  **************************
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 4a8bba7f7..384e985fb 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1912,10 +1912,10 @@ vdbe_metadata_set_col_nullability(struct Vdbe *p, int idx, int nullable)
 }
 
 void
-vdbe_set_metadata_col_autoincrement(struct Vdbe *p, int idx)
+vdbe_metadata_set_col_autoincrement(struct Vdbe *p, int idx)
 {
        assert(idx < p->nResColumn);
-       p->metadata[idx].actoincrement = 1;
+       p->metadata[idx].is_actoincrement = 1;
 }

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

* Re: [Tarantool-patches] [PATCH 6/6] sql: extend result set with alias
  2019-11-28 22:41   ` Vladislav Shpilevoy
@ 2019-12-05 11:51     ` Nikita Pettik
  2019-12-06  0:02       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 40+ messages in thread
From: Nikita Pettik @ 2019-12-05 11:51 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

On 28 Nov 23:41, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> On 27/11/2019 13:15, Nikita Pettik wrote:
> > Each column of result set can feature its name alias. For instance:
> > 
> > SELECT x + 1 AS add FROM ...;
> > 
> > In this case real name of resulting set column is "x + 1" meanwhile
> > "add" is its alias. This patch extends metadata with optional metadata
> > member which corresponds to column's alias.
> 
> I was always thinking that the alias should be returned as a
> name. And the real name should be returned as meta. And looks
> like it is so:
> 
>     tarantool> box.execute('SELECT 1 AS kek')
>     ---
>     - metadata:
>       - name: KEK
>         type: integer
>       rows:
>       - [1]
>     ...
> 
> That makes me think we should not break it. And
> meta should return the real name in case there is
> an alias. Because otherwise the aliases are useless
> in meta.

(ANSI parts which concern Java and CLI are quite complicated to read
 and understand, so I refer to Oracle docs).

https://docs.oracle.com/javase/8/docs/api/java/sql/ResultSetMetaData.html#getColumnLabel-int-

'The suggested title is usually specified by the SQL AS clause.'

https://stackoverflow.com/questions/4271152/getcolumnlabel-vs-getcolumnname

I assume that :getColumnLabel() returns name of label, not real name
(at least it seems to be rational).
 
> Btw the example above is executed on this commit. So
> now the results are inconsistent. Some queries return
> alias in 'name'. Some return a real name in 'name'. I
> think we should keep it as was, and return alias in
> 'name'.

Sorry, now it is fixed:

- metadata:
  - type: integer
    name: '1'
    alias: KEK
  rows:
  - [1]
...

I've extended commit message with doc bot request:

    sql: extend result set with alias
    
    Each column of result set can feature its name alias. For instance:
    
    SELECT x + 1 AS add FROM ...;
    
    In this case real name of resulting set column is "x + 1" meanwhile
    "add" is its alias. This patch extends metadata with optional metadata
    member which corresponds to column's alias.
    
    Closes #4407
    
    @TarantoolBot document
    Title: extended SQL metadata
    
    Before this patch metadata for SQL DQL contained only two fields:
    name and type of each column of result set. Now it may contain
    following properties:
     - collation (in case type of resulting set column is string and
                  collation is different from default "none");
       is encoded with IPROTO_FIELD_COLL key in IPROTO_METADATA map;
     - is_nullable (in case column of result set corresponds to space's
                    field; for expressions like x+1 for the sake of
                    simplicity nullability is omitted);
       is encoded with IPROTO_FIELD_IS_NULLABLE key in IPROTO_METADATA;
     - is_autoincrement (is set only for autoincrement column in result
                         set);
       is encoded with IPROTO_FIELD_IS_AUNTOINCREMENT key in IPROTO_METADATA;
     - alias (if column of result set is specified with AS label);
       is encoded with IPROTO_FIELD_ALIAS key in IPROTO_METADATA map.

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

* Re: [Tarantool-patches] [PATCH 1/6] sql: refactor resulting set metadata
  2019-12-05 11:39     ` Nikita Pettik
@ 2019-12-05 23:58       ` Vladislav Shpilevoy
  2019-12-06 12:48         ` Nikita Pettik
  0 siblings, 1 reply; 40+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-05 23:58 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches

Hi! Thanks for the fixes!

Please, apply:

diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index e5528c0e2..f2cf386bb 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1865,7 +1865,7 @@ vdbe_metadata_set_col_name(struct Vdbe *p, int idx, const char *name)
 {
 	assert(idx < p->nResColumn);
 	if (p->metadata[idx].name != NULL)
-		free((void *)p->metadata[idx].name);
+		free(p->metadata[idx].name);
 	p->metadata[idx].name = strdup(name);
 	if (p->metadata[idx].name == NULL) {
 		diag_set(OutOfMemory, strlen(name) + 1, "strdup", "name");
@@ -1879,7 +1879,7 @@ vdbe_metadata_set_col_type(struct Vdbe *p, int idx, const char *type)
 {
 	assert(idx < p->nResColumn);
 	if (p->metadata[idx].type != NULL)
-		free((void *)p->metadata[idx].type);
+		free(p->metadata[idx].type);
 	p->metadata[idx].type = strdup(type);
 	if (p->metadata[idx].type == NULL) {
 		diag_set(OutOfMemory, strlen(type) + 1, "strdup", "type");

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

* Re: [Tarantool-patches] [PATCH 2/6] sql: fix possible null dereference in sql_expr_coll()
  2019-12-05 11:40     ` Nikita Pettik
@ 2019-12-05 23:59       ` Vladislav Shpilevoy
  2019-12-06 12:48         ` Nikita Pettik
  0 siblings, 1 reply; 40+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-05 23:59 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches

On 05/12/2019 12:40, Nikita Pettik wrote:
> On 28 Nov 23:42, Vladislav Shpilevoy wrote:
>> Thanks for the patch!
>>
>> Is it possible to test this?
> 
> As a pure test case - I've failed to come up with it.
> But the next patch (which adds collation to metadata) definitely fails
> without this fix (when TRIM() or REPLACE() are called without args).

Why does not it fail right after finding that there are no
arguments? TRIM looks strange, but REPLACE expects 3 arguments.

> I can dive into details, but I guess it's not so important here (since
> this is obviously buggy place). 

Is it? As I pointed above, maybe the bug is really in another
place? I see, that REPLACE checks argument count. But somewhy
too late:

    tarantool> box.execute('SELECT REPLACE()')
    ---
    - null
    - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 0'
    ...

> If you want further investigation, let me
> know and I will do it.

Yeah. I think we need to understand the bug before fixing
it.

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

* Re: [Tarantool-patches] [PATCH 4/6] sql: extend result set with nullability
  2019-12-05 11:50     ` Nikita Pettik
@ 2019-12-06  0:00       ` Vladislav Shpilevoy
  2019-12-06 12:49         ` Nikita Pettik
  0 siblings, 1 reply; 40+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-06  0:00 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches

Thanks for the fixes!

On 05/12/2019 12:50, Nikita Pettik wrote:
> On 28 Nov 23:41, Vladislav Shpilevoy wrote:
>> Thanks for the patch!
>>
>> You added a leading whitespace to the commit title.
> 
> Fixed.
>  
>> Here is a strange example:
>>
>>     box.cfg{}
>>     s = box.schema.create_space('TEST', {
>>         format = {{'ID', 'unsigned'}, {'COL', 'unsigned', is_nullable = true}}
>>     })
>>     pk = s:create_index('pk', {parts = {{'ID'}, {'COL', is_nullable = false}}})
>>
>> In that example I defined a 'false-nullable' column. It
>> is nullable in the space format, but is not in the
>> index format. The strictest rule wins, so it is not nullable
>> after all. That can be seen in struct tuple_format. SQL says,
>> that it is nullable. But this is not a real problem. Perhaps
>> this might be even right.
> 
> If it was up to me, I would disallow such possible inconsistency
> between space and index formats. It turns out to be quite
> confusing. I believe there's historical reason for that, tho.

There is a still actual reason - incremental alter. When you
have a space format and an index both specifying is_nullable,
sometimes you need a way to change the is_nullable value. And
if it would need to be the same everywhere, you would need to
drop all the related indexes, change nullability in the space
format, and recreate the indexes. This is long because of
indexes rebuilding.

With the current implementation you can change nullability without
drop of the indexes. Step by step.

And it has nothing to do with transactional ddl.

>>> diff --git a/src/box/execute.c b/src/box/execute.c
>>> index 20bfd0957..98812ae1e 100644
>>> --- a/src/box/execute.c
>>> +++ b/src/box/execute.c
>>> @@ -267,8 +267,10 @@ error:
>>>  	region_truncate(region, svp);
>>>  	return -1;
>>>  }
>>> +
>>>  static size_t
>>> -metadata_map_sizeof(const char *name, const char *type, const char *coll)
>>> +metadata_map_sizeof(const char *name, const char *type, const char *coll,
>>> +		    int nullable)
>>
>> 1. Please, rename to 'is_nullable'. Here and in other places.
> 
> IMHO 'is_' prefix implies boolean value, meanwhile here nullable
> is in fact three-valued: 0, 1 implies nullable/not nullable, -1
> is unknown (for columns of resulting set that are complex expressions).
> So that we can avoid sending 'nullable' property in meta for expressions,
> but always set it for columns. Hence, I omitted is_ prefix on purpose.


Well, then you need to omit it in sql_column_is_nullable() too, because
it returns 3 values.

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

* Re: [Tarantool-patches] [PATCH 6/6] sql: extend result set with alias
  2019-12-05 11:51     ` Nikita Pettik
@ 2019-12-06  0:02       ` Vladislav Shpilevoy
  2019-12-06 12:50         ` Nikita Pettik
  0 siblings, 1 reply; 40+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-06  0:02 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches

Thanks for the fixes!

On 05/12/2019 12:51, Nikita Pettik wrote:
> On 28 Nov 23:41, Vladislav Shpilevoy wrote:
>> Thanks for the patch!
>>
>> On 27/11/2019 13:15, Nikita Pettik wrote:
>>> Each column of result set can feature its name alias. For instance:
>>>
>>> SELECT x + 1 AS add FROM ...;
>>>
>>> In this case real name of resulting set column is "x + 1" meanwhile
>>> "add" is its alias. This patch extends metadata with optional metadata
>>> member which corresponds to column's alias.
>>
>> I was always thinking that the alias should be returned as a
>> name. And the real name should be returned as meta. And looks
>> like it is so:
>>
>>     tarantool> box.execute('SELECT 1 AS kek')
>>     ---
>>     - metadata:
>>       - name: KEK
>>         type: integer
>>       rows:
>>       - [1]
>>     ...
>>
>> That makes me think we should not break it. And
>> meta should return the real name in case there is
>> an alias. Because otherwise the aliases are useless
>> in meta.
> 
> (ANSI parts which concern Java and CLI are quite complicated to read
>  and understand, so I refer to Oracle docs).
> 
> https://docs.oracle.com/javase/8/docs/api/java/sql/ResultSetMetaData.html#getColumnLabel-int-
> 
> 'The suggested title is usually specified by the SQL AS clause.'
> 
> https://stackoverflow.com/questions/4271152/getcolumnlabel-vs-getcolumnname
> 
> I assume that :getColumnLabel() returns name of label, not real name
> (at least it seems to be rational).
>  

I discussed it with Alexander. And that subject is complicated.
I don't know exactly what do we need to return. All the drivers
work differently.

Here is what I understood from the discussion:

    Metadata contains 'label' and 'name'.
    There are 2 cases: SELECTed column is an expression, or a
    table column.

    - In case the result set column is an expression, the
      label is the value after 'AS'. If the alias is not
      specified, the label may be anything. For example,
      the expression string representation. 'Name' is
      undefined.

    - In case the result set column is a table column,
      label is the value after 'AS'. If the alias is not
      specified, it is the original column name (i.e. the
      value before 'AS'). 'Name' is the original column
      name.

'Label' is just something printable to show to a user.
'Name' is something functional. This may be used to generate
an update request.

In our current implementation IPROTO_FIELD_NAME is in fact
'label'. And we don't have 'name'. I think you need to keep
the IPROTO_FIELD_NAME as is, and add 'name' as the driver
expects it. For example, IPROTO_FIELD_ORIG_NAME. I.e. the
original column name for table columns. That will keep
backward compatibility, and will provide all the needed meta.

You also need to get in touch with Alexander about that. I may
be wrong about the points above, and he knows more.

>> Btw the example above is executed on this commit. So
>> now the results are inconsistent. Some queries return
>> alias in 'name'. Some return a real name in 'name'. I
>> think we should keep it as was, and return alias in
>> 'name'.
> 
> Sorry, now it is fixed:
> 
> - metadata:
>   - type: integer
>     name: '1'
>     alias: KEK
>   rows:
>   - [1]
> ...
> 
> I've extended commit message with doc bot request:
> 
>     sql: extend result set with alias
>     
>     Each column of result set can feature its name alias. For instance:
>     
>     SELECT x + 1 AS add FROM ...;
>     
>     In this case real name of resulting set column is "x + 1" meanwhile
>     "add" is its alias. This patch extends metadata with optional metadata
>     member which corresponds to column's alias.
>     
>     Closes #4407
>     
>     @TarantoolBot document
>     Title: extended SQL metadata
>     
>     Before this patch metadata for SQL DQL contained only two fields:
>     name and type of each column of result set. Now it may contain
>     following properties:
>      - collation (in case type of resulting set column is string and
>                   collation is different from default "none");
>        is encoded with IPROTO_FIELD_COLL key in IPROTO_METADATA map;
>      - is_nullable (in case column of result set corresponds to space's
>                     field; for expressions like x+1 for the sake of
>                     simplicity nullability is omitted);
>        is encoded with IPROTO_FIELD_IS_NULLABLE key in IPROTO_METADATA;
>      - is_autoincrement (is set only for autoincrement column in result
>                          set);
>        is encoded with IPROTO_FIELD_IS_AUNTOINCREMENT key in IPROTO_METADATA;
>      - alias (if column of result set is specified with AS label);
>        is encoded with IPROTO_FIELD_ALIAS key in IPROTO_METADATA map.
> 

Sorry, this is not enough. You need to describe the
binary protocol. With exact numeric values for the new
IProto keys. And exact MessagePack types.

Also you didn't say, that omitted nullable means unknown
nullability.

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

* Re: [Tarantool-patches] [PATCH 1/6] sql: refactor resulting set metadata
  2019-12-05 23:58       ` Vladislav Shpilevoy
@ 2019-12-06 12:48         ` Nikita Pettik
  0 siblings, 0 replies; 40+ messages in thread
From: Nikita Pettik @ 2019-12-06 12:48 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

On 06 Dec 00:58, Vladislav Shpilevoy wrote:
> Hi! Thanks for the fixes!
> 
> Please, apply:

Thx, applied.
 
> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> index e5528c0e2..f2cf386bb 100644
> --- a/src/box/sql/vdbeaux.c
> +++ b/src/box/sql/vdbeaux.c
> @@ -1865,7 +1865,7 @@ vdbe_metadata_set_col_name(struct Vdbe *p, int idx, const char *name)
>  {
>  	assert(idx < p->nResColumn);
>  	if (p->metadata[idx].name != NULL)
> -		free((void *)p->metadata[idx].name);
> +		free(p->metadata[idx].name);
>  	p->metadata[idx].name = strdup(name);
>  	if (p->metadata[idx].name == NULL) {
>  		diag_set(OutOfMemory, strlen(name) + 1, "strdup", "name");
> @@ -1879,7 +1879,7 @@ vdbe_metadata_set_col_type(struct Vdbe *p, int idx, const char *type)
>  {
>  	assert(idx < p->nResColumn);
>  	if (p->metadata[idx].type != NULL)
> -		free((void *)p->metadata[idx].type);
> +		free(p->metadata[idx].type);
>  	p->metadata[idx].type = strdup(type);
>  	if (p->metadata[idx].type == NULL) {
>  		diag_set(OutOfMemory, strlen(type) + 1, "strdup", "type");

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

* Re: [Tarantool-patches] [PATCH 2/6] sql: fix possible null dereference in sql_expr_coll()
  2019-12-05 23:59       ` Vladislav Shpilevoy
@ 2019-12-06 12:48         ` Nikita Pettik
  2019-12-17 13:30           ` Sergey Ostanevich
  0 siblings, 1 reply; 40+ messages in thread
From: Nikita Pettik @ 2019-12-06 12:48 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

On 06 Dec 00:59, Vladislav Shpilevoy wrote:
> On 05/12/2019 12:40, Nikita Pettik wrote:
> > On 28 Nov 23:42, Vladislav Shpilevoy wrote:
> >> Thanks for the patch!
> >>
> >> Is it possible to test this?
> > 
> > As a pure test case - I've failed to come up with it.
> > But the next patch (which adds collation to metadata) definitely fails
> > without this fix (when TRIM() or REPLACE() are called without args).
> 
> Why does not it fail right after finding that there are no
> arguments? TRIM looks strange, but REPLACE expects 3 arguments.
> 
> > I can dive into details, but I guess it's not so important here (since
> > this is obviously buggy place). 
> 
> Is it? As I pointed above, maybe the bug is really in another
> place? I see, that REPLACE checks argument count. But somewhy
> too late:
> 
>     tarantool> box.execute('SELECT REPLACE()')
>     ---
>     - null
>     - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 0'
>     ...
> 
> > If you want further investigation, let me
> > know and I will do it.

Sorry, in fact I mean SUBSTR() function. It can take 1 or 2 arguments.
But the check of arguments count is delayed till function execution (see
substrFunc in sql/func.c).
To generate collation for metadata we should call sql_expr_coll()
(without such necessity we avoid call of this function). And it leads
to null-dereference.

Finally, I've come up with example which results in null-dereference on
master branch as well (added it to the current patch):

box.execute("CREATE TABLE t (id INT PRIMARY KEY, a TEXT, b TEXT COLLATE \"binary\", c TEXT COLLATE \"unicode_ci\");")
box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = substr();")

Here we have to call sql_expr_coll() with expression representing
substr() in order to verify that collations of LHS and RHS are
compatible. So we eventually we get in the same situation.
 
> Yeah. I think we need to understand the bug before fixing
> it.

New patch:


    sql: fix possible null dereference in sql_expr_coll()
    
    Some built-in functions can accept different number of arguments.
    Check of argument count for such functions takes place right before
    its execution. So it is possible that expression-list representing
    arguments for built-in function is NULL. On the other hand, in
    sql_expr_coll() (which returns collation of expression) it is assumed
    that if function features SQL_FUNC_DERIVEDCOLL flag (it implies that
    resulting collation depends on collation of arguments) then it has at
    least one argument. The last assumption is wrong considering for example
    SUBSTR() function: it may have 1 or 2 arguments, so check of argument
    count doesn't occur during compilation. Hence, if it is required to
    calculate collation for SUBSTR() function and there's no arguments,
    Tarantool crashes due to null-dereference.
    This patch fixes this bug with one additional check in sql_expr_coll().

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 648b7170e..0bdcfe576 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -332,7 +332,8 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
                                sql_func_by_signature(p->u.zToken, arg_count);
                        if (func == NULL)
                                break;
-                       if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL)) {
+                       if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL) &&
+                           arg_count > 0) {
                                /*
                                 * Now we use quite straightforward
                                 * approach assuming that resulting
diff --git a/test/sql/collation.result b/test/sql/collation.result
index 11962ef47..fbc7ce9aa 100644
--- a/test/sql/collation.result
+++ b/test/sql/collation.result
@@ -292,6 +292,15 @@ box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = c COLLATE \"unicode\";
 - null
 - Illegal mix of collations
 ...
+-- Make sure that using function featuring variable arguemnts
+-- length  and resulting collation which depends on arguments
+-- is processed correctly.
+--
+box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = substr();")
+---
+- null
+- 'Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0'
+...
 -- Compound queries perform implicit comparisons between values.
 -- Hence, rules for collations compatibilities are the same.
 --
diff --git a/test/sql/collation.test.lua b/test/sql/collation.test.lua
index 1be28b3ff..a013253cd 100644
--- a/test/sql/collation.test.lua
+++ b/test/sql/collation.test.lua
@@ -80,6 +80,11 @@ box.execute("SELECT * FROM t WHERE b = c;")
 box.execute("SELECT * FROM t WHERE b COLLATE \"binary\" = c;")
 box.execute("SELECT * FROM t WHERE a = c;")
 box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = c COLLATE \"unicode\";")
+-- Make sure that using function featuring variable arguemnts
+-- length  and resulting collation which depends on arguments
+-- is processed correctly.
+--
+box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = substr();")

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

* Re: [Tarantool-patches] [PATCH 4/6] sql: extend result set with nullability
  2019-12-06  0:00       ` Vladislav Shpilevoy
@ 2019-12-06 12:49         ` Nikita Pettik
  0 siblings, 0 replies; 40+ messages in thread
From: Nikita Pettik @ 2019-12-06 12:49 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

On 06 Dec 01:00, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
> 
> On 05/12/2019 12:50, Nikita Pettik wrote:
> > On 28 Nov 23:41, Vladislav Shpilevoy wrote:
> >> Thanks for the patch!
> >>
> >> You added a leading whitespace to the commit title.
> > 
> > Fixed.
> >  
> >> Here is a strange example:
> >>
> >>     box.cfg{}
> >>     s = box.schema.create_space('TEST', {
> >>         format = {{'ID', 'unsigned'}, {'COL', 'unsigned', is_nullable = true}}
> >>     })
> >>     pk = s:create_index('pk', {parts = {{'ID'}, {'COL', is_nullable = false}}})
> >>
> >> In that example I defined a 'false-nullable' column. It
> >> is nullable in the space format, but is not in the
> >> index format. The strictest rule wins, so it is not nullable
> >> after all. That can be seen in struct tuple_format. SQL says,
> >> that it is nullable. But this is not a real problem. Perhaps
> >> this might be even right.
> > 
> > If it was up to me, I would disallow such possible inconsistency
> > between space and index formats. It turns out to be quite
> > confusing. I believe there's historical reason for that, tho.
> 
> There is a still actual reason - incremental alter. When you
> have a space format and an index both specifying is_nullable,
> sometimes you need a way to change the is_nullable value. And
> if it would need to be the same everywhere, you would need to
> drop all the related indexes, change nullability in the space
> format, and recreate the indexes. This is long because of
> indexes rebuilding.
> 
> With the current implementation you can change nullability without
> drop of the indexes. Step by step.

Thanks for the explanation.
 
> And it has nothing to do with transactional ddl.
> 
> >>> diff --git a/src/box/execute.c b/src/box/execute.c
> >>> index 20bfd0957..98812ae1e 100644
> >>> --- a/src/box/execute.c
> >>> +++ b/src/box/execute.c
> >>> @@ -267,8 +267,10 @@ error:
> >>>  	region_truncate(region, svp);
> >>>  	return -1;
> >>>  }
> >>> +
> >>>  static size_t
> >>> -metadata_map_sizeof(const char *name, const char *type, const char *coll)
> >>> +metadata_map_sizeof(const char *name, const char *type, const char *coll,
> >>> +		    int nullable)
> >>
> >> 1. Please, rename to 'is_nullable'. Here and in other places.
> > 
> > IMHO 'is_' prefix implies boolean value, meanwhile here nullable
> > is in fact three-valued: 0, 1 implies nullable/not nullable, -1
> > is unknown (for columns of resulting set that are complex expressions).
> > So that we can avoid sending 'nullable' property in meta for expressions,
> > but always set it for columns. Hence, I omitted is_ prefix on purpose.
> 
> 
> Well, then you need to omit it in sql_column_is_nullable() too, because
> it returns 3 values.

Fair, renamed.

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

* Re: [Tarantool-patches] [PATCH 6/6] sql: extend result set with alias
  2019-12-06  0:02       ` Vladislav Shpilevoy
@ 2019-12-06 12:50         ` Nikita Pettik
  2019-12-06 21:52           ` Vladislav Shpilevoy
  0 siblings, 1 reply; 40+ messages in thread
From: Nikita Pettik @ 2019-12-06 12:50 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

On 06 Dec 01:02, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
> 
> On 05/12/2019 12:51, Nikita Pettik wrote:
> > On 28 Nov 23:41, Vladislav Shpilevoy wrote:
> >> Thanks for the patch!
> >>
> >> On 27/11/2019 13:15, Nikita Pettik wrote:
> >>> Each column of result set can feature its name alias. For instance:
> >>>
> >>> SELECT x + 1 AS add FROM ...;
> >>>
> >>> In this case real name of resulting set column is "x + 1" meanwhile
> >>> "add" is its alias. This patch extends metadata with optional metadata
> >>> member which corresponds to column's alias.
> >>
> >> I was always thinking that the alias should be returned as a
> >> name. And the real name should be returned as meta. And looks
> >> like it is so:
> >>
> >>     tarantool> box.execute('SELECT 1 AS kek')
> >>     ---
> >>     - metadata:
> >>       - name: KEK
> >>         type: integer
> >>       rows:
> >>       - [1]
> >>     ...
> >>
> >> That makes me think we should not break it. And
> >> meta should return the real name in case there is
> >> an alias. Because otherwise the aliases are useless
> >> in meta.
> > 
> > (ANSI parts which concern Java and CLI are quite complicated to read
> >  and understand, so I refer to Oracle docs).
> > 
> > https://docs.oracle.com/javase/8/docs/api/java/sql/ResultSetMetaData.html#getColumnLabel-int-
> > 
> > 'The suggested title is usually specified by the SQL AS clause.'
> > 
> > https://stackoverflow.com/questions/4271152/getcolumnlabel-vs-getcolumnname
> > 
> > I assume that :getColumnLabel() returns name of label, not real name
> > (at least it seems to be rational).
> >  
> 
> I discussed it with Alexander. And that subject is complicated.
> I don't know exactly what do we need to return. All the drivers
> work differently.
> 
> Here is what I understood from the discussion:
> 
>     Metadata contains 'label' and 'name'.
>     There are 2 cases: SELECTed column is an expression, or a
>     table column.
> 
>     - In case the result set column is an expression, the
>       label is the value after 'AS'. If the alias is not
>       specified, the label may be anything. For example,
>       the expression string representation. 'Name' is
>       undefined.

Why name is undefined? I'd say vice versa - alias is undefined
(for the simplicity sake let's say it equals to name); name is
string representation (in most cases). 

https://grokbase.com/t/postgresql/pgsql-jdbc/047wwctbyf/wrong-column-names-in-resultsetmetadata
'''
Most return the same thing as getColumnName(); not surprising
since that's a good default display title.
'''

Some time ago, there was discussion in dev mailing list concerning
default column names btw.

>     - In case the result set column is a table column,
>       label is the value after 'AS'. If the alias is not
>       specified, it is the original column name (i.e. the
>       value before 'AS'). 'Name' is the original column
>       name.
> 
> 'Label' is just something printable to show to a user.
> 'Name' is something functional. This may be used to generate
> an update request.

For me it sounds extremely wierd, I can't realize how did
you come up with these thoughts...Could you please provide
examples of DBs/drivers where such behavior can be observer?
The only thing I found is an ancient discussion in PostgreSQL
mailing list:
https://grokbase.com/t/postgresql/pgsql-jdbc/047wwctbyf/wrong-column-names-in-resultsetmetadata

All other resources I've visited say that alias is considered to be the
indentifier coming after AS clause; name is always indentifier that
is specified in result set (regardless presence of AS clause).

For instance, DB2 (which we consider to be the closest to ANSI):
https://www.ibm.com/support/knowledgecenter/en/SSEPGG_9.7.0/com.ibm.db2.luw.apdv.java.doc/src/tpc/imjcc_c0052593.html

The only exception is MimerSQL where both getColumnLabel() and
getColumnName() always return alias (p.37 changes in 2.2):
https://download.mimer.com/pub/developer/docs/latest_jdbcguide/mimjdben.pdf

> In our current implementation IPROTO_FIELD_NAME is in fact
> 'label'. And we don't have 'name'. I think you need to keep
> the IPROTO_FIELD_NAME as is, and add 'name' as the driver
> expects it. For example, IPROTO_FIELD_ORIG_NAME. I.e. the
> original column name for table columns. That will keep
> backward compatibility, and will provide all the needed meta.
> 
> You also need to get in touch with Alexander about that. I may
> be wrong about the points above, and he knows more.
> 
> >> Btw the example above is executed on this commit. So
> >> now the results are inconsistent. Some queries return
> >> alias in 'name'. Some return a real name in 'name'. I
> >> think we should keep it as was, and return alias in
> >> 'name'.
> > 
> > Sorry, now it is fixed:
> > 
> > - metadata:
> >   - type: integer
> >     name: '1'
> >     alias: KEK
> >   rows:
> >   - [1]
> > ...
> > 
> > I've extended commit message with doc bot request:
> > 
> >     sql: extend result set with alias
> >     
> >     Each column of result set can feature its name alias. For instance:
> >     
> >     SELECT x + 1 AS add FROM ...;
> >     
> >     In this case real name of resulting set column is "x + 1" meanwhile
> >     "add" is its alias. This patch extends metadata with optional metadata
> >     member which corresponds to column's alias.
> >     
> >     Closes #4407
> >     
> >     @TarantoolBot document
> >     Title: extended SQL metadata
> >     
> >     Before this patch metadata for SQL DQL contained only two fields:
> >     name and type of each column of result set. Now it may contain
> >     following properties:
> >      - collation (in case type of resulting set column is string and
> >                   collation is different from default "none");
> >        is encoded with IPROTO_FIELD_COLL key in IPROTO_METADATA map;
> >      - is_nullable (in case column of result set corresponds to space's
> >                     field; for expressions like x+1 for the sake of
> >                     simplicity nullability is omitted);
> >        is encoded with IPROTO_FIELD_IS_NULLABLE key in IPROTO_METADATA;
> >      - is_autoincrement (is set only for autoincrement column in result
> >                          set);
> >        is encoded with IPROTO_FIELD_IS_AUNTOINCREMENT key in IPROTO_METADATA;
> >      - alias (if column of result set is specified with AS label);
> >        is encoded with IPROTO_FIELD_ALIAS key in IPROTO_METADATA map.
> > 
> 
> Sorry, this is not enough. You need to describe the
> binary protocol. With exact numeric values for the new
> IProto keys. And exact MessagePack types.
> 
> Also you didn't say, that omitted nullable means unknown
> nullability.

Updated request:

    @TarantoolBot document
    Title: extended SQL metadata
    
    Before this patch metadata for SQL DQL contained only two fields:
    name and type of each column of result set. Now it may contain
    following properties:
     - collation (in case type of resulting set column is string and
                  collation is different from default "none");
       is encoded with IPROTO_FIELD_COLL (0x2) key in IPROTO_METADATA map;
       in msgpack is encoded as string and held with MP_STR type;
     - is_nullable (in case column of result set corresponds to space's
                    field; for expressions like x+1 for the sake of
                    simplicity nullability is omitted);
       is encoded with IPROTO_FIELD_IS_NULLABLE key (0x3) in IPROTO_METADATA;
       in msgpack is encoded as boolean and held with MP_BOOL type;
       note that absence of this field implies that nullability is unknown;
     - is_autoincrement (is set only for autoincrement column in result
                         set);
       is encoded with IPROTO_FIELD_IS_AUNTOINCREMENT (0x4) key in IPROTO_METADATA;
       in msgpack is encoded as boolean and held with MP_BOOL type;
     - alias (if column of result set is specified with AS label);
       is encoded with IPROTO_FIELD_ALIAS (0x5) key in IPROTO_METADATA map;
       in msgpack is encoded as string and held with MP_STR type.

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

* Re: [Tarantool-patches] [PATCH 6/6] sql: extend result set with alias
  2019-12-06 12:50         ` Nikita Pettik
@ 2019-12-06 21:52           ` Vladislav Shpilevoy
  0 siblings, 0 replies; 40+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-06 21:52 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches

Thanks for the discussion!

On 06/12/2019 13:50, Nikita Pettik wrote:
> On 06 Dec 01:02, Vladislav Shpilevoy wrote:
>> Thanks for the fixes!
>>
>> On 05/12/2019 12:51, Nikita Pettik wrote:
>>> On 28 Nov 23:41, Vladislav Shpilevoy wrote:
>>>> Thanks for the patch!
>>>>
>>>> On 27/11/2019 13:15, Nikita Pettik wrote:
>>>>> Each column of result set can feature its name alias. For instance:
>>>>>
>>>>> SELECT x + 1 AS add FROM ...;
>>>>>
>>>>> In this case real name of resulting set column is "x + 1" meanwhile
>>>>> "add" is its alias. This patch extends metadata with optional metadata
>>>>> member which corresponds to column's alias.
>>>>
>>>> I was always thinking that the alias should be returned as a
>>>> name. And the real name should be returned as meta. And looks
>>>> like it is so:
>>>>
>>>>     tarantool> box.execute('SELECT 1 AS kek')
>>>>     ---
>>>>     - metadata:
>>>>       - name: KEK
>>>>         type: integer
>>>>       rows:
>>>>       - [1]
>>>>     ...
>>>>
>>>> That makes me think we should not break it. And
>>>> meta should return the real name in case there is
>>>> an alias. Because otherwise the aliases are useless
>>>> in meta.
>>>
>>> (ANSI parts which concern Java and CLI are quite complicated to read
>>>  and understand, so I refer to Oracle docs).
>>>
>>> https://docs.oracle.com/javase/8/docs/api/java/sql/ResultSetMetaData.html#getColumnLabel-int-
>>>
>>> 'The suggested title is usually specified by the SQL AS clause.'
>>>
>>> https://stackoverflow.com/questions/4271152/getcolumnlabel-vs-getcolumnname
>>>
>>> I assume that :getColumnLabel() returns name of label, not real name
>>> (at least it seems to be rational).
>>>  
>>
>> I discussed it with Alexander. And that subject is complicated.
>> I don't know exactly what do we need to return. All the drivers
>> work differently.
>>
>> Here is what I understood from the discussion:
>>
>>     Metadata contains 'label' and 'name'.
>>     There are 2 cases: SELECTed column is an expression, or a
>>     table column.
>>
>>     - In case the result set column is an expression, the
>>       label is the value after 'AS'. If the alias is not
>>       specified, the label may be anything. For example,
>>       the expression string representation. 'Name' is
>>       undefined.
> 
> Why name is undefined? I'd say vice versa - alias is undefined
> (for the simplicity sake let's say it equals to name); name is
> string representation (in most cases). 
> 
> https://grokbase.com/t/postgresql/pgsql-jdbc/047wwctbyf/wrong-column-names-in-resultsetmetadata
> '''
> Most return the same thing as getColumnName(); not surprising
> since that's a good default display title.
> '''

Firstly, there is no 'alias' anywhere. Nobody returns a meta column,
which is just alias or nothing.

Talking of name and label - well, that is what I said. Label is
always defined, and it is a title to display. Name may be
undefined in case it is not a table column, but a constant or an
expression.

In Tarantool it is vice versa. Our IPROTO_FIELD_NAME is always
defined and is in fact a label. And we call it 'name' in fronend.
We just need to add table column name now, and call it somehow
different. So as not to break compatibility. Your patch breaks it.

> 
> Some time ago, there was discussion in dev mailing list concerning
> default column names btw.
> 

I don't remember, or likely I didn't participate. I started
digging into that only now. If you have some points, you need
to bring them up. Alexander has found some discussions in Telegram,
and we talked through them privately. This is from where my
thoughts came, and the links you provided just prove them.

>>     - In case the result set column is a table column,
>>       label is the value after 'AS'. If the alias is not
>>       specified, it is the original column name (i.e. the
>>       value before 'AS'). 'Name' is the original column
>>       name.
>>
>> 'Label' is just something printable to show to a user.
>> 'Name' is something functional. This may be used to generate
>> an update request.
> 
> For me it sounds extremely wierd, I can't realize how did
> you come up with these thoughts...Could you please provide
> examples of DBs/drivers where such behavior can be observer?

MariaDB, MySQL. At least these examples I've seen.

> The only thing I found is an ancient discussion in PostgreSQL
> mailing list:
> https://grokbase.com/t/postgresql/pgsql-jdbc/047wwctbyf/wrong-column-names-in-resultsetmetadata
> 
> All other resources I've visited say that alias is considered to be the
> indentifier coming after AS clause; name is always indentifier that
> is specified in result set (regardless presence of AS clause).
> 
> For instance, DB2 (which we consider to be the closest to ANSI):
> https://www.ibm.com/support/knowledgecenter/en/SSEPGG_9.7.0/com.ibm.db2.luw.apdv.java.doc/src/tpc/imjcc_c0052593.html

The example by the link just proves my words. Label is either
an identifier after AS, or the string before AS. This is always
defined. Name is a table column name. Regardless of whether
there is 'AS', and what is written after it.

But by the link above I don't see an example of SELECT of an
expression. As I understood from the examples Alexander showed
be, name is undefined for expressions.

> 
> The only exception is MimerSQL where both getColumnLabel() and
> getColumnName() always return alias (p.37 changes in 2.2):
> https://download.mimer.com/pub/developer/docs/latest_jdbcguide/mimjdben.pdf

Lol, what is mimersql? This is not our case.

We now return 'name' from box.execute in meta. This is
equivalent to what JDBC expects as 'label'.

Again. Mapping:

       Tarantool                   JDBC
   -------------------------------------------
    IPROTO_FIELD_NAME              Label  <- This one is defined and returned now.

  IPROTO_FIELD_<WHATEVER>          Name   <- This one is not defined now. And
                                             your patch is expected to add it.

Example:

    SELECT column1 AS alias, column2, column3 + 1, 100, 200 AS twohundred;

Result expected by JDBC:

    name = 'column1', label = 'alias';
    name = 'column2', label = 'column2';
           -        , label = 'column3 + 1';
           -        , label = 100;
           -        , label = 'twohundred';

Result we return now:

    IPROTO_FIELD_NAME = 'alias';
    IPROTO_FIELD_NAME = 'column2';
    IPROTO_FIELD_NAME = 'column3 + 1';
    IPROTO_FIELD_NAME = '100';
    IPROTO_FIELD_NAME = 'twohundred';

As you can see, we return JDBC's label now. We need
to add JDBC's name.

>> In our current implementation IPROTO_FIELD_NAME is in fact
>> 'label'. And we don't have 'name'. I think you need to keep
>> the IPROTO_FIELD_NAME as is, and add 'name' as the driver
>> expects it. For example, IPROTO_FIELD_ORIG_NAME. I.e. the
>> original column name for table columns. That will keep
>> backward compatibility, and will provide all the needed meta.
>>
>> You also need to get in touch with Alexander about that. I may
>> be wrong about the points above, and he knows more.
>>

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

* Re: [Tarantool-patches] [PATCH 1/6] sql: refactor resulting set metadata
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 1/6] sql: refactor resulting set metadata Nikita Pettik
  2019-11-28 22:41   ` Vladislav Shpilevoy
@ 2019-12-17 13:23   ` Sergey Ostanevich
  1 sibling, 0 replies; 40+ messages in thread
From: Sergey Ostanevich @ 2019-12-17 13:23 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches, v.shpilevoy

Hi!

LGTM the latest version in branch.

Sergos.


On 27 Nov 15:15, Nikita Pettik wrote:
> Move names and types of resulting set to a separate structure. Simplify
> their storage by introducing separate members for name and type
> (previously names and types were stored in one char * array). It will
> allow us to add new metadata properties with ease.
> 
> Needed for #4407
> ---
>  src/box/sql/delete.c  |  6 ++--
>  src/box/sql/insert.c  |  5 ++--
>  src/box/sql/legacy.c  |  2 +-
>  src/box/sql/pragma.c  | 14 ++++-----
>  src/box/sql/prepare.c |  9 +++---
>  src/box/sql/select.c  | 60 ++++++++++++++++++--------------------
>  src/box/sql/update.c  |  6 ++--
>  src/box/sql/vdbe.h    | 28 ++++++++++--------
>  src/box/sql/vdbeInt.h |  8 ++++-
>  src/box/sql/vdbeapi.c | 81 +++++++++------------------------------------------
>  src/box/sql/vdbeaux.c | 81 +++++++++++++++++++++++++++------------------------
>  11 files changed, 124 insertions(+), 176 deletions(-)
> 
> diff --git a/src/box/sql/delete.c b/src/box/sql/delete.c
> index 91c2157ac..31570099b 100644
> --- a/src/box/sql/delete.c
> +++ b/src/box/sql/delete.c
> @@ -418,10 +418,8 @@ sql_table_delete_from(struct Parse *parse, struct SrcList *tab_list,
>  	    parse->triggered_space != NULL) {
>  		sqlVdbeAddOp2(v, OP_ResultRow, reg_count, 1);
>  		sqlVdbeSetNumCols(v, 1);
> -		sqlVdbeSetColName(v, 0, COLNAME_NAME, "rows deleted",
> -				      SQL_STATIC);
> -		sqlVdbeSetColName(v, 0, COLNAME_DECLTYPE, "integer",
> -				  SQL_STATIC);
> +		vdbe_set_metadata_col_name(v, 0, "rows deleted");
> +		vdbe_set_metadata_col_type(v, 0, "integer");
>  	}
>  
>   delete_from_cleanup:
> diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
> index 70504c800..9be9c191d 100644
> --- a/src/box/sql/insert.c
> +++ b/src/box/sql/insert.c
> @@ -785,9 +785,8 @@ sqlInsert(Parse * pParse,	/* Parser context */
>  			column_name = "rows replaced";
>  		else
>  			column_name = "rows inserted";
> -		sqlVdbeSetColName(v, 0, COLNAME_NAME, column_name, SQL_STATIC);
> -		sqlVdbeSetColName(v, 0, COLNAME_DECLTYPE, "integer",
> -				  SQL_STATIC);
> +		vdbe_set_metadata_col_name(v, 0, column_name);
> +		vdbe_set_metadata_col_type(v, 0, "integer");
>  	}
>  
>   insert_cleanup:
> diff --git a/src/box/sql/legacy.c b/src/box/sql/legacy.c
> index 0b1370f4a..ee58f1eb7 100644
> --- a/src/box/sql/legacy.c
> +++ b/src/box/sql/legacy.c
> @@ -103,7 +103,7 @@ sql_exec(sql * db,	/* The database on which the SQL executes */
>  						    (char *)
>  						    sql_column_name(pStmt,
>  									i);
> -						/* sqlVdbeSetColName() installs column names as UTF8
> +						/* vdbe_set_metadata_col_name() installs column names as UTF8
>  						 * strings so there is no way for sql_column_name() to fail.
>  						 */
>  						assert(azCols[i] != 0);
> diff --git a/src/box/sql/pragma.c b/src/box/sql/pragma.c
> index 92bcf4e68..874eb93d2 100644
> --- a/src/box/sql/pragma.c
> +++ b/src/box/sql/pragma.c
> @@ -120,10 +120,8 @@ vdbe_set_pragma_result_columns(struct Vdbe *v, const struct PragmaName *pragma)
>  	assert(n > 0);
>  	sqlVdbeSetNumCols(v, n);
>  	for (int i = 0, j = pragma->iPragCName; i < n; ++i) {
> -		sqlVdbeSetColName(v, i, COLNAME_NAME, pragCName[j++],
> -				  SQL_STATIC);
> -		sqlVdbeSetColName(v, i, COLNAME_DECLTYPE, pragCName[j++],
> -				  SQL_STATIC);
> +		vdbe_set_metadata_col_name(v, i, pragCName[j++]);
> +		vdbe_set_metadata_col_type(v, i, pragCName[j++]);
>  	}
>  }
>  
> @@ -168,10 +166,10 @@ vdbe_emit_pragma_status(struct Parse *parse)
>  	struct session *user_session = current_session();
>  
>  	sqlVdbeSetNumCols(v, 2);
> -	sqlVdbeSetColName(v, 0, COLNAME_NAME, "pragma_name", SQL_STATIC);
> -	sqlVdbeSetColName(v, 0, COLNAME_DECLTYPE, "text", SQL_STATIC);
> -	sqlVdbeSetColName(v, 1, COLNAME_NAME, "pragma_value", SQL_STATIC);
> -	sqlVdbeSetColName(v, 1, COLNAME_DECLTYPE, "integer", SQL_STATIC);
> +	vdbe_set_metadata_col_name(v, 0, "pragma_name");
> +	vdbe_set_metadata_col_type(v, 0, "text");
> +	vdbe_set_metadata_col_name(v, 1, "pragma_value");
> +	vdbe_set_metadata_col_type(v, 1, "integer");
>  
>  	parse->nMem = 2;
>  	for (int i = 0; i < ArraySize(aPragmaName); ++i) {
> diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
> index 0ecc676e2..2d3466cc7 100644
> --- a/src/box/sql/prepare.c
> +++ b/src/box/sql/prepare.c
> @@ -146,11 +146,10 @@ sqlPrepare(sql * db,	/* Database handle. */
>  		sqlVdbeSetNumCols(sParse.pVdbe, name_count);
>  		for (int i = 0; i < name_count; i++) {
>  			int name_index = 2 * i + name_first;
> -			sqlVdbeSetColName(sParse.pVdbe, i, COLNAME_NAME,
> -					  azColName[name_index], SQL_STATIC);
> -			sqlVdbeSetColName(sParse.pVdbe, i, COLNAME_DECLTYPE,
> -					  azColName[name_index + 1],
> -					  SQL_STATIC);
> +			vdbe_set_metadata_col_name(sParse.pVdbe, i,
> +					  azColName[name_index]);
> +			vdbe_set_metadata_col_type(sParse.pVdbe, i,
> +					  azColName[name_index + 1]);
>  		}
>  	}
>  
> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index 8f93edd16..d6b8a158f 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -1747,15 +1747,18 @@ generateSortTail(Parse * pParse,	/* Parsing context */
>  	sqlVdbeResolveLabel(v, addrBreak);
>  }
>  
> -/*
> +/**
>   * Generate code that will tell the VDBE the names of columns
> - * in the result set.  This information is used to provide the
> - * azCol[] values in the callback.
> + * in the result set. This information is used to provide the
> + * metadata during/after statement execution.
> + *
> + * @param pParse Parsing context.
> + * @param pTabList List of tables.
> + * @param pEList Expressions defining the result set.
>   */
>  static void
> -generateColumnNames(Parse * pParse,	/* Parser context */
> -		    SrcList * pTabList,	/* List of tables */
> -		    ExprList * pEList)	/* Expressions defining the result set */
> +generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
> +			 struct ExprList *pEList)
>  {
>  	Vdbe *v = pParse->pVdbe;
>  	int i, j;
> @@ -1789,12 +1792,11 @@ generateColumnNames(Parse * pParse,	/* Parser context */
>  			continue;
>  		if (p->op == TK_VARIABLE)
>  			var_pos[var_count++] = i;
> -		sqlVdbeSetColName(v, i, COLNAME_DECLTYPE,
> -				  field_type_strs[sql_expr_type(p)], SQL_TRANSIENT);
> +		vdbe_set_metadata_col_type(v, i,
> +					   field_type_strs[sql_expr_type(p)]);
>  		if (pEList->a[i].zName) {
>  			char *zName = pEList->a[i].zName;
> -			sqlVdbeSetColName(v, i, COLNAME_NAME, zName,
> -					      SQL_TRANSIENT);
> +			vdbe_set_metadata_col_name(v, i, zName);
>  		} else if (p->op == TK_COLUMN || p->op == TK_AGG_COLUMN) {
>  			char *zCol;
>  			int iCol = p->iColumn;
> @@ -1807,27 +1809,21 @@ generateColumnNames(Parse * pParse,	/* Parser context */
>  			assert(iCol >= 0 && iCol < (int)space_def->field_count);
>  			zCol = space_def->fields[iCol].name;
>  			if (!shortNames && !fullNames) {
> -				sqlVdbeSetColName(v, i, COLNAME_NAME,
> -						      sqlDbStrDup(db,
> -								      pEList->a[i].zSpan),
> -						      SQL_DYNAMIC);
> +				vdbe_set_metadata_col_name(v, i,
> +							   pEList->a[i].zSpan);
>  			} else if (fullNames) {
> -				char *zName = 0;
> -				zName = sqlMPrintf(db, "%s.%s",
> -						       space_def->name, zCol);
> -				sqlVdbeSetColName(v, i, COLNAME_NAME, zName,
> -						      SQL_DYNAMIC);
> +				const char *zName = tt_sprintf("%s.%s",
> +							       space_def->name,
> +							       zCol);
> +				vdbe_set_metadata_col_name(v, i, zName);
>  			} else {
> -				sqlVdbeSetColName(v, i, COLNAME_NAME, zCol,
> -						      SQL_TRANSIENT);
> +				vdbe_set_metadata_col_name(v, i, zCol);
>  			}
>  		} else {
>  			const char *z = pEList->a[i].zSpan;
> -			z = z == 0 ? sqlMPrintf(db, "column%d",
> -						    i + 1) : sqlDbStrDup(db,
> -									     z);
> -			sqlVdbeSetColName(v, i, COLNAME_NAME, z,
> -					      SQL_DYNAMIC);
> +			if (z == NULL)
> +				z = tt_sprintf("column%d", i + 1);
> +			vdbe_set_metadata_col_name(v, i, z);
>  		}
>  	}
>  	if (var_count == 0)
> @@ -2828,7 +2824,7 @@ multiSelect(Parse * pParse,	/* Parsing context */
>  						Select *pFirst = p;
>  						while (pFirst->pPrior)
>  							pFirst = pFirst->pPrior;
> -						generateColumnNames(pParse,
> +						generate_column_metadata(pParse,
>  								    pFirst->pSrc,
>  								    pFirst->pEList);
>  					}
> @@ -2927,9 +2923,9 @@ multiSelect(Parse * pParse,	/* Parsing context */
>  					Select *pFirst = p;
>  					while (pFirst->pPrior)
>  						pFirst = pFirst->pPrior;
> -					generateColumnNames(pParse,
> -							    pFirst->pSrc,
> -							    pFirst->pEList);
> +					generate_column_metadata(pParse,
> +								 pFirst->pSrc,
> +								 pFirst->pEList);
>  				}
>  				iBreak = sqlVdbeMakeLabel(v);
>  				iCont = sqlVdbeMakeLabel(v);
> @@ -3575,7 +3571,7 @@ multiSelectOrderBy(Parse * pParse,	/* Parsing context */
>  		Select *pFirst = pPrior;
>  		while (pFirst->pPrior)
>  			pFirst = pFirst->pPrior;
> -		generateColumnNames(pParse, pFirst->pSrc, pFirst->pEList);
> +		generate_column_metadata(pParse, pFirst->pSrc, pFirst->pEList);
>  	}
>  
>  	/* Reassembly the compound query so that it will be freed correctly
> @@ -6433,7 +6429,7 @@ sqlSelect(Parse * pParse,		/* The parser context */
>  	/* Identify column names if results of the SELECT are to be output.
>  	 */
>  	if (rc == 0 && pDest->eDest == SRT_Output) {
> -		generateColumnNames(pParse, pTabList, pEList);
> +		generate_column_metadata(pParse, pTabList, pEList);
>  	}
>  
>  	sqlDbFree(db, sAggInfo.aCol);
> diff --git a/src/box/sql/update.c b/src/box/sql/update.c
> index 6d69b7252..881f87d6f 100644
> --- a/src/box/sql/update.c
> +++ b/src/box/sql/update.c
> @@ -498,10 +498,8 @@ sqlUpdate(Parse * pParse,		/* The parser context */
>  	    pParse->triggered_space == NULL) {
>  		sqlVdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
>  		sqlVdbeSetNumCols(v, 1);
> -		sqlVdbeSetColName(v, 0, COLNAME_NAME, "rows updated",
> -				      SQL_STATIC);
> -		sqlVdbeSetColName(v, 0, COLNAME_DECLTYPE, "integer",
> -				  SQL_STATIC);
> +		vdbe_set_metadata_col_name(v, 0, "rows updated");
> +		vdbe_set_metadata_col_type(v, 0, "integer");
>  	}
>  
>   update_cleanup:
> diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
> index 582d48a1f..4142fb6ba 100644
> --- a/src/box/sql/vdbe.h
> +++ b/src/box/sql/vdbe.h
> @@ -148,17 +148,6 @@ struct SubProgram {
>  #define P5_ConstraintUnique  2
>  #define P5_ConstraintFK      4
>  
> -/*
> - * The Vdbe.aColName array contains 5n Mem structures, where n is the
> - * number of columns of data returned by the statement.
> - */
> -#define COLNAME_NAME     0
> -#define COLNAME_DECLTYPE 1
> -#define COLNAME_DATABASE 2
> -#define COLNAME_TABLE    3
> -#define COLNAME_COLUMN   4
> -#define COLNAME_N        2	/* Store the name and decltype */
> -
>  /*
>   * The following macro converts a relative address in the p2 field
>   * of a VdbeOp structure into a negative number.
> @@ -238,6 +227,10 @@ sql_vdbe_set_p4_key_def(struct Parse *parse, struct key_def *key_def);
>  VdbeOp *sqlVdbeGetOp(Vdbe *, int);
>  int sqlVdbeMakeLabel(Vdbe *);
>  void sqlVdbeRunOnlyOnce(Vdbe *);
> +
> +void
> +vdbe_metadata_delete(struct Vdbe *v);
> +
>  void sqlVdbeDelete(Vdbe *);
>  void sqlVdbeClearObject(sql *, Vdbe *);
>  void sqlVdbeMakeReady(Vdbe *, Parse *);
> @@ -248,7 +241,18 @@ void sqlVdbeResetStepResult(Vdbe *);
>  void sqlVdbeRewind(Vdbe *);
>  int sqlVdbeReset(Vdbe *);
>  void sqlVdbeSetNumCols(Vdbe *, int);
> -int sqlVdbeSetColName(Vdbe *, int, int, const char *, void (*)(void *));
> +
> +/**
> + * Set the name of the idx'th column to be returned by the SQL
> + * statement. @name must be a pointer to a nul terminated string.
> + * This call must be made after a call to sqlVdbeSetNumCols().
> + */
> +int
> +vdbe_set_metadata_col_name(struct Vdbe *v, int col_idx, const char *name);
> +
> +int
> +vdbe_set_metadata_col_type(struct Vdbe *v, int col_idx, const char *type);
> +
>  void sqlVdbeCountChanges(Vdbe *);
>  sql *sqlVdbeDb(Vdbe *);
>  void sqlVdbeSetSql(Vdbe *, const char *z, int n, int);
> diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
> index 0f32b4cd6..9ab3753cb 100644
> --- a/src/box/sql/vdbeInt.h
> +++ b/src/box/sql/vdbeInt.h
> @@ -346,6 +346,11 @@ struct ScanStatus {
>  	char *zName;		/* Name of table or index */
>  };
>  
> +struct sql_column_metadata {
> +	const char *name;
> +	const char *type;
> +};
> +
>  /*
>   * An instance of the virtual machine.  This structure contains the complete
>   * state of the virtual machine.
> @@ -394,7 +399,8 @@ struct Vdbe {
>  	Op *aOp;		/* Space to hold the virtual machine's program */
>  	Mem *aMem;		/* The memory locations */
>  	Mem **apArg;		/* Arguments to currently executing user function */
> -	Mem *aColName;		/* Column names to return */
> +	/** SQL metadata for SELECT queries. */
> +	struct sql_column_metadata *metadata;
>  	Mem *pResultSet;	/* Pointer to an array of results */
>  	VdbeCursor **apCsr;	/* One element of this array for each open cursor */
>  	Mem *aVar;		/* Values for the OP_Variable opcode. */
> diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
> index 685212d91..d746a42f2 100644
> --- a/src/box/sql/vdbeapi.c
> +++ b/src/box/sql/vdbeapi.c
> @@ -725,77 +725,24 @@ sql_column_subtype(struct sql_stmt *stmt, int i)
>  	return sql_value_subtype(columnMem(stmt, i));
>  }
>  
> -/*
> - * Convert the N-th element of pStmt->pColName[] into a string using
> - * xFunc() then return that string.  If N is out of range, return 0.
> - *
> - * There are up to 5 names for each column.  useType determines which
> - * name is returned.  Here are the names:
> - *
> - *    0      The column name as it should be displayed for output
> - *    1      The datatype name for the column
> - *    2      The name of the database that the column derives from
> - *    3      The name of the table that the column derives from
> - *    4      The name of the table column that the result column derives from
> - *
> - * If the result is not a simple column reference (if it is an expression
> - * or a constant) then useTypes 2, 3, and 4 return NULL.
> - */
> -static const void *
> -columnName(sql_stmt * pStmt,
> -	   int N, const void *(*xFunc) (Mem *), int useType)
> -{
> -	const void *ret;
> -	Vdbe *p;
> -	int n;
> -	sql *db;
> -	ret = 0;
> -	p = (Vdbe *) pStmt;
> -	db = p->db;
> -	assert(db != 0);
> -	n = sql_column_count(pStmt);
> -	if (N < n && N >= 0) {
> -		N += useType * n;
> -		assert(db->mallocFailed == 0);
> -		ret = xFunc(&p->aColName[N]);
> -		/* A malloc may have failed inside of the xFunc() call. If this
> -		 * is the case, clear the mallocFailed flag and return NULL.
> -		 */
> -		if (db->mallocFailed) {
> -			sqlOomClear(db);
> -			ret = 0;
> -		}
> -	}
> -	return ret;
> -}
> -
>  /*
>   * Return the name of the Nth column of the result set returned by SQL
>   * statement pStmt.
>   */
>  const char *
> -sql_column_name(sql_stmt * pStmt, int N)
> -{
> -	return columnName(pStmt, N, (const void *(*)(Mem *))sql_value_text,
> -			  COLNAME_NAME);
> -}
> -
> -const char *
> -sql_column_datatype(sql_stmt *pStmt, int N)
> +sql_column_name(sql_stmt *stmt, int n)
>  {
> -	return columnName(pStmt, N, (const void *(*)(Mem *))sql_value_text,
> -			  COLNAME_DECLTYPE);
> +	struct Vdbe *p = (struct Vdbe *) stmt;
> +	assert(n < sql_column_count(stmt) && n >= 0);
> +	return p->metadata[n].name;
>  }
>  
> -/*
> - * Return the column declaration type (if applicable) of the 'i'th column
> - * of the result set of SQL statement pStmt.
> - */
>  const char *
> -sql_column_decltype(sql_stmt * pStmt, int N)
> +sql_column_datatype(sql_stmt *stmt, int n)
>  {
> -	return columnName(pStmt, N, (const void *(*)(Mem *))sql_value_text,
> -			  COLNAME_DECLTYPE);
> +	struct Vdbe *p = (struct Vdbe *) stmt;
> +	assert(n < sql_column_count(stmt) && n >= 0);
> +	return p->metadata[n].type;
>  }
>  
>  /******************************* sql_bind_  **************************
> @@ -853,17 +800,15 @@ sql_bind_type(struct Vdbe *v, uint32_t position, const char *type)
>  	if (v->res_var_count < position)
>  		return 0;
>  	int rc = 0;
> -	if (sqlVdbeSetColName(v, v->var_pos[position - 1], COLNAME_DECLTYPE,
> -			      type, SQL_TRANSIENT) != 0)
> +	if (vdbe_set_metadata_col_type(v, v->var_pos[position - 1], type) != 0)
>  		rc = -1;
> -	const char *bind_name = v->aColName[position - 1].z;
> +	const char *bind_name = v->metadata[position - 1].name;
>  	if (strcmp(bind_name, "?") == 0)
>  		return rc;
>  	for (uint32_t i = position; i < v->res_var_count; ++i) {
> -		if (strcmp(bind_name,  v->aColName[i].z) == 0) {
> -			if (sqlVdbeSetColName(v, v->var_pos[i],
> -					      COLNAME_DECLTYPE, type,
> -					      SQL_TRANSIENT) != 0)
> +		if (strcmp(bind_name, v->metadata[i].name) == 0) {
> +			if (vdbe_set_metadata_col_type(v, v->var_pos[i],
> +						       type) != 0)
>  				return -1;
>  		}
>  	}
> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> index a1d658648..db11fbf33 100644
> --- a/src/box/sql/vdbeaux.c
> +++ b/src/box/sql/vdbeaux.c
> @@ -1827,6 +1827,18 @@ Cleanup(Vdbe * p)
>  	p->pResultSet = 0;
>  }
>  
> +void
> +vdbe_metadata_delete(struct Vdbe *v)
> +{
> +	if (v->metadata != NULL) {
> +		for (int i = 0; i < v->nResColumn; ++i) {
> +			free((void *)v->metadata[i].name);
> +			free((void *)v->metadata[i].type);
> +		}
> +		free(v->metadata);
> +	}
> +}
> +
>  /*
>   * Set the number of result columns that will be returned by this SQL
>   * statement. This is now set at compile time, rather than during
> @@ -1836,50 +1848,44 @@ Cleanup(Vdbe * p)
>  void
>  sqlVdbeSetNumCols(Vdbe * p, int nResColumn)
>  {
> -	int n;
> -	sql *db = p->db;
> -
> -	releaseMemArray(p->aColName, p->nResColumn * COLNAME_N);
> -	sqlDbFree(db, p->aColName);
> -	n = nResColumn * COLNAME_N;
> +	vdbe_metadata_delete(p);
>  	p->nResColumn = (u16) nResColumn;
> -	p->aColName = (Mem *) sqlDbMallocRawNN(db, sizeof(Mem) * n);
> -	if (p->aColName == 0)
> +	p->metadata = (struct sql_column_metadata *)
> +		calloc(nResColumn, sizeof(struct sql_column_metadata));
> +	if (p->metadata == NULL) {
> +		diag_set(OutOfMemory,
> +			 nResColumn * sizeof(struct sql_column_metadata),
> +			 "calloc", "metadata");
>  		return;
> -	initMemArray(p->aColName, n, p->db, MEM_Null);
> +	}
>  }
>  
> -/*
> - * Set the name of the idx'th column to be returned by the SQL statement.
> - * zName must be a pointer to a nul terminated string.
> - *
> - * This call must be made after a call to sqlVdbeSetNumCols().
> - *
> - * The final parameter, xDel, must be one of SQL_DYNAMIC, SQL_STATIC
> - * or SQL_TRANSIENT. If it is SQL_DYNAMIC, then the buffer pointed
> - * to by zName will be freed by sqlDbFree() when the vdbe is destroyed.
> - */
>  int
> -sqlVdbeSetColName(Vdbe * p,			/* Vdbe being configured */
> -		      int idx,			/* Index of column zName applies to */
> -		      int var,			/* One of the COLNAME_* constants */
> -		      const char *zName,	/* Pointer to buffer containing name */
> -		      void (*xDel) (void *))	/* Memory management strategy for zName */
> -{
> -	int rc;
> -	Mem *pColName;
> +vdbe_set_metadata_col_name(struct Vdbe *p, int idx, const char *name)
> +{
>  	assert(idx < p->nResColumn);
> -	assert(var < COLNAME_N);
> -	if (p->db->mallocFailed) {
> -		assert(!zName || xDel != SQL_DYNAMIC);
> +	if (p->metadata[idx].name != NULL)
> +		free((void *)p->metadata[idx].name);
> +	p->metadata[idx].name = strdup(name);
> +	if (p->metadata[idx].name == NULL) {
> +		diag_set(OutOfMemory, strlen(name), "strdup", "name");
>  		return -1;
>  	}
> -	assert(p->aColName != 0);
> -	assert(var == COLNAME_NAME || var == COLNAME_DECLTYPE);
> -	pColName = &(p->aColName[idx + var * p->nResColumn]);
> -	rc = sqlVdbeMemSetStr(pColName, zName, -1, 1, xDel);
> -	assert(rc != 0 || !zName || (pColName->flags & MEM_Term) != 0);
> -	return rc;
> +	return 0;
> +}
> +
> +int
> +vdbe_set_metadata_col_type(struct Vdbe *p, int idx, const char *type)
> +{
> +	assert(idx < p->nResColumn);
> +	if (p->metadata[idx].type != NULL)
> +		free((void *)p->metadata[idx].type);
> +	p->metadata[idx].type = strdup(type);
> +	if (p->metadata[idx].type == NULL) {
> +		diag_set(OutOfMemory, strlen(type), "strdup", "type");
> +		return -1;
> +	}
> +	return 0;
>  }
>  
>  /*
> @@ -2230,7 +2236,7 @@ sqlVdbeClearObject(sql * db, Vdbe * p)
>  {
>  	SubProgram *pSub, *pNext;
>  	assert(p->db == 0 || p->db == db);
> -	releaseMemArray(p->aColName, p->nResColumn * COLNAME_N);
> +	vdbe_metadata_delete(p);
>  	for (pSub = p->pProgram; pSub; pSub = pNext) {
>  		pNext = pSub->pNext;
>  		vdbeFreeOpArray(db, pSub->aOp, pSub->nOp);
> @@ -2242,7 +2248,6 @@ sqlVdbeClearObject(sql * db, Vdbe * p)
>  		sqlDbFree(db, p->pFree);
>  	}
>  	vdbeFreeOpArray(db, p->aOp, p->nOp);
> -	sqlDbFree(db, p->aColName);
>  	sqlDbFree(db, p->zSql);
>  }
>  
> -- 
> 2.15.1
> 

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

* Re: [Tarantool-patches] [PATCH 2/6] sql: fix possible null dereference in sql_expr_coll()
  2019-12-06 12:48         ` Nikita Pettik
@ 2019-12-17 13:30           ` Sergey Ostanevich
  2019-12-17 14:44             ` Nikita Pettik
  0 siblings, 1 reply; 40+ messages in thread
From: Sergey Ostanevich @ 2019-12-17 13:30 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches, Vladislav Shpilevoy

Hi!

Although the patch is correct, I don't see a reason to push it as part
of this series. 
We have a bug, we have a repro - it's a standalone fix?

Regards,
Sergos


On 06 Dec 15:48, Nikita Pettik wrote:
> On 06 Dec 00:59, Vladislav Shpilevoy wrote:
> > On 05/12/2019 12:40, Nikita Pettik wrote:
> > > On 28 Nov 23:42, Vladislav Shpilevoy wrote:
> > >> Thanks for the patch!
> > >>
> > >> Is it possible to test this?
> > > 
> > > As a pure test case - I've failed to come up with it.
> > > But the next patch (which adds collation to metadata) definitely fails
> > > without this fix (when TRIM() or REPLACE() are called without args).
> > 
> > Why does not it fail right after finding that there are no
> > arguments? TRIM looks strange, but REPLACE expects 3 arguments.
> > 
> > > I can dive into details, but I guess it's not so important here (since
> > > this is obviously buggy place). 
> > 
> > Is it? As I pointed above, maybe the bug is really in another
> > place? I see, that REPLACE checks argument count. But somewhy
> > too late:
> > 
> >     tarantool> box.execute('SELECT REPLACE()')
> >     ---
> >     - null
> >     - 'Wrong number of arguments is passed to REPLACE(): expected 3, got 0'
> >     ...
> > 
> > > If you want further investigation, let me
> > > know and I will do it.
> 
> Sorry, in fact I mean SUBSTR() function. It can take 1 or 2 arguments.
> But the check of arguments count is delayed till function execution (see
> substrFunc in sql/func.c).
> To generate collation for metadata we should call sql_expr_coll()
> (without such necessity we avoid call of this function). And it leads
> to null-dereference.
> 
> Finally, I've come up with example which results in null-dereference on
> master branch as well (added it to the current patch):
> 
> box.execute("CREATE TABLE t (id INT PRIMARY KEY, a TEXT, b TEXT COLLATE \"binary\", c TEXT COLLATE \"unicode_ci\");")
> box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = substr();")
> 
> Here we have to call sql_expr_coll() with expression representing
> substr() in order to verify that collations of LHS and RHS are
> compatible. So we eventually we get in the same situation.
>  
> > Yeah. I think we need to understand the bug before fixing
> > it.
> 
> New patch:
> 
> 
>     sql: fix possible null dereference in sql_expr_coll()
>     
>     Some built-in functions can accept different number of arguments.
>     Check of argument count for such functions takes place right before
>     its execution. So it is possible that expression-list representing
>     arguments for built-in function is NULL. On the other hand, in
>     sql_expr_coll() (which returns collation of expression) it is assumed
>     that if function features SQL_FUNC_DERIVEDCOLL flag (it implies that
>     resulting collation depends on collation of arguments) then it has at
>     least one argument. The last assumption is wrong considering for example
>     SUBSTR() function: it may have 1 or 2 arguments, so check of argument
>     count doesn't occur during compilation. Hence, if it is required to
>     calculate collation for SUBSTR() function and there's no arguments,
>     Tarantool crashes due to null-dereference.
>     This patch fixes this bug with one additional check in sql_expr_coll().
> 
> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index 648b7170e..0bdcfe576 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -332,7 +332,8 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
>                                 sql_func_by_signature(p->u.zToken, arg_count);
>                         if (func == NULL)
>                                 break;
> -                       if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL)) {
> +                       if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL) &&
> +                           arg_count > 0) {
>                                 /*
>                                  * Now we use quite straightforward
>                                  * approach assuming that resulting
> diff --git a/test/sql/collation.result b/test/sql/collation.result
> index 11962ef47..fbc7ce9aa 100644
> --- a/test/sql/collation.result
> +++ b/test/sql/collation.result
> @@ -292,6 +292,15 @@ box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = c COLLATE \"unicode\";
>  - null
>  - Illegal mix of collations
>  ...
> +-- Make sure that using function featuring variable arguemnts
> +-- length  and resulting collation which depends on arguments
> +-- is processed correctly.
> +--
> +box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = substr();")
> +---
> +- null
> +- 'Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0'
> +...
>  -- Compound queries perform implicit comparisons between values.
>  -- Hence, rules for collations compatibilities are the same.
>  --
> diff --git a/test/sql/collation.test.lua b/test/sql/collation.test.lua
> index 1be28b3ff..a013253cd 100644
> --- a/test/sql/collation.test.lua
> +++ b/test/sql/collation.test.lua
> @@ -80,6 +80,11 @@ box.execute("SELECT * FROM t WHERE b = c;")
>  box.execute("SELECT * FROM t WHERE b COLLATE \"binary\" = c;")
>  box.execute("SELECT * FROM t WHERE a = c;")
>  box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = c COLLATE \"unicode\";")
> +-- Make sure that using function featuring variable arguemnts
> +-- length  and resulting collation which depends on arguments
> +-- is processed correctly.
> +--
> +box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = substr();")
> 

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

* Re: [Tarantool-patches] [PATCH 2/6] sql: fix possible null dereference in sql_expr_coll()
  2019-12-17 13:30           ` Sergey Ostanevich
@ 2019-12-17 14:44             ` Nikita Pettik
  2019-12-17 19:53               ` Nikita Pettik
  0 siblings, 1 reply; 40+ messages in thread
From: Nikita Pettik @ 2019-12-17 14:44 UTC (permalink / raw)
  To: Sergey Ostanevich; +Cc: tarantool-patches, Vladislav Shpilevoy

On 17 Dec 16:30, Sergey Ostanevich wrote:
> Hi!
> 
> Although the patch is correct, I don't see a reason to push it as part
> of this series. 
> We have a bug, we have a repro - it's a standalone fix?

Without it (already existing) tests fail (from sql-tap/ suite) taking
into consideration further patches. So this fix is required for series
to pass all tests.
 
> Regards,
> Sergos
> 

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

* Re: [Tarantool-patches] [PATCH 2/6] sql: fix possible null dereference in sql_expr_coll()
  2019-12-17 14:44             ` Nikita Pettik
@ 2019-12-17 19:53               ` Nikita Pettik
  0 siblings, 0 replies; 40+ messages in thread
From: Nikita Pettik @ 2019-12-17 19:53 UTC (permalink / raw)
  To: Sergey Ostanevich; +Cc: tarantool-patches, Vladislav Shpilevoy

On 17 Dec 17:44, Nikita Pettik wrote:
> On 17 Dec 16:30, Sergey Ostanevich wrote:
> > Hi!
> > 
> > Although the patch is correct, I don't see a reason to push it as part
> > of this series. 
> > We have a bug, we have a repro - it's a standalone fix?
> 
> Without it (already existing) tests fail (from sql-tap/ suite) taking
> into consideration further patches. So this fix is required for series
> to pass all tests.

Pushed to master.
  
> > Regards,
> > Sergos
> > 

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

* Re: [Tarantool-patches] [PATCH 3/6] sql: extend result set with collation
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 3/6] sql: extend result set with collation Nikita Pettik
  2019-11-28 22:41   ` Vladislav Shpilevoy
@ 2019-12-18 11:08   ` Sergey Ostanevich
  2019-12-24  0:44     ` Nikita Pettik
  1 sibling, 1 reply; 40+ messages in thread
From: Sergey Ostanevich @ 2019-12-18 11:08 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches, v.shpilevoy

Hi!

Thanks for the patch!
I'm looking into the latest version of the changes in the branch - just
two comments below.

> --- a/src/box/execute.c
> +++ b/src/box/execute.c
> @@ -267,6 +267,23 @@ error:
>  	region_truncate(region, svp);
>  	return -1;
>  }
> +static size_t

Would you consider put inline here, since it has only one call site. It
also alinged with other static function definitions in this file.

> +metadata_map_sizeof(const char *name, const char *type, const char *coll)
> +{
...
> diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
> index 001af95dc..afbd1e1be 100644
> --- a/src/box/lua/net_box.c
> +++ b/src/box/lua/net_box.c
> @@ -638,6 +638,23 @@ netbox_decode_select(struct lua_State *L)
>  	return 2;
>  }
>  

The naming and 'while' logic implies that you plan to support more than
just 'collation' case. While in the code you have no actions for
different types of metadata. Should it skip next string from the data 
after key is not IPROTO_FIELD_COLL at least - otherwise we will try to
decode next int from string data?

> +/** Decode optional (i.e. may be present in response) metadata fields. */
> +static void
> +decode_metadata_optional(struct lua_State *L, const char **data,
> +			 uint32_t map_size)
> +{
> +	/* 2 is default metadata map size (field name + field size). */
> +	while (map_size-- > 2) {
> +		uint32_t key = mp_decode_uint(data);
> +		uint32_t len;
> +		if (key == IPROTO_FIELD_COLL) {
> +			const char *coll = mp_decode_str(data, &len);
> +			lua_pushlstring(L, coll, len);
> +			lua_setfield(L, -2, "collation");
> +		}
> +	}
> +}
> +
>  /**
>   * Decode IPROTO_METADATA into array of maps.
>   * @param L Lua stack to push result on.


regards,
Sergos

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

* Re: [Tarantool-patches] [PATCH 4/6] sql: extend result set with nullability
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 4/6] sql: extend result set with nullability Nikita Pettik
  2019-11-28 22:41   ` Vladislav Shpilevoy
@ 2019-12-18 13:31   ` Sergey Ostanevich
  1 sibling, 0 replies; 40+ messages in thread
From: Sergey Ostanevich @ 2019-12-18 13:31 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches, v.shpilevoy

Hi! 

Thanks for the patch, again looking on the branch.

Given the feedback from Vlad from previous review - LGTM.

regards,
Sergos

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

* Re: [Tarantool-patches] [PATCH 5/6] sql: extend result set with autoincrement
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 5/6] sql: extend result set with autoincrement Nikita Pettik
  2019-11-28 22:41   ` Vladislav Shpilevoy
@ 2019-12-18 15:17   ` Sergey Ostanevich
  2019-12-24  0:47     ` Nikita Pettik
  1 sibling, 1 reply; 40+ messages in thread
From: Sergey Ostanevich @ 2019-12-18 15:17 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches, v.shpilevoy

Hi!

Thanks for the patch, the latest version in branch LGTM.
Just one nit regarding readability below - not relevant to the patch
itself.

regards,
Sergos

> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index b772bcead..f40178194 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -1841,6 +1841,12 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
>  			if (p->op == TK_COLUMN)
>  				vdbe_set_metadata_col_nullability(v, i,
>  								  is_nullable);
> +			if (pTabList->a[j].space->sequence != NULL) {

The 'j' variable is a liveout from a loop that searches for the table
the expression in progress refers to. Renaming it to something like 
'current_table' would save some time during review and further
development.

> +				int afno =
> +					pTabList->a[j].space->sequence_fieldno;
> +				if (afno == iCol)
> +					vdbe_set_metadata_col_autoincrement(v, i);
> +			}
>  		} else {
>  			const char *z = pEList->a[i].zSpan;
>  			if (z == NULL)

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

* Re: [Tarantool-patches] [PATCH 6/6] sql: extend result set with alias
  2019-11-27 12:15 ` [Tarantool-patches] [PATCH 6/6] sql: extend result set with alias Nikita Pettik
  2019-11-28 22:41   ` Vladislav Shpilevoy
@ 2019-12-19 15:17   ` Sergey Ostanevich
  2019-12-24  0:27     ` Nikita Pettik
  1 sibling, 1 reply; 40+ messages in thread
From: Sergey Ostanevich @ 2019-12-19 15:17 UTC (permalink / raw)
  To: Nikita Pettik; +Cc: tarantool-patches, v.shpilevoy

Hi!

In the current branch I see the following opportunity:

diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index c1770e7b4..3fc8b2f96 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -1828,12 +1828,10 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
                        const char *name = NULL;
                        if (pEList->a[i].zName != NULL) {
                                if (is_full_meta) {
-                                       const char *alias = NULL;
+                                       const char *alias = pEList->a[i].zName;
                                        if (pEList->a[i].zSpan != NULL) {
-                                               alias = pEList->a[i].zName;
                                                name = pEList->a[i].zSpan;
                                        } else {
-                                               alias = pEList->a[i].zName;
                                                name = pEList->a[i].zName;
                                        }
                                        vdbe_metadata_set_col_alias(v, i, alias);
@@ -1868,12 +1866,10 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
                        const char *z = NULL;
                        if (pEList->a[i].zName != NULL) {
                                if (is_full_meta ) {
-                                       const char *alias = NULL;
+                                       const char *alias = pEList->a[i].zName;
                                        if (pEList->a[i].zSpan != NULL) {
-                                               alias = pEList->a[i].zName;
                                                z = pEList->a[i].zSpan;
                                        } else {
-                                               alias = pEList->a[i].zName;
                                                z = pEList->a[i].zName;
                                        }
                                        vdbe_metadata_set_col_alias(v, i, alias);



Although, I see you have unfinished discussion on whether name should be alias 
or vice versa - I won't interrupt you.

Regards,
Sergos

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

* Re: [Tarantool-patches] [PATCH 6/6] sql: extend result set with alias
  2019-12-19 15:17   ` Sergey Ostanevich
@ 2019-12-24  0:27     ` Nikita Pettik
  0 siblings, 0 replies; 40+ messages in thread
From: Nikita Pettik @ 2019-12-24  0:27 UTC (permalink / raw)
  To: Sergey Ostanevich; +Cc: tarantool-patches, v.shpilevoy

On 19 Dec 18:17, Sergey Ostanevich wrote:
> Hi!
> 
> In the current branch I see the following opportunity:

Thanks, Vlad suggested the same refactoring, I've applied it.
 
> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index c1770e7b4..3fc8b2f96 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -1828,12 +1828,10 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
>                         const char *name = NULL;
>                         if (pEList->a[i].zName != NULL) {
>                                 if (is_full_meta) {
> -                                       const char *alias = NULL;
> +                                       const char *alias = pEList->a[i].zName;
>                                         if (pEList->a[i].zSpan != NULL) {
> -                                               alias = pEList->a[i].zName;
>                                                 name = pEList->a[i].zSpan;
>                                         } else {
> -                                               alias = pEList->a[i].zName;
>                                                 name = pEList->a[i].zName;
>                                         }
>                                         vdbe_metadata_set_col_alias(v, i, alias);
> @@ -1868,12 +1866,10 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
>                         const char *z = NULL;
>                         if (pEList->a[i].zName != NULL) {
>                                 if (is_full_meta ) {
> -                                       const char *alias = NULL;
> +                                       const char *alias = pEList->a[i].zName;
>                                         if (pEList->a[i].zSpan != NULL) {
> -                                               alias = pEList->a[i].zName;
>                                                 z = pEList->a[i].zSpan;
>                                         } else {
> -                                               alias = pEList->a[i].zName;
>                                                 z = pEList->a[i].zName;
>                                         }
>                                         vdbe_metadata_set_col_alias(v, i, alias);
> 
> 
> 
> Although, I see you have unfinished discussion on whether name should be alias 
> or vice versa - I won't interrupt you.
> 
> Regards,
> Sergos
> 

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

* Re: [Tarantool-patches] [PATCH 3/6] sql: extend result set with collation
  2019-12-18 11:08   ` Sergey Ostanevich
@ 2019-12-24  0:44     ` Nikita Pettik
  0 siblings, 0 replies; 40+ messages in thread
From: Nikita Pettik @ 2019-12-24  0:44 UTC (permalink / raw)
  To: Sergey Ostanevich; +Cc: tarantool-patches, v.shpilevoy

On 18 Dec 14:08, Sergey Ostanevich wrote:
> Hi!
> 
> Thanks for the patch!
> I'm looking into the latest version of the changes in the branch - just
> two comments below.
> 
> > --- a/src/box/execute.c
> > +++ b/src/box/execute.c
> > @@ -267,6 +267,23 @@ error:
> >  	region_truncate(region, svp);
> >  	return -1;
> >  }
> > +static size_t
> 
> Would you consider put inline here, since it has only one call site. It
> also alinged with other static function definitions in this file.

Ok, added inline attribute. I've pushed fresh branch, so you
can chech changes.
 
> > +metadata_map_sizeof(const char *name, const char *type, const char *coll)
> > +{
> ...
> > diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
> > index 001af95dc..afbd1e1be 100644
> > --- a/src/box/lua/net_box.c
> > +++ b/src/box/lua/net_box.c
> > @@ -638,6 +638,23 @@ netbox_decode_select(struct lua_State *L)
> >  	return 2;
> >  }
> >  
> 
> The naming and 'while' logic implies that you plan to support more than
> just 'collation' case. 

Other cases (nullability, autoincrement, alias) are added in
further patches.

> While in the code you have no actions for
> different types of metadata. Should it skip next string from the data 
> after key is not IPROTO_FIELD_COLL at least - otherwise we will try to
> decode next int from string data?

When we decode map containing metadata, we check its size.
So we can't decode more than given number of map members. In this
particular patch, metadata can consist of 2 (name and type are required)
or 3 (+ collation which is optional) members. There should be assertion
verifying that key is always IPROTO_FIELD_COLL, but to avoid diffs from
patch to patch, I added assertion only in the last patch.

> > +/** Decode optional (i.e. may be present in response) metadata fields. */
> > +static void
> > +decode_metadata_optional(struct lua_State *L, const char **data,
> > +			 uint32_t map_size)
> > +{
> > +	/* 2 is default metadata map size (field name + field size). */
> > +	while (map_size-- > 2) {
> > +		uint32_t key = mp_decode_uint(data);
> > +		uint32_t len;
> > +		if (key == IPROTO_FIELD_COLL) {
> > +			const char *coll = mp_decode_str(data, &len);
> > +			lua_pushlstring(L, coll, len);
> > +			lua_setfield(L, -2, "collation");
> > +		}
> > +	}
> > +}
> > +
> >  /**
> >   * Decode IPROTO_METADATA into array of maps.
> >   * @param L Lua stack to push result on.
> 
> 
> regards,
> Sergos

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

* Re: [Tarantool-patches] [PATCH 5/6] sql: extend result set with autoincrement
  2019-12-18 15:17   ` Sergey Ostanevich
@ 2019-12-24  0:47     ` Nikita Pettik
  0 siblings, 0 replies; 40+ messages in thread
From: Nikita Pettik @ 2019-12-24  0:47 UTC (permalink / raw)
  To: Sergey Ostanevich; +Cc: tarantool-patches, v.shpilevoy

On 18 Dec 18:17, Sergey Ostanevich wrote:
> Hi!
> 
> Thanks for the patch, the latest version in branch LGTM.
> Just one nit regarding readability below - not relevant to the patch
> itself.
> 
> regards,
> Sergos
> 
> > diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> > index b772bcead..f40178194 100644
> > --- a/src/box/sql/select.c
> > +++ b/src/box/sql/select.c
> > @@ -1841,6 +1841,12 @@ generate_column_metadata(struct Parse *pParse, struct SrcList *pTabList,
> >  			if (p->op == TK_COLUMN)
> >  				vdbe_set_metadata_col_nullability(v, i,
> >  								  is_nullable);
> > +			if (pTabList->a[j].space->sequence != NULL) {
> 
> The 'j' variable is a liveout from a loop that searches for the table
> the expression in progress refers to. Renaming it to something like 
> 'current_table' would save some time during review and further
> development.

I try to avoid mixing functional changes and pure refactoring -
it simplifies review and makes git history way cleaner.
In this case, I consider renaming j to something more meaningful
to be a pure refactoring.
 
> > +				int afno =
> > +					pTabList->a[j].space->sequence_fieldno;
> > +				if (afno == iCol)
> > +					vdbe_set_metadata_col_autoincrement(v, i);
> > +			}
> >  		} else {
> >  			const char *z = pEList->a[i].zSpan;
> >  			if (z == NULL)

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

end of thread, other threads:[~2019-12-24  0:47 UTC | newest]

Thread overview: 40+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-11-27 12:15 [Tarantool-patches] [PATCH 0/6] sql: extend response metadata Nikita Pettik
2019-11-27 12:15 ` [Tarantool-patches] [PATCH 1/6] sql: refactor resulting set metadata Nikita Pettik
2019-11-28 22:41   ` Vladislav Shpilevoy
2019-12-05 11:39     ` Nikita Pettik
2019-12-05 23:58       ` Vladislav Shpilevoy
2019-12-06 12:48         ` Nikita Pettik
2019-12-17 13:23   ` Sergey Ostanevich
2019-11-27 12:15 ` [Tarantool-patches] [PATCH 2/6] sql: fix possible null dereference in sql_expr_coll() Nikita Pettik
2019-11-28 22:42   ` Vladislav Shpilevoy
2019-12-05 11:40     ` Nikita Pettik
2019-12-05 23:59       ` Vladislav Shpilevoy
2019-12-06 12:48         ` Nikita Pettik
2019-12-17 13:30           ` Sergey Ostanevich
2019-12-17 14:44             ` Nikita Pettik
2019-12-17 19:53               ` Nikita Pettik
2019-11-27 12:15 ` [Tarantool-patches] [PATCH 3/6] sql: extend result set with collation Nikita Pettik
2019-11-28 22:41   ` Vladislav Shpilevoy
2019-12-05 11:50     ` Nikita Pettik
2019-12-18 11:08   ` Sergey Ostanevich
2019-12-24  0:44     ` Nikita Pettik
2019-11-27 12:15 ` [Tarantool-patches] [PATCH 4/6] sql: extend result set with nullability Nikita Pettik
2019-11-28 22:41   ` Vladislav Shpilevoy
2019-12-05 11:50     ` Nikita Pettik
2019-12-06  0:00       ` Vladislav Shpilevoy
2019-12-06 12:49         ` Nikita Pettik
2019-12-18 13:31   ` Sergey Ostanevich
2019-11-27 12:15 ` [Tarantool-patches] [PATCH 5/6] sql: extend result set with autoincrement Nikita Pettik
2019-11-28 22:41   ` Vladislav Shpilevoy
2019-12-05 11:51     ` Nikita Pettik
2019-12-18 15:17   ` Sergey Ostanevich
2019-12-24  0:47     ` Nikita Pettik
2019-11-27 12:15 ` [Tarantool-patches] [PATCH 6/6] sql: extend result set with alias Nikita Pettik
2019-11-28 22:41   ` Vladislav Shpilevoy
2019-12-05 11:51     ` Nikita Pettik
2019-12-06  0:02       ` Vladislav Shpilevoy
2019-12-06 12:50         ` Nikita Pettik
2019-12-06 21:52           ` Vladislav Shpilevoy
2019-12-19 15:17   ` Sergey Ostanevich
2019-12-24  0:27     ` Nikita Pettik
2019-11-28 22:55 ` [Tarantool-patches] [PATCH 0/6] sql: extend response metadata Vladislav Shpilevoy

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