[Tarantool-patches] [PATCH v2 2/5] sql: introduce SQL built-in functions to parser

Safin Timur tsafin at tarantool.org
Sat Aug 21 03:27:13 MSK 2021


On 19.08.2021 11:35, Vladimir Davydov via Tarantool-patches wrote:
> On Wed, Aug 18, 2021 at 05:34:59PM +0300, imeevma at 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
-------------- next part --------------
From f06233a02a22ad0d5d97f6c9fdccf58e536182a3 Mon Sep 17 00:00:00 2001
Message-Id: <f06233a02a22ad0d5d97f6c9fdccf58e536182a3.1629505300.git.tsafin at tarantool.org>
From: Timur Safin <tsafin at tarantool.org>
Date: Sat, 21 Aug 2021 01:34:17 +0300
Subject: [PATCH] sql: handle sql builtins in parser
To: imeevma at 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



More information about the Tarantool-patches mailing list