Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v2 0/5] Prepare for static arguments type check
@ 2021-08-18 14:34 Mergen Imeev via Tarantool-patches
  2021-08-18 14:34 ` [Tarantool-patches] [PATCH v2 1/5] sql: modify arithmetic aggregate functions Mergen Imeev via Tarantool-patches
                   ` (4 more replies)
  0 siblings, 5 replies; 8+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-08-18 14:34 UTC (permalink / raw)
  To: vdavydov; +Cc: tarantool-patches

This is a preparatory patch-set for adding static and dynamic type
checking to SQL built-in function arguments.

Mergen Imeev (5):
  sql: modify arithmetic aggregate functions
  sql: introduce SQL built-in functions to parser
  sql: separate functions in parser
  sql: separate function flags from functions
  sql: encapsulate SQL built-in functions opcodes

 extra/addopcodes.sh                      |   1 +
 extra/mkkeywordhash.c                    |  37 +-
 src/box/sql/expr.c                       | 158 +++++--
 src/box/sql/func.c                       | 271 ++++++++----
 src/box/sql/parse.y                      | 515 ++++++++++++++++++++++-
 src/box/sql/resolve.c                    |  57 +--
 src/box/sql/select.c                     |  18 +-
 src/box/sql/sqlInt.h                     |  35 +-
 src/box/sql/treeview.c                   |   1 +
 src/box/sql/vdbemem.c                    |   2 +-
 test/box/tx_man.result                   |   4 +-
 test/box/tx_man.test.lua                 |   4 +-
 test/sql-tap/autoindex5.test.lua         |   2 +-
 test/sql-tap/built-in-functions.test.lua | 109 +++++
 test/sql-tap/default.test.lua            |   2 +-
 test/sql-tap/e_select1.test.lua          |   2 +-
 test/sql-tap/func.test.lua               |  92 +---
 test/sql-tap/func2.test.lua              |  18 +-
 test/sql-tap/func5.test.lua              |   6 +-
 test/sql-tap/identifier_case.test.lua    |   9 +-
 test/sql-tap/keyword1.test.lua           |  41 +-
 test/sql-tap/minmax4.test.lua            |   2 +-
 test/sql-tap/misc5.test.lua              |   4 +-
 test/sql-tap/null.test.lua               |   2 +-
 test/sql-tap/select1.test.lua            |  16 +-
 test/sql-tap/select3.test.lua            |  16 +-
 test/sql-tap/select5.test.lua            |   2 +-
 test/sql-tap/select6.test.lua            |  37 +-
 test/sql-tap/select8.test.lua            |   8 +-
 test/sql-tap/selectG.test.lua            |   2 +-
 test/sql-tap/sql-errors.test.lua         |   8 +-
 test/sql-tap/subquery.test.lua           |   8 +-
 test/sql-tap/tkt1449.test.lua            |   2 +-
 test/sql-tap/uuid.test.lua               |   8 +-
 test/sql-tap/where3.test.lua             |   8 +-
 test/sql/boolean.result                  |   6 +-
 test/sql/collation.result                |   6 +-
 test/sql/collation.test.lua              |   4 +-
 test/sql/message-func-indexes.result     |   6 +-
 test/sql/message-func-indexes.test.lua   |   6 +-
 test/sql/types.result                    |   8 +-
 41 files changed, 1181 insertions(+), 362 deletions(-)
 create mode 100755 test/sql-tap/built-in-functions.test.lua

-- 
2.25.1


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

* [Tarantool-patches] [PATCH v2 1/5] sql: modify arithmetic aggregate functions
  2021-08-18 14:34 [Tarantool-patches] [PATCH v2 0/5] Prepare for static arguments type check Mergen Imeev via Tarantool-patches
@ 2021-08-18 14:34 ` Mergen Imeev via Tarantool-patches
  2021-08-18 14:34 ` [Tarantool-patches] [PATCH v2 2/5] sql: introduce SQL built-in functions to parser Mergen Imeev via Tarantool-patches
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 8+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-08-18 14:34 UTC (permalink / raw)
  To: vdavydov; +Cc: tarantool-patches

This patch modifies arithmetic aggredate functions, so after this patch
they will work according to out implicit cast rules. It means that if
argument of SUM ar AVG was INTEGER, then the result will also be
INTEGER. It the argument was DOUBLE, the result will be DOUBLE. NUMBER
values cannot participate in arithmetic operations.

Needed for #4415
Part of #6105
---
 src/box/sql/func.c              | 109 +++++++++++++++-----------------
 test/sql-tap/e_select1.test.lua |   2 +-
 test/sql-tap/func.test.lua      |  80 ++---------------------
 test/sql-tap/minmax4.test.lua   |   2 +-
 test/sql-tap/null.test.lua      |   2 +-
 test/sql-tap/select1.test.lua   |   2 +-
 test/sql-tap/select3.test.lua   |  16 ++---
 test/sql-tap/select5.test.lua   |   2 +-
 test/sql-tap/select6.test.lua   |  21 +++---
 test/sql-tap/selectG.test.lua   |   2 +-
 test/sql-tap/subquery.test.lua  |   8 +--
 test/sql-tap/uuid.test.lua      |   6 +-
 test/sql/boolean.result         |   6 +-
 test/sql/types.result           |   8 +--
 14 files changed, 95 insertions(+), 171 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 1551d3ef2..3267d101e 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1664,59 +1664,30 @@ soundexFunc(sql_context * context, int argc, sql_value ** argv)
  */
 typedef struct SumCtx SumCtx;
 struct SumCtx {
-	double rSum;		/* Floating point sum */
-	int64_t iSum;		/* Integer sum */
-	/** True if iSum < 0. */
-	bool is_neg;
-	i64 cnt;		/* Number of elements summed */
-	u8 overflow;		/* True if integer overflow seen */
-	u8 approx;		/* True if non-integer value was input to the sum */
+	struct Mem mem;
+	uint32_t count;
 };
 
