[Tarantool-patches] [PATCH v1 04/10] sql: static type check for SQL built-in functions

imeevma at tarantool.org imeevma at tarantool.org
Fri Aug 13 06:17:15 MSK 2021


This patch introduces a static type checking mechanism for SQL built-in
functions. However, it is currently disabled as the functions themselves
need to be prepared for such changes.

Part of #6105
---
 src/box/sql/expr.c    |   6 ++-
 src/box/sql/func.c    | 119 +++++++++++++++++++++++++++++++++++++++---
 src/box/sql/prepare.c |   2 +-
 src/box/sql/select.c  |   6 ++-
 4 files changed, 123 insertions(+), 10 deletions(-)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index c67a7091c..fb778f7e3 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -5390,7 +5390,11 @@ analyzeAggregate(Walker * pWalker, Expr * pExpr)
 						       (pExpr, EP_IntValue));
 						pItem->func =
 							sql_func_find(pExpr);
-						assert(pItem->func != NULL);
+						if (pItem->func == NULL) {
+							pParse->is_aborted =
+								true;
+							return WRC_Abort;
+						}
 						assert(pItem->func->def->
 						       language ==
 						       FUNC_LANGUAGE_SQL_BUILTIN &&
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 27e38ec16..b5e6bfe2a 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -2099,6 +2099,106 @@ built_in_func_put(struct sql_func_dictionary *dict)
 	}
 }
 
+/**
+ * Check if there is no need to cast argument to accepted type. Also, in some
+ * cases operation 'op' may be important, for example when given argument is
+ * NULL or is variable.
+ *
+ * Returns TRUE when:
+ *  - when operation is NULL;
+ *  - when accepted type and argument type are equal;
+ *  - when accepted type is ANY;
+ *  - when accepted type is INTEGER and argument type is UNSIGNED.
+ */
+static inline bool
+is_exact(int op, enum field_type a, enum field_type b)
+{
+	return op == TK_NULL || a == b || a == FIELD_TYPE_ANY ||
+	       (a == FIELD_TYPE_INTEGER && b == FIELD_TYPE_UNSIGNED);
+}
+
+/**
+ * Check if the argument MEM type will not change during cast. It means that
+ * either is_exact() returns TRUE or accepted type is metatype that includes
+ * argument type.
+ *
+ * Returns TRUE when:
+ *  - is_exact() returns TRUE;
+ *  - when accepted type is NUMBER and argument type is numeric type;
+ *  - when accepted type is SCALAR and argument type is not MAP or ARRAY.
+ */
+static inline bool
+is_upcast(int op, enum field_type a, enum field_type b)
+{
+	return is_exact(op, a, b) ||
+	       (a == FIELD_TYPE_NUMBER && sql_type_is_numeric(b)) ||
+	       (a == FIELD_TYPE_SCALAR && b != FIELD_TYPE_MAP &&
+		b != FIELD_TYPE_ARRAY);
+}
+
+/**
+ * Check if there is a chance that the argument can be cast to accepted type
+ * according to implicit cast rules.
+ *
+ * Returns TRUE when:
+ *  - is_upcast() returns TRUE;
+ *  - when accepted type and argument type are numeric types;
+ *  - when argument is binded value;
+ *  - when argument type is ANY, which means that is was not resolved.
+ */
+static inline bool
+is_castable(int op, enum field_type a, enum field_type b)
+{
+	return is_upcast(op, a, b) || op == TK_VARIABLE ||
+	       (sql_type_is_numeric(a) && sql_type_is_numeric(b)) ||
+	       b == FIELD_TYPE_ANY;
+}
+
+enum check_type {
+	CHECK_TYPE_EXACT,
+	CHECK_TYPE_UPCAST,
+	CHECK_TYPE_CASTABLE,
+};
+
+static struct func *
+find_compatible(struct Expr *expr, struct sql_func_dictionary *dict,
+		enum check_type check)
+{
+	int n = expr->x.pList != NULL ? expr->x.pList->nExpr : 0;
+	for (uint32_t i = 0; i < dict->count; ++i) {
+		struct func_sql_builtin *func = dict->functions[i];
+		int argc = func->base.def->param_count;
+		if (argc != n && argc != -1)
+			continue;
+		if (n == 0)
+			return &func->base;
+
+		enum field_type *types = func->param_list;
+		bool is_match = true;
+		for (int j = 0; j < n && is_match; ++j) {
+			struct Expr *e = expr->x.pList->a[j].pExpr;
+			enum field_type a = types[argc != -1 ? j : 0];
+			enum field_type b = sql_expr_type(e);
+			switch (check) {
+			case CHECK_TYPE_EXACT:
+				is_match = is_exact(e->op, a, b);
+				break;
+			case CHECK_TYPE_UPCAST:
+				is_match = is_upcast(e->op, a, b);
+				break;
+			case CHECK_TYPE_CASTABLE:
+				is_match = is_castable(e->op, a, b);
+				break;
+			default:
+				unreachable();
+			}
+		}
+		if (is_match)
+			return &func->base;
+	}
+	return NULL;
+}
+
 static struct func *
 find_built_in_func(struct Expr *expr, struct sql_func_dictionary *dict)
 {
@@ -2117,13 +2217,18 @@ find_built_in_func(struct Expr *expr, struct sql_func_dictionary *dict)
 		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, name, str, n);
 		return NULL;
 	}