-/*
- * Routines used to compute the sum, average, and total.
- *
- * The SUM() function follows the (broken) SQL standard which means
- * that it returns NULL if it sums over no inputs.  TOTAL returns
- * 0.0 in that case.  In addition, TOTAL always returns a float where
- * SUM might return an integer if it never encounters a floating point
- * value.  TOTAL never fails, but SUM might through an exception if
- * it overflows an integer.
- */
+/** Routines used to compute the sum() and average(). */
 static void
 sum_step(struct sql_context *context, int argc, sql_value **argv)
 {
 	assert(argc == 1);
 	UNUSED_PARAMETER(argc);
 	struct SumCtx *p = sql_aggregate_context(context, sizeof(*p));
-	int type = sql_value_type(argv[0]);
-	if (type == MP_NIL || p == NULL)
+	if (p == NULL) {
+		context->is_aborted = true;
 		return;
-	if (type != MP_DOUBLE && type != MP_INT && type != MP_UINT) {
-		if (type != MP_STR || mem_to_number(argv[0]) != 0) {
-			diag_set(ClientError, ER_SQL_TYPE_MISMATCH,
-				 mem_str(argv[0]), "number");
-			context->is_aborted = true;
-			return;
-		}
-		type = sql_value_type(argv[0]);
 	}
-	p->cnt++;
-	if (type == MP_INT || type == MP_UINT) {
-		int64_t v = mem_get_int_unsafe(argv[0]);
-		if (type == MP_INT)
-			p->rSum += v;
-		else
-			p->rSum += (uint64_t) v;
-		if ((p->approx | p->overflow) == 0 &&
-		    sql_add_int(p->iSum, p->is_neg, v, type == MP_INT, &p->iSum,
-				&p->is_neg) != 0) {
-			p->overflow = 1;
-		}
-	} else {
-		p->rSum += mem_get_double_unsafe(argv[0]);
-		p->approx = 1;
+	if (p->count == 0) {
+		mem_create(&p->mem);
+		mem_set_uint(&p->mem, 0);
 	}
+	if (argv[0]->type == MEM_TYPE_NULL)
+		return;
+	++p->count;
+	if (mem_add(&p->mem, argv[0], &p->mem) != 0)
+		context->is_aborted = true;
 }
 
 static void
@@ -1724,17 +1695,10 @@ sumFinalize(sql_context * context)
 {
 	SumCtx *p;
 	p = sql_aggregate_context(context, 0);
-	if (p && p->cnt > 0) {
-		if (p->overflow) {
-			diag_set(ClientError, ER_SQL_EXECUTE, "integer "\
-				 "overflow");
-			context->is_aborted = true;
-		} else if (p->approx) {
-			sql_result_double(context, p->rSum);
-		} else {
-			mem_set_int(context->pOut, p->iSum, p->is_neg);
-		}
-	}
+	if (p == NULL || p->count == 0)
+		mem_set_null(context->pOut);
+	else
+		mem_copy_as_ephemeral(context->pOut, &p->mem);
 }
 
 static void
@@ -1742,9 +1706,37 @@ avgFinalize(sql_context * context)
 {
 	SumCtx *p;
 	p = sql_aggregate_context(context, 0);
-	if (p && p->cnt > 0) {
-		sql_result_double(context, p->rSum / (double)p->cnt);
+	if (p == NULL || p->count == 0) {
+		mem_set_null(context->pOut);
+		return;
 	}
+	struct Mem mem;
+	mem_create(&mem);
+	mem_set_uint(&mem, p->count);
+	if (mem_div(&p->mem, &mem, context->pOut) != 0)
+		context->is_aborted = true;
+}
+
+/** Routines used to compute the total(). */
+static void
+total_step(struct sql_context *context, int argc, sql_value **argv)
+{
+	assert(argc == 1);
+	UNUSED_PARAMETER(argc);
+	struct SumCtx *p = sql_aggregate_context(context, sizeof(*p));
+	if (p == NULL) {
+		context->is_aborted = true;
+		return;
+	}
+	if (p->count == 0) {
+		mem_create(&p->mem);
+		mem_set_double(&p->mem, 0.0);
+	}
+	if (argv[0]->type == MEM_TYPE_NULL)
+		return;
+	++p->count;
+	if (mem_add(&p->mem, argv[0], &p->mem) != 0)
+		context->is_aborted = true;
 }
 
 static void
@@ -1752,7 +1744,10 @@ totalFinalize(sql_context * context)
 {
 	SumCtx *p;
 	p = sql_aggregate_context(context, 0);
-	sql_result_double(context, p ? p->rSum : (double)0);
+	if (p == NULL || p->count == 0)
+		mem_set_double(context->pOut, 0.0);
+	else
+		mem_copy_as_ephemeral(context->pOut, &p->mem);
 }
 
 /*
@@ -2505,7 +2500,7 @@ static struct {
 	 .aggregate = FUNC_AGGREGATE_GROUP,
 	 .is_deterministic = false,
 	 .flags = 0,
-	 .call = sum_step,
+	 .call = total_step,
 	 .finalize = totalFinalize,
 	 .export_to_sql = true,
 	}, {
diff --git a/test/sql-tap/e_select1.test.lua b/test/sql-tap/e_select1.test.lua
index 27bd44ac4..da4db5a55 100755
--- a/test/sql-tap/e_select1.test.lua
+++ b/test/sql-tap/e_select1.test.lua
@@ -1181,7 +1181,7 @@ test:do_select_tests(
         {"2", "SELECT sum(j), max(j) FROM c2 GROUP BY (i%3)", {54, 36, 27, 21, 39, 28}},
         {"3", "SELECT sum(j), max(j) FROM c2 GROUP BY (j%2)", {80, 36, 40, 21}},
         {"4", "SELECT 1+sum(j), max(j)+1 FROM c2 GROUP BY (j%2)", {81, 37, 41, 22}},
-        {"5", "SELECT count(*), round(avg(i),2) FROM c1, c2 ON (i=down) GROUP BY j%2", {3, 4.33, 1, 2.0}},
+        {"5", "SELECT count(*), round(avg(i),2) FROM c1, c2 ON (i=down) GROUP BY j%2", {3, 4, 1, 2.0}},
     })
 
 -- EVIDENCE-OF: R-62913-19830 Otherwise, it is evaluated against a single
diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
index 4cc722d1e..e7b35c9d9 100755
--- a/test/sql-tap/func.test.lua
+++ b/test/sql-tap/func.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(14694)
+test:plan(14690)
 
 --!./tcltestrunner.lua
 -- 2001 September 15
@@ -851,7 +851,7 @@ test:do_test(
         ]])
     end, {
         -- <func-8.1>
-        68236, 3, 22745.33, 1, 67890, 5
+        68236, 3, 22745, 1, 67890, 5
         -- </func-8.1>
     })
 
@@ -869,76 +869,6 @@ test:do_execsql_test(
         -- </func-8.2>
     })
 
--- ifcapable tempdb {
---   do_test func-8.3 {
---     execsql {
---       CREATE TEMP TABLE t3 AS SELECT a FROM t2 ORDER BY a DESC;
---       SELECT min('z+'||a||'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP') FROM t3;
---     }
---   } {z+1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP}
--- } else {
---   do_test func-8.3 {
---     execsql {
---       CREATE TABLE t3 AS SELECT a FROM t2 ORDER BY a DESC;
---       SELECT min('z+'||a||'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP') FROM t3;
---     }
---   } {z+1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP}
--- }
--- do_test func-8.4 {
---   execsql {
---     SELECT max('z+'||a||'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP') FROM t3;
---   }
--- } {z+67890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP}
-test:do_execsql_test(
-    "func-8.5",
-    [[
-        SELECT sum(x) FROM (SELECT '9223372036' || '854775807' AS x
-                            UNION ALL SELECT -9223372036854775807)
-    ]], {
-        -- <func-8.5>
-        0
-        -- </func-8.5>
-    })
-
-test:do_execsql_test(
-    "func-8.6",
-    [[
-        SELECT typeof(sum(x)) FROM (SELECT '9223372036' || '854775807' AS x
-                            UNION ALL SELECT -9223372036854775807)
-    ]], {
-        -- <func-8.6>
-        "integer"
-        -- </func-8.6>
-    })
-
-test:do_execsql_test(
-    "func-8.7",
-    [[
-        SELECT typeof(sum(x)) FROM (SELECT '9223372036' || '854775808' AS x
-                            UNION ALL SELECT -9223372036854775807)
-    ]], {
-        -- <func-8.7>
-        "integer"
-        -- </func-8.7>
-    })
-
-test:do_execsql_test(
-    "func-8.8",
-    [[
-        SELECT sum(x)>0.0 FROM (SELECT '9223372036' || '854775808' AS x
-                            UNION ALL SELECT -9223372036850000000)
-    ]], {
-        -- <func-8.8>
-        true
-        -- </func-8.8>
-    })
-
-
-
-
-
-
-
 -- How do you test the random() function in a meaningful, deterministic way?
 --
 test:do_execsql_test(
@@ -1677,7 +1607,7 @@ test:do_catchsql_test(
             UNION ALL SELECT 10 AS x);
     ]], {
     -- <func-18.15.2>
-    1, "Failed to execute SQL statement: integer overflow"
+    1, "Failed to execute SQL statement: integer is overflowed"
     -- </func-18.15.2>
 })
 
@@ -1689,7 +1619,7 @@ test:do_catchsql_test(
             SELECT -10 AS x);
     ]], {
         -- <func-18.18>
-        1, "Failed to execute SQL statement: integer overflow"
+        1, "Failed to execute SQL statement: integer is overflowed"
         -- </func-18.18>
     })
 
@@ -2961,7 +2891,7 @@ test:do_catchsql_test(
         SELECT SUM(X'FF')
     ]], {
         -- <func-76.4>
-        1, "Type mismatch: can not convert varbinary(x'FF') to number"
+        1, "Type mismatch: can not convert varbinary(x'FF') to integer, unsigned or double"
         -- </func-76.4>
     })
 
diff --git a/test/sql-tap/minmax4.test.lua b/test/sql-tap/minmax4.test.lua
index 3a71462e0..392e0798d 100755
--- a/test/sql-tap/minmax4.test.lua
+++ b/test/sql-tap/minmax4.test.lua
@@ -251,7 +251,7 @@ test:do_test(
         ]]
     end, {
         -- <minmax4-2.3>
-        2, 3, 3.0, 1, 5, 1, 1, 1.5, 2, 4
+        2, 3, 3, 1, 5, 1, 1, 1, 2, 4
         -- </minmax4-2.3>
     })
 
diff --git a/test/sql-tap/null.test.lua b/test/sql-tap/null.test.lua
index ca20e3558..d01761a1f 100755
--- a/test/sql-tap/null.test.lua
+++ b/test/sql-tap/null.test.lua
@@ -157,7 +157,7 @@ test:do_execsql_test(
                avg(b), avg(c), min(b), max(b) from t1;
     ]], {
         -- <null-3.1>
-        7, 4, 6, 2, 3, 0.5, 0.5, 0, 1
+        7, 4, 6, 2, 3, 0, 0, 0, 1
         -- </null-3.1>
     })
 
diff --git a/test/sql-tap/select1.test.lua b/test/sql-tap/select1.test.lua
index dbc6e193d..2bf7fd3a2 100755
--- a/test/sql-tap/select1.test.lua
+++ b/test/sql-tap/select1.test.lua
@@ -515,7 +515,7 @@ test:do_catchsql_test(
         SELECT sum(a) FROM t3
     ]], {
         -- <select1-2.17.1>
-        1, "Type mismatch: can not convert string('abc') to number"
+        1, "Type mismatch: can not convert string('abc') to integer, unsigned or double"
         -- </select1-2.17.1>
     })
 
diff --git a/test/sql-tap/select3.test.lua b/test/sql-tap/select3.test.lua
index ffba58ec2..668042c5a 100755
--- a/test/sql-tap/select3.test.lua
+++ b/test/sql-tap/select3.test.lua
@@ -61,7 +61,7 @@ test:do_execsql_test("select3-1.2", [[
   FROM t1
 ]], {
   -- <select3-1.2>
-  1, 0, 31, 5, 496, 124, 16.0, 4.0
+  1, 0, 31, 5, 496, 124, 16, 4.0
   -- </select3-1.2>
 })
 
@@ -69,7 +69,7 @@ test:do_execsql_test("select3-1.3", [[
   SELECT max(n)/avg(n), max(log)/avg(log) FROM t1
 ]], {
   -- <select3-1.3>
-  1.9375, 1.25
+  1, 1
   -- </select3-1.3>
 })
 
@@ -95,7 +95,7 @@ test:do_execsql_test("select3-2.3.1", [[
   SELECT log, avg(n) FROM t1 GROUP BY log ORDER BY log
 ]], {
   -- <select3-2.3.1>
-  0, 1.0, 1, 2.0, 2, 3.5, 3, 6.5, 4, 12.5, 5, 24.0
+  0, 1, 1, 2, 2, 3, 3, 6, 4, 12, 5, 24
   -- </select3-2.3.1>
 })
 
@@ -103,7 +103,7 @@ test:do_execsql_test("select3-2.3.2", [[
   SELECT log, avg(n)+1 FROM t1 GROUP BY log ORDER BY log
 ]], {
   -- <select3-2.3.2>
-  0, 2.0, 1, 3.0, 2, 4.5, 3, 7.5, 4, 13.5, 5, 25.0
+  0, 2, 1, 3, 2, 4, 3, 7, 4, 13, 5, 25.0
   -- </select3-2.3.2>
 })
 
@@ -111,7 +111,7 @@ test:do_execsql_test("select3-2.4", [[
   SELECT log, avg(n)-min(n) FROM t1 GROUP BY log ORDER BY log
 ]], {
   -- <select3-2.4>
-  0, 0.0, 1, 0.0, 2, 0.5, 3, 1.5, 4, 3.5, 5, 7.0
+  0, 0, 1, 0, 2, 0, 3, 1, 4, 3, 5, 7.0
   -- </select3-2.4>
 })
 
@@ -119,7 +119,7 @@ test:do_execsql_test("select3-2.5", [[
   SELECT log*2+1, avg(n)-min(n) FROM t1 GROUP BY log ORDER BY log
 ]], {
   -- <select3-2.5>
-  1, 0.0, 3, 0.0, 5, 0.5, 7, 1.5, 9, 3.5, 11, 7.0
+  1, 0, 3, 0, 5, 0, 7, 1, 9, 3, 11, 7.0
   -- </select3-2.5>
 })
 
@@ -264,7 +264,7 @@ test:do_execsql_test("select3-5.1", [[
   ORDER BY max(n+log*2)+0, avg(n)+0
 ]], {
   -- <select3-5.1>
-  0, 1, 1.0, 1, 1, 1, 2.0, 4, 2, 2, 3.5, 8, 3, 4, 6.5, 14, 4, 8, 12.5, 24, 5, 15, 24.0, 41
+  0, 1, 1, 1, 1, 1, 2, 4, 2, 2, 3, 8, 3, 4, 6, 14, 4, 8, 12, 24, 5, 15, 24, 41
   -- </select3-5.1>
 })
 
@@ -274,7 +274,7 @@ test:do_execsql_test("select3-5.2", [[
   ORDER BY max(n+log*2)+0, GREATEST(log,avg(n))+0
 ]], {
   -- <select3-5.2>
-  0, 1, 1.0, 1, 1, 1, 2.0, 4, 2, 2, 3.5, 8, 3, 4, 6.5, 14, 4, 8, 12.5, 24, 5, 15, 24.0, 41
+  0, 1, 1, 1, 1, 1, 2, 4, 2, 2, 3, 8, 3, 4, 6, 14, 4, 8, 12, 24, 5, 15, 24, 41
   -- </select3-5.2>
 })
 
diff --git a/test/sql-tap/select5.test.lua b/test/sql-tap/select5.test.lua
index b04c9af09..293e1da14 100755
--- a/test/sql-tap/select5.test.lua
+++ b/test/sql-tap/select5.test.lua
@@ -558,7 +558,7 @@ test:do_catchsql_test(
             SELECT 1 FROM jj HAVING avg(s2) = 1 AND avg(s2) = 0;
     ]], {
     -- <select5-9.13.2>
-    1, "Type mismatch: can not convert string('A') to number"
+    1, "Type mismatch: can not convert string('A') to integer, unsigned or double"
     -- </select5-9.13.2>
 })
 
diff --git a/test/sql-tap/select6.test.lua b/test/sql-tap/select6.test.lua
index 8ad91390a..031892112 100755
--- a/test/sql-tap/select6.test.lua
+++ b/test/sql-tap/select6.test.lua
@@ -312,7 +312,7 @@ test:do_execsql_test(
         SELECT a,b,a+b FROM (SELECT avg(x) as a, avg(y) as b FROM t1)
     ]], {
         -- <select6-3.3>
-        10.5, 3.7, 14.2
+        10, 3, 13
         -- </select6-3.3>
     })
 
@@ -322,7 +322,7 @@ test:do_execsql_test(
         SELECT a,b,a+b FROM (SELECT avg(x) as a, avg(y) as b FROM t1 WHERE y=4)
     ]], {
         -- <select6-3.4>
-        11.5, 4.0, 15.5
+        11, 4, 15
         -- </select6-3.4>
     })
 
@@ -332,7 +332,7 @@ test:do_execsql_test(
         SELECT x,y,x+y FROM (SELECT avg(a) as x, avg(b) as y FROM t2 WHERE a=4)
     ]], {
         -- <select6-3.5>
-        4.0, 3.0, 7.0
+        4, 3, 7
         -- </select6-3.5>
     })
 
@@ -343,7 +343,6 @@ test:do_execsql_test(
         WHERE a>10
     ]], {
         -- <select6-3.6>
-        10.5, 3.7, 14.2
         -- </select6-3.6>
     })
 
@@ -365,7 +364,7 @@ test:do_execsql_test(
         WHERE a>10
     ]], {
         -- <select6-3.8>
-        11.5, 4.0, 15.5
+        11, 4, 15
         -- </select6-3.8>
     })
 
@@ -387,7 +386,7 @@ test:do_execsql_test(
         ORDER BY a
     ]], {
         -- <select6-3.10>
-        1.0, 1, 2.0, 2.5, 2, 4.5, 5.5, 3, 8.5, 11.5, 4, 15.5, 18.0, 5, 23.0
+        1, 1, 2, 2, 2, 4, 5, 3, 8, 11, 4, 15, 18, 5, 23
         -- </select6-3.10>
     })
 
@@ -399,7 +398,7 @@ test:do_execsql_test(
         WHERE b<4 ORDER BY a
     ]], {
         -- <select6-3.11>
-        1.0, 1, 2.0, 2.5, 2, 4.5, 5.5, 3, 8.5
+        1, 1, 2, 2, 2, 4, 5, 3, 8
         -- </select6-3.11>
     })
 
@@ -411,7 +410,7 @@ test:do_execsql_test(
         WHERE b<4 ORDER BY a
     ]], {
         -- <select6-3.12>
-        2.5, 2, 4.5, 5.5, 3, 8.5
+        2, 2, 4, 5, 3, 8
         -- </select6-3.12>
     })
 
@@ -423,7 +422,7 @@ test:do_execsql_test(
         ORDER BY a
     ]], {
         -- <select6-3.13>
-        2.5, 2, 4.5, 5.5, 3, 8.5, 11.5, 4, 15.5, 18.0, 5, 23.0
+        2, 2, 4, 5, 3, 8, 11, 4, 15, 18, 5, 23
         -- </select6-3.13>
     })
 
@@ -487,7 +486,7 @@ test:do_execsql_test(
         SELECT avg(y) FROM (SELECT DISTINCT y FROM t1) WHERE y<5 ORDER BY y
     ]], {
         -- <select6-4.4>
-        2.5
+        2
         -- </select6-4.4>
     })
 
@@ -497,7 +496,7 @@ test:do_execsql_test(
         SELECT avg(y) FROM (SELECT DISTINCT y FROM t1 WHERE y<5) ORDER BY y
     ]], {
         -- <select6-4.5>
-        2.5
+        2
         -- </select6-4.5>
     })
 
diff --git a/test/sql-tap/selectG.test.lua b/test/sql-tap/selectG.test.lua
index d416381bd..0690b4c86 100755
--- a/test/sql-tap/selectG.test.lua
+++ b/test/sql-tap/selectG.test.lua
@@ -48,7 +48,7 @@ test:do_test(
             :format(end_time - start_time, time_quota))
     end, {
         -- <100>
-        100000, 5000050000, 50000.5, true
+        100000, 5000050000, 50000, true
         -- </100>
     })
 
diff --git a/test/sql-tap/subquery.test.lua b/test/sql-tap/subquery.test.lua
index 1c5b3d02e..1030864b0 100755
--- a/test/sql-tap/subquery.test.lua
+++ b/test/sql-tap/subquery.test.lua
@@ -466,7 +466,7 @@ test:do_execsql_test(
                              HAVING avg(a.y) > avg(b.y));
     ]], {
         -- <subquery-3.4.1>
-        107, 4.0
+        106,4,107,4
         -- </subquery-3.4.1>
     })
 
@@ -482,7 +482,7 @@ test:do_execsql_test(
                              HAVING avg1 > avg2);
     ]], {
         -- <subquery-3.4.2>
-        107, 4.0
+        106,4,107,4
         -- </subquery-3.4.2>
     })
 
@@ -505,7 +505,7 @@ test:do_execsql_test(
          ORDER BY a.x;
     ]], {
         -- <subquery-3.4.3>
-        106, 4.5, false, true, 107, 4.0, true, false
+        106, 4, true, false, 107, 4, true, false
         -- </subquery-3.4.3>
     })
 
@@ -517,7 +517,7 @@ test:do_execsql_test(
         SELECT max((SELECT avg(y) FROM t35b)) FROM t35a;
     ]], {
         -- <subquery-3.5.1>
-        98.5
+        98
         -- </subquery-3.5.1>
     })
 
diff --git a/test/sql-tap/uuid.test.lua b/test/sql-tap/uuid.test.lua
index 57e638046..ec5caab63 100755
--- a/test/sql-tap/uuid.test.lua
+++ b/test/sql-tap/uuid.test.lua
@@ -237,7 +237,7 @@ test:do_catchsql_test(
     [[
         SELECT AVG(u) from t2;
     ]], {
-        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to number"
+        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to integer, unsigned or double"
     })
 
 test:do_execsql_test(
@@ -445,7 +445,7 @@ test:do_catchsql_test(
     [[
         SELECT SUM(u) from t2;
     ]], {
-        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to number"
+        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to integer, unsigned or double"
     })
 
 test:do_catchsql_test(
@@ -453,7 +453,7 @@ test:do_catchsql_test(
     [[
         SELECT TOTAL(u) from t2;
     ]], {
-        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to number"
+        1, "Type mismatch: can not convert uuid('11111111-1111-1111-1111-111111111111') to integer, unsigned or double"
     })
 
 test:do_execsql_test(
diff --git a/test/sql/boolean.result b/test/sql/boolean.result
index a9ce37e11..7f005df55 100644
--- a/test/sql/boolean.result
+++ b/test/sql/boolean.result
@@ -345,7 +345,7 @@ SELECT typeof(a) FROM t0;
 SELECT AVG(a) FROM t0;
  | ---
  | - null
- | - 'Type mismatch: can not convert boolean(FALSE) to number'
+ | - 'Type mismatch: can not convert boolean(FALSE) to integer, unsigned or double'
  | ...
 SELECT MIN(a) FROM t0;
  | ---
@@ -366,7 +366,7 @@ SELECT MAX(a) FROM t0;
 SELECT SUM(a) FROM t0;
  | ---
  | - null
- | - 'Type mismatch: can not convert boolean(FALSE) to number'
+ | - 'Type mismatch: can not convert boolean(FALSE) to integer, unsigned or double'
  | ...
 SELECT COUNT(a) FROM t0;
  | ---
@@ -379,7 +379,7 @@ SELECT COUNT(a) FROM t0;
 SELECT TOTAL(a) FROM t0;
  | ---
  | - null
- | - 'Type mismatch: can not convert boolean(FALSE) to number'
+ | - 'Type mismatch: can not convert boolean(FALSE) to integer, unsigned or double'
  | ...
 SELECT GROUP_CONCAT(a, ' +++ ') FROM t0;
  | ---
diff --git a/test/sql/types.result b/test/sql/types.result
index 68bdcd62e..336bef416 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -777,7 +777,7 @@ box.execute("SELECT avg(i) FROM t;")
   - name: COLUMN_1
     type: number
   rows:
-  - [6148914691236516864]
+  - [6148914691236517204]
 ...
 box.execute("SELECT total(i) FROM t;")
 ---
@@ -1251,17 +1251,17 @@ box.execute("SELECT * FROM t WHERE v = x'616263'")
 box.execute("SELECT sum(v) FROM t;")
 ---
 - null
-- 'Type mismatch: can not convert varbinary(x''616263'') to number'
+- 'Type mismatch: can not convert varbinary(x''616263'') to integer, unsigned or double'
 ...
 box.execute("SELECT avg(v) FROM t;")
 ---
 - null
-- 'Type mismatch: can not convert varbinary(x''616263'') to number'
+- 'Type mismatch: can not convert varbinary(x''616263'') to integer, unsigned or double'
 ...
 box.execute("SELECT total(v) FROM t;")
 ---
 - null
-- 'Type mismatch: can not convert varbinary(x''616263'') to number'
+- 'Type mismatch: can not convert varbinary(x''616263'') to integer, unsigned or double'
 ...
 box.execute("SELECT min(v) FROM t;")
 ---
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v2 2/5] sql: introduce SQL built-in functions to parser
  2021-08-18 14:34 [Tarantool-patches] [PATCH v2 0/5] Prepare for static arguments type check Mergen Imeev via Tarantool-patches
  2021-08-18 14:34 ` [Tarantool-patches] [PATCH v2 1/5] sql: modify arithmetic aggregate functions Mergen Imeev via Tarantool-patches
@ 2021-08-18 14:34 ` Mergen Imeev via Tarantool-patches
  2021-08-19  8:35   ` Vladimir Davydov via Tarantool-patches
  2021-08-18 14:35 ` [Tarantool-patches] [PATCH v2 3/5] sql: separate functions in parser Mergen Imeev via Tarantool-patches
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 8+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-08-18 14:34 UTC (permalink / raw)
  To: vdavydov; +Cc: tarantool-patches

This patch moves SQL built-in functions to parser, so now names of these
functions are new keywords. Also, but to this the number of arguments
for functions with a variable number of arguments will be checked during
parsing. For functions with a constant number of arguments, it is always
checked during parsing.

Part of #6105
---
 extra/mkkeywordhash.c                    |  37 +-
 src/box/sql/parse.y                      | 507 ++++++++++++++++++++++-
 test/box/tx_man.result                   |   4 +-
 test/box/tx_man.test.lua                 |   4 +-
 test/sql-tap/autoindex5.test.lua         |   2 +-
 test/sql-tap/built-in-functions.test.lua | 109 +++++
 test/sql-tap/default.test.lua            |   2 +-
 test/sql-tap/func.test.lua               |  12 +-
 test/sql-tap/func2.test.lua              |  18 +-
 test/sql-tap/func5.test.lua              |   6 +-
 test/sql-tap/identifier_case.test.lua    |   9 +-
 test/sql-tap/keyword1.test.lua           |  41 +-
 test/sql-tap/misc5.test.lua              |   4 +-
 test/sql-tap/select1.test.lua            |  14 +-
 test/sql-tap/select6.test.lua            |  16 +-
 test/sql-tap/select8.test.lua            |   8 +-
 test/sql-tap/sql-errors.test.lua         |   8 +-
 test/sql-tap/tkt1449.test.lua            |   2 +-
 test/sql-tap/uuid.test.lua               |   2 +-
 test/sql-tap/where3.test.lua             |   8 +-
 test/sql/collation.result                |   6 +-
 test/sql/collation.test.lua              |   4 +-
 test/sql/message-func-indexes.result     |   6 +-
 test/sql/message-func-indexes.test.lua   |   6 +-
 24 files changed, 749 insertions(+), 86 deletions(-)
 create mode 100755 test/sql-tap/built-in-functions.test.lua

diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index 0d998506c..369d9e1dd 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -184,7 +184,6 @@ static Keyword aKeywordTable[] = {
   { "BLOB",                   "TK_STANDARD",    true  },
   { "BINARY",                 "TK_ID",          true  },
   { "CALL",                   "TK_STANDARD",    true  },
-  { "CHAR",                   "TK_CHAR",        true  },
   { "CHARACTER",              "TK_ID",          true  },
   { "CONDITION",              "TK_STANDARD",    true  },
   { "CONNECT",                "TK_STANDARD",    true  },
@@ -251,6 +250,42 @@ static Keyword aKeywordTable[] = {
   { "LEADING",                "TK_LEADING",     true  },
   { "TRAILING",               "TK_TRAILING",    true  },
   { "BOTH",                   "TK_BOTH",        true  },
+  { "ABS",                    "TK_ABS",         true  },
+  { "AVG",                    "TK_AVG",         true  },
+  { "CHAR",                   "TK_CHAR",        true  },
+  { "CHAR_LENGTH",            "TK_CHAR_LEN",    true  },
+  { "CHARACTER_LENGTH",       "TK_CHAR_LEN",    true  },
+  { "COALESCE",               "TK_COALESCE",    true  },
+  { "COUNT",                  "TK_COUNT",       true  },
+  { "GREATEST",               "TK_GREATEST",    true  },
+  { "GROUP_CONCAT",           "TK_GROUP_CONCAT",true  },
+  { "HEX",                    "TK_HEX",         true  },
+  { "IFNULL",                 "TK_IFNULL",      true  },
+  { "LEAST",                  "TK_LEAST",       true  },
+  { "LENGTH",                 "TK_LENGTH",      true  },
+  { "LIKELIHOOD",             "TK_LIKELIHOOD",  true  },
+  { "LIKELY",                 "TK_LIKELY",      true  },
+  { "LOWER",                  "TK_LOWER",       true  },
+  { "MAX",                    "TK_MAX",         true  },
+  { "MIN",                    "TK_MIN",         true  },
+  { "NULLIF",                 "TK_NULLIF",      true  },
+  { "POSITION",               "TK_POSITION",    true  },
+  { "PRINTF",                 "TK_PRINTF",      true  },
+  { "QUOTE",                  "TK_QUOTE",       true  },
+  { "RANDOM",                 "TK_RANDOM",      true  },
+  { "RANDOMBLOB",             "TK_RANDOMBLOB",  true  },
+  { "ROUND",                  "TK_ROUND",       true  },
+  { "ROW_COUNT",              "TK_ROW_COUNT",   true  },
+  { "SOUNDEX",                "TK_SOUNDEX",     true  },
+  { "SUBSTR",                 "TK_SUBSTR",      true  },
+  { "SUM",                    "TK_SUM",         true  },
+  { "TOTAL",                  "TK_TOTAL",       true  },
+  { "TYPEOF",                 "TK_TYPEOF",      true  },
+  { "UNICODE",                "TK_UNICODE",     true  },
+  { "UNLIKELY",               "TK_UNLIKELY",    true  },
+  { "UPPER",                  "TK_UPPER",       true  },
+  { "VERSION",                "TK_VERSION",     true  },
+  { "ZEROBLOB",               "TK_ZEROBLOB",    true  },
 };
 
 /* Number of keywords */
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index bd041e862..cb2e627db 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -1172,27 +1172,506 @@ trim_specification(A) ::= LEADING.  { A = TRIM_LEADING; }
 trim_specification(A) ::= TRAILING. { A = TRIM_TRAILING; }
 trim_specification(A) ::= BOTH.     { A = TRIM_BOTH; }
 
-expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP(E). {
-  if( Y && Y->nExpr>pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG] ){
-    const char *err =
-      tt_sprintf("Number of arguments to function %.*s", X.n, X.z);
-    diag_set(ClientError, ER_SQL_PARSER_LIMIT, err, Y->nExpr,
-             pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
+expr(A) ::= ABS(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ABS", "1", n);
     pParse->is_aborted = true;
+    return;
   }
   A.pExpr = sqlExprFunction(pParse, Y, &X);
-  spanSet(&A,&X,&E);
-  if( D==SF_Distinct && A.pExpr ){
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= AVG(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "AVG", "1", n);
+    pParse->is_aborted = true;
+    return;
   }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
 }
 
-/*
- * type_func(A) ::= DATE(A) .
- * type_func(A) ::= DATETIME(A) .
- */
-type_func(A) ::= CHAR(A) .
-expr(A) ::= type_func(X) LP distinct(D) exprlist(Y) RP(E). {
+expr(A) ::= CHAR(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y != NULL && Y->nExpr > pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]) {
+    const char *str = tt_sprintf("from %d to %d", 0,
+                                 pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "CHAR", str, Y->nExpr);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= CHAR_LEN(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    const char *name = X.n == strlen("CHAR_LENGTH") ? "CHAR_LENGTH" :
+                       "CHARACTER_LENGTH";
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, name, "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= COALESCE(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr < 2 ||
+      Y->nExpr > pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    const char *str = tt_sprintf("from %d to %d", 2,
+                                 pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "COALESCE", str, n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= COUNT(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y != NULL && Y->nExpr > 1) {
+    const char *str = tt_sprintf("from %d to %d", 0, 1);
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "COUNT", str, Y->nExpr);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= COUNT(X) LP STAR RP(E). {
+  A.pExpr = sqlExprFunction(pParse, NULL, &X);
+  spanSet(&A, &X, &E);
+}
+
+expr(A) ::= GREATEST(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr < 2 ||
+      Y->nExpr > pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    const char *str = tt_sprintf("from %d to %d", 2,
+                                 pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "GREATEST", str, n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= GROUP_CONCAT(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr > 2) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    const char *str = tt_sprintf("from %d to %d", 1, 2);
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "GROUP_CONCAT", str, n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= HEX(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "HEX", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= IFNULL(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 2) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "IFNULL", "2", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= LEAST(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr < 2 ||
+      Y->nExpr > pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    const char *str = tt_sprintf("from %d to %d", 2,
+                                 pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "LEAST", str, n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= LENGTH(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "LENGTH", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= LIKELIHOOD(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 2) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "LIKELIHOOD", "2", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= LIKELY(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "LIKELY", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= LOWER(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "LOWER", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= MAX(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "MAX", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= MIN(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "MIN", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= NULLIF(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 2) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "NULLIF", "2", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= POSITION(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 2) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "POSITION", "2", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= PRINTF(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y != NULL && Y->nExpr > pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]) {
+    const char *str = tt_sprintf("from %d to %d", 0,
+                                 pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "PRINTF", str, Y->nExpr);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= QUOTE(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "QUOTE", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= RANDOM(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y != NULL) {
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "RANDOM", "0", Y->nExpr);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= RANDOMBLOB(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "RANDOMBLOB", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= REPLACE(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 3) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "REPLACE", "3", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= ROUND(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr > 2) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    const char *str = tt_sprintf("from %d to %d", 1, 2);
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ROUND", str, n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= ROW_COUNT(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y != NULL) {
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ROW_COUNT", "0", Y->nExpr);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= SOUNDEX(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "SOUNDEX", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= SUBSTR(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr < 2 || Y->nExpr > 3) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    const char *str = tt_sprintf("from %d to %d", 2, 3);
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "SUBSTR", str, n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= SUM(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "SUM", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= TOTAL(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "TOTAL", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= TYPEOF(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "TYPEOF", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= UNICODE(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "UNICODE", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= UNLIKELY(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "UNLIKELY", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= UPPER(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "UPPER", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= UUID(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y != NULL && Y->nExpr > 1) {
+    const char *str = tt_sprintf("from %d to %d", 0, 1);
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "UUID", str, Y->nExpr);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= VERSION(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y != NULL) {
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "VERSION", "0", Y->nExpr);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= ZEROBLOB(X) LP distinct(D) exprlist(Y) RP(E). {
+  if (Y == NULL || Y->nExpr != 1) {
+    int n = Y == NULL ? 0 : Y->nExpr;
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ZEROBLOB", "1", n);
+    pParse->is_aborted = true;
+    return;
+  }
+  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  spanSet(&A, &X, &E);
+  if(D == SF_Distinct && A.pExpr)
+    A.pExpr->flags |= EP_Distinct;
+}
+
+expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP(E). {
   if( Y && Y->nExpr>pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG] ){
     const char *err =
       tt_sprintf("Number of arguments to function %.*s", X.n, X.z);
diff --git a/test/box/tx_man.result b/test/box/tx_man.result
index 786d7fc30..b99fbc2ca 100644
--- a/test/box/tx_man.result
+++ b/test/box/tx_man.result
@@ -2129,11 +2129,11 @@ tx1:rollback()
 
 -- gh-6095: SQL query may crash in MVCC mode if it involves ephemeral spaces.
 --
-box.execute([[ CREATE TABLE test (id INT NOT NULL PRIMARY KEY, count INT NOT NULL)]])
+box.execute([[ CREATE TABLE test (id INT NOT NULL PRIMARY KEY, "COUNT" INT NOT NULL)]])
  | ---
  | - row_count: 1
  | ...
-box.execute([[ UPDATE test SET count = count + 1 WHERE id = 0 ]])
+box.execute([[ UPDATE test SET "COUNT" = "COUNT" + 1 WHERE id = 0 ]])
  | ---
  | - row_count: 0
  | ...
diff --git a/test/box/tx_man.test.lua b/test/box/tx_man.test.lua
index ebb86dd13..1f32bd8dd 100644
--- a/test/box/tx_man.test.lua
+++ b/test/box/tx_man.test.lua
@@ -661,8 +661,8 @@ tx1:rollback()
 
 -- gh-6095: SQL query may crash in MVCC mode if it involves ephemeral spaces.
 --
-box.execute([[ CREATE TABLE test (id INT NOT NULL PRIMARY KEY, count INT NOT NULL)]])
-box.execute([[ UPDATE test SET count = count + 1 WHERE id = 0 ]])
+box.execute([[ CREATE TABLE test (id INT NOT NULL PRIMARY KEY, "COUNT" INT NOT NULL)]])
+box.execute([[ UPDATE test SET "COUNT" = "COUNT" + 1 WHERE id = 0 ]])
 box.execute([[ DROP TABLE test]])
 
 -- https://github.com/tarantool/tarantool/issues/5515
diff --git a/test/sql-tap/autoindex5.test.lua b/test/sql-tap/autoindex5.test.lua
index 488924b09..8d56971df 100755
--- a/test/sql-tap/autoindex5.test.lua
+++ b/test/sql-tap/autoindex5.test.lua
@@ -40,7 +40,7 @@ test:do_execsql_test(
                     release_t TEXT NOT NULL,
                     subrelease TEXT NOT NULL,
                     archive TEXT NOT NULL,
-                    version TEXT NOT NULL,
+                    version_txt TEXT NOT NULL,
                     version_id INTEGER NOT NULL DEFAULT 0,
                     PRIMARY KEY (name, release_t, subrelease, archive));
 
diff --git a/test/sql-tap/built-in-functions.test.lua b/test/sql-tap/built-in-functions.test.lua
new file mode 100755
index 000000000..3575062ae
--- /dev/null
+++ b/test/sql-tap/built-in-functions.test.lua
@@ -0,0 +1,109 @@
+#!/usr/bin/env tarantool
+local test = require("sqltester")
+test:plan(10)
+
+--
+-- Make sure that number of arguments check is checked properly for SQL built-in
+-- functions with variable number of arguments.
+--
+test:do_catchsql_test(
+    "builtins-1.1",
+    [[
+        SELECT COUNT(1, 2);
+    ]],
+    {
+        1, [[Wrong number of arguments is passed to COUNT(): expected from 0 to 1, got 2]]
+    }
+)
+
+test:do_catchsql_test(
+    "builtins-1.2",
+    [[
+        SELECT GREATEST();
+    ]],
+    {
+        1, [[Wrong number of arguments is passed to GREATEST(): expected from 2 to 127, got 0]]
+    }
+)
+
+test:do_catchsql_test(
+    "builtins-1.3",
+    [[
+        SELECT GROUP_CONCAT();
+    ]],
+    {
+        1, [[Wrong number of arguments is passed to GROUP_CONCAT(): expected from 1 to 2, got 0]]
+    }
+)
+
+test:do_catchsql_test(
+    "builtins-1.4",
+    [[
+        SELECT GROUP_CONCAT(1, 2, 3);
+    ]],
+    {
+        1, [[Wrong number of arguments is passed to GROUP_CONCAT(): expected from 1 to 2, got 3]]
+    }
+)
+
+test:do_catchsql_test(
+    "builtins-1.5",
+    [[
+        SELECT LEAST();
+    ]],
+    {
+        1, [[Wrong number of arguments is passed to LEAST(): expected from 2 to 127, got 0]]
+    }
+)
+
+test:do_catchsql_test(
+    "builtins-1.6",
+    [[
+        SELECT ROUND();
+    ]],
+    {
+        1, [[Wrong number of arguments is passed to ROUND(): expected from 1 to 2, got 0]]
+    }
+)
+
+test:do_catchsql_test(
+    "builtins-1.7",
+    [[
+        SELECT ROUND(1, 2, 3);
+    ]],
+    {
+        1, [[Wrong number of arguments is passed to ROUND(): expected from 1 to 2, got 3]]
+    }
+)
+
+test:do_catchsql_test(
+    "builtins-1.8",
+    [[
+        SELECT SUBSTR('1');
+    ]],
+    {
+        1, [[Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 1]]
+    }
+)
+
+test:do_catchsql_test(
+    "builtins-1.9",
+    [[
+        SELECT SUBSTR('1', '2', '3', '4');
+    ]],
+    {
+        1, [[Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 4]]
+    }
+)
+
+test:do_catchsql_test(
+    "builtins-1.10",
+    [[
+        SELECT UUID(1, 2);
+    ]],
+    {
+        1, [[Wrong number of arguments is passed to UUID(): expected from 0 to 1, got 2]]
+    }
+)
+
+test:finish_test()
diff --git a/test/sql-tap/default.test.lua b/test/sql-tap/default.test.lua
index 9c15220eb..41f23386c 100755
--- a/test/sql-tap/default.test.lua
+++ b/test/sql-tap/default.test.lua
@@ -62,7 +62,7 @@ test:do_catchsql_test(
 	CREATE TABLE t3(
 	rowid INTEGER PRIMARY KEY AUTOINCREMENT,
 	x INTEGER,
-	y INTEGER DEFAULT (max(x,5))
+	y INTEGER DEFAULT (greatest(x,5))
 	);
 	]], {
 	-- <default-1.3>
diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
index e7b35c9d9..7dd85025a 100755
--- a/test/sql-tap/func.test.lua
+++ b/test/sql-tap/func.test.lua
@@ -68,7 +68,7 @@ test:do_catchsql_test(
         SELECT length(*) FROM tbl1 ORDER BY t1
     ]], {
         -- <func-1.1>
-        1, "Wrong number of arguments is passed to LENGTH(): expected 1, got 0"
+        1, "Syntax error at line 1 near '*'"
         -- </func-1.1>
     })
 
@@ -427,7 +427,7 @@ test:do_catchsql_test(
         SELECT round(a,b,c) FROM t1
     ]], {
         -- <func-4.5>
-        1, "Wrong number of arguments is passed to ROUND(): expected 1 or 2, got 3"
+        1, "Wrong number of arguments is passed to ROUND(): expected from 1 to 2, got 3"
         -- </func-4.5>
     })
 
@@ -487,7 +487,7 @@ test:do_catchsql_test(
         SELECT round() FROM t1 ORDER BY a
     ]], {
         -- <func-4.11>
-        1, "Wrong number of arguments is passed to ROUND(): expected 1 or 2, got 0"
+        1, "Wrong number of arguments is passed to ROUND(): expected from 1 to 2, got 0"
         -- </func-4.11>
     })
 
@@ -777,7 +777,7 @@ test:do_catchsql_test(
         SELECT upper(*) FROM t2
     ]], {
         -- <func-5.5>
-        1, "Wrong number of arguments is passed to UPPER(): expected 1, got 0"
+        1, "Syntax error at line 1 near '*'"
         -- </func-5.5>
     })
 
@@ -2483,7 +2483,7 @@ test:do_catchsql_test(
         SELECT coalesce()
     ]], {
         -- <func-27.1>
-        1, "Wrong number of arguments is passed to COALESCE(): expected at least two, got 0"
+        1, "Wrong number of arguments is passed to COALESCE(): expected from 2 to 127, got 0"
         -- </func-27.1>
     })
 
@@ -2493,7 +2493,7 @@ test:do_catchsql_test(
         SELECT coalesce(1)
     ]], {
         -- <func-27.2>
-        1, "Wrong number of arguments is passed to COALESCE(): expected at least two, got 1"
+        1, "Wrong number of arguments is passed to COALESCE(): expected from 2 to 127, got 1"
         -- </func-27.2>
     })
 
diff --git a/test/sql-tap/func2.test.lua b/test/sql-tap/func2.test.lua
index 95b8a1f5f..792f020f1 100755
--- a/test/sql-tap/func2.test.lua
+++ b/test/sql-tap/func2.test.lua
@@ -50,7 +50,7 @@ test:do_catchsql_test(
         SELECT SUBSTR()
     ]], {
         -- <func2-1.2.1>
-        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 0"
         -- </func2-1.2.1>
     })
 
@@ -60,7 +60,7 @@ test:do_catchsql_test(
         SELECT SUBSTR('Supercalifragilisticexpialidocious')
     ]], {
         -- <func2-1.2.2>
-        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 1"
         -- </func2-1.2.2>
     })
 
@@ -70,7 +70,7 @@ test:do_catchsql_test(
         SELECT SUBSTR('Supercalifragilisticexpialidocious', 1,1,1)
     ]], {
         -- <func2-1.2.3>
-        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 4"
         -- </func2-1.2.3>
     })
 
@@ -673,7 +673,7 @@ if ("ሴ" ~= "u1234")
             SELECT SUBSTR()
         ]], {
             -- <func2-2.1.2>
-            1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0"
+            1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 0"
             -- </func2-2.1.2>
         })
 
@@ -683,7 +683,7 @@ if ("ሴ" ~= "u1234")
             SELECT SUBSTR('hiሴho')
         ]], {
             -- <func2-2.1.3>
-            1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1"
+            1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 1"
             -- </func2-2.1.3>
         })
 
@@ -693,7 +693,7 @@ if ("ሴ" ~= "u1234")
             SELECT SUBSTR('hiሴho', 1,1,1)
         ]], {
             -- <func2-2.1.4>
-            1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4"
+            1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 4"
             -- </func2-2.1.4>
         })
 
@@ -1038,7 +1038,7 @@ test:do_catchsql_test(
         SELECT SUBSTR()
     ]], {
         -- <func2-3.1.2>
-        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 0"
         -- </func2-3.1.2>
     })
 
@@ -1048,7 +1048,7 @@ test:do_catchsql_test(
         SELECT SUBSTR(x'1234')
     ]], {
         -- <func2-3.1.3>
-        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 1"
         -- </func2-3.1.3>
     })
 
@@ -1058,7 +1058,7 @@ test:do_catchsql_test(
         SELECT SUBSTR(x'1234', 1,1,1)
     ]], {
         -- <func2-3.1.4>
-        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, got 4"
         -- </func2-3.1.4>
     })
 
diff --git a/test/sql-tap/func5.test.lua b/test/sql-tap/func5.test.lua
index 44755b1f7..71c971fe4 100755
--- a/test/sql-tap/func5.test.lua
+++ b/test/sql-tap/func5.test.lua
@@ -276,19 +276,19 @@ test:do_catchsql_test(
     "func-5-5.1",
     [[
         SELECT LEAST(false);
-    ]], { 1, "Wrong number of arguments is passed to LEAST(): expected at least two, got 1" } )
+    ]], { 1, "Wrong number of arguments is passed to LEAST(): expected from 2 to 127, got 1" } )
 
 test:do_catchsql_test(
     "func-5-5.2",
     [[
         SELECT GREATEST('abc');
-    ]], { 1, "Wrong number of arguments is passed to GREATEST(): expected at least two, got 1" } )
+    ]], { 1, "Wrong number of arguments is passed to GREATEST(): expected from 2 to 127, got 1" } )
 
 test:do_catchsql_test(
     "func-5-5.3",
     [[
         SELECT LEAST();
-    ]], { 1, "Wrong number of arguments is passed to LEAST(): expected at least two, got 0" } )
+    ]], { 1, "Wrong number of arguments is passed to LEAST(): expected from 2 to 127, got 0" } )
 
 -- Make sure that ifnull() returns type of corresponding (i.e. first
 -- non-null) argument.
diff --git a/test/sql-tap/identifier_case.test.lua b/test/sql-tap/identifier_case.test.lua
index 097fa1b64..41668ddc5 100755
--- a/test/sql-tap/identifier_case.test.lua
+++ b/test/sql-tap/identifier_case.test.lua
@@ -200,7 +200,6 @@ for _, row in ipairs(data) do
         row[3])
 end
 
-
 -- Check that collaiton names work as identifiers
 data = {
     { 1,  [[ binary ]], {1, "Collation 'BINARY' does not exist"}},
@@ -208,7 +207,9 @@ data = {
     { 3,  [["binary"]], {0}},
     { 4,  [["bInaRy"]], {1, "Collation 'bInaRy' does not exist"}},
     { 5,  [["unicode"]], {0}},
-    { 6,  [[ unicode ]], {1,"Collation 'UNICODE' does not exist"}},
+    { 6,  [[ unicode ]], {1, [[At line 1 at or near position 51: keyword ]]..
+                             [['unicode' is reserved. Please use double ]]..
+                             [[quotes if 'unicode' is an identifier.]]}},
     { 7,  [["UNICODE"]], {1,"Collation 'UNICODE' does not exist"}},
     { 8,  [[NONE]], {1,"Collation 'NONE' does not exist"}},
     { 9,  [["none"]], {0}},
@@ -243,7 +244,9 @@ data = {
     { 3,  [[ 'a' < 'b' collate 'binary' ]], {1, [[Syntax error at line 1 near ''binary'']]}},
     { 4,  [[ 'a' < 'b' collate "unicode" ]], {0, {true}}},
     { 5,  [[ '5' < 'b' collate "unicode" ]], {0, {true}}},
-    { 6,  [[ '5' < 'b' collate unicode ]], {1,"Collation 'UNICODE' does not exist"}},
+    { 6,  [[ '5' < 'b' collate unicode ]], {1,
+        [[At line 1 at or near position 43: keyword 'unicode' is reserved. ]]..
+        [[Please use double quotes if 'unicode' is an identifier.]]}},
     { 7,  [[ '5' < 'b' collate "unicode_ci" ]], {0, {true}}},
     { 8,  [[ '5' < 'b' collate NONE ]], {1, "Collation 'NONE' does not exist"}},
     { 9,  [[ '5' < 'b' collate "none" ]], {0, {true}}},
diff --git a/test/sql-tap/keyword1.test.lua b/test/sql-tap/keyword1.test.lua
index f9a9c6865..a0461abfd 100755
--- a/test/sql-tap/keyword1.test.lua
+++ b/test/sql-tap/keyword1.test.lua
@@ -1,6 +1,6 @@
 #!/usr/bin/env tarantool
 local test = require("sqltester")
-test:plan(184)
+test:plan(219)
 
 --!./tcltestrunner.lua
 -- 2009 January 29
@@ -138,7 +138,6 @@ local bannedkws = {
 	"asensitive",
 	"binary",
 	"call",
-	"char",
 	"character",
 	"condition",
 	"connect",
@@ -192,7 +191,43 @@ local bannedkws = {
 	"varchar",
 	"varbinary",
 	"whenever",
-	"while"
+	"while",
+	"abs",
+	"avg",
+	"char",
+	"char_length",
+	"character_length",
+	"coalesce",
+	"count",
+	"greatest",
+	"group_concat",
+	"hex",
+	"ifnull",
+	"least",
+	"length",
+	"likelihood",
+	"likely",
+	"lower",
+	"max",
+	"min",
+	"nullif",
+	"position",
+	"printf",
+	"quote",
+	"random",
+	"randomblob",
+	"round",
+	"row_count",
+	"soundex",
+	"substr",
+	"sum",
+	"total",
+	"typeof",
+	"unicode",
+	"unlikely",
+	"upper",
+	"version",
+	"zeroblob"
 }
 local exprkw = [[
     "cast",
diff --git a/test/sql-tap/misc5.test.lua b/test/sql-tap/misc5.test.lua
index 38eddfc64..55c8cef39 100755
--- a/test/sql-tap/misc5.test.lua
+++ b/test/sql-tap/misc5.test.lua
@@ -176,10 +176,10 @@ test:do_execsql_test(
               --     one      10
               --     three    5
               --
-              SELECT DISTINCT artist,sum(timesplayed) AS total
+              SELECT DISTINCT artist,sum(timesplayed) AS "TOTAL"
               FROM songs
               GROUP BY LOWER(artist)
-              ORDER BY total DESC
+              ORDER BY "TOTAL" DESC
               LIMIT 10
             )
             WHERE artist <> ''
diff --git a/test/sql-tap/select1.test.lua b/test/sql-tap/select1.test.lua
index 2bf7fd3a2..568b3ee2d 100755
--- a/test/sql-tap/select1.test.lua
+++ b/test/sql-tap/select1.test.lua
@@ -250,7 +250,7 @@ test:do_catchsql_test(
         SELECT count(f1,f2) FROM test1
     ]], {
         -- <select1-2.1>
-        1, "Wrong number of arguments is passed to COUNT(): expected 0 or 1, got 2"
+        1, "Wrong number of arguments is passed to COUNT(): expected from 0 to 1, got 2"
         -- </select1-2.1>
     })
 
@@ -330,7 +330,7 @@ test:do_catchsql_test(
         SELECT min(*) FROM test1
     ]], {
         -- <select1-2.6>
-        1, "Wrong number of arguments is passed to MIN(): expected 1, got 0"
+        1, "Syntax error at line 1 near '*'"
         -- </select1-2.6>
     })
 
@@ -395,7 +395,7 @@ test:do_catchsql_test(
         SELECT MAX(*) FROM test1
     ]], {
         -- <select1-2.9>
-        1, "Wrong number of arguments is passed to MAX(): expected 1, got 0"
+        1, "Syntax error at line 1 near '*'"
         -- </select1-2.9>
     })
 
@@ -475,7 +475,7 @@ test:do_catchsql_test(
         SELECT SUM(*) FROM test1
     ]], {
         -- <select1-2.14>
-        1, "Wrong number of arguments is passed to SUM(): expected 1, got 0"
+        1, "Syntax error at line 1 near '*'"
         -- </select1-2.14>
     })
 
@@ -697,7 +697,7 @@ test:do_catchsql_test(
         SELECT f1 FROM test1 WHERE count(f1,f2)!=11
     ]], {
         -- <select1-3.9>
-        1, "misuse of aggregate function COUNT()"
+        1, "Wrong number of arguments is passed to COUNT(): expected from 0 to 1, got 2"
         -- </select1-3.9>
     })
 