-	struct func *func = NULL;
-	for (uint32_t i = 0; i < dict->count; ++i) {
-		func = &dict->functions[i]->base;
-		if (func->def->param_count == n)
-			break;
-	}
-	return func;
+	struct func *func = find_compatible(expr, dict, CHECK_TYPE_EXACT);
+	if (func != NULL)
+		return func;
+	func = find_compatible(expr, dict, CHECK_TYPE_UPCAST);
+	if (func != NULL)
+		return func;
+	func = find_compatible(expr, dict, CHECK_TYPE_CASTABLE);
+	if (func != NULL)
+		return func;
+	diag_set(ClientError, ER_SQL_EXECUTE,
+		 tt_sprintf("wrong arguments for function %s()", name));
+	return NULL;
 }
 
 struct func *
diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index d859a0243..b986630e9 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -89,7 +89,7 @@ sql_stmt_compile(const char *zSql, int nBytes, struct Vdbe *pReprepare,
 	} else {
 		sqlRunParser(&sParse, zSql);
 	}
-	assert(0 == sParse.nQueryLoop);
+	assert(0 == sParse.nQueryLoop || sParse.is_aborted);
 
 	if (db->mallocFailed)
 		sParse.is_aborted = true;
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index a26ca54a3..774d06fa9 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -6264,7 +6264,7 @@ sqlSelect(Parse * pParse,		/* The parser context */
 			sNC.ncFlags &= ~NC_InAggFunc;
 		}
 		sAggInfo.mxReg = pParse->nMem;
-		if (db->mallocFailed)
+		if (pParse->is_aborted)
 			goto select_end;
 
 		/* Processing for aggregates with GROUP BY is very different and
@@ -6473,6 +6473,8 @@ sqlSelect(Parse * pParse,		/* The parser context */
 			 */
 			sqlVdbeJumpHere(v, addr1);
 			updateAccumulator(pParse, &sAggInfo);
+			if (pParse->is_aborted)
+				goto select_end;
 			sqlVdbeAddOp2(v, OP_Integer, 1, iUseFlag);
 			VdbeComment((v, "indicate data in accumulator"));
 
@@ -6636,6 +6638,8 @@ sqlSelect(Parse * pParse,		/* The parser context */
 					goto select_end;
 				}
 				updateAccumulator(pParse, &sAggInfo);
+				if (pParse->is_aborted)
+					goto select_end;
 				assert(pMinMax == 0 || pMinMax->nExpr == 1);
 				if (sqlWhereIsOrdered(pWInfo) > 0) {
 					sqlVdbeGoto(v,
-- 
2.25.1



More information about the Tarantool-patches mailing list