@@ -1982,8 +1982,8 @@ test:do_test(
         -- This used to seg-fault when the problem existed.
         return test:execsql [[
             SELECT count(
-              (SELECT a FROM abc WHERE a = NULL AND b >= upper.c)
-            ) FROM abc AS upper;
+              (SELECT a FROM abc WHERE a = NULL AND b >= "UPPER".c)
+            ) FROM abc AS "UPPER";
         ]]
     end, {
         -- <select1-13.1>
diff --git a/test/sql-tap/select6.test.lua b/test/sql-tap/select6.test.lua
index 031892112..2142aaf46 100755
--- a/test/sql-tap/select6.test.lua
+++ b/test/sql-tap/select6.test.lua
@@ -152,9 +152,9 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select6-1.9",
     [=[
-        SELECT q, p, r, min
+        SELECT q, p, r, m
         FROM (SELECT count(*) as p , y as q FROM t1 GROUP BY y) AS a,
-             (SELECT max(x) as r, y as s, min(x)+y AS min FROM t1 GROUP BY y) as b
+             (SELECT max(x) as r, y as s, min(x)+y AS m FROM t1 GROUP BY y) as b
         WHERE q=s ORDER BY s
     ]=], {
         -- <select6-1.9>
@@ -245,9 +245,9 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select6-2.7",
     [=[
-        SELECT a.b, a.count, max, count
-        FROM (SELECT count(*) AS count, b FROM t2 GROUP BY b) AS a,
-             (SELECT max(a) AS max, b FROM t2 GROUP BY b) as b
+        SELECT a.b, a.c, m, c
+        FROM (SELECT count(*) AS c, b FROM t2 GROUP BY b) AS a,
+             (SELECT max(a) AS m, b FROM t2 GROUP BY b) as b
         WHERE a.b=b.b ORDER BY a.b
     ]=], {
         -- <select6-2.7>
@@ -429,8 +429,8 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select6-3.14",
     [=[
-        SELECT count, y FROM (SELECT count(*) AS count, y FROM t1 GROUP BY y)
-        ORDER BY count
+        SELECT c, y FROM (SELECT count(*) AS c, y FROM t1 GROUP BY y)
+        ORDER BY c
     ]=], {
         -- <select6-3.14>
         1, 1, 2, 2, 4, 3, 5, 5, 8, 4
@@ -440,7 +440,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select6-3.15",
     [=[
-        SELECT count, y FROM (SELECT count(*) AS count, y FROM t1 GROUP BY y)
+        SELECT c, y FROM (SELECT count(*) AS c, y FROM t1 GROUP BY y)
         ORDER BY y
     ]=], {
         -- <select6-3.15>
diff --git a/test/sql-tap/select8.test.lua b/test/sql-tap/select8.test.lua
index 747dd9bca..336ea1c0b 100755
--- a/test/sql-tap/select8.test.lua
+++ b/test/sql-tap/select8.test.lua
@@ -32,7 +32,7 @@ test:execsql [[
     INSERT INTO songs VALUES(6,'two',11);
 ]]
 local result = test:execsql [[
-    SELECT DISTINCT artist,sum(timesplayed) AS total
+    SELECT DISTINCT artist,sum(timesplayed) AS "TOTAL"
     FROM songs
     GROUP BY LOWER(artist)
 ]]
@@ -48,7 +48,7 @@ end
 test:do_execsql_test(
     "select8-1.1",
     [[
-        SELECT DISTINCT artist,sum(timesplayed) AS total
+        SELECT DISTINCT artist,sum(timesplayed) AS "TOTAL"
         FROM songs
         GROUP BY LOWER(artist)
         LIMIT 1 OFFSET 1
@@ -57,7 +57,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select8-1.2",
     [[
-        SELECT DISTINCT artist,sum(timesplayed) AS total
+        SELECT DISTINCT artist,sum(timesplayed) AS "TOTAL"
         FROM songs
         GROUP BY LOWER(artist)
         LIMIT 2 OFFSET 1
@@ -66,7 +66,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select8-1.3",
     [[
-        SELECT DISTINCT artist,sum(timesplayed) AS total
+        SELECT DISTINCT artist,sum(timesplayed) AS "TOTAL"
         FROM songs
         GROUP BY LOWER(artist)
         LIMIT 1000 OFFSET 2
diff --git a/test/sql-tap/sql-errors.test.lua b/test/sql-tap/sql-errors.test.lua
index daf0ee643..f85a31010 100755
--- a/test/sql-tap/sql-errors.test.lua
+++ b/test/sql-tap/sql-errors.test.lua
@@ -40,7 +40,7 @@ test:do_catchsql_test(
 test:do_catchsql_test(
 	"sql-errors-1.3",
 	[[
-		CREATE TABLE t3 (i INT PRIMARY KEY, a INT DEFAULT(MAX(i, 1)));
+		CREATE TABLE t3 (i INT PRIMARY KEY, a INT DEFAULT(GREATEST(i, 1)));
 	]], {
 		-- <sql-errors-1.3>
 		1,"Failed to create space 'T3': default value of column 'A' is not constant"
@@ -183,7 +183,8 @@ test:do_catchsql_test(
 	select_statement,
 	{
 		-- <sql-errors-1.16>
-		1,"Number of arguments to function CHAR 128 exceeds the limit (127)"
+		1, [[Wrong number of arguments is passed to CHAR(): ]]..
+		   [[expected from 0 to 127, got 128]]
 		-- </sql-errors-1.16>
 	})
 
@@ -198,7 +199,8 @@ test:do_catchsql_test(
 	select_statement,
 	{
 		-- <sql-errors-1.17>
-		1,"Number of arguments to function MAX 128 exceeds the limit (127)"
+		1, [[Wrong number of arguments is passed to MAX(): ]]..
+		   [[expected 1, got 128]]
 		-- </sql-errors-1.17>
 	})
 
diff --git a/test/sql-tap/tkt1449.test.lua b/test/sql-tap/tkt1449.test.lua
index ef69b09ac..085a333e5 100755
--- a/test/sql-tap/tkt1449.test.lua
+++ b/test/sql-tap/tkt1449.test.lua
@@ -40,7 +40,7 @@ test:do_execsql_test(
         CREATE TABLE ATTACHMENTS(TQUNID varchar(36) not null, OBJECTID varchar(50) null, ISSUEID varchar(50) null, DATASTREAM SCALAR not null, CONTENTENCODING varchar(50) null, CONTENTCHARSET varchar(50) null, CONTENTTYPE varchar(100) null, CONTENTID varchar(100) null, CONTENTLOCATION varchar(100) null, CONTENTNAME varchar(100) not null, constraint PK_ATTACHMENTS primary key (TQUNID));
         CREATE TABLE COMPLIANCEPOLICIES(CLASSID int null, SEQNO int not null, LASTMODONNODEID varchar(50) not null, PREVMODONNODEID varchar(50) null, ISSUEID varchar(50) not null, OBJECTID varchar(50) not null, REVISIONNUM int not null, CONTAINERID varchar(50) not null, AUTHORID varchar(50) not null, CREATIONDATE varchar(25) null, LASTMODIFIEDDATE varchar(25) null, UPDATENUMBER int null, PREVREVISIONNUM int null, LASTCMD int null, LASTCMDACLVERSION int null, USERDEFINEDFIELD varchar(300) null, LASTMODIFIEDBYID varchar(50) null, BODY text null, constraint PK_COMPLIANCEPOLICIES primary key (ISSUEID, OBJECTID));
         CREATE TABLE DBHISTORY(id INT primary key, "DATETIME" varchar(25) not null, OPERATION varchar(20) not null, KUBIVERSION varchar(100) not null, FROMVERSION int null, TOVERSION int null);
-        CREATE TABLE DBINFO(id INT primary key, FINGERPRINT varchar(32) not null, VERSION int not null);
+        CREATE TABLE DBINFO(id INT primary key, FINGERPRINT varchar(32) not null, "VERSION" int not null);
         CREATE TABLE DETACHEDATTACHMENTS (TQUNID varchar(36) not null, ISSUEID varchar(50) not null, OBJECTID varchar(50) not null, PATH varchar(300) not null, DETACHEDFILELASTMODTIMESTAMP varchar(25) null, CONTENTID varchar(100) not null, constraint PK_DETACHEDATTACHMENTS primary key (TQUNID));
         CREATE TABLE DOCREFERENCES(CLASSID int null, SEQNO int not null, LASTMODONNODEID varchar(50) not null, PREVMODONNODEID varchar(50) null, ISSUEID varchar(50) not null, OBJECTID varchar(50) not null, REVISIONNUM int not null, CONTAINERID varchar(50) not null, AUTHORID varchar(50) not null, CREATIONDATE varchar(25) null, LASTMODIFIEDDATE varchar(25) null, UPDATENUMBER int null, PREVREVISIONNUM int null, LASTCMD int null, LASTCMDACLVERSION int null, USERDEFINEDFIELD varchar(300) null, LASTMODIFIEDBYID varchar(50) null, REFERENCEDOCUMENTID varchar(50) null, constraint PK_DOCREFERENCES primary key (ISSUEID, OBJECTID));
         CREATE TABLE DQ (TQUNID varchar(36) not null, ISSUEID varchar(50) not null, DEPENDSID varchar(50) null, DEPENDSTYPE int null, DEPENDSCOMMANDSTREAM SCALAR null, DEPENDSNODEIDSEQNOKEY varchar(100) null, DEPENDSACLVERSION int null, constraint PK_DQ primary key (TQUNID));
diff --git a/test/sql-tap/uuid.test.lua b/test/sql-tap/uuid.test.lua
index ec5caab63..fd7c52351 100755
--- a/test/sql-tap/uuid.test.lua
+++ b/test/sql-tap/uuid.test.lua
@@ -1290,7 +1290,7 @@ test:do_catchsql_test(
     [[
         SELECT uuid(4, 5);
     ]], {
-        1, "Wrong number of arguments is passed to UUID(): expected one or zero, got 2"
+        1, "Wrong number of arguments is passed to UUID(): expected from 0 to 1, got 2"
     })
 
 -- Make sure the uuid() function generates a new UUID each time when called.
diff --git a/test/sql-tap/where3.test.lua b/test/sql-tap/where3.test.lua
index 9b2bc2e25..eda106f68 100755
--- a/test/sql-tap/where3.test.lua
+++ b/test/sql-tap/where3.test.lua
@@ -444,19 +444,19 @@ test:do_execsql_test(
     [[
         CREATE TABLE aaa (id INTEGER PRIMARY KEY, type INTEGER,
                           fk TEXT DEFAULT NULL, parent INTEGER,
-                          position INTEGER, title TEXT,
+                          "POSITION" INTEGER, title TEXT,
                           keyword_id INTEGER, folder_type TEXT,
                           dateAdded INTEGER, lastModified INTEGER);
         CREATE INDEX aaa_111 ON aaa (fk, type);
-        CREATE INDEX aaa_222 ON aaa (parent, position);
+        CREATE INDEX aaa_222 ON aaa (parent, "POSITION");
         CREATE INDEX aaa_333 ON aaa (fk, lastModified);
         CREATE TABLE bbb (id INTEGER PRIMARY KEY, type INTEGER,
                           fk TEXT DEFAULT NULL, parent INTEGER,
-                          position INTEGER, title TEXT,
+                          "POSITION" INTEGER, title TEXT,
                           keyword_id INTEGER, folder_type TEXT,
                           dateAdded INTEGER, lastModified INTEGER);
         CREATE INDEX bbb_111 ON bbb (fk, type);
-        CREATE INDEX bbb_222 ON bbb (parent, position);
+        CREATE INDEX bbb_222 ON bbb (parent, "POSITION");
         CREATE INDEX bbb_333 ON bbb (fk, lastModified);
 
          SELECT bbb.title AS tag_title
diff --git a/test/sql/collation.result b/test/sql/collation.result
index 1fcc212d8..a94a047dc 100644
--- a/test/sql/collation.result
+++ b/test/sql/collation.result
@@ -63,7 +63,7 @@ box.execute([[INSERT INTO tu VALUES ('Latin Small Letter Dotless I U+0131','ı')
 - row_count: 1
 ...
 -- Without collation
-box.execute([[SELECT descriptor, upper(letter) AS upper,lower(letter) AS lower FROM tu;]])
+box.execute([[SELECT descriptor, upper(letter) AS "UPPER",lower(letter) AS "LOWER" FROM tu;]])
 ---
 - metadata:
   - name: DESCRIPTOR
@@ -79,7 +79,7 @@ box.execute([[SELECT descriptor, upper(letter) AS upper,lower(letter) AS lower F
   - ['Latin Small Letter I U+0069', 'I', 'i']
 ...
 -- With collation
-box.execute([[SELECT descriptor, upper(letter COLLATE "TURKISH") AS upper,lower(letter COLLATE "TURKISH") AS lower FROM tu;]])
+box.execute([[SELECT descriptor, upper(letter COLLATE "TURKISH") AS "UPPER", lower(letter COLLATE "TURKISH") AS "LOWER" FROM tu;]])
 ---
 - metadata:
   - name: DESCRIPTOR
@@ -298,7 +298,7 @@ box.execute("SELECT * FROM t WHERE a COLLATE \"binary\" = c COLLATE \"unicode\";
 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'
+- 'Wrong number of arguments is passed to SUBSTR(): expected from 2 to 3, 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 407fc19dc..6d0f4f30c 100644
--- a/test/sql/collation.test.lua
+++ b/test/sql/collation.test.lua
@@ -21,9 +21,9 @@ box.execute([[INSERT INTO tu VALUES ('Latin Small Letter I U+0069','i');]])
 box.execute([[INSERT INTO tu VALUES ('Latin Capital Letter I With Dot Above U+0130','İ');]])
 box.execute([[INSERT INTO tu VALUES ('Latin Small Letter Dotless I U+0131','ı');]])
 -- Without collation
-box.execute([[SELECT descriptor, upper(letter) AS upper,lower(letter) AS lower FROM tu;]])
+box.execute([[SELECT descriptor, upper(letter) AS "UPPER",lower(letter) AS "LOWER" FROM tu;]])
 -- With collation
-box.execute([[SELECT descriptor, upper(letter COLLATE "TURKISH") AS upper,lower(letter COLLATE "TURKISH") AS lower FROM tu;]])
+box.execute([[SELECT descriptor, upper(letter COLLATE "TURKISH") AS "UPPER", lower(letter COLLATE "TURKISH") AS "LOWER" FROM tu;]])
 box.internal.collation.drop('TURKISH')
 
 -- For de-DE result is actually the same
diff --git a/test/sql/message-func-indexes.result b/test/sql/message-func-indexes.result
index 002dbd883..def2528ee 100644
--- a/test/sql/message-func-indexes.result
+++ b/test/sql/message-func-indexes.result
@@ -12,7 +12,7 @@ box.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, a INTEGER)")
 ---
 - row_count: 1
 ...
-box.execute("CREATE TABLE t2(object INTEGER PRIMARY KEY, price INTEGER, count INTEGER)")
+box.execute([[CREATE TABLE t2(object INTEGER PRIMARY KEY, price INTEGER, "COUNT" INTEGER)]])
 ---
 - row_count: 1
 ...
@@ -36,12 +36,12 @@ box.execute("CREATE INDEX i4 ON t2(price)")
 ---
 - row_count: 1
 ...
-box.execute("CREATE INDEX i5 ON t2(count + 1)")
+box.execute([[CREATE INDEX i5 ON t2("COUNT" + 1)]])
 ---
 - null
 - Tarantool does not support functional indexes
 ...
-box.execute("CREATE INDEX i6 ON t2(count * price)")
+box.execute([[CREATE INDEX i6 ON t2("COUNT" * price)]])
 ---
 - null
 - Tarantool does not support functional indexes
diff --git a/test/sql/message-func-indexes.test.lua b/test/sql/message-func-indexes.test.lua
index 3cbfe3e61..ca3e54731 100644
--- a/test/sql/message-func-indexes.test.lua
+++ b/test/sql/message-func-indexes.test.lua
@@ -4,7 +4,7 @@ _ = box.space._session_settings:update('sql_default_engine', {{'=', 2, engine}})
 
 -- Creating tables.
 box.execute("CREATE TABLE t1(id INTEGER PRIMARY KEY, a INTEGER)")
-box.execute("CREATE TABLE t2(object INTEGER PRIMARY KEY, price INTEGER, count INTEGER)")
+box.execute([[CREATE TABLE t2(object INTEGER PRIMARY KEY, price INTEGER, "COUNT" INTEGER)]])
 
 -- Expressions that're supposed to create functional indexes
 -- should return certain message.
@@ -12,8 +12,8 @@ box.execute("CREATE INDEX i1 ON t1(a+1)")
 box.execute("CREATE INDEX i2 ON t1(a)")
 box.execute("CREATE INDEX i3 ON t2(price + 100)")
 box.execute("CREATE INDEX i4 ON t2(price)")
-box.execute("CREATE INDEX i5 ON t2(count + 1)")
-box.execute("CREATE INDEX i6 ON t2(count * price)")
+box.execute([[CREATE INDEX i5 ON t2("COUNT" + 1)]])
+box.execute([[CREATE INDEX i6 ON t2("COUNT" * price)]])
 
 -- Cleaning up.
 box.execute("DROP TABLE t1")
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v2 3/5] sql: separate functions in parser
  2021-08-18 14:34 [Tarantool-patches] [PATCH v2 0/5] Prepare for static arguments type check Mergen Imeev via Tarantool-patches
  2021-08-18 14:34 ` [Tarantool-patches] [PATCH v2 1/5] sql: modify arithmetic aggregate functions Mergen Imeev via Tarantool-patches
  2021-08-18 14:34 ` [Tarantool-patches] [PATCH v2 2/5] sql: introduce SQL built-in functions to parser Mergen Imeev via Tarantool-patches
@ 2021-08-18 14:35 ` Mergen Imeev via Tarantool-patches
  2021-08-18 14:35 ` [Tarantool-patches] [PATCH v2 4/5] sql: separate function flags from functions Mergen Imeev via Tarantool-patches
  2021-08-18 14:35 ` [Tarantool-patches] [PATCH v2 5/5] sql: encapsulate SQL built-in functions opcodes Mergen Imeev via Tarantool-patches
  4 siblings, 0 replies; 8+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-08-18 14:35 UTC (permalink / raw)
  To: vdavydov; +Cc: tarantool-patches

This patch separates SQL built-in functions from user-defined functions
when creating a VDBE. This makes it easier to validate user-defined
functions, and we can now modify built-in SQL functions without breaking
user-defined functions.

Part of #6105
---
 extra/addopcodes.sh    |   1 +
 src/box/sql/expr.c     | 113 +++++++++++++++++++++++++++++++++++------
 src/box/sql/func.c     |   2 +-
 src/box/sql/parse.y    |  82 +++++++++++++++---------------
 src/box/sql/resolve.c  |  24 ++++++++-
 src/box/sql/select.c   |   2 +-
 src/box/sql/sqlInt.h   |   9 ++++
 src/box/sql/treeview.c |   1 +
 src/box/sql/vdbemem.c  |   2 +-
 9 files changed, 174 insertions(+), 62 deletions(-)

diff --git a/extra/addopcodes.sh b/extra/addopcodes.sh
index 3f8cfdf02..e07a97ae9 100755
--- a/extra/addopcodes.sh
+++ b/extra/addopcodes.sh
@@ -53,6 +53,7 @@ extras="            \
     LINEFEED        \
     SPACE           \
     ILLEGAL         \
+    BUILT_IN_FUNC   \
 "
 
 IFS=" "
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 8902c648f..6c24dc09a 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -137,6 +137,29 @@ sql_expr_type(struct Expr *pExpr)
 	return pExpr->type;
 }
 
+struct func *
+sql_func_by_signature(const char *name, uint32_t argc)
+{
+	struct func *func = func_by_name(name, strlen(name));
+	if (func == NULL) {
+		diag_set(ClientError, ER_NO_SUCH_FUNCTION, name);
+		return NULL;
+	}
+	if (!func->def->exports.sql) {
+		diag_set(ClientError, ER_SQL_PARSER_GENERIC,
+			 tt_sprintf("function %s() is not available in "
+				    "SQL", name));
+		return NULL;
+	}
+	if (func->def->param_count != (int)argc) {
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, name,
+			 tt_sprintf("%d", func->def->param_count),
+			 argc);
+		return NULL;
+	}
+	return func;
+}
+
 enum field_type *
 field_type_sequence_dup(struct Parse *parse, enum field_type *types,
 			uint32_t len)
@@ -202,7 +225,7 @@ sqlExprSkipCollate(Expr * pExpr)
 		if (ExprHasProperty(pExpr, EP_Unlikely)) {
 			assert(!ExprHasProperty(pExpr, EP_xIsSelect));
 			assert(pExpr->x.pList->nExpr > 0);
-			assert(pExpr->op == TK_FUNCTION);
+			assert(pExpr->op == TK_BUILT_IN_FUNC);
 			pExpr = pExpr->x.pList->a[0].pExpr;
 		} else {
 			assert(pExpr->op == TK_COLLATE);
@@ -325,7 +348,7 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
 			*coll_id = lhs_coll_id;
 			break;
 		}
-		if (op == TK_FUNCTION) {
+		if (op == TK_BUILT_IN_FUNC) {
 			uint32_t arg_count = p->x.pList == NULL ? 0 :
 					     p->x.pList->nExpr;
 			uint32_t flags = sql_func_flags(p->u.zToken);
@@ -1029,7 +1052,8 @@ sql_expr_new_dequoted(struct sql *db, int op, const struct Token *token)
 	e->u.zToken = (char *) &e[1];
 	if (token->z[0] == '"')
 		e->flags |= EP_DblQuoted;
-	if (op != TK_ID && op != TK_COLLATE && op != TK_FUNCTION) {
+	if (op != TK_ID && op != TK_COLLATE && op != TK_FUNCTION &&
+	    op != TK_BUILT_IN_FUNC) {
 		memcpy(e->u.zToken, token->z, token->n);
 		e->u.zToken[token->n] = '\0';
 		sqlDequote(e->u.zToken);
@@ -1193,6 +1217,25 @@ sqlExprFunction(Parse * pParse, ExprList * pList, Token * pToken)
 	return new_expr;
 }
 
+struct Expr *
+sql_expr_new_built_in(struct Parse *parser, struct ExprList *list,
+		      struct Token *token)
+{
+	struct sql *db = parser->db;
+	assert(token != NULL);
+	struct Expr *new_expr = sql_expr_new_dequoted(db, TK_BUILT_IN_FUNC,
+						      token);
+	if (new_expr == NULL) {
+		sql_expr_list_delete(db, list);
+		parser->is_aborted = true;
+		return NULL;
+	}
+	new_expr->x.pList = list;
+	assert(!ExprHasProperty(new_expr, EP_xIsSelect));
+	sqlExprSetHeightAndFlags(parser, new_expr);
+	return new_expr;
+}
+
 /*
  * Assign a variable number to an expression that encodes a
  * wildcard in the original SQL statement.
@@ -2050,6 +2093,7 @@ exprNodeIsConstant(Walker * pWalker, Expr * pExpr)
 		 * and either pWalker->eCode==4 or 5 or the function has the
 		 * SQL_FUNC_CONST flag.
 		 */
+	case TK_BUILT_IN_FUNC:
 	case TK_FUNCTION:
 		if (pWalker->eCode >= 4 || ExprHasProperty(pExpr, EP_ConstFunc)) {
 			return WRC_Continue;
@@ -3917,6 +3961,53 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			break;
 		}
 	case TK_FUNCTION:{
+		struct ExprList *args;
+		uint32_t argc;
+		/* Mask of function arguments that are constant */
+		uint32_t mask = 0;
+
+		assert(!ExprHasProperty(pExpr, EP_xIsSelect));
+		if (ExprHasProperty(pExpr, EP_TokenOnly)) {
+			args = NULL;
+		} else {
+			args = pExpr->x.pList;
+		}
+		argc = args != NULL ? args->nExpr : 0;
+		assert(!ExprHasProperty(pExpr, EP_IntValue));
+		const char *name = pExpr->u.zToken;
+		struct func *func = sql_func_by_signature(name, argc);
+		if (func == NULL) {
+			pParse->is_aborted = true;
+			break;
+		}
+		for (uint32_t i = 0; i < argc; i++) {
+			if (i < 32 && sqlExprIsConstant(args->a[i].pExpr))
+				mask |= MASKBIT32(i);
+		}
+		if (args != NULL) {
+			if (mask != 0) {
+				r1 = pParse->nMem + 1;
+				pParse->nMem += argc;
+			} else {
+				r1 = sqlGetTempRange(pParse, argc);
+			}
+
+			sqlExprCachePush(pParse);
+			sqlExprCodeExprList(pParse, args, r1, 0,
+					    SQL_ECEL_DUP | SQL_ECEL_FACTOR);
+			sqlExprCachePop(pParse);
+		} else {
+			r1 = 0;
+		}
+		sqlVdbeAddOp4(v, OP_FunctionByName, mask, r1, target,
+			      sqlDbStrNDup(pParse->db, name, strlen(name)),
+			      P4_DYNAMIC);
+		sqlVdbeChangeP5(v, argc);
+		if (argc != 0 && mask == 0)
+			sqlReleaseTempRange(pParse, r1, argc);
+		return target;
+	}
+	case TK_BUILT_IN_FUNC: {
 			ExprList *pFarg;	/* List of function arguments */
 			int nFarg;	/* Number of function arguments */
 			u32 constMask = 0;	/* Mask of function arguments that are constant */
@@ -4077,18 +4168,8 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				sqlVdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
 						  (char *)coll, P4_COLLSEQ);
 			}
-			if (func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
-				sqlVdbeAddOp4(v, OP_BuiltinFunction0, constMask,
-					      r1, target, (char *)func,
-					      P4_FUNC);
-			} else {
-				sqlVdbeAddOp4(v, OP_FunctionByName, constMask,
-					      r1, target,
-					      sqlDbStrNDup(pParse->db,
-							   func->def->name,
-							   func->def->name_len),
-					      P4_DYNAMIC);
-			}
+			sqlVdbeAddOp4(v, OP_BuiltinFunction0, constMask, r1,
+				      target, (char *)func, P4_FUNC);
 			sqlVdbeChangeP5(v, (u8) nFarg);
 			if (nFarg && constMask == 0) {
 				sqlReleaseTempRange(pParse, r1, nFarg);
@@ -5028,7 +5109,7 @@ sqlExprCompare(Expr * pA, Expr * pB, int iTab)
 	}
 	if (pA->op != TK_COLUMN_REF && pA->op != TK_AGG_COLUMN &&
 	    pA->u.zToken) {
-		if (pA->op == TK_FUNCTION) {
+		if (pA->op == TK_BUILT_IN_FUNC) {
 			if (sqlStrICmp(pA->u.zToken, pB->u.zToken) != 0)
 				return 2;
 		} else if (strcmp(pA->u.zToken, pB->u.zToken) != 0) {
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 3267d101e..492960443 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1903,7 +1903,7 @@ groupConcatFinalize(sql_context * context)
 int
 sql_is_like_func(struct Expr *expr)
 {
-	if (expr->op != TK_FUNCTION || !expr->x.pList ||
+	if (expr->op != TK_BUILT_IN_FUNC || !expr->x.pList ||
 	    expr->x.pList->nExpr != 2)
 		return 0;
 	assert(!ExprHasProperty(expr, EP_xIsSelect));
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index cb2e627db..2327482ec 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -1125,7 +1125,7 @@ expr(A) ::= CAST(X) LP expr(E) AS typedef(T) RP(Y). {
 }
 
 expr(A) ::= TRIM(X) LP trim_operands(Y) RP(E). {
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
 }
 
@@ -1179,7 +1179,7 @@ expr(A) ::= ABS(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1192,7 +1192,7 @@ expr(A) ::= AVG(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1206,7 +1206,7 @@ expr(A) ::= CHAR(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1221,7 +1221,7 @@ expr(A) ::= CHAR_LEN(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1237,7 +1237,7 @@ expr(A) ::= COALESCE(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1250,14 +1250,14 @@ expr(A) ::= COUNT(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
 }
 
 expr(A) ::= COUNT(X) LP STAR RP(E). {
-  A.pExpr = sqlExprFunction(pParse, NULL, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, NULL, &X);
   spanSet(&A, &X, &E);
 }
 
@@ -1271,7 +1271,7 @@ expr(A) ::= GREATEST(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1285,7 +1285,7 @@ expr(A) ::= GROUP_CONCAT(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1298,7 +1298,7 @@ expr(A) ::= HEX(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1311,7 +1311,7 @@ expr(A) ::= IFNULL(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1327,7 +1327,7 @@ expr(A) ::= LEAST(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1340,7 +1340,7 @@ expr(A) ::= LENGTH(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1353,7 +1353,7 @@ expr(A) ::= LIKELIHOOD(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1366,7 +1366,7 @@ expr(A) ::= LIKELY(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1379,7 +1379,7 @@ expr(A) ::= LOWER(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1392,7 +1392,7 @@ expr(A) ::= MAX(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1405,7 +1405,7 @@ expr(A) ::= MIN(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1418,7 +1418,7 @@ expr(A) ::= NULLIF(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1431,7 +1431,7 @@ expr(A) ::= POSITION(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1445,7 +1445,7 @@ expr(A) ::= PRINTF(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1458,7 +1458,7 @@ expr(A) ::= QUOTE(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1470,7 +1470,7 @@ expr(A) ::= RANDOM(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1483,7 +1483,7 @@ expr(A) ::= RANDOMBLOB(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1496,7 +1496,7 @@ expr(A) ::= REPLACE(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1510,7 +1510,7 @@ expr(A) ::= ROUND(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1522,7 +1522,7 @@ expr(A) ::= ROW_COUNT(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1535,7 +1535,7 @@ expr(A) ::= SOUNDEX(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1549,7 +1549,7 @@ expr(A) ::= SUBSTR(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1562,7 +1562,7 @@ expr(A) ::= SUM(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1575,7 +1575,7 @@ expr(A) ::= TOTAL(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1588,7 +1588,7 @@ expr(A) ::= TYPEOF(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1601,7 +1601,7 @@ expr(A) ::= UNICODE(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1614,7 +1614,7 @@ expr(A) ::= UNLIKELY(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1627,7 +1627,7 @@ expr(A) ::= UPPER(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1640,7 +1640,7 @@ expr(A) ::= UUID(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1652,7 +1652,7 @@ expr(A) ::= VERSION(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1665,7 +1665,7 @@ expr(A) ::= ZEROBLOB(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sqlExprFunction(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1753,7 +1753,7 @@ expr(A) ::= expr(A) likeop(OP) expr(Y).  [LIKE_KW]  {
   OP.n &= 0x7fffffff;
   pList = sql_expr_list_append(pParse->db,NULL, Y.pExpr);
   pList = sql_expr_list_append(pParse->db,pList, A.pExpr);
-  A.pExpr = sqlExprFunction(pParse, pList, &OP);
+  A.pExpr = sql_expr_new_built_in(pParse, pList, &OP);
   exprNot(pParse, bNot, &A);
   A.zEnd = Y.zEnd;
   if( A.pExpr ) A.pExpr->flags |= EP_InfixFunc;
@@ -1765,7 +1765,7 @@ expr(A) ::= expr(A) likeop(OP) expr(Y) ESCAPE expr(E).  [LIKE_KW]  {
   pList = sql_expr_list_append(pParse->db,NULL, Y.pExpr);
   pList = sql_expr_list_append(pParse->db,pList, A.pExpr);
   pList = sql_expr_list_append(pParse->db,pList, E.pExpr);
-  A.pExpr = sqlExprFunction(pParse, pList, &OP);
+  A.pExpr = sql_expr_new_built_in(pParse, pList, &OP);
   exprNot(pParse, bNot, &A);
   A.zEnd = E.zEnd;
   if( A.pExpr ) A.pExpr->flags |= EP_InfixFunc;
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 35faddab5..32ab1ac68 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -589,7 +589,27 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 
 		/* Resolve function names
 		 */
-	case TK_FUNCTION:{
+	case TK_FUNCTION: {
+		struct ExprList *args = pExpr->x.pList;
+		uint32_t argc = args == NULL ? 0 : args->nExpr;
+
+		assert(!ExprHasProperty(pExpr, EP_xIsSelect));
+		const char *name = pExpr->u.zToken;
+		struct func *func = sql_func_by_signature(name, argc);
+		if (func == NULL) {
+			pParse->is_aborted = true;
+			pNC->nErr++;
+			return WRC_Abort;
+		}
+		pExpr->type = func->def->returns;
+		assert(!func->def->is_deterministic ||
+		       (pNC->ncFlags & NC_IdxExpr) == 0);
+		if (func->def->is_deterministic)
+			ExprSetProperty(pExpr, EP_ConstFunc);
+		sqlWalkExprList(pWalker, args);
+		return WRC_Prune;
+	}
+	case TK_BUILT_IN_FUNC: {
 			ExprList *pList = pExpr->x.pList;	/* The argument list */
 			int n = pList ? pList->nExpr : 0;	/* Number of arguments */
 			int nId;	/* Number of characters in function name */
@@ -1453,7 +1473,7 @@ resolveSelectStep(Walker * pWalker, Select * p)
  * Function calls are checked to make sure that the function is
  * defined and that the correct number of arguments are specified.
  * If the function is an aggregate function, then the NC_HasAgg flag is
- * set and the opcode is changed from TK_FUNCTION to TK_AGG_FUNCTION.
+ * set and the opcode is changed from TK_BUILT_IN_FUNC to TK_AGG_FUNCTION.
  * If an expression contains aggregate functions then the EP_Agg
  * property on the expression is set.
  *
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 021e0ebd5..8003703a1 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -758,7 +758,7 @@ setJoinExpr(Expr * p, int iTable)
 		assert(!ExprHasProperty(p, EP_TokenOnly | EP_Reduced));
 		ExprSetVVAProperty(p, EP_NoReduce);
 		p->iRightJoinTable = (i16) iTable;
-		if (p->op == TK_FUNCTION && p->x.pList) {
+		if (p->op == TK_BUILT_IN_FUNC && p->x.pList) {
 			int i;
 			for (i = 0; i < p->x.pList->nExpr; i++) {
 				setJoinExpr(p->x.pList->a[i].pExpr, iTable);
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 540c3a2ff..1fd9d2bbf 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -2702,6 +2702,11 @@ Expr *sqlExprFunction(Parse *, ExprList *, Token *);
 void sqlExprAssignVarNumber(Parse *, Expr *, u32);
 ExprList *sqlExprListAppendVector(Parse *, ExprList *, IdList *, Expr *);
 
+/** Construct a new expression node for a built-in function. */
+struct Expr *
+sql_expr_new_built_in(struct Parse *parser, struct ExprList *list,
+		      struct Token *token);
+
 /**
  * Set the sort order for the last element on the given ExprList.
  *
@@ -4391,6 +4396,10 @@ sql_func_flag_is_set(struct func *func, uint16_t flag)
 struct func *
 sql_func_find(struct Expr *expr);
 
+/** Return user-defined function with given name and number of arguments. */
+struct func *
+sql_func_by_signature(const char *name, uint32_t argc);
+
 /**
  * Return the parameters of the function with the given name. If the function
  * with the given name does not exist, or the function is not a built-in SQL
diff --git a/src/box/sql/treeview.c b/src/box/sql/treeview.c
index 5f042ce8b..4cf43073c 100644
--- a/src/box/sql/treeview.c
+++ b/src/box/sql/treeview.c
@@ -481,6 +481,7 @@ sqlTreeViewExpr(TreeView * pView, const Expr * pExpr, u8 moreToFollow)
 		}
 
 	case TK_AGG_FUNCTION:
+	case TK_BUILT_IN_FUNC:
 	case TK_FUNCTION:{
 			ExprList *pFarg;	/* List of function arguments */
 			if (ExprHasProperty(pExpr, EP_TokenOnly)) {
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 499089c8d..d2de8dc3d 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -332,7 +332,7 @@ valueFromExpr(sql * db,	/* The database connection */
 	}
 #endif
 
-	else if (op == TK_FUNCTION && pCtx != 0) {
+	else if (op == TK_BUILT_IN_FUNC && pCtx != 0) {
 		rc = valueFromFunction(db, pExpr, type, &pVal, pCtx);
 	}
 
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v2 4/5] sql: separate function flags from functions
  2021-08-18 14:34 [Tarantool-patches] [PATCH v2 0/5] Prepare for static arguments type check Mergen Imeev via Tarantool-patches
                   ` (2 preceding siblings ...)
  2021-08-18 14:35 ` [Tarantool-patches] [PATCH v2 3/5] sql: separate functions in parser Mergen Imeev via Tarantool-patches
@ 2021-08-18 14:35 ` Mergen Imeev via Tarantool-patches
  2021-08-18 14:35 ` [Tarantool-patches] [PATCH v2 5/5] sql: encapsulate SQL built-in functions opcodes Mergen Imeev via Tarantool-patches
  4 siblings, 0 replies; 8+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-08-18 14:35 UTC (permalink / raw)
  To: vdavydov; +Cc: tarantool-patches

This patch separates function flags from function definition. This
allows us not to look for a function where we need its flags, but not
the function itself. It also allows us to search for a function after
the function arguments have been properly initialized, which will help
us to check the types of those arguments.

Part of #6105
---
 src/box/sql/expr.c    |  49 +++++++--------
 src/box/sql/func.c    | 137 +++++++++++++++++++++++++++++++++++++-----
 src/box/sql/parse.y   |  82 ++++++++++++-------------
 src/box/sql/resolve.c |  33 ++++------
 src/box/sql/select.c  |  12 ++--
 src/box/sql/sqlInt.h  |  20 +++---
 6 files changed, 214 insertions(+), 119 deletions(-)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 6c24dc09a..0544539d2 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -351,7 +351,7 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
 		if (op == TK_BUILT_IN_FUNC) {
 			uint32_t arg_count = p->x.pList == NULL ? 0 :
 					     p->x.pList->nExpr;
-			uint32_t flags = sql_func_flags(p->u.zToken);
+			uint32_t flags = sql_func_flags(p->func_id);
 			if (((flags & SQL_FUNC_DERIVEDCOLL) != 0) &&
 			    arg_count > 0) {
 				/*
@@ -1219,7 +1219,7 @@ sqlExprFunction(Parse * pParse, ExprList * pList, Token * pToken)
 
 struct Expr *
 sql_expr_new_built_in(struct Parse *parser, struct ExprList *list,
-		      struct Token *token)
+		      struct Token *token, uint8_t id)
 {
 	struct sql *db = parser->db;
 	assert(token != NULL);
@@ -1231,6 +1231,7 @@ sql_expr_new_built_in(struct Parse *parser, struct ExprList *list,
 		return NULL;
 	}
 	new_expr->x.pList = list;
+	new_expr->func_id = id;
 	assert(!ExprHasProperty(new_expr, EP_xIsSelect));
 	sqlExprSetHeightAndFlags(parser, new_expr);
 	return new_expr;
@@ -1513,6 +1514,8 @@ sql_expr_dup(struct sql *db, struct Expr *p, int flags, char **buffer)
 			nToken = sqlStrlen30(p->u.zToken) + 1;
 		else
 			nToken = 0;
+		if (p->op == TK_BUILT_IN_FUNC)
+			pNew->func_id = p->func_id;
 		if (flags) {
 			assert(ExprHasProperty(p, EP_Reduced) == 0);
 			memcpy(zAlloc, p, nNewSize);
@@ -1549,7 +1552,6 @@ sql_expr_dup(struct sql *db, struct Expr *p, int flags, char **buffer)
 					sql_expr_list_dup(db, p->x.pList, flags);
 			}
 		}
-
 		/* Fill in pNew->pLeft and pNew->pRight. */
 		if (ExprHasProperty(pNew, EP_Reduced | EP_TokenOnly)) {
 			zAlloc += dupedExprNodeSize(p, flags);
@@ -4022,21 +4024,17 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			}
 			nFarg = pFarg ? pFarg->nExpr : 0;
 			assert(!ExprHasProperty(pExpr, EP_IntValue));
-			struct func *func = sql_func_find(pExpr);
-			if (func == NULL) {
-				pParse->is_aborted = true;
-				break;
-			}
+			uint32_t flags = sql_func_flags(pExpr->func_id);
 			/* Attempt a direct implementation of the built-in COALESCE() and
 			 * IFNULL() functions.  This avoids unnecessary evaluation of
 			 * arguments past the first non-NULL argument.
 			 */
-			if (sql_func_flag_is_set(func, SQL_FUNC_COALESCE)) {
+			if ((flags & SQL_FUNC_COALESCE) != 0) {
 				int endCoalesce = sqlVdbeMakeLabel(v);
 				if (nFarg < 2) {
 					diag_set(ClientError,
 						 ER_FUNC_WRONG_ARG_COUNT,
-						 func->def->name,
+						 pExpr->u.zToken,
 						 "at least two", nFarg);
 					pParse->is_aborted = true;
 					break;
@@ -4062,11 +4060,11 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			/* The UNLIKELY() function is a no-op.  The result is the value
 			 * of the first argument.
 			 */
-			if (sql_func_flag_is_set(func, SQL_FUNC_UNLIKELY)) {
+			if ((flags & SQL_FUNC_UNLIKELY) != 0) {
 				if (nFarg < 1) {
 					diag_set(ClientError,
 						 ER_FUNC_WRONG_ARG_COUNT,
-						 func->def->name,
+						 pExpr->u.zToken,
 						 "at least one", nFarg);
 					pParse->is_aborted = true;
 					break;
@@ -4092,8 +4090,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			 * is done using ANSI rules from
 			 * collations_check_compatibility().
 			 */
-			if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL) &&
-			    nFarg > 0) {
+			if ((flags & SQL_FUNC_NEEDCOLL) != 0 && nFarg > 0) {
 				struct coll *unused = NULL;
 				uint32_t curr_id = COLL_NONE;
 				bool is_curr_forced = false;
@@ -4140,8 +4137,8 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				 * or OPFLAG_TYPEOFARG respectively, to avoid unnecessary data
 				 * loading.
 				 */
-				if (sql_func_flag_is_set(func, SQL_FUNC_LENGTH |
-							       SQL_FUNC_TYPEOF)) {
+				if ((flags & (SQL_FUNC_LENGTH |
+					      SQL_FUNC_TYPEOF)) != 0) {
 					u8 exprOp;
 					assert(nFarg == 1);
 					assert(pFarg->a[0].pExpr != 0);
@@ -4164,10 +4161,15 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			} else {
 				r1 = 0;
 			}
-			if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) {
+			if ((flags & SQL_FUNC_NEEDCOLL) != 0) {
 				sqlVdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
 						  (char *)coll, P4_COLLSEQ);
 			}
+			struct func *func = sql_func_find(pExpr);
+			if (func == NULL) {
+				pParse->is_aborted = true;
+				break;
+			}
 			sqlVdbeAddOp4(v, OP_BuiltinFunction0, constMask, r1,
 				      target, (char *)func, P4_FUNC);
 			sqlVdbeChangeP5(v, (u8) nFarg);
@@ -5470,15 +5472,10 @@ analyzeAggregate(Walker * pWalker, Expr * pExpr)
 						pItem->iMem = ++pParse->nMem;
 						assert(!ExprHasProperty
 						       (pExpr, EP_IntValue));
-						pItem->func =
-							sql_func_find(pExpr);
-						assert(pItem->func != NULL);
-						assert(pItem->func->def->
-						       language ==
-						       FUNC_LANGUAGE_SQL_BUILTIN &&
-						       pItem->func->def->
-						       aggregate ==
-						       FUNC_AGGREGATE_GROUP);
+						pItem->flags =
+							sql_func_flags(pExpr->func_id);
+						assert((pItem->flags &
+							SQL_FUNC_AGG) != 0);
 						if (pExpr->flags & EP_Distinct) {
 							pItem->iDistinct =
 								pParse->nTab++;
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 492960443..29235ed62 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1907,8 +1907,7 @@ sql_is_like_func(struct Expr *expr)
 	    expr->x.pList->nExpr != 2)
 		return 0;
 	assert(!ExprHasProperty(expr, EP_xIsSelect));
-	struct func *func = sql_func_find(expr);
-	if (func == NULL || !sql_func_flag_is_set(func, SQL_FUNC_LIKE))
+	if ((sql_func_flags(expr->func_id) & SQL_FUNC_LIKE) == 0)
 		return 0;
 	return 1;
 }
@@ -2677,19 +2676,6 @@ sql_func_find(struct Expr *expr)
 	return func;
 }
 
-uint32_t
-sql_func_flags(const char *name)
-{
-	struct func *func = built_in_func_get(name);
-	if (func == NULL)
-		return 0;
-	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
-	uint32_t flags = ((struct func_sql_builtin *)func)->flags;
-	if (func->def->aggregate == FUNC_AGGREGATE_GROUP)
-		flags |= SQL_FUNC_AGG;
-	return flags;
-}
-
 static struct func_vtab func_sql_builtin_vtab;
 
 void
@@ -2770,3 +2756,124 @@ static struct func_vtab func_sql_builtin_vtab = {
 	.call = func_sql_builtin_call_stub,
 	.destroy = func_sql_builtin_destroy,
 };
+
+uint32_t
+sql_func_flags(uint8_t id)
+{
+	switch(id) {
+	case TK_ABS:
+	case TK_CHAR:
+	case TK_CHAR_LEN:
+	case TK_HEX:
+	case TK_PRINTF:
+	case TK_QUOTE:
+	case TK_ROUND:
+	case TK_ROW_COUNT:
+	case TK_SOUNDEX:
+	case TK_UNICODE:
+	case TK_VERSION:
+	case TK_ZEROBLOB:
+		return SQL_FUNC_DETERM;
+	case TK_AVG:
+	case TK_COUNT:
+	case TK_GROUP_CONCAT:
+	case TK_SUM:
+	case TK_TOTAL:
+		return SQL_FUNC_AGG;
+	case TK_COALESCE:
+	case TK_IFNULL:
+		return SQL_FUNC_COALESCE | SQL_FUNC_DETERM;
+	case TK_GREATEST:
+		return SQL_FUNC_MAX | SQL_FUNC_NEEDCOLL | SQL_FUNC_DETERM;
+	case TK_LEAST:
+		return SQL_FUNC_MIN | SQL_FUNC_NEEDCOLL | SQL_FUNC_DETERM;
+	case TK_LENGTH:
+		return SQL_FUNC_LENGTH | SQL_FUNC_DETERM;
+	case TK_LIKE_KW:
+		return SQL_FUNC_LIKE | SQL_FUNC_NEEDCOLL | SQL_FUNC_DETERM;
+	case TK_LIKELIHOOD:
+	case TK_LIKELY:
+	case TK_UNLIKELY:
+		return SQL_FUNC_UNLIKELY | SQL_FUNC_DETERM;
+	case TK_LOWER:
+	case TK_UPPER:
+		return SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL |
+		       SQL_FUNC_DETERM;
+	case TK_MAX:
+		return SQL_FUNC_MAX | SQL_FUNC_AGG | SQL_FUNC_NEEDCOLL;
+	case TK_MIN:
+		return SQL_FUNC_MIN | SQL_FUNC_AGG | SQL_FUNC_NEEDCOLL;
+	case TK_NULLIF:
+	case TK_POSITION:
+		return SQL_FUNC_NEEDCOLL | SQL_FUNC_DETERM;
+	case TK_RANDOM:
+	case TK_RANDOMBLOB:
+	case TK_UUID:
+		return 0;
+	case TK_REPLACE:
+	case TK_SUBSTR:
+	case TK_TRIM:
+		return SQL_FUNC_DERIVEDCOLL | SQL_FUNC_DETERM;
+	case TK_TYPEOF:
+		return SQL_FUNC_TYPEOF | SQL_FUNC_DETERM;
+	default:
+		unreachable();
+	}
+	return 0;
+}
+
+enum field_type
+sql_func_result(struct Expr *expr)
+{
+	switch(expr->func_id) {
+	case TK_ABS:
+	case TK_AVG:
+	case TK_SUM:
+	case TK_TOTAL:
+		return FIELD_TYPE_NUMBER;
+	case TK_CHAR:
+	case TK_GROUP_CONCAT:
+	case TK_HEX:
+	case TK_LOWER:
+	case TK_PRINTF:
+	case TK_QUOTE:
+	case TK_REPLACE:
+	case TK_SOUNDEX:
+	case TK_SUBSTR:
+	case TK_TRIM:
+	case TK_TYPEOF:
+	case TK_UNICODE:
+	case TK_UPPER:
+	case TK_VERSION:
+		return FIELD_TYPE_STRING;
+	case TK_CHAR_LEN:
+	case TK_COUNT:
+	case TK_LENGTH:
+	case TK_LIKE_KW:
+	case TK_POSITION:
+	case TK_RANDOM:
+	case TK_ROUND:
+	case TK_ROW_COUNT:
+		return FIELD_TYPE_INTEGER;
+	case TK_COALESCE:
+	case TK_GREATEST:
+	case TK_IFNULL:
+	case TK_LEAST:
+	case TK_MAX:
+	case TK_MIN:
+	case TK_NULLIF:
+		return FIELD_TYPE_SCALAR;
+	case TK_LIKELIHOOD:
+	case TK_LIKELY:
+	case TK_UNLIKELY:
+		return FIELD_TYPE_BOOLEAN;
+	case TK_RANDOMBLOB:
+	case TK_ZEROBLOB:
+		return FIELD_TYPE_VARBINARY;
+	case TK_UUID:
+		return FIELD_TYPE_UUID;
+	default:
+		unreachable();
+	}
+	return field_type_MAX;
+}
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 2327482ec..4a4ca96c3 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -1125,7 +1125,7 @@ expr(A) ::= CAST(X) LP expr(E) AS typedef(T) RP(Y). {
 }
 
 expr(A) ::= TRIM(X) LP trim_operands(Y) RP(E). {
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_TRIM);
   spanSet(&A, &X, &E);
 }
 
@@ -1179,7 +1179,7 @@ expr(A) ::= ABS(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_ABS);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1192,7 +1192,7 @@ expr(A) ::= AVG(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_AVG);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1206,7 +1206,7 @@ expr(A) ::= CHAR(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_CHAR);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1221,7 +1221,7 @@ expr(A) ::= CHAR_LEN(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_CHAR_LEN);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1237,7 +1237,7 @@ expr(A) ::= COALESCE(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_COALESCE);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1250,14 +1250,14 @@ expr(A) ::= COUNT(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_COUNT);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
 }
 
 expr(A) ::= COUNT(X) LP STAR RP(E). {
-  A.pExpr = sql_expr_new_built_in(pParse, NULL, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, NULL, &X, TK_COUNT);
   spanSet(&A, &X, &E);
 }
 
@@ -1271,7 +1271,7 @@ expr(A) ::= GREATEST(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_GREATEST);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1285,7 +1285,7 @@ expr(A) ::= GROUP_CONCAT(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_GROUP_CONCAT);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1298,7 +1298,7 @@ expr(A) ::= HEX(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_HEX);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1311,7 +1311,7 @@ expr(A) ::= IFNULL(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_IFNULL);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1327,7 +1327,7 @@ expr(A) ::= LEAST(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_LEAST);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1340,7 +1340,7 @@ expr(A) ::= LENGTH(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_LENGTH);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1353,7 +1353,7 @@ expr(A) ::= LIKELIHOOD(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_LIKELIHOOD);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1366,7 +1366,7 @@ expr(A) ::= LIKELY(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_LIKELY);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1379,7 +1379,7 @@ expr(A) ::= LOWER(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_LOWER);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1392,7 +1392,7 @@ expr(A) ::= MAX(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_MAX);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1405,7 +1405,7 @@ expr(A) ::= MIN(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_MIN);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1418,7 +1418,7 @@ expr(A) ::= NULLIF(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_NULLIF);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1431,7 +1431,7 @@ expr(A) ::= POSITION(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_POSITION);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1445,7 +1445,7 @@ expr(A) ::= PRINTF(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_PRINTF);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1458,7 +1458,7 @@ expr(A) ::= QUOTE(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_QUOTE);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1470,7 +1470,7 @@ expr(A) ::= RANDOM(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_RANDOM);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1483,7 +1483,7 @@ expr(A) ::= RANDOMBLOB(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_RANDOMBLOB);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1496,7 +1496,7 @@ expr(A) ::= REPLACE(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_REPLACE);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1510,7 +1510,7 @@ expr(A) ::= ROUND(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_ROUND);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1522,7 +1522,7 @@ expr(A) ::= ROW_COUNT(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_ROW_COUNT);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1535,7 +1535,7 @@ expr(A) ::= SOUNDEX(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_SOUNDEX);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1549,7 +1549,7 @@ expr(A) ::= SUBSTR(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_SUBSTR);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1562,7 +1562,7 @@ expr(A) ::= SUM(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_SUM);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1575,7 +1575,7 @@ expr(A) ::= TOTAL(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_TOTAL);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1588,7 +1588,7 @@ expr(A) ::= TYPEOF(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_TYPEOF);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1601,7 +1601,7 @@ expr(A) ::= UNICODE(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_UNICODE);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1614,7 +1614,7 @@ expr(A) ::= UNLIKELY(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_UNLIKELY);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1627,7 +1627,7 @@ expr(A) ::= UPPER(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_UPPER);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1640,7 +1640,7 @@ expr(A) ::= UUID(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_UUID);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1652,7 +1652,7 @@ expr(A) ::= VERSION(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_VERSION);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1665,7 +1665,7 @@ expr(A) ::= ZEROBLOB(X) LP distinct(D) exprlist(Y) RP(E). {
     pParse->is_aborted = true;
     return;
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X);
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_ZEROBLOB);
   spanSet(&A, &X, &E);
   if(D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
@@ -1753,7 +1753,7 @@ expr(A) ::= expr(A) likeop(OP) expr(Y).  [LIKE_KW]  {
   OP.n &= 0x7fffffff;
   pList = sql_expr_list_append(pParse->db,NULL, Y.pExpr);
   pList = sql_expr_list_append(pParse->db,pList, A.pExpr);
-  A.pExpr = sql_expr_new_built_in(pParse, pList, &OP);
+  A.pExpr = sql_expr_new_built_in(pParse, pList, &OP, TK_LIKE_KW);
   exprNot(pParse, bNot, &A);
   A.zEnd = Y.zEnd;
   if( A.pExpr ) A.pExpr->flags |= EP_InfixFunc;
@@ -1765,7 +1765,7 @@ expr(A) ::= expr(A) likeop(OP) expr(Y) ESCAPE expr(E).  [LIKE_KW]  {
   pList = sql_expr_list_append(pParse->db,NULL, Y.pExpr);
   pList = sql_expr_list_append(pParse->db,pList, A.pExpr);
   pList = sql_expr_list_append(pParse->db,pList, E.pExpr);
-  A.pExpr = sql_expr_new_built_in(pParse, pList, &OP);
+  A.pExpr = sql_expr_new_built_in(pParse, pList, &OP, TK_LIKE_KW);
   exprNot(pParse, bNot, &A);
   A.zEnd = E.zEnd;
   if( A.pExpr ) A.pExpr->flags |= EP_InfixFunc;
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 32ab1ac68..833ccbc83 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -618,19 +618,9 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 			assert(!ExprHasProperty(pExpr, EP_xIsSelect));
 			zId = pExpr->u.zToken;
 			nId = sqlStrlen30(zId);
-			struct func *func = sql_func_find(pExpr);
-			if (func == NULL) {
-				pParse->is_aborted = true;
-				pNC->nErr++;
-				return WRC_Abort;
-			}
-			bool is_agg = func->def->aggregate ==
-				      FUNC_AGGREGATE_GROUP;
-			assert(!is_agg || func->def->language ==
-					  FUNC_LANGUAGE_SQL_BUILTIN);
-			pExpr->type = func->def->returns;
-			if (sql_func_flag_is_set(func, SQL_FUNC_UNLIKELY) &&
-			    n == 2) {
+			uint32_t flags = sql_func_flags(pExpr->func_id);
+			bool is_agg = (flags & SQL_FUNC_AGG) != 0;
+			if ((flags & SQL_FUNC_UNLIKELY) != 0 && n == 2) {
 				ExprSetProperty(pExpr, EP_Unlikely | EP_Skip);
 				pExpr->iTable =
 					exprProbability(pList->a[1].pExpr);
@@ -643,19 +633,18 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 					pNC->nErr++;
 					return WRC_Abort;
 				}
-			} else if (sql_func_flag_is_set(func,
-							SQL_FUNC_UNLIKELY)) {
+			} else if ((flags & SQL_FUNC_UNLIKELY) != 0) {
 				ExprSetProperty(pExpr, EP_Unlikely | EP_Skip);
 				/*
 				 * unlikely() probability is
 				 * 0.0625, likely() is 0.9375
 				 */
-				pExpr->iTable = func->def->name[0] == 'u' ?
+				pExpr->iTable = zId[0] == 'u' ?
 						8388608 : 125829120;
 			}
-			assert(!func->def->is_deterministic ||
+			assert(((flags & SQL_FUNC_DETERM) == 0) ||
 			       (pNC->ncFlags & NC_IdxExpr) == 0);
-			if (func->def->is_deterministic)
+			if ((flags & SQL_FUNC_DETERM) != 0)
 				ExprSetProperty(pExpr, EP_ConstFunc);
 			if (is_agg && (pNC->ncFlags & NC_AllowAgg) == 0) {
 				const char *err =
@@ -681,16 +670,16 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 					pExpr->op2++;
 					pNC2 = pNC2->pNext;
 				}
-				assert(func != NULL);
 				if (pNC2) {
 					pNC2->ncFlags |= NC_HasAgg;
-					if (sql_func_flag_is_set(func,
-							         SQL_FUNC_MIN |
-								 SQL_FUNC_MAX))
+					if ((flags & (SQL_FUNC_MIN |
+						      SQL_FUNC_MAX)) != 0)
 						pNC2->ncFlags |= NC_MinMaxAgg;
 				}
 				pNC->ncFlags |= NC_AllowAgg;
 			}
+			pExpr->type = sql_func_result(pExpr);
+			assert(pExpr->type != field_type_MAX);
 			return WRC_Prune;
 		}
 	case TK_SELECT:
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 8003703a1..2f8a7a031 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4647,9 +4647,7 @@ is_simple_count(struct Select *select, struct AggInfo *agg_info)
 		return NULL;
 	if (NEVER(agg_info->nFunc == 0))
 		return NULL;
-	assert(agg_info->aFunc->func->def->language ==
-	       FUNC_LANGUAGE_SQL_BUILTIN);
-	if (sql_func_flag_is_set(agg_info->aFunc->func, SQL_FUNC_COUNT) ||
+	if ((agg_info->aFunc->flags & SQL_FUNC_COUNT) != 0 ||
 	    (agg_info->aFunc->pExpr->x.pList != NULL &&
 	     agg_info->aFunc->pExpr->x.pList->nExpr > 0))
 		return NULL;
@@ -5576,7 +5574,8 @@ finalizeAggFunctions(Parse * pParse, AggInfo * pAggInfo)
 		assert(!ExprHasProperty(pF->pExpr, EP_xIsSelect));
 		sqlVdbeAddOp2(v, OP_AggFinal, pF->iMem,
 				  pList ? pList->nExpr : 0);
-		sqlVdbeAppendP4(v, pF->func, P4_FUNC);
+		struct func *func = sql_func_find(pF->pExpr);
+		sqlVdbeAppendP4(v, func, P4_FUNC);
 	}
 }
 
@@ -5617,7 +5616,7 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 			vdbe_insert_distinct(pParse, pF->iDistinct, pF->reg_eph,
 					     addrNext, 1, regAgg);
 		}
-		if (sql_func_flag_is_set(pF->func, SQL_FUNC_NEEDCOLL)) {
+		if ((pF->flags & SQL_FUNC_NEEDCOLL) != 0) {
 			struct coll *coll = NULL;
 			struct ExprList_item *pItem;
 			int j;
@@ -5636,7 +5635,8 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 					  (char *)coll, P4_COLLSEQ);
 		}
 		sqlVdbeAddOp3(v, OP_AggStep0, 0, regAgg, pF->iMem);
-		sqlVdbeAppendP4(v, pF->func, P4_FUNC);
+		struct func *func = sql_func_find(pF->pExpr);
+		sqlVdbeAppendP4(v, func, P4_FUNC);
 		sqlVdbeChangeP5(v, (u8) nArg);
 		sql_expr_type_cache_change(pParse, regAgg, nArg);
 		sqlReleaseTempRange(pParse, regAgg, nArg);
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 1fd9d2bbf..01701e271 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -1188,6 +1188,8 @@ struct type_def {
  */
 /** Function is one of aggregate functions. */
 #define SQL_FUNC_AGG      0x0001
+/** Function is deterministic. */
+#define SQL_FUNC_DETERM   0x0002
 #define SQL_FUNC_LIKE     0x0004	/* Candidate for the LIKE optimization */
 #define SQL_FUNC_NEEDCOLL 0x0020	/* sqlGetFuncCollSeq() might be called.
 					 * The flag is set when the collation
@@ -1398,8 +1400,7 @@ struct AggInfo {
 				 */
 	struct AggInfo_func {	/* For each aggregate function */
 		Expr *pExpr;	/* Expression encoding the function */
-		/** The aggregate function implementation. */
-		struct func *func;
+		uint32_t flags;
 		int iMem;	/* Memory location that acts as accumulator */
 		int iDistinct;	/* Ephemeral table used to enforce DISTINCT */
 		/**
@@ -1494,6 +1495,9 @@ struct Expr {
 	 * access them will result in a segfault or malfunction.
 	 ********************************************************************/
 
+	/** SQL Built-in function id. */
+	uint8_t func_id;
+
 	Expr *pLeft;		/* Left subnode */
 	Expr *pRight;		/* Right subnode */
 	union {
@@ -2705,7 +2709,7 @@ ExprList *sqlExprListAppendVector(Parse *, ExprList *, IdList *, Expr *);
 /** Construct a new expression node for a built-in function. */
 struct Expr *
 sql_expr_new_built_in(struct Parse *parser, struct ExprList *list,
-		      struct Token *token);
+		      struct Token *token, uint8_t id);
 
 /**
  * Set the sort order for the last element on the given ExprList.
@@ -4400,13 +4404,11 @@ sql_func_find(struct Expr *expr);
 struct func *
 sql_func_by_signature(const char *name, uint32_t argc);
 
-/**
- * Return the parameters of the function with the given name. If the function
- * with the given name does not exist, or the function is not a built-in SQL
- * function, 0 is returned, which means no parameters have been set.
- */
 uint32_t
-sql_func_flags(const char *name);
+sql_func_flags(uint8_t id);
+
+enum field_type
+sql_func_result(struct Expr *expr);
 
 /**
  * Generate VDBE code to halt execution with correct error if
-- 
2.25.1


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

* [Tarantool-patches] [PATCH v2 5/5] sql: encapsulate SQL built-in functions opcodes
  2021-08-18 14:34 [Tarantool-patches] [PATCH v2 0/5] Prepare for static arguments type check Mergen Imeev via Tarantool-patches
                   ` (3 preceding siblings ...)
  2021-08-18 14:35 ` [Tarantool-patches] [PATCH v2 4/5] sql: separate function flags from functions Mergen Imeev via Tarantool-patches
@ 2021-08-18 14:35 ` Mergen Imeev via Tarantool-patches
  4 siblings, 0 replies; 8+ messages in thread
From: Mergen Imeev via Tarantool-patches @ 2021-08-18 14:35 UTC (permalink / raw)
  To: vdavydov; +Cc: tarantool-patches

This patch encapsulates opcodes for SQL built-in functions, which allows
us to modify the structure of SQL built-in functions and add an
ApplyType opcode if necessary.

Part of #6105
---
 src/box/sql/expr.c   |  8 +++-----
 src/box/sql/func.c   | 23 +++++++++++++++++++++++
 src/box/sql/select.c | 12 ++++--------
 src/box/sql/sqlInt.h |  8 ++++++++
 4 files changed, 38 insertions(+), 13 deletions(-)

diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 0544539d2..bf3ee359b 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -4165,14 +4165,12 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				sqlVdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
 						  (char *)coll, P4_COLLSEQ);
 			}
-			struct func *func = sql_func_find(pExpr);
-			if (func == NULL) {
+			if (sql_emit_func_call(v, pExpr, OP_BuiltinFunction0,
+					       constMask, r1, target,
+					       nFarg) != 0) {
 				pParse->is_aborted = true;
 				break;
 			}
-			sqlVdbeAddOp4(v, OP_BuiltinFunction0, constMask, r1,
-				      target, (char *)func, P4_FUNC);
-			sqlVdbeChangeP5(v, (u8) nFarg);
 			if (nFarg && constMask == 0) {
 				sqlReleaseTempRange(pParse, r1, nFarg);
 			}
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 29235ed62..5b42fa767 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -2877,3 +2877,26 @@ sql_func_result(struct Expr *expr)
 	}
 	return field_type_MAX;
 }
+
+int
+sql_emit_func_call(struct Vdbe *vdbe, struct Expr *expr, int op, int mask,
+		   int r1, int r2, uint8_t argc)
+{
+	struct func *func = sql_func_find(expr);
+	if (func == NULL)
+		return -1;
+	sqlVdbeAddOp4(vdbe, op, mask, r1, r2, (char *)func, P4_FUNC);
+	sqlVdbeChangeP5(vdbe, argc);
+	return 0;
+}
+
+int
+sql_emit_func_finalize(struct Vdbe *vdbe, struct Expr *expr, int reg,
+		       uint8_t argc)
+{
+	struct func *func = sql_func_find(expr);
+	if (func == NULL)
+		return -1;
+	sqlVdbeAddOp4(vdbe, OP_AggFinal, reg, argc, 0, (char *)func, P4_FUNC);
+	return 0;
+}
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 2f8a7a031..87f2012f1 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -5572,10 +5572,8 @@ finalizeAggFunctions(Parse * pParse, AggInfo * pAggInfo)
 	for (i = 0, pF = pAggInfo->aFunc; i < pAggInfo->nFunc; i++, pF++) {
 		ExprList *pList = pF->pExpr->x.pList;
 		assert(!ExprHasProperty(pF->pExpr, EP_xIsSelect));
-		sqlVdbeAddOp2(v, OP_AggFinal, pF->iMem,
-				  pList ? pList->nExpr : 0);
-		struct func *func = sql_func_find(pF->pExpr);
-		sqlVdbeAppendP4(v, func, P4_FUNC);
+		sql_emit_func_finalize(v, pF->pExpr, pF->iMem,
+				       pList ? pList->nExpr : 0);
 	}
 }
 
@@ -5634,10 +5632,8 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 			sqlVdbeAddOp4(v, OP_CollSeq, regHit, 0, 0,
 					  (char *)coll, P4_COLLSEQ);
 		}
-		sqlVdbeAddOp3(v, OP_AggStep0, 0, regAgg, pF->iMem);
-		struct func *func = sql_func_find(pF->pExpr);
-		sqlVdbeAppendP4(v, func, P4_FUNC);
-		sqlVdbeChangeP5(v, (u8) nArg);
+		sql_emit_func_call(v, pF->pExpr, OP_AggStep0, 0, regAgg,
+				   pF->iMem, nArg);
 		sql_expr_type_cache_change(pParse, regAgg, nArg);
 		sqlReleaseTempRange(pParse, regAgg, nArg);
 		if (addrNext) {
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 01701e271..5966f841f 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -4410,6 +4410,14 @@ sql_func_flags(uint8_t id);
 enum field_type
 sql_func_result(struct Expr *expr);
 
+int
+sql_emit_func_call(struct Vdbe *vdbe, struct Expr *expr, int op, int mask,
+		   int r1, int r2, uint8_t argc);
+
+int
+sql_emit_func_finalize(struct Vdbe *vdbe, struct Expr *expr, int reg,
+		       uint8_t argc);
+
 /**
  * Generate VDBE code to halt execution with correct error if
  * the object with specified key is already present (or doesn't
-- 
2.25.1


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

* Re: [Tarantool-patches] [PATCH v2 2/5] sql: introduce SQL built-in functions to parser
  2021-08-18 14:34 ` [Tarantool-patches] [PATCH v2 2/5] sql: introduce SQL built-in functions to parser Mergen Imeev via Tarantool-patches
@ 2021-08-19  8:35   ` Vladimir Davydov via Tarantool-patches
  2021-08-21  0:27     ` Safin Timur via Tarantool-patches
  0 siblings, 1 reply; 8+ messages in thread
From: Vladimir Davydov via Tarantool-patches @ 2021-08-19  8:35 UTC (permalink / raw)
  To: imeevma; +Cc: tarantool-patches

On Wed, Aug 18, 2021 at 05:34:59PM +0300, imeevma@tarantool.org wrote:
> diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
> index 0d998506c..369d9e1dd 100644
> --- a/extra/mkkeywordhash.c
> +++ b/extra/mkkeywordhash.c
> @@ -184,7 +184,6 @@ static Keyword aKeywordTable[] = {
>    { "BLOB",                   "TK_STANDARD",    true  },
>    { "BINARY",                 "TK_ID",          true  },
>    { "CALL",                   "TK_STANDARD",    true  },
> -  { "CHAR",                   "TK_CHAR",        true  },
>    { "CHARACTER",              "TK_ID",          true  },
>    { "CONDITION",              "TK_STANDARD",    true  },
>    { "CONNECT",                "TK_STANDARD",    true  },
> @@ -251,6 +250,42 @@ static Keyword aKeywordTable[] = {
>    { "LEADING",                "TK_LEADING",     true  },
>    { "TRAILING",               "TK_TRAILING",    true  },
>    { "BOTH",                   "TK_BOTH",        true  },
> +  { "ABS",                    "TK_ABS",         true  },
> +  { "AVG",                    "TK_AVG",         true  },
> +  { "CHAR",                   "TK_CHAR",        true  },
> +  { "CHAR_LENGTH",            "TK_CHAR_LEN",    true  },
> +  { "CHARACTER_LENGTH",       "TK_CHAR_LEN",    true  },
> +  { "COALESCE",               "TK_COALESCE",    true  },
> +  { "COUNT",                  "TK_COUNT",       true  },
> +  { "GREATEST",               "TK_GREATEST",    true  },
> +  { "GROUP_CONCAT",           "TK_GROUP_CONCAT",true  },
> +  { "HEX",                    "TK_HEX",         true  },
> +  { "IFNULL",                 "TK_IFNULL",      true  },
> +  { "LEAST",                  "TK_LEAST",       true  },
> +  { "LENGTH",                 "TK_LENGTH",      true  },
> +  { "LIKELIHOOD",             "TK_LIKELIHOOD",  true  },
> +  { "LIKELY",                 "TK_LIKELY",      true  },
> +  { "LOWER",                  "TK_LOWER",       true  },
> +  { "MAX",                    "TK_MAX",         true  },
> +  { "MIN",                    "TK_MIN",         true  },
> +  { "NULLIF",                 "TK_NULLIF",      true  },
> +  { "POSITION",               "TK_POSITION",    true  },
> +  { "PRINTF",                 "TK_PRINTF",      true  },
> +  { "QUOTE",                  "TK_QUOTE",       true  },
> +  { "RANDOM",                 "TK_RANDOM",      true  },
> +  { "RANDOMBLOB",             "TK_RANDOMBLOB",  true  },
> +  { "ROUND",                  "TK_ROUND",       true  },
> +  { "ROW_COUNT",              "TK_ROW_COUNT",   true  },
> +  { "SOUNDEX",                "TK_SOUNDEX",     true  },
> +  { "SUBSTR",                 "TK_SUBSTR",      true  },
> +  { "SUM",                    "TK_SUM",         true  },
> +  { "TOTAL",                  "TK_TOTAL",       true  },
> +  { "TYPEOF",                 "TK_TYPEOF",      true  },
> +  { "UNICODE",                "TK_UNICODE",     true  },
> +  { "UNLIKELY",               "TK_UNLIKELY",    true  },
> +  { "UPPER",                  "TK_UPPER",       true  },
> +  { "VERSION",                "TK_VERSION",     true  },
> +  { "ZEROBLOB",               "TK_ZEROBLOB",    true  },

Should be sorted?

>  };
>  
>  /* Number of keywords */
> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
> index bd041e862..cb2e627db 100644
> --- a/src/box/sql/parse.y
> +++ b/src/box/sql/parse.y
> @@ -1172,27 +1172,506 @@ trim_specification(A) ::= LEADING.  { A = TRIM_LEADING; }
>  trim_specification(A) ::= TRAILING. { A = TRIM_TRAILING; }
>  trim_specification(A) ::= BOTH.     { A = TRIM_BOTH; }
>  
> -expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP(E). {
> -  if( Y && Y->nExpr>pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG] ){
> -    const char *err =
> -      tt_sprintf("Number of arguments to function %.*s", X.n, X.z);
> -    diag_set(ClientError, ER_SQL_PARSER_LIMIT, err, Y->nExpr,
> -             pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
> +expr(A) ::= ABS(X) LP distinct(D) exprlist(Y) RP(E). {
> +  if (Y == NULL || Y->nExpr != 1) {
> +    int n = Y == NULL ? 0 : Y->nExpr;
> +    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ABS", "1", n);
>      pParse->is_aborted = true;
> +    return;
>    }
>    A.pExpr = sqlExprFunction(pParse, Y, &X);
> -  spanSet(&A,&X,&E);
> -  if( D==SF_Distinct && A.pExpr ){
> +  spanSet(&A, &X, &E);
> +  if(D == SF_Distinct && A.pExpr)
>      A.pExpr->flags |= EP_Distinct;
> +}
> +
> +expr(A) ::= AVG(X) LP distinct(D) exprlist(Y) RP(E). {
> +  if (Y == NULL || Y->nExpr != 1) {
> +    int n = Y == NULL ? 0 : Y->nExpr;
> +    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "AVG", "1", n);
> +    pParse->is_aborted = true;
> +    return;
>    }
> +  A.pExpr = sqlExprFunction(pParse, Y, &X);
> +  spanSet(&A, &X, &E);
> +  if(D == SF_Distinct && A.pExpr)
> +    A.pExpr->flags |= EP_Distinct;
>  }
>  
> -/*
> - * type_func(A) ::= DATE(A) .
> - * type_func(A) ::= DATETIME(A) .
> - */
> -type_func(A) ::= CHAR(A) .
> -expr(A) ::= type_func(X) LP distinct(D) exprlist(Y) RP(E). {
> +expr(A) ::= CHAR(X) LP distinct(D) exprlist(Y) RP(E). {
> +  if (Y != NULL && Y->nExpr > pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]) {
> +    const char *str = tt_sprintf("from %d to %d", 0,
> +                                 pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
> +    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "CHAR", str, Y->nExpr);
> +    pParse->is_aborted = true;
> +    return;
> +  }
> +  A.pExpr = sqlExprFunction(pParse, Y, &X);
> +  spanSet(&A, &X, &E);
> +  if(D == SF_Distinct && A.pExpr)
> +    A.pExpr->flags |= EP_Distinct;
> +}
> +
> +expr(A) ::= CHAR_LEN(X) LP distinct(D) exprlist(Y) RP(E). {
> +  if (Y == NULL || Y->nExpr != 1) {
> +    int n = Y == NULL ? 0 : Y->nExpr;
> +    const char *name = X.n == strlen("CHAR_LENGTH") ? "CHAR_LENGTH" :
> +                       "CHARACTER_LENGTH";
> +    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, name, "1", n);
> +    pParse->is_aborted = true;
> +    return;
> +  }
> +  A.pExpr = sqlExprFunction(pParse, Y, &X);
> +  spanSet(&A, &X, &E);
> +  if(D == SF_Distinct && A.pExpr)
> +    A.pExpr->flags |= EP_Distinct;
> +}
> +
> +expr(A) ::= COALESCE(X) LP distinct(D) exprlist(Y) RP(E). {
> +  if (Y == NULL || Y->nExpr < 2 ||
> +      Y->nExpr > pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]) {
> +    int n = Y == NULL ? 0 : Y->nExpr;
> +    const char *str = tt_sprintf("from %d to %d", 2,
> +                                 pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
> +    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "COALESCE", str, n);
> +    pParse->is_aborted = true;
> +    return;
> +  }
> +  A.pExpr = sqlExprFunction(pParse, Y, &X);
> +  spanSet(&A, &X, &E);
> +  if(D == SF_Distinct && A.pExpr)
> +    A.pExpr->flags |= EP_Distinct;
> +}

Can you the code to a helper function to avoid copy-paste?

> diff --git a/test/box/tx_man.result b/test/box/tx_man.result
> index 786d7fc30..b99fbc2ca 100644
> --- a/test/box/tx_man.result
> +++ b/test/box/tx_man.result
> @@ -2129,11 +2129,11 @@ tx1:rollback()
>  
>  -- gh-6095: SQL query may crash in MVCC mode if it involves ephemeral spaces.
>  --
> -box.execute([[ CREATE TABLE test (id INT NOT NULL PRIMARY KEY, count INT NOT NULL)]])
> +box.execute([[ CREATE TABLE test (id INT NOT NULL PRIMARY KEY, "COUNT" INT NOT NULL)]])
>   | ---
>   | - row_count: 1
>   | ...
> -box.execute([[ UPDATE test SET count = count + 1 WHERE id = 0 ]])
> +box.execute([[ UPDATE test SET "COUNT" = "COUNT" + 1 WHERE id = 0 ]])

This looks bad. MySQL and PostgreSQL allow that.

>   | ---
>   | - row_count: 0
>   | ...
> diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
> index e7b35c9d9..7dd85025a 100755
> --- a/test/sql-tap/func.test.lua
> +++ b/test/sql-tap/func.test.lua
> @@ -68,7 +68,7 @@ test:do_catchsql_test(
>          SELECT length(*) FROM tbl1 ORDER BY t1
>      ]], {
>          -- <func-1.1>
> -        1, "Wrong number of arguments is passed to LENGTH(): expected 1, got 0"
> +        1, "Syntax error at line 1 near '*'"

This is probably okay.

>          -- </func-1.1>
>      })
>  
> @@ -2483,7 +2483,7 @@ test:do_catchsql_test(
>          SELECT coalesce()
>      ]], {
>          -- <func-27.1>
> -        1, "Wrong number of arguments is passed to COALESCE(): expected at least two, got 0"
> +        1, "Wrong number of arguments is passed to COALESCE(): expected from 2 to 127, got 0"

And this too.

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

* Re: [Tarantool-patches] [PATCH v2 2/5] sql: introduce SQL built-in functions to parser
  2021-08-19  8:35   ` Vladimir Davydov via Tarantool-patches
@ 2021-08-21  0:27     ` Safin Timur via Tarantool-patches
  0 siblings, 0 replies; 8+ messages in thread
From: Safin Timur via Tarantool-patches @ 2021-08-21  0:27 UTC (permalink / raw)
  To: Vladimir Davydov, imeevma; +Cc: tarantool-patches

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

On 19.08.2021 11:35, Vladimir Davydov via Tarantool-patches wrote:
> On Wed, Aug 18, 2021 at 05:34:59PM +0300, imeevma@tarantool.org wrote:
>> diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
>> index 0d998506c..369d9e1dd 100644
>> --- a/extra/mkkeywordhash.c
>> +++ b/extra/mkkeywordhash.c
>> @@ -184,7 +184,6 @@ static Keyword aKeywordTable[] = {
>>     { "BLOB",                   "TK_STANDARD",    true  },
>>     { "BINARY",                 "TK_ID",          true  },
>>     { "CALL",                   "TK_STANDARD",    true  },
>> -  { "CHAR",                   "TK_CHAR",        true  },
>>     { "CHARACTER",              "TK_ID",          true  },
>>     { "CONDITION",              "TK_STANDARD",    true  },
>>     { "CONNECT",                "TK_STANDARD",    true  },
>> @@ -251,6 +250,42 @@ static Keyword aKeywordTable[] = {
>>     { "LEADING",                "TK_LEADING",     true  },
>>     { "TRAILING",               "TK_TRAILING",    true  },
>>     { "BOTH",                   "TK_BOTH",        true  },
>> +  { "ABS",                    "TK_ABS",         true  },
>> +  { "AVG",                    "TK_AVG",         true  },
>> +  { "CHAR",                   "TK_CHAR",        true  },
>> +  { "CHAR_LENGTH",            "TK_CHAR_LEN",    true  },
>> +  { "CHARACTER_LENGTH",       "TK_CHAR_LEN",    true  },
>> +  { "COALESCE",               "TK_COALESCE",    true  },
>> +  { "COUNT",                  "TK_COUNT",       true  },
>> +  { "GREATEST",               "TK_GREATEST",    true  },
>> +  { "GROUP_CONCAT",           "TK_GROUP_CONCAT",true  },
>> +  { "HEX",                    "TK_HEX",         true  },
>> +  { "IFNULL",                 "TK_IFNULL",      true  },
>> +  { "LEAST",                  "TK_LEAST",       true  },
>> +  { "LENGTH",                 "TK_LENGTH",      true  },
>> +  { "LIKELIHOOD",             "TK_LIKELIHOOD",  true  },
>> +  { "LIKELY",                 "TK_LIKELY",      true  },
>> +  { "LOWER",                  "TK_LOWER",       true  },
>> +  { "MAX",                    "TK_MAX",         true  },
>> +  { "MIN",                    "TK_MIN",         true  },
>> +  { "NULLIF",                 "TK_NULLIF",      true  },
>> +  { "POSITION",               "TK_POSITION",    true  },
>> +  { "PRINTF",                 "TK_PRINTF",      true  },
>> +  { "QUOTE",                  "TK_QUOTE",       true  },
>> +  { "RANDOM",                 "TK_RANDOM",      true  },
>> +  { "RANDOMBLOB",             "TK_RANDOMBLOB",  true  },
>> +  { "ROUND",                  "TK_ROUND",       true  },
>> +  { "ROW_COUNT",              "TK_ROW_COUNT",   true  },
>> +  { "SOUNDEX",                "TK_SOUNDEX",     true  },
>> +  { "SUBSTR",                 "TK_SUBSTR",      true  },
>> +  { "SUM",                    "TK_SUM",         true  },
>> +  { "TOTAL",                  "TK_TOTAL",       true  },
>> +  { "TYPEOF",                 "TK_TYPEOF",      true  },
>> +  { "UNICODE",                "TK_UNICODE",     true  },
>> +  { "UNLIKELY",               "TK_UNLIKELY",    true  },
>> +  { "UPPER",                  "TK_UPPER",       true  },
>> +  { "VERSION",                "TK_VERSION",     true  },
>> +  { "ZEROBLOB",               "TK_ZEROBLOB",    true  },
> 
> Should be sorted?

They all made reserved (last true) - that's one of a problems in a patch.

> 
>>   };
>>   
>>   /* Number of keywords */
>> diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
>> index bd041e862..cb2e627db 100644
>> --- a/src/box/sql/parse.y
>> +++ b/src/box/sql/parse.y
>> @@ -1172,27 +1172,506 @@ trim_specification(A) ::= LEADING.  { A =
> TRIM_LEADING; }
>>   trim_specification(A) ::= TRAILING. { A = TRIM_TRAILING; }
>>   trim_specification(A) ::= BOTH.     { A = TRIM_BOTH; }
>>   
>> -expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP(E). {
>> -  if( Y && Y->nExpr>pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG] ){
>> -    const char *err =
>> -      tt_sprintf("Number of arguments to function %.*s", X.n, X.z);
>> -    diag_set(ClientError, ER_SQL_PARSER_LIMIT, err, Y->nExpr,
>> -             pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
>> +expr(A) ::= ABS(X) LP distinct(D) exprlist(Y) RP(E). {
>> +  if (Y == NULL || Y->nExpr != 1) {
>> +    int n = Y == NULL ? 0 : Y->nExpr;
>> +    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ABS", "1", n);
>>       pParse->is_aborted = true;
>> +    return;
>>     }
>>     A.pExpr = sqlExprFunction(pParse, Y, &X);
>> -  spanSet(&A,&X,&E);
>> -  if( D==SF_Distinct && A.pExpr ){
>> +  spanSet(&A, &X, &E);
>> +  if(D == SF_Distinct && A.pExpr)
>>       A.pExpr->flags |= EP_Distinct;
>> +}
>> +
>> +expr(A) ::= AVG(X) LP distinct(D) exprlist(Y) RP(E). {
>> +  if (Y == NULL || Y->nExpr != 1) {
>> +    int n = Y == NULL ? 0 : Y->nExpr;
>> +    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "AVG", "1", n);
>> +    pParse->is_aborted = true;
>> +    return;
>>     }
>> +  A.pExpr = sqlExprFunction(pParse, Y, &X);
>> +  spanSet(&A, &X, &E);
>> +  if(D == SF_Distinct && A.pExpr)
>> +    A.pExpr->flags |= EP_Distinct;
>>   }
>>   
>> -/*
>> - * type_func(A) ::= DATE(A) .
>> - * type_func(A) ::= DATETIME(A) .
>> - */
>> -type_func(A) ::= CHAR(A) .
>> -expr(A) ::= type_func(X) LP distinct(D) exprlist(Y) RP(E). {
>> +expr(A) ::= CHAR(X) LP distinct(D) exprlist(Y) RP(E). {
>> +  if (Y != NULL && Y->nExpr >
> pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]) {
>> +    const char *str = tt_sprintf("from %d to %d", 0,
>> +
> pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
>> +    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "CHAR", str,
> Y->nExpr);
>> +    pParse->is_aborted = true;
>> +    return;
>> +  }
>> +  A.pExpr = sqlExprFunction(pParse, Y, &X);
>> +  spanSet(&A, &X, &E);
>> +  if(D == SF_Distinct && A.pExpr)
>> +    A.pExpr->flags |= EP_Distinct;
>> +}
>> +
>> +expr(A) ::= CHAR_LEN(X) LP distinct(D) exprlist(Y) RP(E). {
>> +  if (Y == NULL || Y->nExpr != 1) {
>> +    int n = Y == NULL ? 0 : Y->nExpr;
>> +    const char *name = X.n == strlen("CHAR_LENGTH") ? "CHAR_LENGTH" :
>> +                       "CHARACTER_LENGTH";
>> +    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, name, "1", n);
>> +    pParse->is_aborted = true;
>> +    return;
>> +  }
>> +  A.pExpr = sqlExprFunction(pParse, Y, &X);
>> +  spanSet(&A, &X, &E);
>> +  if(D == SF_Distinct && A.pExpr)
>> +    A.pExpr->flags |= EP_Distinct;
>> +}
>> +
>> +expr(A) ::= COALESCE(X) LP distinct(D) exprlist(Y) RP(E). {
>> +  if (Y == NULL || Y->nExpr < 2 ||
>> +      Y->nExpr > pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]) {
>> +    int n = Y == NULL ? 0 : Y->nExpr;
>> +    const char *str = tt_sprintf("from %d to %d", 2,
>> +
> pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
>> +    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "COALESCE", str, n);
>> +    pParse->is_aborted = true;
>> +    return;
>> +  }
>> +  A.pExpr = sqlExprFunction(pParse, Y, &X);
>> +  spanSet(&A, &X, &E);
>> +  if(D == SF_Distinct && A.pExpr)
>> +    A.pExpr->flags |= EP_Distinct;
>> +}
> 
> Can you the code to a helper function to avoid copy-paste?

Yup, a lot of copy-paste. Hard to distibgush the core of each. This all 
could make significantly shorter:

-------------------------------------------------------------------
expr(A) ::= builtin_fn(X) LP distinct(D) exprlist(Y) RP(E). {
   /* variable number of arguments */
   int args = X.args, maxargs = X.maxargs;
   int n = Y == NULL ? 0 : Y->nExpr;

   if (X.vararg) {
     int limit_args = pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG];

     /* less than minimal # of expected or more than maximal */
     if (n < args || n > limit_args) {
       const char *str = tt_sprintf("from %d to %d", 0, limit_args);
       diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
                 TOKEN_STR(X.token), str, Y->nExpr);
       pParse->is_aborted = true;
       return;
     }
   } else {
     /* not expected number - args */
     if (maxargs == 0 && n != args) {
       diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, TOKEN_STR(X.token),
               tt_sprintf("%d", args), n);
       pParse->is_aborted = true;
       return;
     }
     /* from args till maxargs  */
     if (n < args || (maxargs != 0 && n > maxargs)) {/* or more than 
expected */
       const char *str = tt_sprintf("from %d to %d", args, maxargs);
       diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, 
TOKEN_STR(X.token), str, n);
       pParse->is_aborted = true;
       return;
     }
   }
   A.pExpr = sql_expr_new_built_in(pParse, Y, &X.token, X.id);
   spanSet(&A, &X.token, &E);
   if (D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
}

%type builtin_fn {struct builtin_fn_def}

builtin_fn(A) ::= ABS(T). { FUNC_ARG_N(A, T, TK_ABS, 1); }
builtin_fn(A) ::= AVG(T). { FUNC_ARG_N(A, T, TK_AVG, 1); }
builtin_fn(A) ::= CHAR(T). { FUNC_ARG_VAR(A, T, TK_CHAR); }
builtin_fn(A) ::= CHAR_LEN(T). { FUNC_ARG_N(A, T, TK_CHAR_LENGTH, 1); }
builtin_fn(A) ::= COALESCE(T). { FUNC_ARG_VAR_N(A, T, TK_COALESCE, 2); }
/*builtin_fn(A) ::=(T) COUNT. { FUNC_ARG_N(A, T, TK_COUNT, 1); }*/
builtin_fn(A) ::= GREATEST(T). { FUNC_ARG_VAR_N(A, T, TK_GREATEST, 2); }
builtin_fn(A) ::= GROUP_CONCAT(T). { FUNC_ARG_N_M(A, T, TK_GROUP_CONCAT, 
1, 2); }
....
-------------------------------------------------------------------

More details you could see in apatch attached.

> 
>> diff --git a/test/box/tx_man.result b/test/box/tx_man.result
>> index 786d7fc30..b99fbc2ca 100644
>> --- a/test/box/tx_man.result
>> +++ b/test/box/tx_man.result
>> @@ -2129,11 +2129,11 @@ tx1:rollback()
>>   
>>   -- gh-6095: SQL query may crash in MVCC mode if it involves ephemeral
> spaces.
>>   --
>> -box.execute([[ CREATE TABLE test (id INT NOT NULL PRIMARY KEY, count
> INT NOT NULL)]])
>> +box.execute([[ CREATE TABLE test (id INT NOT NULL PRIMARY KEY, "COUNT"
> INT NOT NULL)]])
>>    | ---
>>    | - row_count: 1
>>    | ...
>> -box.execute([[ UPDATE test SET count = count + 1 WHERE id = 0 ]])
>> +box.execute([[ UPDATE test SET "COUNT" = "COUNT" + 1 WHERE id = 0 ]])
> 
> This looks bad. MySQL and PostgreSQL allow that.

I've fixed it in a patch suggested...
> 
>>    | ---
>>    | - row_count: 0
>>    | ...
>> diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
>> index e7b35c9d9..7dd85025a 100755
>> --- a/test/sql-tap/func.test.lua
>> +++ b/test/sql-tap/func.test.lua
>> @@ -68,7 +68,7 @@ test:do_catchsql_test(
>>           SELECT length(*) FROM tbl1 ORDER BY t1
>>       ]], {
>>           -- <func-1.1>
>> -        1, "Wrong number of arguments is passed to LENGTH(): expected
> 1, got 0"
>> +        1, "Syntax error at line 1 near '*'"
> 
> This is probably okay.
> 
>>           -- </func-1.1>
>>       })
>>   
>> @@ -2483,7 +2483,7 @@ test:do_catchsql_test(
>>           SELECT coalesce()
>>       ]], {
>>           -- <func-27.1>
>> -        1, "Wrong number of arguments is passed to COALESCE(): expected
> at least two, got 0"
>> +        1, "Wrong number of arguments is passed to COALESCE(): expected
> from 2 to 127, got 0"
> 
> And this too.
> 

[Patch attached is for branch imeevma/gh-6105-check-types-no-test]

Best Regards,
Timur

[-- Attachment #2: 0001-sql-handle-sql-builtins-in-parser.patch --]
[-- Type: text/plain, Size: 31193 bytes --]

From f06233a02a22ad0d5d97f6c9fdccf58e536182a3 Mon Sep 17 00:00:00 2001
Message-Id: <f06233a02a22ad0d5d97f6c9fdccf58e536182a3.1629505300.git.tsafin@tarantool.org>
From: Timur Safin <tsafin@tarantool.org>
Date: Sat, 21 Aug 2021 01:34:17 +0300
Subject: [PATCH] sql: handle sql builtins in parser
To: imeevma@tarantool.org

Refactored a way how builtins declared in parse.y:
- made handling code to not duplicate in each case;
- properly initialized id fallback so they all
  could be used in non-functions context.
---
 extra/mkkeywordhash.c    |  72 ++---
 src/box/sql/parse.y      | 571 ++++++---------------------------------
 src/box/sql/parse_def.h  |  53 ++++
 test/box/tx_man.result   |  13 +-
 test/box/tx_man.test.lua |   4 +-
 5 files changed, 189 insertions(+), 524 deletions(-)

diff --git a/extra/mkkeywordhash.c b/extra/mkkeywordhash.c
index 235010da4..3d6f5c9bc 100644
--- a/extra/mkkeywordhash.c
+++ b/extra/mkkeywordhash.c
@@ -251,42 +251,42 @@ static Keyword aKeywordTable[] = {
   { "LEADING",                "TK_LEADING",     true  },
   { "TRAILING",               "TK_TRAILING",    true  },
   { "BOTH",                   "TK_BOTH",        true  },
-  { "ABS",                    "TK_ABS",         true  },
-  { "AVG",                    "TK_AVG",         true  },
-  { "CHAR",                   "TK_CHAR",        true  },
-  { "CHAR_LENGTH",            "TK_CHAR_LEN",    true  },
-  { "CHARACTER_LENGTH",       "TK_CHAR_LEN",    true  },
-  { "COALESCE",               "TK_COALESCE",    true  },
-  { "COUNT",                  "TK_COUNT",       true  },
-  { "GREATEST",               "TK_GREATEST",    true  },
-  { "GROUP_CONCAT",           "TK_GROUP_CONCAT",true  },
-  { "HEX",                    "TK_HEX",         true  },
-  { "IFNULL",                 "TK_IFNULL",      true  },
-  { "LEAST",                  "TK_LEAST",       true  },
-  { "LENGTH",                 "TK_LENGTH",      true  },
-  { "LIKELIHOOD",             "TK_LIKELIHOOD",  true  },
-  { "LIKELY",                 "TK_LIKELY",      true  },
-  { "LOWER",                  "TK_LOWER",       true  },
-  { "MAX",                    "TK_MAX",         true  },
-  { "MIN",                    "TK_MIN",         true  },
-  { "NULLIF",                 "TK_NULLIF",      true  },
-  { "POSITION",               "TK_POSITION",    true  },
-  { "PRINTF",                 "TK_PRINTF",      true  },
-  { "QUOTE",                  "TK_QUOTE",       true  },
-  { "RANDOM",                 "TK_RANDOM",      true  },
-  { "RANDOMBLOB",             "TK_RANDOMBLOB",  true  },
-  { "ROUND",                  "TK_ROUND",       true  },
-  { "ROW_COUNT",              "TK_ROW_COUNT",   true  },
-  { "SOUNDEX",                "TK_SOUNDEX",     true  },
-  { "SUBSTR",                 "TK_SUBSTR",      true  },
-  { "SUM",                    "TK_SUM",         true  },
-  { "TOTAL",                  "TK_TOTAL",       true  },
-  { "TYPEOF",                 "TK_TYPEOF",      true  },
-  { "UNICODE",                "TK_UNICODE",     true  },
-  { "UNLIKELY",               "TK_UNLIKELY",    true  },
-  { "UPPER",                  "TK_UPPER",       true  },
-  { "VERSION",                "TK_VERSION",     true  },
-  { "ZEROBLOB",               "TK_ZEROBLOB",    true  },
+  { "ABS",                    "TK_ABS",         false },
+  { "AVG",                    "TK_AVG",         false },
+  { "CHAR",                   "TK_CHAR",        false },
+  { "CHAR_LENGTH",            "TK_CHAR_LEN",    false },
+  { "CHARACTER_LENGTH",       "TK_CHAR_LEN",    false },
+  { "COALESCE",               "TK_COALESCE",    false },
+  { "COUNT",                  "TK_COUNT",       false },
+  { "GREATEST",               "TK_GREATEST",    false },
+  { "GROUP_CONCAT",           "TK_GROUP_CONCAT",false },
+  { "HEX",                    "TK_HEX",         false },
+  { "IFNULL",                 "TK_IFNULL",      false },
+  { "LEAST",                  "TK_LEAST",       false },
+  { "LENGTH",                 "TK_LENGTH",      false },
+  { "LIKELIHOOD",             "TK_LIKELIHOOD",  false },
+  { "LIKELY",                 "TK_LIKELY",      false },
+  { "LOWER",                  "TK_LOWER",       false },
+  { "MAX",                    "TK_MAX",         false },
+  { "MIN",                    "TK_MIN",         false },
+  { "NULLIF",                 "TK_NULLIF",      false },
+  { "POSITION",               "TK_POSITION",    false },
+  { "PRINTF",                 "TK_PRINTF",      false },
+  { "QUOTE",                  "TK_QUOTE",       false },
+  { "RANDOM",                 "TK_RANDOM",      false },
+  { "RANDOMBLOB",             "TK_RANDOMBLOB",  false },
+  { "ROUND",                  "TK_ROUND",       false },
+  { "ROW_COUNT",              "TK_ROW_COUNT",   false },
+  { "SOUNDEX",                "TK_SOUNDEX",     false },
+  { "SUBSTR",                 "TK_SUBSTR",      false },
+  { "SUM",                    "TK_SUM",         false },
+  { "TOTAL",                  "TK_TOTAL",       false },
+  { "TYPEOF",                 "TK_TYPEOF",      false },
+  { "UNICODE",                "TK_UNICODE",     false },
+  { "UNLIKELY",               "TK_UNLIKELY",    false },
+  { "UPPER",                  "TK_UPPER",       false },
+  { "VERSION",                "TK_VERSION",     false },
+  { "ZEROBLOB",               "TK_ZEROBLOB",    false },
 };
 
 /* Number of keywords */
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index 6d65788e3..00c755a4b 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -267,9 +267,17 @@ columnlist ::= tcons.
   ABORT ACTION ADD AFTER AUTOINCREMENT BEFORE CASCADE
   CONFLICT DEFERRED END ENGINE FAIL
   IGNORE INITIALLY INSTEAD NO MATCH PLAN
-  QUERY KEY OFFSET RAISE RELEASE REPLACE RESTRICT
-  RENAME CTIME_KW IF ENABLE DISABLE UUID
+  QUERY KEY OFFSET RAISE RELEASE RESTRICT
+  RENAME CTIME_KW IF ENABLE DISABLE 
+  ABS
+
+  AVG CHAR CHAR_LENGTH CHARACTER_LENGTH COALESCE COUNT
+  GREATEST GROUP_CONCAT HEX IFNULL LEAST LENGTH LIKELIHOOD
+  LIKELY LOWER MAX MIN NULLIF POSITION PRINTF QUOTE RANDOM
+  RANDOMBLOB REPLACE ROUND ROW_COUNT SOUNDEX SUBSTR SUM
+  TOTAL TYPEOF UNICODE UNLIKELY UPPER UUID VERSION
   .
+
 %wildcard ANY.
 
 
@@ -1172,87 +1180,98 @@ trim_specification(A) ::= LEADING.  { A = TRIM_LEADING; }
 trim_specification(A) ::= TRAILING. { A = TRIM_TRAILING; }
 trim_specification(A) ::= BOTH.     { A = TRIM_BOTH; }
 
-expr(A) ::= ABS(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ABS", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_ABS);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
+expr(A) ::= builtin_fn(X) LP distinct(D) exprlist(Y) RP(E). {
+  /* variable number of arguments */
+  int args = X.args, maxargs = X.maxargs;
+  int n = Y == NULL ? 0 : Y->nExpr;
 
-expr(A) ::= AVG(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "AVG", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_AVG);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
+  if (X.vararg) {
+    int limit_args = pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG];
 
-expr(A) ::= CHAR(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y != NULL && Y->nExpr > pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]) {
-    const char *str = tt_sprintf("from %d to %d", 0,
-                                 pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "CHAR", str, Y->nExpr);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_CHAR);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= CHAR_LEN(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    const char *name = X.n == strlen("CHAR_LENGTH") ? "CHAR_LENGTH" :
-                       "CHARACTER_LENGTH";
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, name, "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_CHAR_LEN);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= COALESCE(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr < 2 ||
-      Y->nExpr > pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    const char *str = tt_sprintf("from %d to %d", 2,
-                                 pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "COALESCE", str, n);
-    pParse->is_aborted = true;
-    return;
+    /* less than minimal # of expected or more than maximal */
+    if (n < args || n > limit_args) { 
+      const char *str = tt_sprintf("from %d to %d", 0, limit_args);
+      diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+                TOKEN_STR(X.token), str, Y->nExpr);
+      pParse->is_aborted = true;
+      return;
+    }
+  } else {
+    /* not expected number - args */
+    if (maxargs == 0 && n != args) {
+      diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, TOKEN_STR(X.token),
+              tt_sprintf("%d", args), n);
+      pParse->is_aborted = true;
+      return;
+    }
+    /* from args till maxargs  */
+    if (n < args || (maxargs != 0 && n > maxargs)) {/* or more than expected */
+      const char *str = tt_sprintf("from %d to %d", args, maxargs);
+      diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, TOKEN_STR(X.token), str, n);
+      pParse->is_aborted = true;
+      return;
+    }
   }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_COALESCE);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
+  A.pExpr = sql_expr_new_built_in(pParse, Y, &X.token, X.id);
+  spanSet(&A, &X.token, &E);
+  if (D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
 }
 
+%type builtin_fn {struct builtin_fn_def}
+
+builtin_fn(A) ::= ABS(T). { FUNC_ARG_N(A, T, TK_ABS, 1); }
+builtin_fn(A) ::= AVG(T). { FUNC_ARG_N(A, T, TK_AVG, 1); }
+builtin_fn(A) ::= CHAR(T). { FUNC_ARG_VAR(A, T, TK_CHAR); }
+builtin_fn(A) ::= CHAR_LEN(T). { FUNC_ARG_N(A, T, TK_CHAR_LENGTH, 1); }
+builtin_fn(A) ::= COALESCE(T). { FUNC_ARG_VAR_N(A, T, TK_COALESCE, 2); }
+/*builtin_fn(A) ::=(T) COUNT. { FUNC_ARG_N(A, T, TK_COUNT, 1); }*/
+builtin_fn(A) ::= GREATEST(T). { FUNC_ARG_VAR_N(A, T, TK_GREATEST, 2); }
+builtin_fn(A) ::= GROUP_CONCAT(T). { FUNC_ARG_N_M(A, T, TK_GROUP_CONCAT, 1, 2); }
+builtin_fn(A) ::= HEX(T). { FUNC_ARG_N(A, T, TK_HEX, 1); }
+builtin_fn(A) ::= IFNULL(T). { FUNC_ARG_N(A, T, TK_IFNULL, 2); }
+builtin_fn(A) ::= LEAST(T). { FUNC_ARG_VAR_N(A, T, TK_LEAST, 2); }
+builtin_fn(A) ::= LENGTH(T). { FUNC_ARG_N(A, T, TK_LENGTH, 1); }
+builtin_fn(A) ::= LIKELIHOOD(T). { FUNC_ARG_N(A, T, TK_LIKELIHOOD, 2); }
+builtin_fn(A) ::= LIKELY(T). { FUNC_ARG_N(A, T, TK_LIKELY, 1); }
+builtin_fn(A) ::= LOWER(T). { FUNC_ARG_N(A, T, TK_LOWER, 1);}
+builtin_fn(A) ::= MAX(T). { FUNC_ARG_N(A, T, TK_MAX, 1); }
+builtin_fn(A) ::= MIN(T). { FUNC_ARG_N(A, T, TK_MIN, 1); }
+builtin_fn(A) ::= NULLIF(T). { FUNC_ARG_N(A, T, TK_NULLIF, 2); }
+builtin_fn(A) ::= POSITION(T). { FUNC_ARG_N(A, T, TK_POSITION, 2); }
+builtin_fn(A) ::= PRINTF(T). { FUNC_ARG_VAR(A, T, TK_PRINTF); }
+builtin_fn(A) ::= QUOTE(T). { FUNC_ARG_N(A, T, TK_QUOTE, 1); }
+builtin_fn(A) ::= RANDOM(T). { FUNC_ARG_N(A, T, TK_RANDOM, 0); }
+builtin_fn(A) ::= RANDOMBLOB(T). { FUNC_ARG_N(A, T, TK_RANDOMBLOB, 1); }
+builtin_fn(A) ::= REPLACE(T). { FUNC_ARG_N(A, T, TK_REPLACE, 3); }
+builtin_fn(A) ::= ROUND(T). { FUNC_ARG_N_M(A, T, TK_ROUND, 1, 2); }
+builtin_fn(A) ::= ROW_COUNT(T). { FUNC_ARG_N(A, T, TK_ROW_COUNT, 0); }
+builtin_fn(A) ::= SOUNDEX(T). { FUNC_ARG_N(A, T, TK_SOUNDEX, 1); }
+builtin_fn(A) ::= SUBSTR(T). { FUNC_ARG_N_M(A, T, TK_SUBSTR, 2, 3); }
+builtin_fn(A) ::= SUM(T). { FUNC_ARG_N(A, T, TK_SUM, 1); }
+builtin_fn(A) ::= TOTAL(T). { FUNC_ARG_N(A, T, TK_TOTAL, 1); }
+builtin_fn(A) ::= TYPEOF(T). { FUNC_ARG_N(A, T, TK_TYPEOF, 1); }
+builtin_fn(A) ::= UNICODE(T). { FUNC_ARG_N(A, T, TK_UNICODE, 1); }
+builtin_fn(A) ::= UNLIKELY(T). { FUNC_ARG_N(A, T, TK_UNLIKELY, 1); }
+builtin_fn(A) ::= UPPER(T). { FUNC_ARG_N(A, T, TK_UPPER, 1); }
+builtin_fn(A) ::= UUID(T). { FUNC_ARG_N(A, T, TK_UUID, 1); }
+builtin_fn(A) ::= VERSION(T). { FUNC_ARG_N(A, T, TK_VERSION, 0); }
+builtin_fn(A) ::= ZEROBLOB(T). { FUNC_ARG_N(A, T, TK_ZEROBLOB, 1); }
+
+/* to avoid shift-reduce conflict here we have to expand this rule
+   manually for TK_COUNT */
 expr(A) ::= COUNT(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y != NULL && Y->nExpr > 1) {
-    const char *str = tt_sprintf("from %d to %d", 0, 1);
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "COUNT", str, Y->nExpr);
+  int n = Y == NULL ? 0 : Y->nExpr;
+
+  /* not expected number of arguments */
+  if (n != 1) {
+    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, TOKEN_STR(X), "1", n);
     pParse->is_aborted = true;
     return;
   }
   A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_COUNT);
   spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
+  if (D == SF_Distinct && A.pExpr)
     A.pExpr->flags |= EP_Distinct;
 }
 
@@ -1261,416 +1280,6 @@ expr(A) ::= COUNT(X) LP STAR RP(E). {
   spanSet(&A, &X, &E);
 }
 
-expr(A) ::= GREATEST(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr < 2 ||
-      Y->nExpr > pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    const char *str = tt_sprintf("from %d to %d", 2,
-                                 pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "GREATEST", str, n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_GREATEST);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= GROUP_CONCAT(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr > 2) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    const char *str = tt_sprintf("from %d to %d", 1, 2);
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "GROUP_CONCAT", str, n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_GROUP_CONCAT);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= HEX(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "HEX", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_HEX);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= IFNULL(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 2) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "IFNULL", "2", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_IFNULL);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= LEAST(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr < 2 ||
-      Y->nExpr > pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    const char *str = tt_sprintf("from %d to %d", 2,
-                                 pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "LEAST", str, n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_LEAST);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= LENGTH(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "LENGTH", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_LENGTH);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= LIKELIHOOD(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 2) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "LIKELIHOOD", "2", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_LIKELIHOOD);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= LIKELY(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "LIKELY", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_LIKELY);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= LOWER(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "LOWER", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_LOWER);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= MAX(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "MAX", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_MAX);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= MIN(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "MIN", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_MIN);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= NULLIF(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 2) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "NULLIF", "2", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_NULLIF);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= POSITION(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 2) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "POSITION", "2", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_POSITION);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= PRINTF(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y != NULL && Y->nExpr > pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]) {
-    const char *str = tt_sprintf("from %d to %d", 0,
-                                 pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG]);
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "PRINTF", str, Y->nExpr);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_PRINTF);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= QUOTE(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "QUOTE", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_QUOTE);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= RANDOM(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y != NULL) {
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "RANDOM", "0", Y->nExpr);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_RANDOM);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= RANDOMBLOB(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "RANDOMBLOB", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_RANDOMBLOB);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= REPLACE(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 3) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "REPLACE", "3", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_REPLACE);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= ROUND(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr > 2) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    const char *str = tt_sprintf("from %d to %d", 1, 2);
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ROUND", str, n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_ROUND);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= ROW_COUNT(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y != NULL) {
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ROW_COUNT", "0", Y->nExpr);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_ROW_COUNT);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= SOUNDEX(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "SOUNDEX", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_SOUNDEX);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= SUBSTR(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr < 2 || Y->nExpr > 3) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    const char *str = tt_sprintf("from %d to %d", 2, 3);
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "SUBSTR", str, n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_SUBSTR);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= SUM(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "SUM", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_SUM);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= TOTAL(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "TOTAL", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_TOTAL);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= TYPEOF(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "TYPEOF", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_TYPEOF);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= UNICODE(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "UNICODE", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_UNICODE);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= UNLIKELY(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "UNLIKELY", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_UNLIKELY);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= UPPER(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "UPPER", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_UPPER);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= UUID(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y != NULL && Y->nExpr > 1) {
-    const char *str = tt_sprintf("from %d to %d", 0, 1);
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "UUID", str, Y->nExpr);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_UUID);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= VERSION(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y != NULL) {
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "VERSION", "0", Y->nExpr);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_VERSION);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
-expr(A) ::= ZEROBLOB(X) LP distinct(D) exprlist(Y) RP(E). {
-  if (Y == NULL || Y->nExpr != 1) {
-    int n = Y == NULL ? 0 : Y->nExpr;
-    diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ZEROBLOB", "1", n);
-    pParse->is_aborted = true;
-    return;
-  }
-  A.pExpr = sql_expr_new_built_in(pParse, Y, &X, TK_ZEROBLOB);
-  spanSet(&A, &X, &E);
-  if(D == SF_Distinct && A.pExpr)
-    A.pExpr->flags |= EP_Distinct;
-}
-
 expr(A) ::= id(X) LP distinct(D) exprlist(Y) RP(E). {
   if( Y && Y->nExpr>pParse->db->aLimit[SQL_LIMIT_FUNCTION_ARG] ){
     const char *err =
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index c5e093517..904d15dfb 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -320,6 +320,59 @@ struct create_index_def {
 	enum sort_order sort_order;
 };
 
+struct builtin_fn_def {
+	struct Token token;
+	/* Token ID */
+	uint8_t id;
+	/** variable number of arguments */
+	bool vararg;
+	/** for variargs = minimal number of arguments 
+	 *  otherwise - exat number of arguments 
+	 */
+	int args;
+	/** maximum # of arguments if defined
+	 * otherwise should be euqal to args
+	 */
+	 int maxargs;
+};
+
+#define TOKEN_STR(token)    tt_sprintf("%.*s", (token).n, (token).z)
+
+/** exactly N args, most frequently N==1 */
+#define FUNC_ARG_N(A, T, t, N) {      \
+	A.id = t;                     \
+	A.token = T;                  \
+	A.vararg = false;             \
+	A.args =  N;                  \
+	A.maxargs = 0;                \
+}
+
+/** from N, to M number of args */
+#define FUNC_ARG_N_M(A, T, t, N, M) { \
+	A.id = t;                     \
+	A.token = T;                  \
+	A.vararg = false;             \
+	A.args = N;                   \
+	A.maxargs = M;                \
+}
+
+/** variable # of args */
+#define FUNC_ARG_VAR(A, T, t) {       \
+	A.id = t;                     \
+	A.token = T;                  \
+	A.vararg = true;              \
+	A.args = A.maxargs = 0;       \
+}
+
+/** variable # of args, at least N */
+#define FUNC_ARG_VAR_N(A, T, t, N) {  \
+	A.id = t;                     \
+	A.token = T;                  \
+	A.vararg = true;              \
+	A.args = N;                   \
+	A.maxargs = 0;                \
+}
+
 /** Basic initialisers of parse structures.*/
 static inline void
 alter_entity_def_init(struct alter_entity_def *alter_def,
diff --git a/test/box/tx_man.result b/test/box/tx_man.result
index 22d8cf450..3b691ae88 100644
--- a/test/box/tx_man.result
+++ b/test/box/tx_man.result
@@ -2129,13 +2129,14 @@ tx1:rollback()
 
 -- gh-6095: SQL query may crash in MVCC mode if it involves ephemeral spaces.
 --
-box.execute([[ CREATE TABLE test (id INT NOT NULL PRIMARY KEY, "COUNT" INT NOT NULL)]])
+box.execute([[ CREATE TABLE test (id INT NOT NULL PRIMARY KEY, count INT NOT NULL)]])
  | ---
  | - row_count: 1
  | ...
-box.execute([[ UPDATE test SET "COUNT" = "COUNT" + 1 WHERE id = 0 ]])
+box.execute([[ UPDATE test SET count = count + 1 WHERE id = 0 ]])
  | ---
- | - row_count: 0
+ | - null
+ | - Syntax error at line 1 near '+'
  | ...
 box.execute([[ DROP TABLE test]])
  | ---
@@ -3641,7 +3642,8 @@ tx1:begin()
  | ...
 tx1('box.execute([[SELECT COUNT() from k1]])')
  | ---
- | - - {'rows': [[0]], 'metadata': [{'type': 'integer', 'name': 'COLUMN_1'}]}
+ | - - null
+ |   - 'Wrong number of arguments is passed to COUNT(): expected 1, got 0'
  | ...
 box.execute([[INSERT INTO k1 VALUES (1);]])
  | ---
@@ -3649,7 +3651,8 @@ box.execute([[INSERT INTO k1 VALUES (1);]])
  | ...
 tx1('box.execute([[SELECT COUNT() from k1]])')
  | ---
- | - - {'rows': [[0]], 'metadata': [{'type': 'integer', 'name': 'COLUMN_1'}]}
+ | - - null
+ |   - 'Wrong number of arguments is passed to COUNT(): expected 1, got 0'
  | ...
 tx1:commit()
  | ---
diff --git a/test/box/tx_man.test.lua b/test/box/tx_man.test.lua
index 3d488c2de..19bc14a67 100644
--- a/test/box/tx_man.test.lua
+++ b/test/box/tx_man.test.lua
@@ -661,8 +661,8 @@ tx1:rollback()
 
 -- gh-6095: SQL query may crash in MVCC mode if it involves ephemeral spaces.
 --
-box.execute([[ CREATE TABLE test (id INT NOT NULL PRIMARY KEY, "COUNT" INT NOT NULL)]])
-box.execute([[ UPDATE test SET "COUNT" = "COUNT" + 1 WHERE id = 0 ]])
+box.execute([[ CREATE TABLE test (id INT NOT NULL PRIMARY KEY, count INT NOT NULL)]])
+box.execute([[ UPDATE test SET count = count + 1 WHERE id = 0 ]])
 box.execute([[ DROP TABLE test]])
 
 -- https://github.com/tarantool/tarantool/issues/5515
-- 
2.29.2


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

end of thread, other threads:[~2021-08-21  0:27 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-08-18 14:34 [Tarantool-patches] [PATCH v2 0/5] Prepare for static arguments type check Mergen Imeev via Tarantool-patches
2021-08-18 14:34 ` [Tarantool-patches] [PATCH v2 1/5] sql: modify arithmetic aggregate functions Mergen Imeev via Tarantool-patches
2021-08-18 14:34 ` [Tarantool-patches] [PATCH v2 2/5] sql: introduce SQL built-in functions to parser Mergen Imeev via Tarantool-patches
2021-08-19  8:35   ` Vladimir Davydov via Tarantool-patches
2021-08-21  0:27     ` Safin Timur via Tarantool-patches
2021-08-18 14:35 ` [Tarantool-patches] [PATCH v2 3/5] sql: separate functions in parser Mergen Imeev via Tarantool-patches
2021-08-18 14:35 ` [Tarantool-patches] [PATCH v2 4/5] sql: separate function flags from functions Mergen Imeev via Tarantool-patches
2021-08-18 14:35 ` [Tarantool-patches] [PATCH v2 5/5] sql: encapsulate SQL built-in functions opcodes Mergen Imeev via Tarantool-patches

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