Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH v3 0/9] sql: uniform SQL and Lua functions subsystem
@ 2019-08-16 13:26 Kirill Shcherbatov
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 1/9] sql: remove SQL_PreferBuiltin flag Kirill Shcherbatov
                   ` (8 more replies)
  0 siblings, 9 replies; 19+ messages in thread
From: Kirill Shcherbatov @ 2019-08-16 13:26 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

This patchset reworks SQL functions subsystem. Previously Tarantool's
SQL used an own FuncDef function representation and corresponding
FuncDef cache. Replacing it with Tarantool's uniform function
representation makes possible to call any function on any supported
language in SQL.

Branch: http://github.com/tarantool/tarantool/tree/kshch/gh-4182-persistent-functions
Issue: https://github.com/tarantool/tarantool/issues/2233

Kirill Shcherbatov (9):
  sql: remove SQL_PreferBuiltin flag
  sql: GREATEST, LEAST instead of MIN/MAX overload
  sql: wrap all trim functions in dispatcher
  sql: rework SQL_FUNC_COUNT flag semantics
  sql: rename OP_Function to OP_BuiltinFunction
  sql: remove SQL_FUNC_SLOCHNG flag
  sql: get rid of FuncDef function hash
  sql: get rid of box.internal.sql_function_create
  sql: better error messages on invalid arguments

 src/box/lua/lua_sql.h           |  39 --
 src/box/port.h                  |  17 +
 src/box/sql.h                   |   5 +
 src/box/sql/sqlInt.h            | 252 +++------
 src/box/sql/vdbe.h              |  11 +-
 src/box/sql/vdbeInt.h           |  26 +-
 src/lib/core/port.h             |  15 +
 src/box/call.c                  |   1 +
 src/box/execute.c               |   1 +
 src/box/func.c                  |  34 +-
 src/box/lua/call.c              |   6 +-
 src/box/lua/lua_sql.c           | 205 -------
 src/box/port.c                  |   4 +
 src/box/sql/analyze.c           |  40 +-
 src/box/sql/callback.c          | 211 --------
 src/box/sql/date.c              |  28 -
 src/box/sql/expr.c              |  65 +--
 src/box/sql/func.c              | 933 ++++++++++++++++++++++++++------
 src/box/sql/global.c            |   7 -
 src/box/sql/main.c              | 137 -----
 src/box/sql/resolve.c           |  69 +--
 src/box/sql/select.c            |  12 +-
 src/box/sql/vdbe.c              |  95 +++-
 src/box/sql/vdbeapi.c           |  17 +-
 src/box/sql/vdbeaux.c           |  33 +-
 src/box/sql/vdbemem.c           |  67 +--
 src/box/sql/whereexpr.c         |   2 +-
 src/box/CMakeLists.txt          |   1 -
 src/box/alter.cc                |   6 +
 src/box/bootstrap.snap          | Bin 5907 -> 5934 bytes
 src/box/lua/schema.lua          |   3 +-
 src/box/lua/upgrade.lua         |  20 +
 test-run                        |   2 +-
 test/box-py/bootstrap.result    |   4 +-
 test/box/access.result          |   2 +-
 test/box/access.test.lua        |   2 +-
 test/box/access_bin.result      |   2 +-
 test/box/access_bin.test.lua    |   2 +-
 test/box/access_misc.result     | 134 ++---
 test/box/access_sysview.result  |   8 +-
 test/box/errinj.result          |   5 +
 test/box/errinj.test.lua        |   3 +
 test/box/function1.result       | 183 ++++++-
 test/box/function1.test.lua     |  64 ++-
 test/sql-tap/alias.test.lua     |  11 +-
 test/sql-tap/check.test.lua     |  11 +-
 test/sql-tap/coalesce.test.lua  |   2 +-
 test/sql-tap/func.test.lua      |  22 +-
 test/sql-tap/func2.test.lua     |  18 +-
 test/sql-tap/func3.test.lua     |  12 +-
 test/sql-tap/func5.test.lua     |  61 ++-
 test/sql-tap/in1.test.lua       |   6 +-
 test/sql-tap/insert1.test.lua   |   2 +-
 test/sql-tap/limit.test.lua     |   4 +-
 test/sql-tap/lua_sql.test.lua   | 121 +++--
 test/sql-tap/misc1.test.lua     |   2 +-
 test/sql-tap/select1.test.lua   |  34 +-
 test/sql-tap/select2.test.lua   |   6 +-
 test/sql-tap/select3.test.lua   |   2 +-
 test/sql-tap/subquery.test.lua  |  21 +-
 test/sql-tap/trigger9.test.lua  |   8 +-
 test/sql-tap/where2.test.lua    |   4 +-
 test/sql-tap/with1.test.lua     |   2 +-
 test/sql/errinj.result          |  26 -
 test/sql/errinj.test.lua        |  10 -
 test/sql/func-recreate.result   |  41 +-
 test/sql/func-recreate.test.lua |  26 +-
 test/sql/icu-upper-lower.result |   4 +-
 test/sql/iproto.result          |   8 +-
 test/sql/iproto.test.lua        |   4 +-
 test/sql/types.result           |  12 +-
 test/sql/types.test.lua         |   6 +-
 test/wal_off/func_max.result    |   8 +-
 73 files changed, 1763 insertions(+), 1504 deletions(-)
 delete mode 100644 src/box/lua/lua_sql.h
 delete mode 100644 src/box/lua/lua_sql.c

-- 
2.22.1

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

* [tarantool-patches] [PATCH v3 1/9] sql: remove SQL_PreferBuiltin flag
  2019-08-16 13:26 [tarantool-patches] [PATCH v3 0/9] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
@ 2019-08-16 13:26 ` Kirill Shcherbatov
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 2/9] sql: GREATEST, LEAST instead of MIN/MAX overload Kirill Shcherbatov
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 19+ messages in thread
From: Kirill Shcherbatov @ 2019-08-16 13:26 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

The SQL_PreferBuiltin flag is redundant (because builtin names
are forbidden for UDFs) so we may to remove it.

Needed for #4113, #2200, #2233
---
 src/box/sql/sqlInt.h   | 1 -
 src/box/sql/callback.c | 9 +--------
 2 files changed, 1 insertion(+), 9 deletions(-)

diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 87e5d22ee..4adb30c5c 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -1196,7 +1196,6 @@ struct sql {
 #define SQL_ReverseOrder   0x00020000	/* Reverse unordered SELECTs */
 #define SQL_RecTriggers    0x00040000	/* Enable recursive triggers */
 #define SQL_AutoIndex      0x00100000	/* Enable automatic indexes */
-#define SQL_PreferBuiltin  0x00200000	/* Preference to built-in funcs */
 #define SQL_EnableTrigger  0x01000000	/* True to enable triggers */
 #define SQL_DeferFKs       0x02000000	/* Defer all FK constraints */
 #define SQL_VdbeEQP        0x08000000	/* Debug EXPLAIN QUERY PLAN */
diff --git a/src/box/sql/callback.c b/src/box/sql/callback.c
index 49197532e..6c272de71 100644
--- a/src/box/sql/callback.c
+++ b/src/box/sql/callback.c
@@ -191,7 +191,6 @@ sqlFindFunction(sql * db,	/* An open database */
 	int bestScore = 0;	/* Score of best match */
 	int h;			/* Hash value */
 	int nName;		/* Length of the name */
-	struct session *user_session = current_session();
 
 	assert(nArg >= (-2));
 	assert(nArg >= (-1) || createFlag == 0);
@@ -210,10 +209,6 @@ sqlFindFunction(sql * db,	/* An open database */
 	}
 
 	/* If no match is found, search the built-in functions.
-	 *
-	 * If the SQL_PreferBuiltin flag is set, then search the built-in
-	 * functions even if a prior app-defined function was found.  And give
-	 * priority to built-in functions.
 	 *
 	 * Except, if createFlag is true, that means that we are trying to
 	 * install a new function.  Whatever FuncDef structure is returned it will
@@ -221,9 +216,7 @@ sqlFindFunction(sql * db,	/* An open database */
 	 * new function.  But the FuncDefs for built-in functions are read-only.
 	 * So we must not search for built-ins when creating a new function.
 	 */
-	if (!createFlag &&
-	    (pBest == 0
-	     || (user_session->sql_flags & SQL_PreferBuiltin) != 0)) {
+	if (!createFlag && (pBest == NULL)) {
 		bestScore = 0;
 		h = (sqlUpperToLower[(u8) zName[0]] +
 		     nName) % SQL_FUNC_HASH_SZ;
-- 
2.22.1

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

* [tarantool-patches] [PATCH v3 2/9] sql: GREATEST, LEAST instead of MIN/MAX overload
  2019-08-16 13:26 [tarantool-patches] [PATCH v3 0/9] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 1/9] sql: remove SQL_PreferBuiltin flag Kirill Shcherbatov
@ 2019-08-16 13:26 ` Kirill Shcherbatov
  2019-08-16 18:57   ` [tarantool-patches] " n.pettik
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 3/9] sql: wrap all trim functions in dispatcher Kirill Shcherbatov
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 19+ messages in thread
From: Kirill Shcherbatov @ 2019-08-16 13:26 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

This patch does two things: renames existing scalar min/max
functions and reserves names for them in NoSQL cache.

Moreover it is an important step to get rid of function's name
overloading required for replace FuncDef cache with Tarantool's
function cache.

Closes #4405
Needed for #2200, #4113, #2233

@TarantoolBot document
Title: Scalar functions MIN/MAX are renamed to LEAST/GREATEST

The MIN/MAX functions are typically used only as aggregate
functions in other RDBMS(MSSQL, Postgress, MySQL, Oracle) while
Tarantool's SQLite legacy code use them also in meaning
GREATEST/LEAST scalar function. Now it fixed.
---
 src/box/sql/func.c             |   6 +-
 src/box/bootstrap.snap         | Bin 5907 -> 5934 bytes
 src/box/lua/upgrade.lua        |  20 +++++
 test-run                       |   2 +-
 test/box-py/bootstrap.result   |   4 +-
 test/box/access.result         |   2 +-
 test/box/access.test.lua       |   2 +-
 test/box/access_bin.result     |   2 +-
 test/box/access_bin.test.lua   |   2 +-
 test/box/access_misc.result    | 134 +++++++++++++++++----------------
 test/box/access_sysview.result |   8 +-
 test/box/errinj.result         |   5 ++
 test/box/errinj.test.lua       |   3 +
 test/box/function1.result      |   8 +-
 test/box/function1.test.lua    |   2 +-
 test/sql-tap/coalesce.test.lua |   2 +-
 test/sql-tap/func3.test.lua    |  12 +--
 test/sql-tap/func5.test.lua    |  34 ++++-----
 test/sql-tap/in1.test.lua      |   6 +-
 test/sql-tap/insert1.test.lua  |   2 +-
 test/sql-tap/misc1.test.lua    |   2 +-
 test/sql-tap/select1.test.lua  |  22 +++---
 test/sql-tap/select2.test.lua  |   6 +-
 test/sql-tap/select3.test.lua  |   2 +-
 test/sql-tap/with1.test.lua    |   2 +-
 test/sql/iproto.result         |   8 +-
 test/sql/iproto.test.lua       |   4 +-
 test/sql/types.result          |  12 +--
 test/sql/types.test.lua        |   6 +-
 test/wal_off/func_max.result   |   8 +-
 30 files changed, 180 insertions(+), 148 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index faf86bd4b..e00764c3f 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1843,12 +1843,10 @@ sqlRegisterBuiltinFunctions(void)
 		FUNCTION_COLL(trim, 1, 3, 0, trim_func_one_arg),
 		FUNCTION_COLL(trim, 2, 3, 0, trim_func_two_args),
 		FUNCTION_COLL(trim, 3, 3, 0, trim_func_three_args),
-		FUNCTION(min, -1, 0, 1, minmaxFunc, FIELD_TYPE_SCALAR),
-		FUNCTION(min, 0, 0, 1, 0, FIELD_TYPE_SCALAR),
+		FUNCTION(least, -1, 0, 1, minmaxFunc, FIELD_TYPE_SCALAR),
 		AGGREGATE2(min, 1, 0, 1, minmaxStep, minMaxFinalize,
 			   SQL_FUNC_MINMAX, FIELD_TYPE_SCALAR),
-		FUNCTION(max, -1, 1, 1, minmaxFunc, FIELD_TYPE_SCALAR),
-		FUNCTION(max, 0, 1, 1, 0, FIELD_TYPE_SCALAR),
+		FUNCTION(greatest, -1, 1, 1, minmaxFunc, FIELD_TYPE_SCALAR),
 		AGGREGATE2(max, 1, 1, 1, minmaxStep, minMaxFinalize,
 			   SQL_FUNC_MINMAX, FIELD_TYPE_SCALAR),
 		FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQL_FUNC_TYPEOF,
diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap

diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index 046eb3ee4..2abd75dff 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -910,6 +910,25 @@ local function upgrade_to_2_2_1()
     create_func_index()
 end
 
+--------------------------------------------------------------------------------
+-- Tarantool 2.3.0
+--------------------------------------------------------------------------------
+
+local function upgrade_to_2_3_0()
+    log.info("Create GREATEST and LEAST SQL Builtins")
+    local _func = box.space[box.schema.FUNC_ID]
+    local _priv = box.space[box.schema.PRIV_ID]
+    local datetime = os.date("%Y-%m-%d %H:%M:%S")
+    local new_builtins = {"GREATEST", "LEAST"}
+    for _, v in pairs(new_builtins) do
+        local t = _func:auto_increment({ADMIN, v, 1, 'SQL_BUILTIN', '',
+                                       'function', {}, 'any', 'none', 'none',
+                                        false, false, true, {}, setmap({}), '',
+                                        datetime, datetime})
+        _priv:replace{ADMIN, PUBLIC, 'function', t.id, box.priv.X}
+    end
+end
+
 --------------------------------------------------------------------------------
 
 local function get_version()
@@ -944,6 +963,7 @@ local function upgrade(options)
         {version = mkversion(2, 1, 2), func = upgrade_to_2_1_2, auto = true},
         {version = mkversion(2, 1, 3), func = upgrade_to_2_1_3, auto = true},
         {version = mkversion(2, 2, 1), func = upgrade_to_2_2_1, auto = true},
+        {version = mkversion(2, 3, 0), func = upgrade_to_2_3_0, auto = true},
     }
 
     for _, handler in ipairs(handlers) do
diff --git a/test-run b/test-run
index e22daf92d..39ee6e4b8 160000
--- a/test-run
+++ b/test-run
@@ -1 +1 @@
-Subproject commit e22daf92db64f968eea2678a635b36ddae0cff3c
+Subproject commit 39ee6e4b827b3a3493592089d76c1a66afd694f0
diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
index 7fc0ca97d..a59979e62 100644
--- a/test/box-py/bootstrap.result
+++ b/test/box-py/bootstrap.result
@@ -4,7 +4,7 @@ box.internal.bootstrap()
 box.space._schema:select{}
 ---
 - - ['max_id', 511]
-  - ['version', 2, 2, 1]
+  - ['version', 2, 3, 0]
 ...
 box.space._cluster:select{}
 ---
@@ -236,6 +236,8 @@ box.space._priv:select{}
   - [1, 2, 'function', 63, 4]
   - [1, 2, 'function', 64, 4]
   - [1, 2, 'function', 65, 4]
+  - [1, 2, 'function', 66, 4]
+  - [1, 2, 'function', 67, 4]
   - [1, 2, 'space', 276, 2]
   - [1, 2, 'space', 277, 1]
   - [1, 2, 'space', 281, 1]
diff --git a/test/box/access.result b/test/box/access.result
index ba72b5f74..3976adfde 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -691,7 +691,7 @@ box.schema.func.exists(1)
 ---
 - true
 ...
-box.schema.func.exists(66)
+box.schema.func.exists(68)
 ---
 - false
 ...
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index 219cdb04a..89a63c904 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -276,7 +276,7 @@ box.schema.user.exists{}
 box.schema.func.exists('nosuchfunc')
 box.schema.func.exists('guest')
 box.schema.func.exists(1)
-box.schema.func.exists(66)
+box.schema.func.exists(68)
 box.schema.func.exists('box.schema.user.info')
 box.schema.func.exists()
 box.schema.func.exists(nil)
diff --git a/test/box/access_bin.result b/test/box/access_bin.result
index 3fc0749f7..9f3ec8ada 100644
--- a/test/box/access_bin.result
+++ b/test/box/access_bin.result
@@ -299,7 +299,7 @@ box.schema.user.grant('guest', 'execute', 'universe')
 function f1() return box.space._func:get(1)[4] end
 ---
 ...
-function f2() return box.space._func:get(66)[4] end
+function f2() return box.space._func:get(68)[4] end
 ---
 ...
 box.schema.func.create('f1')
diff --git a/test/box/access_bin.test.lua b/test/box/access_bin.test.lua
index 48528c07f..6ca752dd4 100644
--- a/test/box/access_bin.test.lua
+++ b/test/box/access_bin.test.lua
@@ -113,7 +113,7 @@ test:drop()
 -- notice that guest can execute stuff, but can't read space _func
 box.schema.user.grant('guest', 'execute', 'universe')
 function f1() return box.space._func:get(1)[4] end
-function f2() return box.space._func:get(66)[4] end
+function f2() return box.space._func:get(68)[4] end
 box.schema.func.create('f1')
 box.schema.func.create('f2',{setuid=true})
 c = net.connect(box.cfg.listen)
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index cfb72a9a6..a1b6435bc 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -839,136 +839,140 @@ box.space._space:select()
 box.space._func:select()
 ---
 session = nil
 ---
diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result
index a82127ebb..3072b7394 100644
--- a/test/box/access_sysview.result
+++ b/test/box/access_sysview.result
@@ -258,11 +258,11 @@ box.session.su('guest')
 ...
 #box.space._vpriv:select{}
 ---
-- 80
+- 82
 ...
 #box.space._vfunc:select{}
 ---
-- 65
+- 67
 ...
 #box.space._vcollation:select{}
 ---
@@ -290,11 +290,11 @@ box.session.su('guest')
 ...
 #box.space._vpriv:select{}
 ---
-- 80
+- 82
 ...
 #box.space._vfunc:select{}
 ---
-- 65
+- 67
 ...
 #box.space._vsequence:select{}
 ---
diff --git a/test/box/errinj.result b/test/box/errinj.result
index 5784758da..eaeb7d1e8 100644
--- a/test/box/errinj.result
+++ b/test/box/errinj.result
@@ -4,6 +4,11 @@ errinj = box.error.injection
 net_box = require('net.box')
 ---
 ...
+-- Test that recovery had been completed without errors
+box.error.last() == nil
+---
+- true
+...
 space = box.schema.space.create('tweedledum')
 ---
 ...
diff --git a/test/box/errinj.test.lua b/test/box/errinj.test.lua
index 093168b7d..620f7c880 100644
--- a/test/box/errinj.test.lua
+++ b/test/box/errinj.test.lua
@@ -1,6 +1,9 @@
 errinj = box.error.injection
 net_box = require('net.box')
 
+-- Test that recovery had been completed without errors
+box.error.last() == nil
+
 space = box.schema.space.create('tweedledum')
 index = space:create_index('primary', { type = 'hash' })
 
diff --git a/test/box/function1.result b/test/box/function1.result
index 00e5880cd..5b091f72b 100644
--- a/test/box/function1.result
+++ b/test/box/function1.result
@@ -97,7 +97,7 @@ box.func["function1.args"]
   exports:
     lua: true
     sql: false
-  id: 66
+  id: 68
   setuid: false
   is_multikey: false
   is_deterministic: false
@@ -417,7 +417,7 @@ func
   exports:
     lua: true
     sql: false
-  id: 66
+  id: 68
   setuid: false
   is_multikey: false
   is_deterministic: false
@@ -489,7 +489,7 @@ func
   exports:
     lua: true
     sql: false
-  id: 66
+  id: 68
   setuid: false
   is_multikey: false
   is_deterministic: false
@@ -806,7 +806,7 @@ sql_builtin_list = {
 	"RANDOMBLOB", "NULLIF", "ZEROBLOB", "MIN", "MAX", "COALESCE", "EVERY",
 	"EXISTS", "EXTRACT", "SOME", "GREATER", "LESSER", "SOUNDEX",
 	"LIKELIHOOD", "LIKELY", "UNLIKELY", "_sql_stat_get", "_sql_stat_push",
-	"_sql_stat_init",
+	"_sql_stat_init", "GREATEST", "LEAST"
 }
 test_run:cmd("setopt delimiter ''");
 ---
diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua
index 5eb597d16..f894472f8 100644
--- a/test/box/function1.test.lua
+++ b/test/box/function1.test.lua
@@ -285,7 +285,7 @@ sql_builtin_list = {
 	"RANDOMBLOB", "NULLIF", "ZEROBLOB", "MIN", "MAX", "COALESCE", "EVERY",
 	"EXISTS", "EXTRACT", "SOME", "GREATER", "LESSER", "SOUNDEX",
 	"LIKELIHOOD", "LIKELY", "UNLIKELY", "_sql_stat_get", "_sql_stat_push",
-	"_sql_stat_init",
+	"_sql_stat_init", "GREATEST", "LEAST"
 }
 test_run:cmd("setopt delimiter ''");
 ok = true
diff --git a/test/sql-tap/coalesce.test.lua b/test/sql-tap/coalesce.test.lua
index 5740c1b37..5501e6992 100755
--- a/test/sql-tap/coalesce.test.lua
+++ b/test/sql-tap/coalesce.test.lua
@@ -103,7 +103,7 @@ test:do_test(
     "coalesce-1.6",
     function()
         return test:execsql [[
-            SELECT coalesce(b,NOT b,-b,abs(b),lower(b),length(b),min(b,5),b*123,c)
+            SELECT coalesce(b,NOT b,-b,abs(b),lower(b),length(b),LEAST(b,5),b*123,c)
               FROM t1 ORDER BY a;
         ]]
     end, {
diff --git a/test/sql-tap/func3.test.lua b/test/sql-tap/func3.test.lua
index 2d0579d93..95d96965e 100755
--- a/test/sql-tap/func3.test.lua
+++ b/test/sql-tap/func3.test.lua
@@ -135,8 +135,8 @@ test:do_catchsql_test(
 test:do_test(
     "func3-5.20",
     function()
-        return test:execsql "EXPLAIN SELECT likelihood(min(1.0+'2.0',4*11), 0.5)"
-    end, test:execsql("EXPLAIN SELECT min(1.0+'2.0',4*11)"))
+        return test:execsql "EXPLAIN SELECT likelihood(LEAST(1.0+'2.0',4*11), 0.5)"
+    end, test:execsql("EXPLAIN SELECT LEAST(1.0+'2.0',4*11)"))
 
 -- EVIDENCE-OF: R-11152-23456 The unlikely(X) function returns the
 -- argument X unchanged.
@@ -208,8 +208,8 @@ test:do_execsql_test(
 test:do_test(
     "func3-5.39",
     function()
-        return test:execsql "EXPLAIN SELECT unlikely(min(1.0+'2.0',4*11))"
-    end, test:execsql "EXPLAIN SELECT min(1.0+'2.0',4*11)")
+        return test:execsql "EXPLAIN SELECT unlikely(LEAST(1.0+'2.0',4*11))"
+    end, test:execsql "EXPLAIN SELECT LEAST(1.0+'2.0',4*11)")
 
 -- EVIDENCE-OF: R-23735-03107 The likely(X) function returns the argument
 -- X unchanged.
@@ -281,8 +281,8 @@ test:do_execsql_test(
 test:do_test(
     "func3-5.59",
     function()
-        return test:execsql "EXPLAIN SELECT likely(min(1.0+'2.0',4*11))"
-    end, test:execsql "EXPLAIN SELECT min(1.0+'2.0',4*11)")
+        return test:execsql "EXPLAIN SELECT likely(LEAST(1.0+'2.0',4*11))"
+    end, test:execsql "EXPLAIN SELECT LEAST(1.0+'2.0',4*11)")
 
 
 --
diff --git a/test/sql-tap/func5.test.lua b/test/sql-tap/func5.test.lua
index 6da089994..0b255e659 100755
--- a/test/sql-tap/func5.test.lua
+++ b/test/sql-tap/func5.test.lua
@@ -98,13 +98,13 @@ test:do_execsql_test(
         -- </func5-2.2>
     })
 
--- The following tests ensures that max() and min() functions
--- raise error if argument's collations are incompatible.
+-- The following tests ensures that GREATEST() and LEAST()
+-- functions raise error if argument's collations are incompatible.
 
 test:do_catchsql_test(
     "func-5-3.1",
     [[
-        SELECT max('a' COLLATE "unicode", 'A' COLLATE "unicode_ci");
+        SELECT GREATEST('a' COLLATE "unicode", 'A' COLLATE "unicode_ci");
     ]],
     {
         -- <func5-3.1>
@@ -120,7 +120,7 @@ test:do_catchsql_test(
         CREATE TABLE test2 (s2 VARCHAR(5) PRIMARY KEY COLLATE "unicode_ci");
         INSERT INTO test1 VALUES ('a');
         INSERT INTO test2 VALUES ('a');
-        SELECT max(s1, s2) FROM test1 JOIN test2;
+        SELECT GREATEST(s1, s2) FROM test1 JOIN test2;
     ]],
     {
         -- <func5-3.2>
@@ -132,7 +132,7 @@ test:do_catchsql_test(
 test:do_catchsql_test(
     "func-5-3.3",
     [[
-        SELECT max ('abc', 'asd' COLLATE "binary", 'abc' COLLATE "unicode")
+        SELECT GREATEST ('abc', 'asd' COLLATE "binary", 'abc' COLLATE "unicode")
     ]],
     {
         -- <func5-3.3>
@@ -144,7 +144,7 @@ test:do_catchsql_test(
 test:do_execsql_test(
     "func-5-3.4",
     [[
-        SELECT max (s1, 'asd' COLLATE "binary", s2) FROM test1 JOIN test2;
+        SELECT GREATEST (s1, 'asd' COLLATE "binary", s2) FROM test1 JOIN test2;
     ]], {
         -- <func5-3.4>
         "asd"
@@ -161,7 +161,7 @@ test:do_catchsql_test(
         INSERT INTO test3 VALUES ('a');
         INSERT INTO test4 VALUES ('a');
         INSERT INTO test5 VALUES ('a');
-        SELECT max(s3, s4, s5) FROM test3 JOIN test4 JOIN test5;
+        SELECT GREATEST(s3, s4, s5) FROM test3 JOIN test4 JOIN test5;
     ]],
     {
         -- <func5-3.5>
@@ -173,7 +173,7 @@ test:do_catchsql_test(
 test:do_catchsql_test(
     "func-5-3.6",
     [[
-        SELECT min('a' COLLATE "unicode", 'A' COLLATE "unicode_ci");
+        SELECT LEAST('a' COLLATE "unicode", 'A' COLLATE "unicode_ci");
     ]],
     {
         -- <func5-3.6>
@@ -185,7 +185,7 @@ test:do_catchsql_test(
 test:do_catchsql_test(
     "func-5-3.7",
     [[
-        SELECT min(s1, s2) FROM test1 JOIN test2;
+        SELECT LEAST(s1, s2) FROM test1 JOIN test2;
     ]],
     {
         -- <func5-3.7>
@@ -197,7 +197,7 @@ test:do_catchsql_test(
 test:do_catchsql_test(
     "func-5-3.8",
     [[
-        SELECT min ('abc', 'asd' COLLATE "binary", 'abc' COLLATE "unicode")
+        SELECT LEAST('abc', 'asd' COLLATE "binary", 'abc' COLLATE "unicode")
     ]],
     {
         -- <func5-3.8>
@@ -209,7 +209,7 @@ test:do_catchsql_test(
 test:do_execsql_test(
     "func-5-3.9",
     [[
-        SELECT min (s1, 'asd' COLLATE "binary", s2) FROM test1 JOIN test2;
+        SELECT LEAST(s1, 'asd' COLLATE "binary", s2) FROM test1 JOIN test2;
     ]], {
         -- <func5-3.9>
         "a"
@@ -220,7 +220,7 @@ test:do_execsql_test(
 test:do_catchsql_test(
     "func-5.3.10",
     [[
-        SELECT min(s3, s4, s5) FROM test3 JOIN test4 JOIN test5;
+        SELECT LEAST(s3, s4, s5) FROM test3 JOIN test4 JOIN test5;
     ]],
     {
         -- <func5-3.10>
@@ -229,32 +229,32 @@ test:do_catchsql_test(
     }
 )
 
--- Order of arguments of min/max functions doesn't affect
+-- Order of arguments of LEAST/GREATEST functions doesn't affect
 -- the result: boolean is always less than numbers, which
 -- are less than strings.
 --
 test:do_execsql_test(
     "func-5-4.1",
     [[
-        SELECT max (false, 'STR', 1, 0.5);
+        SELECT GREATEST (false, 'STR', 1, 0.5);
     ]], { "STR" } )
 
 test:do_execsql_test(
     "func-5-4.2",
     [[
-        SELECT max ('STR', 1, 0.5, false);
+        SELECT GREATEST ('STR', 1, 0.5, false);
     ]], { "STR" } )
 
 test:do_execsql_test(
     "func-5-4.3",
     [[
-        SELECT min ('STR', 1, 0.5, false);
+        SELECT LEAST('STR', 1, 0.5, false);
     ]], { false } )
 
 test:do_execsql_test(
     "func-5-4.4",
     [[
-        SELECT min (false, 'STR', 1, 0.5);
+        SELECT LEAST(false, 'STR', 1, 0.5);
     ]], { false } )
 
 test:finish_test()
diff --git a/test/sql-tap/in1.test.lua b/test/sql-tap/in1.test.lua
index ba4c06936..570cc1779 100755
--- a/test/sql-tap/in1.test.lua
+++ b/test/sql-tap/in1.test.lua
@@ -177,7 +177,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "in-2.7",
     [[
-        SELECT a FROM t1 WHERE b IN (max(5,10,b),20)
+        SELECT a FROM t1 WHERE b IN (GREATEST(5,10,b),20)
     ]], {
         -- <in-2.7>
         4, 5, 6, 7, 8, 9, 10
@@ -197,7 +197,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "in-2.9",
     [[
-        SELECT a FROM t1 WHERE b IN (max(5,10),20)
+        SELECT a FROM t1 WHERE b IN (GREATEST(5,10),20)
     ]], {
         -- <in-2.9>
         
@@ -207,7 +207,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "in-2.10",
     [[
-        SELECT a FROM t1 WHERE min(0, CAST(b IN (a,30) AS INT)) <> 0
+        SELECT a FROM t1 WHERE LEAST(0, CAST(b IN (a,30) AS INT)) <> 0
     ]], {
         -- <in-2.10>
         
diff --git a/test/sql-tap/insert1.test.lua b/test/sql-tap/insert1.test.lua
index 5316b858b..d28baf11a 100755
--- a/test/sql-tap/insert1.test.lua
+++ b/test/sql-tap/insert1.test.lua
@@ -273,7 +273,7 @@ test:do_catchsql_test("insert-4.6", [[
 })
 
 test:do_execsql_test("insert-4.7", [[
-  INSERT INTO t3 VALUES(min(1,2,3),max(1,2,3),99);
+  INSERT INTO t3 VALUES(LEAST(1,2,3),GREATEST(1,2,3),99);
   SELECT * FROM t3 WHERE c=99;
 ]], {
   -- <insert-4.7>
diff --git a/test/sql-tap/misc1.test.lua b/test/sql-tap/misc1.test.lua
index d5e17ce01..b84093e3c 100755
--- a/test/sql-tap/misc1.test.lua
+++ b/test/sql-tap/misc1.test.lua
@@ -330,7 +330,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "misc1-6.4",
     [[
-        SELECT abort+"asc",max(key,"pragma",temp) FROM t4
+        SELECT abort+"asc",GREATEST(key,"pragma",temp) FROM t4
     ]], {
         -- <misc1-6.4>
         3, 17
diff --git a/test/sql-tap/select1.test.lua b/test/sql-tap/select1.test.lua
index 87689a584..924c0ccb1 100755
--- a/test/sql-tap/select1.test.lua
+++ b/test/sql-tap/select1.test.lua
@@ -105,7 +105,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select1-1.8.2",
     [[
-        SELECT *, min(f1,f2), max(f1,f2) FROM test1
+        SELECT *, LEAST(f1,f2), GREATEST(f1,f2) FROM test1
     ]], {
         -- <select1-1.8.2>
         11, 22, 11, 22
@@ -197,7 +197,7 @@ test:do_execsql_test(
 
 test:do_execsql_test(
     "select1-1.12",
-    [[SELECT max(test1.f1,test2.r1), min(test1.f2,test2.r2)
+    [[SELECT GREATEST(test1.f1,test2.r1), LEAST(test1.f2,test2.r2)
            FROM test2, test1]], {
         -- <select1-1.12>
         11, 2
@@ -206,7 +206,7 @@ test:do_execsql_test(
 
 test:do_execsql_test(
     "select1-1.13",
-    [[SELECT min(test1.f1,test2.r1), max(test1.f2,test2.r2)
+    [[SELECT LEAST(test1.f1,test2.r1), GREATEST(test1.f2,test2.r2)
            FROM test1, test2]], {
         -- <select1-1.13>
         1, 22
@@ -343,7 +343,7 @@ test:do_test(
     function()
         local msg
         local v = pcall(function()
-            msg = test:execsql "SELECT MIN(f1,f2) FROM test1"
+            msg = test:execsql "SELECT LEAST(f1,f2) FROM test1"
             end)
         v = v == true and {0} or {1} 
         return table.insert(v,table.sort(msg) or msg) or v
@@ -408,7 +408,7 @@ test:do_test(
     function()
         local msg
         local v = pcall(function()
-            msg = test:execsql "SELECT max(f1,f2) FROM test1"
+            msg = test:execsql "SELECT GREATEST(f1,f2) FROM test1"
             end)
         v = v == true and {0} or {1} 
         return table.insert(v,table.sort(msg) or msg) or v
@@ -423,7 +423,7 @@ test:do_test(
     function()
         local msg
         local v = pcall(function()
-            msg = test:execsql "SELECT MAX(f1,f2)+1 FROM test1"
+            msg = test:execsql "SELECT GREATEST(f1,f2)+1 FROM test1"
             end)
         v = v == true and {0} or {1} 
         return table.insert(v,table.sort(msg) or msg) or v
@@ -526,7 +526,7 @@ test:do_catchsql_test(
 test:do_catchsql_test(
     "select1-2.19",
     [[
-        SELECT SUM(min(f1,f2)) FROM test1
+        SELECT SUM(LEAST(f1,f2)) FROM test1
     ]], {
         -- <select1-2.19>
         0, {44}
@@ -660,7 +660,7 @@ test:do_test(
     function()
         local msg
         local v = pcall(function()
-            msg = test:execsql "SELECT f1 FROM test1 WHERE min(f1,f2)!=11"
+            msg = test:execsql "SELECT f1 FROM test1 WHERE LEAST(f1,f2)!=11"
             end)
         v = v == true and {0} or {1} 
         return table.insert(v,table.sort(msg) or msg) or v
@@ -675,7 +675,7 @@ test:do_test(
     function()
         local msg
         local v = pcall(function()
-            msg = test:execsql "SELECT f1 FROM test1 WHERE max(f1,f2)!=11"
+            msg = test:execsql "SELECT f1 FROM test1 WHERE GREATEST(f1,f2)!=11"
             end)
         v = v == true and {0} or {1} 
         return table.insert(v,table.sort(msg) or msg) or v
@@ -720,7 +720,7 @@ test:do_catchsql_test(
 test:do_catchsql_test(
     "select1-4.3",
     [[
-        SELECT f1 FROM test1 ORDER BY min(f1,f2)
+        SELECT f1 FROM test1 ORDER BY LEAST(f1,f2)
     ]], {
         -- <select1-4.3>
         0, {11, 33}
@@ -1546,7 +1546,7 @@ end
 test:do_execsql_test(
     "select1-8.5",
     [[
-        SELECT min(1,2,3), -max(1,2,3)
+        SELECT LEAST(1,2,3), -GREATEST(1,2,3)
         FROM test1 ORDER BY f1
     ]], {
         -- <select1-8.5>
diff --git a/test/sql-tap/select2.test.lua b/test/sql-tap/select2.test.lua
index e08c8b3b6..24fad2149 100755
--- a/test/sql-tap/select2.test.lua
+++ b/test/sql-tap/select2.test.lua
@@ -201,7 +201,7 @@ test:do_execsql_test(
         INSERT INTO aa VALUES(3);
         INSERT INTO bb VALUES(2);
         INSERT INTO bb VALUES(4);
-        SELECT * FROM aa, bb WHERE max(a,b)>2;
+        SELECT * FROM aa, bb WHERE GREATEST(a,b)>2;
     ]], {
         -- <select2-4.1>
         1, 4, 3, 2, 3, 4
@@ -232,7 +232,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select2-4.4",
     [[
-        SELECT * FROM aa, bb WHERE min(a,b) <> 0;
+        SELECT * FROM aa, bb WHERE LEAST(a,b) <> 0;
     ]], {
         -- <select2-4.4>
         1, 2, 1, 4, 3, 2, 3, 4
@@ -242,7 +242,7 @@ test:do_execsql_test(
 test:do_execsql_test(
     "select2-4.5",
     [[
-        SELECT * FROM aa, bb WHERE NOT min(a,b) <> 0;
+        SELECT * FROM aa, bb WHERE NOT LEAST(a,b) <> 0;
     ]], {
         -- <select2-4.5>
         1, 0, 3, 0
diff --git a/test/sql-tap/select3.test.lua b/test/sql-tap/select3.test.lua
index b51a9e8b6..19f853dc7 100755
--- a/test/sql-tap/select3.test.lua
+++ b/test/sql-tap/select3.test.lua
@@ -271,7 +271,7 @@ test:do_execsql_test("select3-5.1", [[
 test:do_execsql_test("select3-5.2", [[
   SELECT log, count(*), avg(n), max(n+log*2) FROM t1 
   GROUP BY log 
-  ORDER BY max(n+log*2)+0, min(log,avg(n))+0
+  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
diff --git a/test/sql-tap/with1.test.lua b/test/sql-tap/with1.test.lua
index 6985c589e..f82b73e63 100755
--- a/test/sql-tap/with1.test.lua
+++ b/test/sql-tap/with1.test.lua
@@ -547,7 +547,7 @@ test:do_execsql_test("8.1-mandelbrot", [[
       SELECT max(iter), cx, cy FROM m GROUP BY cx, cy
     ),
     a(t) AS (
-      SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '') 
+      SELECT group_concat( substr(' .+*#', 1+LEAST(iter/7,4), 1), '')
       FROM m2 GROUP BY cy
     )
   SELECT group_concat(TRIM(TRAILING FROM t),x'0a') FROM a;
diff --git a/test/sql/iproto.result b/test/sql/iproto.result
index f43e87117..1e5c30aec 100644
--- a/test/sql/iproto.result
+++ b/test/sql/iproto.result
@@ -788,18 +788,18 @@ res.metadata
 -- arguments of all scalar type, we can't say nothing more than
 -- SCALAR.
 --
-cn:execute("SELECT min(1, 2, 3);")
+cn:execute("SELECT LEAST(1, 2, 3);")
 ---
 - metadata:
-  - name: min(1, 2, 3)
+  - name: LEAST(1, 2, 3)
     type: scalar
   rows:
   - [1]
 ...
-cn:execute("SELECT max(1, 2, 3);")
+cn:execute("SELECT GREATEST(1, 2, 3);")
 ---
 - metadata:
-  - name: max(1, 2, 3)
+  - name: GREATEST(1, 2, 3)
     type: scalar
   rows:
   - [3]
diff --git a/test/sql/iproto.test.lua b/test/sql/iproto.test.lua
index dd60afe79..5dfe95ccc 100644
--- a/test/sql/iproto.test.lua
+++ b/test/sql/iproto.test.lua
@@ -240,8 +240,8 @@ res.metadata
 -- arguments of all scalar type, we can't say nothing more than
 -- SCALAR.
 --
-cn:execute("SELECT min(1, 2, 3);")
-cn:execute("SELECT max(1, 2, 3);")
+cn:execute("SELECT LEAST(1, 2, 3);")
+cn:execute("SELECT GREATEST(1, 2, 3);")
 
 cn:close()
 box.execute('DROP TABLE t1')
diff --git a/test/sql/types.result b/test/sql/types.result
index 83820af53..65b3ba79d 100644
--- a/test/sql/types.result
+++ b/test/sql/types.result
@@ -680,10 +680,10 @@ box.execute("SELECT quote(b) FROM t;")
   - ['false']
   - ['NULL']
 ...
-box.execute("SELECT min(b, true) FROM t;")
+box.execute("SELECT LEAST(b, true) FROM t;")
 ---
 - metadata:
-  - name: min(b, true)
+  - name: LEAST(b, true)
     type: scalar
   rows:
   - [true]
@@ -1550,10 +1550,10 @@ box.execute("SELECT quote(i) FROM t;")
   rows:
   - [18446744073709551613]
 ...
-box.execute("SELECT min(-1, i) FROM t;")
+box.execute("SELECT LEAST(-1, i) FROM t;")
 ---
 - metadata:
-  - name: min(-1, i)
+  - name: LEAST(-1, i)
     type: scalar
   rows:
   - [-1]
@@ -2023,10 +2023,10 @@ box.execute("SELECT quote(v) FROM t;")
   rows:
   - ['X''616263''']
 ...
-box.execute("SELECT min(v, x'') FROM t;")
+box.execute("SELECT LEAST(v, x'') FROM t;")
 ---
 - metadata:
-  - name: min(v, x'')
+  - name: LEAST(v, x'')
     type: scalar
   rows:
   - ['']
diff --git a/test/sql/types.test.lua b/test/sql/types.test.lua
index 0d005b377..2ba37c092 100644
--- a/test/sql/types.test.lua
+++ b/test/sql/types.test.lua
@@ -146,7 +146,7 @@ box.execute("SELECT upper(b) FROM t;")
 box.execute("SELECT abs(b) FROM t;")
 box.execute("SELECT typeof(b) FROM t;")
 box.execute("SELECT quote(b) FROM t;")
-box.execute("SELECT min(b, true) FROM t;")
+box.execute("SELECT LEAST(b, true) FROM t;")
 box.execute("SELECT quote(b) FROM t;")
 
 -- Test index search using boolean values.
@@ -335,7 +335,7 @@ box.execute("SELECT upper(i) FROM t;")
 box.execute("SELECT abs(i) FROM t;")
 box.execute("SELECT typeof(i) FROM t;")
 box.execute("SELECT quote(i) FROM t;")
-box.execute("SELECT min(-1, i) FROM t;")
+box.execute("SELECT LEAST(-1, i) FROM t;")
 box.execute("SELECT quote(i) FROM t;")
 
 box.execute("CREATE INDEX i ON t(i);")
@@ -444,7 +444,7 @@ box.execute("SELECT upper(v) FROM t;")
 box.execute("SELECT abs(v) FROM t;")
 box.execute("SELECT typeof(v) FROM t;")
 box.execute("SELECT quote(v) FROM t;")
-box.execute("SELECT min(v, x'') FROM t;")
+box.execute("SELECT LEAST(v, x'') FROM t;")
 
 box.execute("CREATE INDEX iv ON t(v);")
 box.execute("SELECT v FROM t WHERE v = x'616263';")
diff --git a/test/wal_off/func_max.result b/test/wal_off/func_max.result
index a3ab5b431..78db38d6b 100644
--- a/test/wal_off/func_max.result
+++ b/test/wal_off/func_max.result
@@ -42,11 +42,11 @@ test_run:cmd("setopt delimiter ''");
 ...
 func_limit()
 ---
-- error: 'Failed to create function ''func31936'': function id is too big'
+- error: 'Failed to create function ''func31934'': function id is too big'
 ...
 drop_limit_func()
 ---
-- error: Function 'func31936' does not exist
+- error: Function 'func31934' does not exist
 ...
 box.schema.user.create('testuser')
 ---
@@ -62,11 +62,11 @@ session.su('testuser')
 ...
 func_limit()
 ---
-- error: 'Failed to create function ''func31936'': function id is too big'
+- error: 'Failed to create function ''func31934'': function id is too big'
 ...
 drop_limit_func()
 ---
-- error: Function 'func31936' does not exist
+- error: Function 'func31934' does not exist
 ...
 session.su('admin')
 ---
-- 
2.22.1

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

* [tarantool-patches] [PATCH v3 3/9] sql: wrap all trim functions in dispatcher
  2019-08-16 13:26 [tarantool-patches] [PATCH v3 0/9] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 1/9] sql: remove SQL_PreferBuiltin flag Kirill Shcherbatov
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 2/9] sql: GREATEST, LEAST instead of MIN/MAX overload Kirill Shcherbatov
@ 2019-08-16 13:26 ` Kirill Shcherbatov
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 4/9] sql: rework SQL_FUNC_COUNT flag semantics Kirill Shcherbatov
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 19+ messages in thread
From: Kirill Shcherbatov @ 2019-08-16 13:26 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

A new dispatcher function trim_func calls corresponding trim_
function implementation in relation with number of argc - a count
of arguments.

This is an important step to get rid of function's name
overloading required for replace FuncDef cache with Tarantool's
function cache.

Needed for #2200, #4113, #2233
---
 src/box/sql/func.c | 80 ++++++++++++++++++++++++++++------------------
 1 file changed, 49 insertions(+), 31 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index e00764c3f..6bd2d0cd7 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1404,21 +1404,19 @@ trim_prepare_char_len(struct sql_context *context,
  * the trimming set.
  */
 static void
-trim_func_one_arg(struct sql_context *context, int argc, sql_value **argv)
+trim_func_one_arg(struct sql_context *context, sql_value *arg)
 {
-	assert(argc == 1);
-	(void) argc;
 	/* In case of VARBINARY type default trim octet is X'00'. */
 	const unsigned char *default_trim;
-	enum mp_type val_type = sql_value_type(argv[0]);
+	enum mp_type val_type = sql_value_type(arg);
 	if (val_type == MP_NIL)
 		return;
 	if (val_type == MP_BIN)
 		default_trim = (const unsigned char *) "\0";
 	else
 		default_trim = (const unsigned char *) " ";
-	int input_str_sz = sql_value_bytes(argv[0]);
-	const unsigned char *input_str = sql_value_text(argv[0]);
+	int input_str_sz = sql_value_bytes(arg);
+	const unsigned char *input_str = sql_value_text(arg);
 	uint8_t trim_char_len[1] = { 1 };
 	trim_procedure(context, TRIM_BOTH, default_trim, trim_char_len, 1,
 		       input_str, input_str_sz);
@@ -1436,24 +1434,21 @@ trim_func_one_arg(struct sql_context *context, int argc, sql_value **argv)
  * procedure with the specified side and " " as the trimming set.
  */
 static void
-trim_func_two_args(struct sql_context *context, int argc, sql_value **argv)
+trim_func_two_args(struct sql_context *context, sql_value *arg1,
+		   sql_value *arg2)
 {
-	assert(argc == 2);
-	(void) argc;
-
 	const unsigned char *input_str, *trim_set;
-	if ((input_str = sql_value_text(argv[1])) == NULL)
+	if ((input_str = sql_value_text(arg2)) == NULL)
 		return;
 
-	int input_str_sz = sql_value_bytes(argv[1]);
-	if (sql_value_type(argv[0]) == MP_INT ||
-	    sql_value_type(argv[0]) == MP_UINT) {
+	int input_str_sz = sql_value_bytes(arg2);
+	if (sql_value_type(arg1) == MP_INT || sql_value_type(arg1) == MP_UINT) {
 		uint8_t len_one = 1;
-		trim_procedure(context, sql_value_int(argv[0]),
+		trim_procedure(context, sql_value_int(arg1),
 			       (const unsigned char *) " ", &len_one, 1,
 			       input_str, input_str_sz);
-	} else if ((trim_set = sql_value_text(argv[0])) != NULL) {
-		int trim_set_sz = sql_value_bytes(argv[0]);
+	} else if ((trim_set = sql_value_text(arg1)) != NULL) {
+		int trim_set_sz = sql_value_bytes(arg1);
 		uint8_t *char_len;
 		int char_cnt = trim_prepare_char_len(context, trim_set,
 						     trim_set_sz, &char_len);
@@ -1473,29 +1468,52 @@ trim_func_two_args(struct sql_context *context, int argc, sql_value **argv)
  * call trimming procedure with that args.
  */
 static void
-trim_func_three_args(struct sql_context *context, int argc, sql_value **argv)
+trim_func_three_args(struct sql_context *context, sql_value *arg1,
+		     sql_value *arg2, sql_value *arg3)
 {
-	assert(argc == 3);
-	(void) argc;
-
-	assert(sql_value_type(argv[0]) == MP_INT ||
-	       sql_value_type(argv[0]) == MP_UINT);
+	assert(sql_value_type(arg1) == MP_INT || sql_value_type(arg1) == MP_UINT);
 	const unsigned char *input_str, *trim_set;
-	if ((input_str = sql_value_text(argv[2])) == NULL ||
-	    (trim_set = sql_value_text(argv[1])) == NULL)
+	if ((input_str = sql_value_text(arg3)) == NULL ||
+	    (trim_set = sql_value_text(arg2)) == NULL)
 		return;
 
-	int trim_set_sz = sql_value_bytes(argv[1]);
-	int input_str_sz = sql_value_bytes(argv[2]);
+	int trim_set_sz = sql_value_bytes(arg2);
+	int input_str_sz = sql_value_bytes(arg3);
 	uint8_t *char_len;
 	int char_cnt = trim_prepare_char_len(context, trim_set, trim_set_sz,
 					     &char_len);
 	if (char_cnt == -1)
 		return;
-	trim_procedure(context, sql_value_int(argv[0]), trim_set, char_len,
+	trim_procedure(context, sql_value_int(arg1), trim_set, char_len,
 		       char_cnt, input_str, input_str_sz);
 	sql_free(char_len);
 }
+
+/**
+ * Normalize args from @a argv input array when it has one,
+ * two or three args.
+ *
+ * This is a dispatcher function that calls corresponding
+ * implementation depending on the number of arguments.
+*/
+static void
+trim_func(struct sql_context *context, int argc, sql_value **argv)
+{
+	switch (argc) {
+	case 1:
+		trim_func_one_arg(context, argv[0]);
+		break;
+	case 2:
+		trim_func_two_args(context, argv[0], argv[1]);
+		break;
+	case 3:
+		trim_func_three_args(context, argv[0], argv[1], argv[2]);
+		break;
+	default:
+		unreachable();
+	}
+}
+
 /*
  * Compute the soundex encoding of a word.
  *
@@ -1840,9 +1858,9 @@ sqlRegisterBuiltinFunctions(void)
 			  FIELD_TYPE_BOOLEAN),
 		FUNCTION2(likely, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
 			  FIELD_TYPE_BOOLEAN),
-		FUNCTION_COLL(trim, 1, 3, 0, trim_func_one_arg),
-		FUNCTION_COLL(trim, 2, 3, 0, trim_func_two_args),
-		FUNCTION_COLL(trim, 3, 3, 0, trim_func_three_args),
+		FUNCTION_COLL(trim, 1, 3, 0, trim_func),
+		FUNCTION_COLL(trim, 2, 3, 0, trim_func),
+		FUNCTION_COLL(trim, 3, 3, 0, trim_func),
 		FUNCTION(least, -1, 0, 1, minmaxFunc, FIELD_TYPE_SCALAR),
 		AGGREGATE2(min, 1, 0, 1, minmaxStep, minMaxFinalize,
 			   SQL_FUNC_MINMAX, FIELD_TYPE_SCALAR),
-- 
2.22.1

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

* [tarantool-patches] [PATCH v3 4/9] sql: rework SQL_FUNC_COUNT flag semantics
  2019-08-16 13:26 [tarantool-patches] [PATCH v3 0/9] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
                   ` (2 preceding siblings ...)
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 3/9] sql: wrap all trim functions in dispatcher Kirill Shcherbatov
@ 2019-08-16 13:26 ` Kirill Shcherbatov
  2019-08-16 18:55   ` [tarantool-patches] " n.pettik
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 5/9] sql: rename OP_Function to OP_BuiltinFunction Kirill Shcherbatov
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 19+ messages in thread
From: Kirill Shcherbatov @ 2019-08-16 13:26 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

Tarantool's SQL engine generates a different VDBE bytecode
for ..COUNT(*).. and ..COUNT(fieldname).. operations:
the first one produces a lightweight OP_Count operation that uses
native mechanism to report the count of record in index while
the second one pessimistically opens a space read iterator and
uses Count aggregate function.

A helper routine is_simple_count decides whether such
optimisation is correct. It used to use SQL_FUNC_COUNT flag to
mark a dummy (non-functional) function entry with 0 arguments.
This patch changes SQL_FUNC_COUNT semantics: now it is a marker
of any COUNT function, while is_simple_count relies on count
of arguments to distinguish aggregate and non-aggregate
functions.

Needed for #2200, #4113, #2233
---
 src/box/sql/sqlInt.h | 3 ++-
 src/box/sql/func.c   | 4 ++--
 src/box/sql/select.c | 4 +++-
 3 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 4adb30c5c..bc3c639e6 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -1312,7 +1312,8 @@ struct FuncDestructor {
 					 */
 #define SQL_FUNC_LENGTH   0x0040	/* Built-in length() function */
 #define SQL_FUNC_TYPEOF   0x0080	/* Built-in typeof() function */
-#define SQL_FUNC_COUNT    0x0100	/* Built-in count(*) aggregate */
+/** Built-in count() function. */
+#define SQL_FUNC_COUNT    0x0100
 #define SQL_FUNC_COALESCE 0x0200	/* Built-in coalesce() or ifnull() */
 #define SQL_FUNC_UNLIKELY 0x0400	/* Built-in unlikely() function */
 #define SQL_FUNC_CONSTANT 0x0800	/* Constant inputs give a constant output */
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 6bd2d0cd7..07c019db9 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1903,9 +1903,9 @@ sqlRegisterBuiltinFunctions(void)
 		AGGREGATE(avg, 1, 0, 0, sum_step, avgFinalize,
 			  FIELD_TYPE_NUMBER),
 		AGGREGATE2(count, 0, 0, 0, countStep, countFinalize,
+			  SQL_FUNC_COUNT, FIELD_TYPE_INTEGER),
+		AGGREGATE2(count, 1, 0, 0, countStep, countFinalize,
 			   SQL_FUNC_COUNT, FIELD_TYPE_INTEGER),
-		AGGREGATE(count, 1, 0, 0, countStep, countFinalize,
-			  FIELD_TYPE_INTEGER),
 		AGGREGATE(group_concat, 1, 0, 0, groupConcatStep,
 			  groupConcatFinalize, FIELD_TYPE_STRING),
 		AGGREGATE(group_concat, 2, 0, 0, groupConcatStep,
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index c31076694..ddb5de87e 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4381,7 +4381,9 @@ is_simple_count(struct Select *select, struct AggInfo *agg_info)
 		return NULL;
 	if (NEVER(agg_info->nFunc == 0))
 		return NULL;
-	if ((agg_info->aFunc[0].pFunc->funcFlags & SQL_FUNC_COUNT) == 0)
+	if ((agg_info->aFunc->pFunc->funcFlags & SQL_FUNC_COUNT) == 0 ||
+	    (agg_info->aFunc->pExpr->x.pList != NULL &&
+	     agg_info->aFunc->pExpr->x.pList->nExpr > 0))
 		return NULL;
 	if (expr->flags & EP_Distinct)
 		return NULL;
-- 
2.22.1

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

* [tarantool-patches] [PATCH v3 5/9] sql: rename OP_Function to OP_BuiltinFunction
  2019-08-16 13:26 [tarantool-patches] [PATCH v3 0/9] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
                   ` (3 preceding siblings ...)
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 4/9] sql: rework SQL_FUNC_COUNT flag semantics Kirill Shcherbatov
@ 2019-08-16 13:26 ` Kirill Shcherbatov
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 6/9] sql: remove SQL_FUNC_SLOCHNG flag Kirill Shcherbatov
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 19+ messages in thread
From: Kirill Shcherbatov @ 2019-08-16 13:26 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

Renamed OP_Function opcode to OP_BuiltinFunction to introduce a
new OP_Function operation in a new meaning: a new OP_Function
would call Tarantool's function with new port-based API while
legacy OP_BuiltinFunction is an efficient implementation of
SQL Builtins functions.

Needed for #2200, #4113, #2233
---
 src/box/sql/vdbeInt.h |  3 ++-
 src/box/sql/analyze.c |  8 ++++----
 src/box/sql/expr.c    |  2 +-
 src/box/sql/vdbe.c    | 29 +++++++++++++++--------------
 4 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index 5582d9506..f77c019fb 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -312,7 +312,8 @@ struct sql_context {
 	FuncDef *pFunc;		/* Pointer to function information */
 	Mem *pMem;		/* Memory cell used to store aggregate context */
 	Vdbe *pVdbe;		/* The VM that owns this context */
-	int iOp;		/* Instruction number of OP_Function */
+	/** Instruction number of OP_BuiltinFunction0. */
+	int iOp;
 	/*
 	 * True, if an error occurred during the execution of the
 	 * function.
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index 3d45adf32..bd52d12df 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -718,7 +718,7 @@ callStatGet(Vdbe * v, int regStat4, int iParam, int regOut)
 	struct FuncDef *func =
 		sqlFindFunction(sql_get(), "_sql_stat_get", 2, 0);
 	assert(func != NULL);
-	sqlVdbeAddOp4(v, OP_Function0, 0, regStat4, regOut,
+	sqlVdbeAddOp4(v, OP_BuiltinFunction0, 0, regStat4, regOut,
 		      (char *)func, P4_FUNCDEF);
 	sqlVdbeChangeP5(v, 2);
 }
@@ -858,8 +858,8 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
 		struct FuncDef *init_func =
 			sqlFindFunction(sql_get(), "_sql_stat_init", 3, 0);
 		assert(init_func != NULL);
-		sqlVdbeAddOp4(v, OP_Function0, 0, stat4_reg + 1, stat4_reg,
-			      (char *)init_func, P4_FUNCDEF);
+		sqlVdbeAddOp4(v, OP_BuiltinFunction0, 0, stat4_reg + 1,
+			      stat4_reg, (char *)init_func, P4_FUNCDEF);
 		sqlVdbeChangeP5(v, 3);
 		/*
 		 * Implementation of the following:
@@ -959,7 +959,7 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
 		struct FuncDef *push_func =
 			sqlFindFunction(sql_get(), "_sql_stat_push", 3, 0);
 		assert(push_func != NULL);
-		sqlVdbeAddOp4(v, OP_Function0, 1, stat4_reg, tmp_reg,
+		sqlVdbeAddOp4(v, OP_BuiltinFunction0, 1, stat4_reg, tmp_reg,
 			      (char *)push_func, P4_FUNCDEF);
 		sqlVdbeChangeP5(v, 3);
 		sqlVdbeAddOp2(v, OP_Next, idx_cursor, next_row_addr);
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 88c602fde..1f9d91705 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -4132,7 +4132,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				sqlVdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
 						  (char *)coll, P4_COLLSEQ);
 			}
-			sqlVdbeAddOp4(v, OP_Function0, constMask, r1,
+			sqlVdbeAddOp4(v, OP_BuiltinFunction0, constMask, r1,
 					  target, (char *)pDef, P4_FUNCDEF);
 			sqlVdbeChangeP5(v, (u8) nFarg);
 			if (nFarg && constMask == 0) {
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index e096e1f65..28552f64a 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1664,7 +1664,7 @@ case OP_CollSeq: {
 	break;
 }
 
-/* Opcode: Function0 P1 P2 P3 P4 P5
+/* Opcode: BuiltinFunction0 P1 P2 P3 P4 P5
  * Synopsis: r[P3]=func(r[P2@P5])
  *
  * Invoke a user function (P4 is a pointer to a FuncDef object that
@@ -1676,9 +1676,9 @@ case OP_CollSeq: {
  * function was determined to be constant at compile time. If the first
  * argument was constant then bit 0 of P1 is set.
  *
- * See also: Function, AggStep, AggFinal
+ * See also: BuiltinFunction, AggStep, AggFinal
  */
-/* Opcode: Function P1 P2 P3 P4 P5
+/* Opcode: BuiltinFunction P1 P2 P3 P4 P5
  * Synopsis: r[P3]=func(r[P2@P5])
  *
  * Invoke a user function (P4 is a pointer to an sql_context object that
@@ -1690,16 +1690,17 @@ case OP_CollSeq: {
  * function was determined to be constant at compile time. If the first
  * argument was constant then bit 0 of P1 is set.
  *
- * SQL functions are initially coded as OP_Function0 with P4 pointing
- * to a FuncDef object.  But on first evaluation, the P4 operand is
- * automatically converted into an sql_context object and the operation
- * changed to this OP_Function opcode.  In this way, the initialization of
- * the sql_context object occurs only once, rather than once for each
- * evaluation of the function.
+ * SQL functions are initially coded as OP_BuiltinFunction0 with
+ * P4 pointing to a FuncDef object.  But on first evaluation,
+ * the P4 operand is automatically converted into an sql_context
+ * object and the operation changed to this OP_BuiltinFunction
+ * opcode.  In this way, the initialization of the sql_context
+ * object occurs only once, rather than once for each evaluation
+ * of the function.
  *
- * See also: Function0, AggStep, AggFinal
+ * See also: BuiltinFunction0, AggStep, AggFinal
  */
-case OP_Function0: {
+case OP_BuiltinFunction0: {
 	int n;
 	sql_context *pCtx;
 
@@ -1717,11 +1718,11 @@ case OP_Function0: {
 	pCtx->argc = n;
 	pOp->p4type = P4_FUNCCTX;
 	pOp->p4.pCtx = pCtx;
-	pOp->opcode = OP_Function;
-	/* Fall through into OP_Function */
+	pOp->opcode = OP_BuiltinFunction;
+	/* Fall through into OP_BuiltinFunction */
 	FALLTHROUGH;
 }
-case OP_Function: {
+case OP_BuiltinFunction: {
 	int i;
 	sql_context *pCtx;
 
-- 
2.22.1

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

* [tarantool-patches] [PATCH v3 6/9] sql: remove SQL_FUNC_SLOCHNG flag
  2019-08-16 13:26 [tarantool-patches] [PATCH v3 0/9] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
                   ` (4 preceding siblings ...)
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 5/9] sql: rename OP_Function to OP_BuiltinFunction Kirill Shcherbatov
@ 2019-08-16 13:26 ` Kirill Shcherbatov
  2019-08-16 18:54   ` [tarantool-patches] " n.pettik
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 7/9] sql: get rid of FuncDef function hash Kirill Shcherbatov
                   ` (2 subsequent siblings)
  8 siblings, 1 reply; 19+ messages in thread
From: Kirill Shcherbatov @ 2019-08-16 13:26 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

The SQL_FUNC_SLOCHNG flag was useful for datetime function
that are currently not supported. So it could be removed.

Needed for #2200, #4113, #2233
---
 src/box/sql/sqlInt.h  | 15 +--------------
 src/box/sql/resolve.c |  4 +---
 src/box/sql/vdbemem.c |  6 ++----
 3 files changed, 4 insertions(+), 21 deletions(-)

diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index bc3c639e6..5a3e8f1c1 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -1318,9 +1318,7 @@ struct FuncDestructor {
 #define SQL_FUNC_UNLIKELY 0x0400	/* Built-in unlikely() function */
 #define SQL_FUNC_CONSTANT 0x0800	/* Constant inputs give a constant output */
 #define SQL_FUNC_MINMAX   0x1000	/* True for min() and max() aggregates */
-#define SQL_FUNC_SLOCHNG  0x2000	/* "Slow Change". Value constant during a
-					 * single query - might change over time
-					 */
+
 /**
  * If function returns string, it may require collation to be
  * applied on its result. For instance, result of substr()
@@ -1359,11 +1357,6 @@ enum trim_side_mask {
  *   VFUNCTION(zName, nArg, iArg, bNC, xFunc)
  *     Like FUNCTION except it omits the sql_FUNC_CONSTANT flag.
  *
- *   DFUNCTION(zName, nArg, iArg, bNC, xFunc)
- *     Like FUNCTION except it omits the sql_FUNC_CONSTANT flag and
- *     adds the sql_FUNC_SLOCHNG flag.  Used for date & time functions,
- *     but not during a single query.
- *
  *   AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal)
  *     Used to create an aggregate function definition implemented by
  *     the C functions xStep and xFinal. The first four parameters
@@ -1387,15 +1380,9 @@ enum trim_side_mask {
 #define VFUNCTION(zName, nArg, iArg, bNC, xFunc, type) \
   {nArg, (bNC*SQL_FUNC_NEEDCOLL), \
    SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define DFUNCTION(zName, nArg, iArg, bNC, xFunc, type) \
-  {nArg, SQL_FUNC_SLOCHNG|(bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
 #define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags, type) \
   {nArg,SQL_FUNC_CONSTANT|(bNC*SQL_FUNC_NEEDCOLL)|extraFlags,\
    SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \
-  {nArg, SQL_FUNC_SLOCHNG|(bNC*SQL_FUNC_NEEDCOLL), \
-   pArg, 0, xFunc, 0, #zName, {SQL_AFF_STRING, {0}}}
 #define LIKEFUNC(zName, nArg, arg, flags, type) \
   {nArg, SQL_FUNC_NEEDCOLL|SQL_FUNC_CONSTANT|flags, \
    (void *)(SQL_INT_TO_PTR(arg)), 0, likeFunc, 0, #zName, {0}, type}
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 0b90edd06..207f57ba6 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -647,9 +647,7 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 						    'u' ? 8388608 : 125829120;
 					}
 				}
-				if (pDef->
-				    funcFlags & (SQL_FUNC_CONSTANT |
-						 SQL_FUNC_SLOCHNG)) {
+				if ((pDef->funcFlags & SQL_FUNC_CONSTANT) != 0) {
 					/* For the purposes of the EP_ConstFunc flag, date and time
 					 * functions and other functions that change slowly are considered
 					 * constant because they are constant for the duration of one query
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index b8c31ecec..5516d7fb1 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -1302,11 +1302,9 @@ valueFromFunction(sql * db,	/* The database connection */
 		nVal = pList->nExpr;
 	pFunc = sqlFindFunction(db, p->u.zToken, nVal, 0);
 	assert(pFunc);
-	if ((pFunc->funcFlags & (SQL_FUNC_CONSTANT | SQL_FUNC_SLOCHNG)) ==
-	    0 || (pFunc->funcFlags & SQL_FUNC_NEEDCOLL)
-	    ) {
+	if ((pFunc->funcFlags & SQL_FUNC_CONSTANT) == 0 ||
+	    (pFunc->funcFlags & SQL_FUNC_NEEDCOLL))
 		return 0;
-	}
 
 	if (pList) {
 		apVal =
-- 
2.22.1

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

* [tarantool-patches] [PATCH v3 7/9] sql: get rid of FuncDef function hash
  2019-08-16 13:26 [tarantool-patches] [PATCH v3 0/9] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
                   ` (5 preceding siblings ...)
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 6/9] sql: remove SQL_FUNC_SLOCHNG flag Kirill Shcherbatov
@ 2019-08-16 13:26 ` Kirill Shcherbatov
  2019-08-16 14:09   ` [tarantool-patches] " Konstantin Osipov
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 8/9] sql: get rid of box.internal.sql_function_create Kirill Shcherbatov
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 9/9] sql: better error messages on invalid arguments Kirill Shcherbatov
  8 siblings, 1 reply; 19+ messages in thread
From: Kirill Shcherbatov @ 2019-08-16 13:26 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

Now it is possible to move all SQL builtin functions to
Tarantool's function hash. An existent FuncDef function
representation was replaced with func_sql_builtin class.
It has a sql-specific method :call and :finalize, while
port API call is not supported and protected with stubs.

This patch removes FuncDef hash and sql_function_create endpoint,
but doesn't introduce something instead. Therefore few affected
tests are disabled. A required functionality would be fixed in
the next patch.

Following tests using sql_create_function are broken now.
They are going to be fixed in the next commit:
 sql-tap/alias.test.lua sql-tap/check.test.lua
 sql-tap/func5.test.lua sql-tap/lua_sql.test.lua
 sql-tap/subquery.test.lua sql-tap/trigger9.test.lua
 sql/errinj.result sql/errinj.test.lua
 sql/func-recreate.test.lua

Part of #2200, #4113, #2233
---
 src/box/lua/lua_sql.h        |  39 ---
 src/box/sql.h                |   5 +
 src/box/sql/sqlInt.h         | 235 ++++----------
 src/box/sql/vdbe.h           |   9 +-
 src/box/sql/vdbeInt.h        |  23 +-
 src/box/func.c               |  34 +-
 src/box/lua/call.c           |   2 -
 src/box/lua/lua_sql.c        | 205 -------------
 src/box/sql/analyze.c        |  34 +-
 src/box/sql/callback.c       | 204 ------------
 src/box/sql/date.c           |  28 --
 src/box/sql/expr.c           |  65 ++--
 src/box/sql/func.c           | 579 +++++++++++++++++++++++++++--------
 src/box/sql/global.c         |   7 -
 src/box/sql/main.c           | 137 ---------
 src/box/sql/resolve.c        |  53 ++--
 src/box/sql/select.c         |  10 +-
 src/box/sql/vdbe.c           |  20 +-
 src/box/sql/vdbeapi.c        |  17 +-
 src/box/sql/vdbeaux.c        |  31 +-
 src/box/sql/vdbemem.c        |  65 ++--
 src/box/sql/whereexpr.c      |   2 +-
 src/box/CMakeLists.txt       |   1 -
 src/box/alter.cc             |   6 +
 test/box/function1.result    |   4 +
 test/box/function1.test.lua  |   2 +
 test/sql-tap/where2.test.lua |   4 +-
 27 files changed, 698 insertions(+), 1123 deletions(-)
 delete mode 100644 src/box/lua/lua_sql.h
 delete mode 100644 src/box/lua/lua_sql.c

diff --git a/src/box/lua/lua_sql.h b/src/box/lua/lua_sql.h
deleted file mode 100644
index b81093eca..000000000
--- a/src/box/lua/lua_sql.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * 1. Redistributions of source code must retain the above
- *    copyright notice, this list of conditions and the
- *    following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above
- *    copyright notice, this list of conditions and the following
- *    disclaimer in the documentation and/or other materials
- *    provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
- * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#ifndef TARANTOOL_LUA_SQL_H
-#define TARANTOOL_LUA_SQL_H
-
-int
-lbox_sql_create_function(struct lua_State *L);
-
-#endif //TARANTOOL_LUA_SQL_H
-
diff --git a/src/box/sql.h b/src/box/sql.h
index 9ccecf28c..6eaf5ba3a 100644
--- a/src/box/sql.h
+++ b/src/box/sql.h
@@ -68,6 +68,7 @@ struct Expr;
 struct Parse;
 struct Select;
 struct Table;
+struct func_def;
 struct sql_trigger;
 struct space_def;
 
@@ -348,6 +349,10 @@ sql_src_list_entry_name(const struct SrcList *list, int i);
 void
 sqlSrcListDelete(struct sql *db, struct SrcList *list);
 
+/** Construct a SQL builtin function object. */
+struct func *
+func_sql_builtin_new(struct func_def *def);
+
 /**
  * Auxiliary VDBE structure to speed-up tuple data field access.
  * A memory allocation that manage this structure must have
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 5a3e8f1c1..6397dafb5 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -70,6 +70,8 @@
 #include "box/column_mask.h"
 #include "parse_def.h"
 #include "box/field_def.h"
+#include "box/func.h"
+#include "box/func_def.h"
 #include "box/sql.h"
 #include "box/txn.h"
 #include "trivia/util.h"
@@ -541,9 +543,6 @@ void
 sql_row_count(struct sql_context *context, MAYBE_UNUSED int unused1,
 	      MAYBE_UNUSED sql_value **unused2);
 
-void *
-sql_user_data(sql_context *);
-
 void *
 sql_aggregate_context(sql_context *,
 			  int nBytes);
@@ -566,26 +565,6 @@ sql_initialize(void);
 #define SQL_TRACE_ROW        0x04
 #define SQL_TRACE_CLOSE      0x08
 
-#define SQL_DETERMINISTIC    0x800
-
-int
-sql_create_function_v2(sql * db,
-			   const char *zFunctionName,
-			   enum field_type type,
-			   int nArg,
-			   int flags,
-			   void *pApp,
-			   void (*xFunc) (sql_context *,
-					  int,
-					  sql_value **),
-			   void (*xStep) (sql_context *,
-					  int,
-					  sql_value **),
-			   void (*xFinal)
-			   (sql_context *),
-			   void (*xDestroy) (void *)
-	);
-
 #define SQL_OPEN_READONLY         0x00000001	/* Ok for sql_open_v2() */
 #define SQL_OPEN_READWRITE        0x00000002	/* Ok for sql_open_v2() */
 #define SQL_OPEN_CREATE           0x00000004	/* Ok for sql_open_v2() */
@@ -1039,9 +1018,6 @@ typedef struct Column Column;
 typedef struct Expr Expr;
 typedef struct ExprList ExprList;
 typedef struct ExprSpan ExprSpan;
-typedef struct FuncDestructor FuncDestructor;
-typedef struct FuncDef FuncDef;
-typedef struct FuncDefHash FuncDefHash;
 typedef struct IdList IdList;
 typedef struct KeyClass KeyClass;
 typedef struct Lookaside Lookaside;
@@ -1123,18 +1099,6 @@ struct LookasideSlot {
 	LookasideSlot *pNext;	/* Next buffer in the list of free buffers */
 };
 
-/*
- * A hash table for built-in function definitions.  (Application-defined
- * functions use a regular table table from hash.h.)
- *
- * Hash each FuncDef structure into one of the FuncDefHash.a[] slots.
- * Collisions are on the FuncDef.u.pHash chain.
- */
-#define SQL_FUNC_HASH_SZ 23
-struct FuncDefHash {
-	FuncDef *a[SQL_FUNC_HASH_SZ];	/* Hash table for functions */
-};
-
 /*
  * Each database connection is an instance of the following structure.
  */
@@ -1244,65 +1208,11 @@ struct type_def {
 };
 
 /*
- * Each SQL function is defined by an instance of the following
- * structure.  For global built-in functions (ex: substr(), max(), count())
- * a pointer to this structure is held in the sqlBuiltinFunctions object.
- * For per-connection application-defined functions, a pointer to this
- * structure is held in the db->aHash hash table.
- *
- * The u.pHash field is used by the global built-ins.  The u.pDestructor
- * field is used by per-connection app-def functions.
- */
-struct FuncDef {
-	i8 nArg;		/* Number of arguments.  -1 means unlimited */
-	u16 funcFlags;		/* Some combination of sql_FUNC_* */
-	void *pUserData;	/* User data parameter */
-	FuncDef *pNext;		/* Next function with same name */
-	void (*xSFunc) (sql_context *, int, sql_value **);	/* func or agg-step */
-	void (*xFinalize) (sql_context *);	/* Agg finalizer */
-	const char *zName;	/* SQL name of the function. */
-	union {
-		FuncDef *pHash;	/* Next with a different name but the same hash */
-		FuncDestructor *pDestructor;	/* Reference counted destructor function */
-	} u;
-	/* Return type. */
-	enum field_type ret_type;
-};
-
-/*
- * This structure encapsulates a user-function destructor callback (as
- * configured using create_function_v2()) and a reference counter. When
- * create_function_v2() is called to create a function with a destructor,
- * a single object of this type is allocated. FuncDestructor.nRef is set to
- * the number of FuncDef objects created (either 1 or 3, depending on whether
- * or not the specified encoding is sql_ANY). The FuncDef.pDestructor
- * member of each of the new FuncDef objects is set to point to the allocated
- * FuncDestructor.
- *
- * Thereafter, when one of the FuncDef objects is deleted, the reference
- * count on this object is decremented. When it reaches 0, the destructor
- * is invoked and the FuncDestructor structure freed.
- */
-struct FuncDestructor {
-	int nRef;
-	void (*xDestroy) (void *);
-	void *pUserData;
-};
-
-/*
- * Possible values for FuncDef.flags.  Note that the _LENGTH and _TYPEOF
- * values must correspond to OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG.  And
- * sql_FUNC_CONSTANT must be the same as sql_DETERMINISTIC.  There
- * are assert() statements in the code to verify this.
- *
  * Value constraints (enforced via assert()):
- *     SQL_FUNC_MINMAX    ==  NC_MinMaxAgg      == SF_MinMaxAgg
  *     SQL_FUNC_LENGTH    ==  OPFLAG_LENGTHARG
  *     SQL_FUNC_TYPEOF    ==  OPFLAG_TYPEOFARG
- *     SQL_FUNC_CONSTANT  ==  sql_DETERMINISTIC from the API
  */
 #define SQL_FUNC_LIKE     0x0004	/* Candidate for the LIKE optimization */
-#define SQL_FUNC_EPHEM    0x0010	/* Ephemeral.  Delete with VDBE */
 #define SQL_FUNC_NEEDCOLL 0x0020	/* sqlGetFuncCollSeq() might be called.
 					 * The flag is set when the collation
 					 * of function arguments should be
@@ -1316,9 +1226,10 @@ struct FuncDestructor {
 #define SQL_FUNC_COUNT    0x0100
 #define SQL_FUNC_COALESCE 0x0200	/* Built-in coalesce() or ifnull() */
 #define SQL_FUNC_UNLIKELY 0x0400	/* Built-in unlikely() function */
-#define SQL_FUNC_CONSTANT 0x0800	/* Constant inputs give a constant output */
-#define SQL_FUNC_MINMAX   0x1000	/* True for min() and max() aggregates */
-
+/** Built-in min() or least() function. */
+#define SQL_FUNC_MIN      0x1000
+/** Built-in max() or greatest() function. */
+#define SQL_FUNC_MAX   0x2000
 /**
  * If function returns string, it may require collation to be
  * applied on its result. For instance, result of substr()
@@ -1338,61 +1249,6 @@ enum trim_side_mask {
 	TRIM_BOTH = TRIM_LEADING | TRIM_TRAILING
 };
 
-/*
- * The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are
- * used to create the initializers for the FuncDef structures.
- *
- *   FUNCTION(zName, nArg, iArg, bNC, xFunc)
- *     Used to create a scalar function definition of a function zName
- *     implemented by C function xFunc that accepts nArg arguments. The
- *     value passed as iArg is cast to a (void*) and made available
- *     as the user-data (sql_user_data()) for the function. If
- *     argument bNC is true, then the sql_FUNC_NEEDCOLL flag is set.
- *
- *   FUNCTION_COLL
- *     Like FUNCTION except it assumes that function returns
- *     STRING which collation should be derived from first
- *     argument (trim, substr etc).
- *
- *   VFUNCTION(zName, nArg, iArg, bNC, xFunc)
- *     Like FUNCTION except it omits the sql_FUNC_CONSTANT flag.
- *
- *   AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal)
- *     Used to create an aggregate function definition implemented by
- *     the C functions xStep and xFinal. The first four parameters
- *     are interpreted in the same way as the first 4 parameters to
- *     FUNCTION().
- *
- *   LIKEFUNC(zName, nArg, pArg, flags)
- *     Used to create a scalar function definition of a function zName
- *     that accepts nArg arguments and is implemented by a call to C
- *     function likeFunc. Argument pArg is cast to a (void *) and made
- *     available as the function user-data (sql_user_data()). The
- *     FuncDef.flags variable is set to the value passed as the flags
- *     parameter.
- */
-#define FUNCTION(zName, nArg, iArg, bNC, xFunc, type) \
-  {nArg, SQL_FUNC_CONSTANT|(bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define FUNCTION_COLL(zName, nArg, iArg, bNC, xFunc) \
-  {nArg, SQL_FUNC_CONSTANT|SQL_FUNC_DERIVEDCOLL|(bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, FIELD_TYPE_STRING}
-#define VFUNCTION(zName, nArg, iArg, bNC, xFunc, type) \
-  {nArg, (bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags, type) \
-  {nArg,SQL_FUNC_CONSTANT|(bNC*SQL_FUNC_NEEDCOLL)|extraFlags,\
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define LIKEFUNC(zName, nArg, arg, flags, type) \
-  {nArg, SQL_FUNC_NEEDCOLL|SQL_FUNC_CONSTANT|flags, \
-   (void *)(SQL_INT_TO_PTR(arg)), 0, likeFunc, 0, #zName, {0}, type}
-#define AGGREGATE(zName, nArg, arg, nc, xStep, xFinal, type) \
-  {nArg, (nc*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}, type}
-#define AGGREGATE2(zName, nArg, arg, nc, xStep, xFinal, extraFlags, type) \
-  {nArg, (nc*SQL_FUNC_NEEDCOLL)|extraFlags, \
-   SQL_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}, type}
-
 /*
  * All current savepoints are stored in a linked list starting at
  * sql.pSavepoint. The first element in the list is the most recently
@@ -1585,7 +1441,8 @@ struct AggInfo {
 				 */
 	struct AggInfo_func {	/* For each aggregate function */
 		Expr *pExpr;	/* Expression encoding the function */
-		FuncDef *pFunc;	/* The aggregate function implementation */
+		/** The aggregate function implementation. */
+		struct func *func;
 		int iMem;	/* Memory location that acts as accumulator */
 		int iDistinct;	/* Ephemeral table used to enforce DISTINCT */
 		/**
@@ -2014,7 +1871,7 @@ struct NameContext {
  *
  * Value constraints (all checked via assert()):
  *    NC_HasAgg    == SF_HasAgg
- *    NC_MinMaxAgg == SF_MinMaxAgg == sql_FUNC_MINMAX
+ *    NC_MinMaxAgg == SF_MinMaxAgg
  *
  */
 #define NC_AllowAgg  0x0001	/* Aggregate functions are allowed here */
@@ -2072,7 +1929,7 @@ struct Select {
  *
  * Value constraints (all checked via assert())
  *     SF_HasAgg     == NC_HasAgg
- *     SF_MinMaxAgg  == NC_MinMaxAgg     == sql_FUNC_MINMAX
+ *     SF_MinMaxAgg  == NC_MinMaxAgg
  *     SF_FixedLimit == WHERE_USE_LIMIT
  */
 #define SF_Distinct       0x00001	/* Output should be DISTINCT */
@@ -3533,10 +3390,6 @@ void sqlSelectSetName(Select *, const char *);
 #else
 #define sqlSelectSetName(A,B)
 #endif
-void sqlInsertBuiltinFuncs(FuncDef *, int);
-FuncDef *sqlFindFunction(sql *, const char *, int, u8);
-void sqlRegisterBuiltinFunctions(void);
-void sqlRegisterDateTimeFunctions(void);
 
 /**
  * Evaluate a view and store its result in an ephemeral table.
@@ -4109,7 +3962,6 @@ extern const unsigned char sqlUpperToLower[];
 extern const unsigned char sqlCtypeMap[];
 extern const Token sqlIntTokens[];
 extern SQL_WSD struct sqlConfig sqlConfig;
-extern FuncDefHash sqlBuiltinFunctions;
 extern int sqlPendingByte;
 
 /**
@@ -4245,21 +4097,13 @@ sql_key_info_to_key_def(struct sql_key_info *key_info);
  * Check if the function implements LIKE-style comparison & if it
  * is appropriate to apply a LIKE query optimization.
  *
- * @param db database structure.
  * @param pExpr pointer to a function-implementing expression.
  * @param[out] is_like_ci true if LIKE is case insensitive.
  *
  * @retval 1 if LIKE optimization can be used, 0 otherwise.
  */
 int
-sql_is_like_func(struct sql *db, struct Expr *expr);
-
-int sqlCreateFunc(sql *, const char *, enum field_type,
-		      int, int, void *,
-		      void (*)(sql_context *, int, sql_value **),
-		      void (*)(sql_context *, int, sql_value **),
-		      void (*)(sql_context *),
-		      FuncDestructor * pDestructor);
+sql_is_like_func(struct Expr *expr);
 
 /** Set OOM error flag. */
 static inline void
@@ -4296,7 +4140,6 @@ sql_expr_new_column(struct sql *db, struct SrcList *src_list, int src_idx,
 
 int sqlExprCheckIN(Parse *, Expr *);
 
-void sqlAnalyzeFunctions(void);
 int sqlStat4ProbeSetValue(Parse *, struct index_def *, UnpackedRecord **, Expr *, int,
 			      int, int *);
 int sqlStat4ValueFromExpr(Parse *, Expr *, enum field_type type,
@@ -4487,9 +4330,59 @@ Expr *sqlExprForVectorField(Parse *, Expr *, int);
  */
 extern int sqlSubProgramsRemaining;
 
-/** Register built-in functions to work with ANALYZE data. */
-void
-sql_register_analyze_builtins(void);
+struct func_sql_builtin {
+	/** Function object base class. */
+	struct func base;
+	/** A bitmask of SQL flags. */
+	uint16_t flags;
+	/** User data to pass in call method. */
+	void *user_data;
+	/**
+	 * A VDBE-memory-compatible call method for a function.
+	 * SQL Builting functions doesn't use base class call
+	 * method to gain the best possible performance for SQL
+	 * requests. As builtin functions are predefined and
+	 * non of them modifie schema, access checks are
+	 * redundant, functions have the same execution
+	 * privileges as SQL.
+	 */
+	void (*call)(sql_context *ctx, int argc, sql_value **argv);
+	/**
+	 * A VDBE-memory-compatible finalize method
+	 * (is valid only for aggregate function).
+	 */
+	void (*finalize)(sql_context *ctx);
+};
+
+/**
+ * Test whether SQL-specific flag is set for given function.
+ * Currently only SQL Builtin Functions have such hint flags,
+ * so function returns false for other functions. Such approach
+ * decreases code complexity and allows do not distinguish
+ * functions by implementation details where it is unnecessary.
+ *
+ * Returns true when given flag is set for a given function and
+ * false otherwise.
+ */
+static inline bool
+sql_func_flag_is_set(struct func *func, uint16_t flag)
+{
+	if (func->def->language != FUNC_LANGUAGE_SQL_BUILTIN)
+		return false;
+	return (((struct func_sql_builtin *)func)->flags & flag) != 0;
+}
+
+/**
+ * A SQL method to find a function in a hash by its name and
+ * count of arguments. Only functions that have 'SQL' engine
+ * export field set true and have exactly the same signature
+ * are returned.
+ *
+ * Returns not NULL function pointer when a valid and exported
+ * to SQL engine function is found and NULL otherwise.
+ */
+struct func *
+sql_func_by_signature(const char *name, int argc);
 
 /**
  * Generate VDBE code to halt execution with correct error if
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 8f16202ba..4f639ec38 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -72,7 +72,11 @@ struct VdbeOp {
 		char *z;	/* Pointer to data for string (char array) types */
 		i64 *pI64;	/* Used when p4type is P4_INT64/UINT64 */
 		double *pReal;	/* Used when p4type is P4_REAL */
-		FuncDef *pFunc;	/* Used when p4type is P4_FUNCDEF */
+		/**
+		 * A pointer to function implementation.
+		 * Used when p4type is P4_FUNC.
+		 */
+		struct func *func;
 		sql_context *pCtx;	/* Used when p4type is P4_FUNCCTX */
 		struct coll *pColl;	/* Used when p4type is P4_COLLSEQ */
 		Mem *pMem;	/* Used when p4type is P4_MEM */
@@ -122,7 +126,8 @@ struct SubProgram {
 #define P4_DYNAMIC  (-1)	/* Pointer to a string obtained from sqlMalloc() */
 #define P4_STATIC   (-2)	/* Pointer to a static string */
 #define P4_COLLSEQ  (-3)	/* P4 is a pointer to a CollSeq structure */
-#define P4_FUNCDEF  (-4)	/* P4 is a pointer to a FuncDef structure */
+/** P4 is a pointer to a func structure. */
+#define P4_FUNC     (-4)
 #define P4_MEM      (-7)	/* P4 is a pointer to a Mem*    structure */
 #define P4_TRANSIENT  0		/* P4 is a pointer to a transient string */
 #define P4_REAL     (-9)	/* P4 is a 64-bit floating point value */
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index f77c019fb..d0c15d1fe 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -46,6 +46,8 @@
  */
 typedef struct VdbeOp Op;
 
+struct func;
+
 /*
  * Boolean values
  */
@@ -168,7 +170,11 @@ struct Mem {
 		bool b;         /* Boolean value used when MEM_Bool is set in flags */
 		int nZero;	/* Used when bit MEM_Zero is set in flags */
 		void *p;	/* Generic pointer */
-		FuncDef *pDef;	/* Used only when flags==MEM_Agg */
+		/**
+		 * A pointer to function implementation.
+		 * Used only when flags==MEM_Agg.
+		 */
+		struct func *func;
 		VdbeFrame *pFrame;	/* Used when flags==MEM_Frame */
 	} u;
 	u32 flags;		/* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
@@ -309,7 +315,8 @@ mem_apply_numeric_type(struct Mem *record);
  */
 struct sql_context {
 	Mem *pOut;		/* The return value is stored here */
-	FuncDef *pFunc;		/* Pointer to function information */
+	/* A pointer to function implementation. */
+	struct func *func;
 	Mem *pMem;		/* Memory cell used to store aggregate context */
 	Vdbe *pVdbe;		/* The VM that owns this context */
 	/** Instruction number of OP_BuiltinFunction0. */
@@ -516,7 +523,17 @@ int sqlVdbeMemNumerify(Mem *);
 int sqlVdbeMemCast(Mem *, enum field_type type);
 int sqlVdbeMemFromBtree(BtCursor *, u32, u32, Mem *);
 void sqlVdbeMemRelease(Mem * p);
-int sqlVdbeMemFinalize(Mem *, FuncDef *);
+
+/**
+ * Memory cell pMem contains the context of an aggregate function.
+ * This routine calls the finalize method for that function. The
+ * result of the aggregate is stored back into mem.
+ *
+ * Returns -1 if the finalizer reports an error. 0 otherwise.
+ */
+int
+sql_vdbemem_finalize(struct Mem *mem, struct func *func);
+
 const char *sqlOpcodeName(int);
 int sqlVdbeMemGrow(Mem * pMem, int n, int preserve);
 int sqlVdbeMemClearAndResize(Mem * pMem, int n);
diff --git a/src/box/func.c b/src/box/func.c
index b35d05dca..12e603b3f 100644
--- a/src/box/func.c
+++ b/src/box/func.c
@@ -39,6 +39,7 @@
 #include "port.h"
 #include "schema.h"
 #include "session.h"
+#include "sql.h"
 #include <dlfcn.h>
 
 /**
@@ -381,39 +382,6 @@ restore:
 static struct func *
 func_c_new(struct func_def *def);
 
-/** A stub object for SQL builtins to avoid name clash with UDF. */
-static struct func_vtab func_sql_builtin_vtab;
-
-/** Construct a SQL builtin function object. */
-struct func *
-func_sql_builtin_new(struct func_def *def)
-{
-	assert(def->language == FUNC_LANGUAGE_SQL_BUILTIN);
-	struct func *func =
-		(struct func *) malloc(sizeof(*func));
-	if (func == NULL) {
-		diag_set(OutOfMemory, sizeof(*func), "malloc", "func");
-		return NULL;
-	}
-	/** Don't export SQL builtins in Lua for now. */
-	def->exports.lua = false;
-	func->vtab = &func_sql_builtin_vtab;
-	return func;
-}
-
-static void
-func_sql_builtin_destroy(struct func *func)
-{
-	assert(func->vtab == &func_sql_builtin_vtab);
-	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
-	free(func);
-}
-
-static struct func_vtab func_sql_builtin_vtab = {
-	.call = NULL,
-	.destroy = func_sql_builtin_destroy,
-};
-
 struct func *
 func_new(struct func_def *def)
 {
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index 0ac2eb7a6..001578b5a 100644
--- a/src/box/lua/call.c
+++ b/src/box/lua/call.c
@@ -44,7 +44,6 @@
 #include "box/port.h"
 #include "box/lua/tuple.h"
 #include "small/obuf.h"
-#include "lua_sql.h"
 #include "trivia/util.h"
 #include "mpstream.h"
 
@@ -968,7 +967,6 @@ static struct trigger on_alter_func_in_lua = {
 
 static const struct luaL_Reg boxlib_internal[] = {
 	{"call_loadproc",  lbox_call_loadproc},
-	{"sql_create_function",  lbox_sql_create_function},
 	{"module_reload", lbox_module_reload},
 	{"func_call", lbox_func_call},
 	{NULL, NULL}
diff --git a/src/box/lua/lua_sql.c b/src/box/lua/lua_sql.c
deleted file mode 100644
index 67a51a82c..000000000
--- a/src/box/lua/lua_sql.c
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * 1. Redistributions of source code must retain the above
- *    copyright notice, this list of conditions and the
- *    following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above
- *    copyright notice, this list of conditions and the following
- *    disclaimer in the documentation and/or other materials
- *    provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
- * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include "lua.h"
-#include "lua/utils.h"
-
-#include "box/lua/call.h"
-#include "box/sql/sqlInt.h"
-#include "box/sql/vdbeInt.h"
-
-struct lua_sql_func_info {
-	int func_ref;
-};
-
-/**
- * This function is callback which is called by sql engine.
- *
- * Purpose of this function is to call lua func from sql.
- * Lua func should be previously registered in sql
- * (see lbox_sql_create_function).
- */
-static void
-lua_sql_call(sql_context *pCtx, int nVal, sql_value **apVal) {
-	lua_State *L = lua_newthread(tarantool_L);
-	int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX);
-	struct lua_sql_func_info *func_info = sql_user_data(pCtx);
-
-	lua_rawgeti(L, LUA_REGISTRYINDEX, func_info->func_ref);
-	for (int i = 0; i < nVal; i++) {
-		sql_value *param = apVal[i];
-		switch (sql_value_type(param)) {
-		case MP_INT:
-			luaL_pushint64(L, sql_value_int64(param));
-			break;
-		case MP_UINT:
-			luaL_pushuint64(L, sql_value_uint64(param));
-			break;
-		case MP_DOUBLE:
-			lua_pushnumber(L, sql_value_double(param));
-			break;
-		case MP_STR:
-			lua_pushstring(L, (const char *) sql_value_text(param));
-			break;
-		case MP_BIN:
-			lua_pushlstring(L, sql_value_blob(param),
-					(size_t) sql_value_bytes(param));
-			break;
-		case MP_NIL:
-			lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_nil_ref);
-			break;
-		case MP_BOOL:
-			lua_pushboolean(L, sql_value_boolean(param));
-			break;
-		default:
-			diag_set(ClientError, ER_SQL_EXECUTE, "Unsupported "\
-				 "type passed to Lua");
-			pCtx->is_aborted = true;
-			goto error;
-		}
-	}
-	if (lua_pcall(L, lua_gettop(L) - 1, 1, 0) != 0){
-		diag_set(ClientError, ER_SQL_EXECUTE, lua_tostring(L, -1));
-		pCtx->is_aborted = true;
-		goto error;
-	}
-	switch(lua_type(L, -1)) {
-	case LUA_TBOOLEAN:
-		sql_result_bool(pCtx, lua_toboolean(L, -1));
-		break;
-	case LUA_TNUMBER:
-		sql_result_double(pCtx, lua_tonumber(L, -1));
-		break;
-	case LUA_TSTRING:
-		sql_result_text(pCtx, lua_tostring(L, -1), -1,
-				    SQL_TRANSIENT);
-		break;
-	case LUA_TNIL:
-		sql_result_null(pCtx);
-		break;
-	default:
-		diag_set(ClientError, ER_SQL_EXECUTE, "Unsupported type "\
-			 "passed from Lua");
-		pCtx->is_aborted = true;
-		goto error;
-	}
-error:
-	luaL_unref(tarantool_L, LUA_REGISTRYINDEX, coro_ref);
-	return;
-}
-
-static void
-lua_sql_destroy(void *p)
-{
-	struct lua_sql_func_info *func_info = p;
-	luaL_unref(tarantool_L, LUA_REGISTRYINDEX, func_info->func_ref);
-	free(func_info);
-	return;
-}
-
-/**
- * A helper to register lua function in SQL during runtime.
- * It makes available queries like this: "SELECT lua_func(arg);"
- *
- * sql_create_function *p argument is used to store func ref
- * to lua function (it identifies actual lua func to call if there
- * are many of them). SQL function must have name and type of
- * returning value. Additionally, it can feature number of
- * arguments and deterministic flag.
- */
-int
-lbox_sql_create_function(struct lua_State *L)
-{
-	struct sql *db = sql_get();
-	if (db == NULL)
-		return luaL_error(L, "Please call box.cfg{} first");
-	int argc = lua_gettop(L);
-	/*
-	 * Three function prototypes are possible:
-	 * 1. sql_create_function("func_name", "type", func);
-	 * 2. sql_create_function("func_name", "type", func,
-	 *                        func_arg_num);
-	 * 3. sql_create_function("func_name", "type", func,
-	 *                        func_arg_num, is_deterministic);
-	 */
-	if (!(argc == 3 && lua_isstring(L, 1) && lua_isstring(L, 2) &&
-	    lua_isfunction(L, 3)) &&
-	    !(argc == 4 && lua_isstring(L, 1) && lua_isstring(L, 2) &&
-	      lua_isfunction(L, 3) && lua_isnumber(L, 4)) &&
-	    !(argc == 5 && lua_isstring(L, 1) && lua_isstring(L, 2) &&
-	      lua_isfunction(L, 3) && lua_isnumber(L, 4) &&
-	      lua_isboolean(L, 5)))
-		return luaL_error(L, "Invalid arguments");
-	enum field_type type;
-	const char *type_arg = lua_tostring(L, 2);
-	if (strcmp(type_arg, "INT") == 0 || strcmp(type_arg, "INTEGER") == 0)
-		type = FIELD_TYPE_INTEGER;
-	else if (strcmp(type_arg, "TEXT") == 0)
-		type = FIELD_TYPE_STRING;
-	else if (strcmp(type_arg, "NUMBER") == 0)
-		type = FIELD_TYPE_NUMBER;
-	else if (strcmp(type_arg, "VARBINARY") == 0)
-		type = FIELD_TYPE_SCALAR;
-	else if (strcmp(type_arg, "BOOL") == 0 ||
-		 strcmp(type_arg, "BOOLEAN") == 0)
-		type = FIELD_TYPE_BOOLEAN;
-	else
-		return luaL_error(L, "Unknown type");
-	/* -1 indicates any number of arguments. */
-	int func_arg_num = -1;
-	bool is_deterministic = false;
-	if (argc == 4) {
-		func_arg_num = lua_tointeger(L, 4);
-		lua_pop(L, 1);
-	} else if (argc == 5) {
-		is_deterministic = lua_toboolean(L, 5);
-		func_arg_num = lua_tointeger(L, 4);
-		lua_pop(L, 2);
-	}
-	size_t name_len;
-	const char *name = lua_tolstring(L, 1, &name_len);
-	char *normalized_name =
-		sql_normalized_name_region_new(&fiber()->gc, name, name_len);
-	if (normalized_name == NULL)
-		return luaT_error(L);
-	struct lua_sql_func_info *func_info =
-		(struct lua_sql_func_info *) malloc(sizeof(*func_info));
-	if (func_info == NULL)
-		return luaL_error(L, "out of memory");
-	func_info->func_ref = luaL_ref(L, LUA_REGISTRYINDEX);
-	int rc = sql_create_function_v2(db, normalized_name, type, func_arg_num,
-					   is_deterministic ? SQL_DETERMINISTIC : 0,
-					   func_info, lua_sql_call, NULL, NULL,
-					   lua_sql_destroy);
-	if (rc != 0)
-		return luaT_error(L);
-	return 0;
-}
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index bd52d12df..b9858c8d6 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -263,7 +263,7 @@ stat4Destructor(void *pOld)
  * return value is BLOB, but it is really just a pointer to the Stat4Accum
  * object.
  */
-static void
+MAYBE_UNUSED static void
 statInit(sql_context * context, int argc, sql_value ** argv)
 {
 	Stat4Accum *p;
@@ -535,7 +535,7 @@ samplePushPrevious(Stat4Accum * p, int iChng)
  *
  * The R parameter is only used for STAT4
  */
-static void
+MAYBE_UNUSED static void
 statPush(sql_context * context, int argc, sql_value ** argv)
 {
 	int i;
@@ -608,7 +608,7 @@ statPush(sql_context * context, int argc, sql_value ** argv)
  * The content to returned is determined by the parameter J
  * which is one of the STAT_GET_xxxx values defined above.
  */
-static void
+MAYBE_UNUSED static void
 statGet(sql_context * context, int argc, sql_value ** argv)
 {
 	Stat4Accum *p = (Stat4Accum *) sql_value_blob(argv[0]);
@@ -715,11 +715,10 @@ callStatGet(Vdbe * v, int regStat4, int iParam, int regOut)
 {
 	assert(regOut != regStat4 && regOut != regStat4 + 1);
 	sqlVdbeAddOp2(v, OP_Integer, iParam, regStat4 + 1);
-	struct FuncDef *func =
-		sqlFindFunction(sql_get(), "_sql_stat_get", 2, 0);
+	struct func *func = sql_func_by_signature("_sql_stat_get", 2);
 	assert(func != NULL);
 	sqlVdbeAddOp4(v, OP_BuiltinFunction0, 0, regStat4, regOut,
-		      (char *)func, P4_FUNCDEF);
+		      (char *)func, P4_FUNC);
 	sqlVdbeChangeP5(v, 2);
 }
 
@@ -855,11 +854,11 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
 		sqlVdbeAddOp2(v, OP_Count, idx_cursor, stat4_reg + 3);
 		sqlVdbeAddOp2(v, OP_Integer, part_count, stat4_reg + 1);
 		sqlVdbeAddOp2(v, OP_Integer, part_count, stat4_reg + 2);
-		struct FuncDef *init_func =
-			sqlFindFunction(sql_get(), "_sql_stat_init", 3, 0);
+		struct func *init_func =
+			sql_func_by_signature("_sql_stat_init", 3);
 		assert(init_func != NULL);
 		sqlVdbeAddOp4(v, OP_BuiltinFunction0, 0, stat4_reg + 1,
-			      stat4_reg, (char *)init_func, P4_FUNCDEF);
+			      stat4_reg, (char *)init_func, P4_FUNC);
 		sqlVdbeChangeP5(v, 3);
 		/*
 		 * Implementation of the following:
@@ -956,11 +955,11 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
 		sqlVdbeAddOp3(v, OP_MakeRecord, stat_key_reg,
 				  pk_part_count, key_reg);
 		assert(chng_reg == (stat4_reg + 1));
-		struct FuncDef *push_func =
-			sqlFindFunction(sql_get(), "_sql_stat_push", 3, 0);
+		struct func *push_func =
+			sql_func_by_signature("_sql_stat_push", 3);
 		assert(push_func != NULL);
 		sqlVdbeAddOp4(v, OP_BuiltinFunction0, 1, stat4_reg, tmp_reg,
-			      (char *)push_func, P4_FUNCDEF);
+			      (char *)push_func, P4_FUNC);
 		sqlVdbeChangeP5(v, 3);
 		sqlVdbeAddOp2(v, OP_Next, idx_cursor, next_row_addr);
 		/* Add the entry to the stat1 table. */
@@ -1746,14 +1745,3 @@ fail:
 	box_txn_rollback();
 	return -1;
 }
-
-void
-sql_register_analyze_builtins(void)
-{
-	static FuncDef funcs[] = {
-		FUNCTION(_sql_stat_get, 2, 0, 0, statGet, FIELD_TYPE_ANY),
-		FUNCTION(_sql_stat_push, 3, 0, 0, statPush, FIELD_TYPE_ANY),
-		FUNCTION(_sql_stat_init, 3, 0, 0, statInit, FIELD_TYPE_ANY),
-	};
-	sqlInsertBuiltinFuncs(funcs, nelem(funcs));
-}
diff --git a/src/box/sql/callback.c b/src/box/sql/callback.c
index 6c272de71..290363db6 100644
--- a/src/box/sql/callback.c
+++ b/src/box/sql/callback.c
@@ -56,207 +56,3 @@ sql_get_coll_seq(Parse *parser, const char *name, uint32_t *coll_id)
 		return p->coll;
 	}
 }
-
-/* During the search for the best function definition, this procedure
- * is called to test how well the function passed as the first argument
- * matches the request for a function with nArg arguments in a system
- * that uses encoding enc. The value returned indicates how well the
- * request is matched. A higher value indicates a better match.
- *
- * If nArg is -1 that means to only return a match (non-zero) if p->nArg
- * is also -1.  In other words, we are searching for a function that
- * takes a variable number of arguments.
- *
- * If nArg is -2 that means that we are searching for any function
- * regardless of the number of arguments it uses, so return a positive
- * match score for any
- *
- * The returned value is always between 0 and 6, as follows:
- *
- * 0: Not a match.
- * 1: UTF8/16 conversion required and function takes any number of arguments.
- * 2: UTF16 byte order change required and function takes any number of args.
- * 3: encoding matches and function takes any number of arguments
- * 4: UTF8/16 conversion required - argument count matches exactly
- * 5: UTF16 byte order conversion required - argument count matches exactly
- * 6: Perfect match:  encoding and argument count match exactly.
- *
- * If nArg==(-2) then any function with a non-null xSFunc is
- * a perfect match and any function with xSFunc NULL is
- * a non-match.
- */
-#define FUNC_PERFECT_MATCH 4	/* The score for a perfect match */
-static int
-matchQuality(FuncDef * p,	/* The function we are evaluating for match quality */
-	     int nArg		/* Desired number of arguments.  (-1)==any */
-    )
-{
-	int match;
-
-	/* nArg of -2 is a special case */
-	if (nArg == (-2))
-		return (p->xSFunc == 0) ? 0 : FUNC_PERFECT_MATCH;
-
-	/* Wrong number of arguments means "no match" */
-	if (p->nArg != nArg && p->nArg >= 0)
-		return 0;
-
-	/* Give a better score to a function with a specific number of arguments
-	 * than to function that accepts any number of arguments.
-	 */
-	if (p->nArg == nArg) {
-		match = 4;
-	} else {
-		match = 1;
-	}
-
-	return match;
-}
-
-/*
- * Search a FuncDefHash for a function with the given name.  Return
- * a pointer to the matching FuncDef if found, or 0 if there is no match.
- */
-static FuncDef *
-functionSearch(int h,		/* Hash of the name */
-	       const char *zFunc	/* Name of function */
-    )
-{
-	FuncDef *p;
-	for (p = sqlBuiltinFunctions.a[h]; p; p = p->u.pHash) {
-		if (sqlStrICmp(p->zName, zFunc) == 0) {
-			return p;
-		}
-	}
-	return 0;
-}
-
-/*
- * Insert a new FuncDef into a FuncDefHash hash table.
- */
-void
-sqlInsertBuiltinFuncs(FuncDef * aDef,	/* List of global functions to be inserted */
-			  int nDef	/* Length of the apDef[] list */
-    )
-{
-	int i;
-	for (i = 0; i < nDef; i++) {
-		FuncDef *pOther;
-		const char *zName = aDef[i].zName;
-		int nName = sqlStrlen30(zName);
-		int h =
-		    (sqlUpperToLower[(u8) zName[0]] +
-		     nName) % SQL_FUNC_HASH_SZ;
-		pOther = functionSearch(h, zName);
-		if (pOther) {
-			assert(pOther != &aDef[i] && pOther->pNext != &aDef[i]);
-			aDef[i].pNext = pOther->pNext;
-			pOther->pNext = &aDef[i];
-		} else {
-			aDef[i].pNext = 0;
-			aDef[i].u.pHash = sqlBuiltinFunctions.a[h];
-			sqlBuiltinFunctions.a[h] = &aDef[i];
-		}
-	}
-}
-
-/*
- * Locate a user function given a name, a number of arguments and a flag
- * indicating whether the function prefers UTF-16 over UTF-8.  Return a
- * pointer to the FuncDef structure that defines that function, or return
- * NULL if the function does not exist.
- *
- * If the createFlag argument is true, then a new (blank) FuncDef
- * structure is created and liked into the "db" structure if a
- * no matching function previously existed.
- *
- * If nArg is -2, then the first valid function found is returned.  A
- * function is valid if xSFunc is non-zero.  The nArg==(-2)
- * case is used to see if zName is a valid function name for some number
- * of arguments.  If nArg is -2, then createFlag must be 0.
- *
- * If createFlag is false, then a function with the required name and
- * number of arguments may be returned even if the eTextRep flag does not
- * match that requested.
- */
-FuncDef *
-sqlFindFunction(sql * db,	/* An open database */
-		    const char *zName,	/* Name of the function.  zero-terminated */
-		    int nArg,	/* Number of arguments.  -1 means any number */
-		    u8 createFlag	/* Create new entry if true and does not otherwise exist */
-    )
-{
-	FuncDef *p;		/* Iterator variable */
-	FuncDef *pBest = 0;	/* Best match found so far */
-	int bestScore = 0;	/* Score of best match */
-	int h;			/* Hash value */
-	int nName;		/* Length of the name */
-
-	assert(nArg >= (-2));
-	assert(nArg >= (-1) || createFlag == 0);
-	nName = sqlStrlen30(zName);
-
-	/* First search for a match amongst the application-defined functions.
-	 */
-	p = (FuncDef *) sqlHashFind(&db->aFunc, zName);
-	while (p) {
-		int score = matchQuality(p, nArg);
-		if (score > bestScore) {
-			pBest = p;
-			bestScore = score;
-		}
-		p = p->pNext;
-	}
-
-	/* If no match is found, search the built-in functions.
-	 *
-	 * Except, if createFlag is true, that means that we are trying to
-	 * install a new function.  Whatever FuncDef structure is returned it will
-	 * have fields overwritten with new information appropriate for the
-	 * new function.  But the FuncDefs for built-in functions are read-only.
-	 * So we must not search for built-ins when creating a new function.
-	 */
-	if (!createFlag && (pBest == NULL)) {
-		bestScore = 0;
-		h = (sqlUpperToLower[(u8) zName[0]] +
-		     nName) % SQL_FUNC_HASH_SZ;
-		p = functionSearch(h, zName);
-		while (p) {
-			int score = matchQuality(p, nArg);
-			if (score > bestScore) {
-				pBest = p;
-				bestScore = score;
-			}
-			p = p->pNext;
-		}
-	}
-
-	/* If the createFlag parameter is true and the search did not reveal an
-	 * exact match for the name, number of arguments and encoding, then add a
-	 * new entry to the hash table and return it.
-	 */
-	if (createFlag && bestScore < FUNC_PERFECT_MATCH &&
-	    (pBest =
-	     sqlDbMallocZero(db, sizeof(*pBest) + nName + 1)) != 0) {
-		FuncDef *pOther;
-		pBest->zName = (const char *)&pBest[1];
-		pBest->nArg = (u16) nArg;
-		pBest->funcFlags = 0;
-		memcpy((char *)&pBest[1], zName, nName + 1);
-		pOther =
-		    (FuncDef *) sqlHashInsert(&db->aFunc, pBest->zName,
-						  pBest);
-		if (pOther == pBest) {
-			sqlDbFree(db, pBest);
-			sqlOomFault(db);
-			return 0;
-		} else {
-			pBest->pNext = pOther;
-		}
-	}
-
-	if (pBest && (pBest->xSFunc || createFlag)) {
-		return pBest;
-	}
-	return 0;
-}
diff --git a/src/box/sql/date.c b/src/box/sql/date.c
index 2e2a71ad2..dffc23616 100644
--- a/src/box/sql/date.c
+++ b/src/box/sql/date.c
@@ -1290,31 +1290,3 @@ currentTimeFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 }
 #endif
-
-/*
- * This function registered all of the above C functions as SQL
- * functions.  This should be the only routine in this file with
- * external linkage.
- */
-void
-sqlRegisterDateTimeFunctions(void)
-{
-	static FuncDef aDateTimeFuncs[] = {
-#if 0
-		DFUNCTION(julianday, -1, 0, 0, juliandayFunc, FIELD_TYPE_NUMBER),
-		DFUNCTION(date, -1, 0, 0, dateFunc, FIELD_TYPE_STRING),
-		DFUNCTION(time, -1, 0, 0, timeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(datetime, -1, 0, 0, datetimeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(strftime, -1, 0, 0, strftimeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(current_time, 0, 0, 0, ctimeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc,
-			  FIELD_TYPE_STRING),
-		DFUNCTION(current_date, 0, 0, 0, cdateFunc, FIELD_TYPE_STRING),
-		STR_FUNCTION(current_time, 0, "%H:%M:%S", 0, currentTimeFunc),
-		STR_FUNCTION(current_date, 0, "%Y-%m-%d", 0, currentTimeFunc),
-		STR_FUNCTION(current_timestamp, 0, "%Y-%m-%d %H:%M:%S", 0,
-			     currentTimeFunc),
-#endif
-	};
-	sqlInsertBuiltinFuncs(aDateTimeFuncs, ArraySize(aDateTimeFuncs));
-}
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 1f9d91705..7a354a00a 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -328,12 +328,11 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
 		if (op == TK_FUNCTION) {
 			uint32_t arg_count = p->x.pList == NULL ? 0 :
 					     p->x.pList->nExpr;
-			struct FuncDef *func = sqlFindFunction(parse->db,
-							       p->u.zToken,
-							       arg_count, 0);
+			struct func *func =
+				sql_func_by_signature(p->u.zToken, arg_count);
 			if (func == NULL)
 				break;
-			if ((func->funcFlags & SQL_FUNC_DERIVEDCOLL) != 0) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL)) {
 				/*
 				 * Now we use quite straightforward
 				 * approach assuming that resulting
@@ -342,7 +341,7 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
 				 * built-in functions: trim, upper,
 				 * lower, replace, substr.
 				 */
-				assert(func->ret_type == FIELD_TYPE_STRING);
+				assert(func->def->returns == FIELD_TYPE_STRING);
 				p = p->x.pList->a->pExpr;
 				continue;
 			}
@@ -3975,11 +3974,9 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 	case TK_FUNCTION:{
 			ExprList *pFarg;	/* List of function arguments */
 			int nFarg;	/* Number of function arguments */
-			FuncDef *pDef;	/* The function definition object */
 			const char *zId;	/* The function name */
 			u32 constMask = 0;	/* Mask of function arguments that are constant */
 			int i;	/* Loop counter */
-			sql *db = pParse->db;	/* The database connection */
 			struct coll *coll = NULL;
 
 			assert(!ExprHasProperty(pExpr, EP_xIsSelect));
@@ -3991,8 +3988,8 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			nFarg = pFarg ? pFarg->nExpr : 0;
 			assert(!ExprHasProperty(pExpr, EP_IntValue));
 			zId = pExpr->u.zToken;
-			pDef = sqlFindFunction(db, zId, nFarg, 0);
-			if (pDef == 0 || pDef->xFinalize != 0) {
+			struct func *func = sql_func_by_signature(zId, nFarg);
+			if (func == NULL) {
 				diag_set(ClientError, ER_NO_SUCH_FUNCTION,
 					 zId);
 				pParse->is_aborted = true;
@@ -4002,7 +3999,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			 * IFNULL() functions.  This avoids unnecessary evaluation of
 			 * arguments past the first non-NULL argument.
 			 */
-			if (pDef->funcFlags & SQL_FUNC_COALESCE) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_COALESCE)) {
 				int endCoalesce = sqlVdbeMakeLabel(v);
 				assert(nFarg >= 2);
 				sqlExprCode(pParse, pFarg->a[0].pExpr,
@@ -4026,7 +4023,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			/* The UNLIKELY() function is a no-op.  The result is the value
 			 * of the first argument.
 			 */
-			if (pDef->funcFlags & SQL_FUNC_UNLIKELY) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_UNLIKELY)) {
 				assert(nFarg >= 1);
 				return sqlExprCodeTarget(pParse,
 							     pFarg->a[0].pExpr,
@@ -4049,7 +4046,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			 * is done using ANSI rules from
 			 * collations_check_compatibility().
 			 */
-			if ((pDef->funcFlags & SQL_FUNC_NEEDCOLL) != 0) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) {
 				struct coll *unused = NULL;
 				uint32_t curr_id = COLL_NONE;
 				bool is_curr_forced = false;
@@ -4096,9 +4093,8 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				 * or OPFLAG_TYPEOFARG respectively, to avoid unnecessary data
 				 * loading.
 				 */
-				if ((pDef->
-				     funcFlags & (SQL_FUNC_LENGTH |
-						  SQL_FUNC_TYPEOF)) != 0) {
+				if (sql_func_flag_is_set(func, SQL_FUNC_LENGTH |
+							       SQL_FUNC_TYPEOF)) {
 					u8 exprOp;
 					assert(nFarg == 1);
 					assert(pFarg->a[0].pExpr != 0);
@@ -4109,14 +4105,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 						       OPFLAG_LENGTHARG);
 						assert(SQL_FUNC_TYPEOF ==
 						       OPFLAG_TYPEOFARG);
-						testcase(pDef->
-							 funcFlags &
-							 OPFLAG_LENGTHARG);
-						pFarg->a[0].pExpr->op2 =
-						    pDef->
-						    funcFlags &
-						    (OPFLAG_LENGTHARG |
-						     OPFLAG_TYPEOFARG);
+						pFarg->a[0].pExpr->op2 = true;
 					}
 				}
 
@@ -4128,12 +4117,15 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			} else {
 				r1 = 0;
 			}
-			if (pDef->funcFlags & SQL_FUNC_NEEDCOLL) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) {
 				sqlVdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
 						  (char *)coll, P4_COLLSEQ);
 			}
-			sqlVdbeAddOp4(v, OP_BuiltinFunction0, constMask, r1,
-					  target, (char *)pDef, P4_FUNCDEF);
+			assert(func->def->language ==
+			       FUNC_LANGUAGE_SQL_BUILTIN);
+			int op = OP_BuiltinFunction0;
+			sqlVdbeAddOp4(v, op, constMask, r1, target,
+				      (char *)func, P4_FUNC);
 			sqlVdbeChangeP5(v, (u8) nFarg);
 			if (nFarg && constMask == 0) {
 				sqlReleaseTempRange(pParse, r1, nFarg);
@@ -5441,12 +5433,21 @@ analyzeAggregate(Walker * pWalker, Expr * pExpr)
 						pItem->iMem = ++pParse->nMem;
 						assert(!ExprHasProperty
 						       (pExpr, EP_IntValue));
-						pItem->pFunc = sqlFindFunction(
-							pParse->db,
-							pExpr->u.zToken,
-							pExpr->x.pList ?
-							pExpr->x.pList->nExpr : 0,
-							0);
+						const char *name =
+							pExpr->u.zToken;
+						uint32_t argc =
+							pExpr->x.pList != NULL ?
+							pExpr->x.pList->nExpr : 0;
+						pItem->func =
+							sql_func_by_signature(
+								name, argc);
+						assert(pItem->func != NULL);
+						assert(pItem->func->def->
+						       language ==
+						       FUNC_LANGUAGE_SQL_BUILTIN &&
+						       pItem->func->def->
+						       aggregate ==
+						       FUNC_AGGREGATE_GROUP);
 						if (pExpr->flags & EP_Distinct) {
 							pItem->iDistinct =
 								pParse->nTab++;
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 07c019db9..3b24e4804 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -45,6 +45,7 @@
 #include <unicode/uchar.h>
 #include <unicode/ucol.h>
 #include "box/coll_id_cache.h"
+#include "box/schema.h"
 
 /*
  * Return the collating function associated with a function.
@@ -77,12 +78,13 @@ static void
 minmaxFunc(sql_context * context, int argc, sql_value ** argv)
 {
 	int i;
-	int mask;		/* 0 for min() or 0xffffffff for max() */
 	int iBest;
 	struct coll *pColl;
+	struct func_sql_builtin *func =
+		(struct func_sql_builtin *)context->func;
 
 	assert(argc > 1);
-	mask = sql_user_data(context) == 0 ? 0 : -1;
+	int mask = (func->flags & SQL_FUNC_MAX) != 0 ? -1 : 0;
 	pColl = sqlGetFuncCollSeq(context);
 	assert(mask == -1 || mask == 0);
 	iBest = 0;
@@ -1713,6 +1715,8 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 	Mem *pBest;
 	UNUSED_PARAMETER(NotUsed);
 
+	struct func_sql_builtin *func =
+		(struct func_sql_builtin *)context->func;
 	pBest = (Mem *) sql_aggregate_context(context, sizeof(*pBest));
 	if (!pBest)
 		return;
@@ -1721,20 +1725,17 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 		if (pBest->flags)
 			sqlSkipAccumulatorLoad(context);
 	} else if (pBest->flags) {
-		int max;
 		int cmp;
 		struct coll *pColl = sqlGetFuncCollSeq(context);
-		/* This step function is used for both the min() and max() aggregates,
-		 * the only difference between the two being that the sense of the
-		 * comparison is inverted. For the max() aggregate, the
-		 * sql_user_data() function returns (void *)-1. For min() it
-		 * returns (void *)db, where db is the sql* database pointer.
-		 * Therefore the next statement sets variable 'max' to 1 for the max()
-		 * aggregate, or 0 for min().
+		/*
+		 * This step function is used for both the min()
+		 * and max() aggregates, the only difference
+		 * between the two being that the sense of the
+		 * comparison is inverted.
 		 */
-		max = sql_user_data(context) != 0;
+		bool is_max = (func->flags & SQL_FUNC_MAX) != 0;
 		cmp = sqlMemCompare(pBest, pArg, pColl);
-		if ((max && cmp < 0) || (!max && cmp > 0)) {
+		if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
 			sqlVdbeMemCopy(pBest, pArg);
 		} else {
 			sqlSkipAccumulatorLoad(context);
@@ -1817,127 +1818,461 @@ groupConcatFinalize(sql_context * context)
 }
 
 int
-sql_is_like_func(struct sql *db, struct Expr *expr)
+sql_is_like_func(struct Expr *expr)
 {
 	if (expr->op != TK_FUNCTION || !expr->x.pList ||
 	    expr->x.pList->nExpr != 2)
 		return 0;
 	assert(!ExprHasProperty(expr, EP_xIsSelect));
-	struct FuncDef *func = sqlFindFunction(db, expr->u.zToken, 2, 0);
-	assert(func != NULL);
-	if ((func->funcFlags & SQL_FUNC_LIKE) == 0)
+	struct func *func = sql_func_by_signature(expr->u.zToken, 2);
+	if (func == NULL || !sql_func_flag_is_set(func, SQL_FUNC_LIKE))
 		return 0;
 	return 1;
 }
 
-/*
- * All of the FuncDef structures in the aBuiltinFunc[] array above
- * to the global function hash table.  This occurs at start-time (as
- * a consequence of calling sql_initialize()).
- *
- * After this routine runs
+static int
+sql_builtin_call_stub(struct func *func, struct port *args, struct port *ret)
+{
+	(void) func; (void) args; (void) ret;
+	diag_set(ClientError, ER_UNSUPPORTED,
+		 "sql builtin function", "Lua frontend");
+	return -1;
+}
+
+static void
+sql_builtin_stub(sql_context *ctx, int argc, sql_value **argv)
+{
+	(void) argc; (void) argv;
+	diag_set(ClientError, ER_SQL_EXECUTE,
+		 tt_sprintf("function '%s' is not implemented",
+			    ctx->func->def->name));
+	ctx->is_aborted = true;
+}
+
+struct func *
+sql_func_by_signature(const char *name, int argc)
+{
+	struct func *func = func_by_name(name, strlen(name));
+	if (func == NULL || !func->def->exports.sql)
+		return NULL;
+	if (func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
+		if (strcmp(func->def->name, "COALESCE") == 0) {
+			if (argc == 0 || argc == 1)
+				return NULL;
+			argc = func->def->param_count;
+		} else if (strcmp(func->def->name, "COUNT") == 0) {
+			if (argc != 0 && argc != 1)
+				return NULL;
+			argc = func->def->param_count;
+		} else if (strcmp(func->def->name, "GROUP_CONCAT") == 0) {
+			if (argc != 1 && argc != 2)
+				return NULL;
+			argc = func->def->param_count;
+		} else if (strcmp(func->def->name, "LIKE") == 0) {
+			if (argc != 2 && argc != 3)
+				return NULL;
+			argc = func->def->param_count;
+		} else if (strcmp(func->def->name, "ROUND") == 0) {
+			if (argc != 1 && argc != 2)
+				return NULL;
+			argc = func->def->param_count;
+		} else if (strcmp(func->def->name, "SUBSTR") == 0) {
+			if (argc != 2 && argc != 3)
+				return NULL;
+			argc = func->def->param_count;
+		} else if (strcmp(func->def->name, "TRIM") == 0) {
+			if (argc != 1 && argc != 2 && argc != 3)
+				return NULL;
+			argc = func->def->param_count;
+		}
+	}
+	if (func->def->param_count != -1 && argc != func->def->param_count)
+		return NULL;
+	return func;
+}
+
+/**
+ * A sequence of SQL builtins definitions in
+ * lexicographic order.
  */
-void
-sqlRegisterBuiltinFunctions(void)
+static struct {
+	const char *name;
+	int param_count;
+	enum field_type returns;
+	enum func_aggregate aggregate;
+	bool is_deterministic;
+	uint16_t flags;
+	void (*call)(sql_context *ctx, int argc, sql_value **argv);
+	void (*finalize)(sql_context *ctx);
+} sql_builtins[] = {
+	{.name = "ABS",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = absFunc,
+	 .finalize = NULL},
+	{.name = "AVG",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .is_deterministic = false,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .flags = 0,
+	 .call = sum_step,
+	 .finalize = avgFinalize},
+	{.name = "CHAR",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .is_deterministic = true,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .flags = 0,
+	 .call = charFunc,
+	 .finalize = NULL},
+	{.name = "CHARACTER_LENGTH",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = lengthFunc,
+	 .finalize = NULL},
+	{.name = "CHAR_LENGTH",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = lengthFunc,
+	 .finalize = NULL},
+	{.name = "COALESCE",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_COALESCE,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL},
+	{.name = "COUNT",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = countStep,
+	 .finalize = countFinalize},
+	{.name = "GREATEST",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MAX,
+	 .call = minmaxFunc,
+	 .finalize = NULL},
+	{.name = "GROUP_CONCAT",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = groupConcatStep,
+	 .finalize = groupConcatFinalize},
+	{.name = "HEX",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = hexFunc,
+	 .finalize = NULL},
+	{.name = "IFNULL",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_COALESCE,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL},
+	{.name = "LEAST",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MIN,
+	 .call = minmaxFunc,
+	 .finalize = NULL},
+	{.name = "LENGTH",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_LENGTH,
+	 .call = lengthFunc,
+	 .finalize = NULL},
+	{.name = "LIKE",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_LIKE,
+	 .call = likeFunc,
+	 .finalize = NULL},
+	{.name = "LIKELIHOOD",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_BOOLEAN,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_UNLIKELY,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL},
+	{.name = "LIKELY",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_BOOLEAN,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_UNLIKELY,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL},
+	{.name = "LOWER",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
+	 .call = LowerICUFunc,
+	 .finalize = NULL},
+	{.name = "MAX",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MAX,
+	 .call = minmaxStep,
+	 .finalize = minMaxFinalize},
+	{.name = "MIN",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MIN,
+	 .call = minmaxStep,
+	 .finalize = minMaxFinalize},
+	{.name = "NULLIF",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL,
+	 .call = nullifFunc,
+	 .finalize = NULL},
+	{.name = "POSITION",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL,
+	 .call = position_func,
+	 .finalize = NULL},
+	{.name = "PRINTF",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = printfFunc,
+	 .finalize = NULL},
+	{.name = "QUOTE",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = quoteFunc,
+	 .finalize = NULL},
+	{.name = "RANDOM",
+	 .param_count = 0,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = randomFunc,
+	 .finalize = NULL},
+	{.name = "RANDOMBLOB",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_VARBINARY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = randomBlob,
+	 .finalize = NULL},
+	{.name = "REPLACE",
+	 .param_count = 3,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL,
+	 .call = replaceFunc,
+	 .finalize = NULL},
+	{.name = "ROUND",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = roundFunc,
+	 .finalize = NULL},
+	{.name = "ROW_COUNT",
+	 .param_count = 0,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = sql_row_count,
+	 .finalize = NULL},
+	{.name = "SOUNDEX",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = soundexFunc,
+	 .finalize = NULL},
+	{.name = "SUBSTR",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL,
+	 .call = substrFunc,
+	 .finalize = NULL},
+	{.name = "SUM",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = sum_step,
+	 .finalize = sumFinalize},
+	{.name = "TOTAL",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = sum_step,
+	 .finalize = totalFinalize},
+	{.name = "TRIM",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL,
+	 .call = trim_func,
+	 .finalize = NULL},
+	{.name = "TYPEOF",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_TYPEOF,
+	 .call = typeofFunc,
+	 .finalize = NULL},
+	{.name = "UNICODE",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = unicodeFunc,
+	 .finalize = NULL},
+	{.name = "UNLIKELY",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_BOOLEAN,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_UNLIKELY,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL},
+	{.name = "UPPER",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
+	 .call = UpperICUFunc,
+	 .finalize = NULL},
+	{.name = "VERSION",
+	 .param_count = 0,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = sql_func_version,
+	 .finalize = NULL},
+	{.name = "ZEROBLOB",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_VARBINARY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = zeroblobFunc,
+	 .finalize = NULL},
+};
+
+
+static struct func_vtab func_sql_builtin_vtab;
+
+struct func *
+func_sql_builtin_new(struct func_def *def)
 {
-	/*
-	 * The following array holds FuncDef structures for all of the functions
-	 * defined in this file.
-	 *
-	 * The array cannot be constant since changes are made to the
-	 * FuncDef.pHash elements at start-time.  The elements of this array
-	 * are read-only after initialization is complete.
-	 *
-	 * For peak efficiency, put the most frequently used function last.
-	 */
-	static FuncDef aBuiltinFunc[] = {
-		FUNCTION(soundex, 1, 0, 0, soundexFunc, FIELD_TYPE_STRING),
-		FUNCTION2(unlikely, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION2(likelihood, 2, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION2(likely, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION_COLL(trim, 1, 3, 0, trim_func),
-		FUNCTION_COLL(trim, 2, 3, 0, trim_func),
-		FUNCTION_COLL(trim, 3, 3, 0, trim_func),
-		FUNCTION(least, -1, 0, 1, minmaxFunc, FIELD_TYPE_SCALAR),
-		AGGREGATE2(min, 1, 0, 1, minmaxStep, minMaxFinalize,
-			   SQL_FUNC_MINMAX, FIELD_TYPE_SCALAR),
-		FUNCTION(greatest, -1, 1, 1, minmaxFunc, FIELD_TYPE_SCALAR),
-		AGGREGATE2(max, 1, 1, 1, minmaxStep, minMaxFinalize,
-			   SQL_FUNC_MINMAX, FIELD_TYPE_SCALAR),
-		FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQL_FUNC_TYPEOF,
-			  FIELD_TYPE_STRING),
-		FUNCTION2(length, 1, 0, 0, lengthFunc, SQL_FUNC_LENGTH,
-			  FIELD_TYPE_INTEGER),
-		FUNCTION(char_length, 1, 0, 0, lengthFunc, FIELD_TYPE_INTEGER),
-		FUNCTION(character_length, 1, 0, 0, lengthFunc,
-			 FIELD_TYPE_INTEGER),
-		FUNCTION(position, 2, 0, 1, position_func, FIELD_TYPE_INTEGER),
-		FUNCTION(printf, -1, 0, 0, printfFunc, FIELD_TYPE_STRING),
-		FUNCTION(unicode, 1, 0, 0, unicodeFunc, FIELD_TYPE_STRING),
-		FUNCTION(char, -1, 0, 0, charFunc, FIELD_TYPE_STRING),
-		FUNCTION(abs, 1, 0, 0, absFunc, FIELD_TYPE_NUMBER),
-		FUNCTION(round, 1, 0, 0, roundFunc, FIELD_TYPE_INTEGER),
-		FUNCTION(round, 2, 0, 0, roundFunc, FIELD_TYPE_INTEGER),
-		FUNCTION_COLL(upper, 1, 0, 1, UpperICUFunc),
-		FUNCTION_COLL(lower, 1, 0, 1, LowerICUFunc),
-		FUNCTION(hex, 1, 0, 0, hexFunc, FIELD_TYPE_STRING),
-		FUNCTION2(ifnull, 2, 0, 0, noopFunc, SQL_FUNC_COALESCE,
-			  FIELD_TYPE_INTEGER),
-		VFUNCTION(random, 0, 0, 0, randomFunc, FIELD_TYPE_INTEGER),
-		VFUNCTION(randomblob, 1, 0, 0, randomBlob, FIELD_TYPE_VARBINARY),
-		FUNCTION(nullif, 2, 0, 1, nullifFunc, FIELD_TYPE_SCALAR),
-		FUNCTION(version, 0, 0, 0, sql_func_version, FIELD_TYPE_STRING),
-		FUNCTION(quote, 1, 0, 0, quoteFunc, FIELD_TYPE_STRING),
-		VFUNCTION(row_count, 0, 0, 0, sql_row_count, FIELD_TYPE_INTEGER),
-		FUNCTION_COLL(replace, 3, 0, 0, replaceFunc),
-		FUNCTION(zeroblob, 1, 0, 0, zeroblobFunc, FIELD_TYPE_VARBINARY),
-		FUNCTION_COLL(substr, 2, 0, 0, substrFunc),
-		FUNCTION_COLL(substr, 3, 0, 0, substrFunc),
-		AGGREGATE(sum, 1, 0, 0, sum_step, sumFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE(total, 1, 0, 0, sum_step, totalFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE(avg, 1, 0, 0, sum_step, avgFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE2(count, 0, 0, 0, countStep, countFinalize,
-			  SQL_FUNC_COUNT, FIELD_TYPE_INTEGER),
-		AGGREGATE2(count, 1, 0, 0, countStep, countFinalize,
-			   SQL_FUNC_COUNT, FIELD_TYPE_INTEGER),
-		AGGREGATE(group_concat, 1, 0, 0, groupConcatStep,
-			  groupConcatFinalize, FIELD_TYPE_STRING),
-		AGGREGATE(group_concat, 2, 0, 0, groupConcatStep,
-			  groupConcatFinalize, FIELD_TYPE_STRING),
-
-		LIKEFUNC(like, 2, 1, SQL_FUNC_LIKE,
-			 FIELD_TYPE_INTEGER),
-		LIKEFUNC(like, 3, 1, SQL_FUNC_LIKE,
-			 FIELD_TYPE_INTEGER),
-		FUNCTION(coalesce, 1, 0, 0, 0, FIELD_TYPE_SCALAR),
-		FUNCTION(coalesce, 0, 0, 0, 0, FIELD_TYPE_SCALAR),
-		FUNCTION2(coalesce, -1, 0, 0, noopFunc, SQL_FUNC_COALESCE,
-			  FIELD_TYPE_SCALAR),
-	};
-	sql_register_analyze_builtins();
-	sqlRegisterDateTimeFunctions();
-	sqlInsertBuiltinFuncs(aBuiltinFunc, ArraySize(aBuiltinFunc));
-
-#if 0				/* Enable to print out how the built-in functions are hashed */
-	{
-		int i;
-		FuncDef *p;
-		for (i = 0; i < SQL_FUNC_HASH_SZ; i++) {
-			printf("FUNC-HASH %02d:", i);
-			for (p = sqlBuiltinFunctions.a[i]; p;
-			     p = p->u.pHash) {
-				int n = sqlStrlen30(p->zName);
-				int h = p->zName[0] + n;
-				printf(" %s(%d)", p->zName, h);
-			}
-			printf("\n");
+	assert(def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	struct func_sql_builtin *func =
+		(struct func_sql_builtin *) calloc(1, sizeof(*func));
+	if (func == NULL) {
+		diag_set(OutOfMemory, sizeof(*func), "malloc", "func");
+		return NULL;
+	}
+	func->base.def = def;
+	func->base.vtab = &func_sql_builtin_vtab;
+	func->call = sql_builtin_stub;
+	/** Binary search for corresponding builtin entry. */
+	int idx = -1, left = 0, right = nelem(sql_builtins) - 1;
+	while (left <= right) {
+		uint32_t mid = (left + right) / 2;
+		int rc = strcmp(def->name, sql_builtins[mid].name);
+		if (rc == 0) {
+			idx = mid;
+			break;
 		}
+		if (rc < 0)
+			right = mid - 1;
+		else
+			left = mid + 1;
 	}
-#endif
+	/* Some builtins are not implemented yet. */
+	if (idx == -1)
+		goto end;
+	func->flags = sql_builtins[idx].flags;
+	func->call = sql_builtins[idx].call;
+	func->finalize = sql_builtins[idx].finalize;
+	def->param_count = sql_builtins[idx].param_count;
+	def->is_deterministic = sql_builtins[idx].is_deterministic;
+	def->returns = sql_builtins[idx].returns;
+	def->aggregate = sql_builtins[idx].aggregate;
+	def->exports.sql = true;
+end:
+	return &func->base;
 }
+
+static void
+func_sql_builtin_destroy(struct func *func)
+{
+	assert(func->vtab == &func_sql_builtin_vtab);
+	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	free(func);
+}
+
+static struct func_vtab func_sql_builtin_vtab = {
+	.call = sql_builtin_call_stub,
+	.destroy = func_sql_builtin_destroy,
+};
diff --git a/src/box/sql/global.c b/src/box/sql/global.c
index 6cadef809..c25b83de1 100644
--- a/src/box/sql/global.c
+++ b/src/box/sql/global.c
@@ -162,13 +162,6 @@ SQL_WSD struct sqlConfig sqlConfig = {
 	0x7ffffffe		/* iOnceResetThreshold */
 };
 
-/*
- * Hash table for global functions - functions common to all
- * database connections.  After initialization, this table is
- * read-only.
- */
-FuncDefHash sqlBuiltinFunctions;
-
 /*
  * The value of the "pending" byte must be 0x40000000 (1 byte past the
  * 1-gibabyte boundary) in a compatible database.  sql never uses
diff --git a/src/box/sql/main.c b/src/box/sql/main.c
index 748dc1b8e..0b20f2132 100644
--- a/src/box/sql/main.c
+++ b/src/box/sql/main.c
@@ -131,9 +131,6 @@ sql_initialize(void)
 	if (sqlGlobalConfig.isInit == 0
 	    && sqlGlobalConfig.inProgress == 0) {
 		sqlGlobalConfig.inProgress = 1;
-		memset(&sqlBuiltinFunctions, 0,
-		       sizeof(sqlBuiltinFunctions));
-		sqlRegisterBuiltinFunctions();
 		sql_os_init();
 		sqlGlobalConfig.isInit = 1;
 		sqlGlobalConfig.inProgress = 0;
@@ -242,25 +239,6 @@ sqlCloseSavepoints(Vdbe * pVdbe)
 	pVdbe->anonymous_savepoint = NULL;
 }
 
-/*
- * Invoke the destructor function associated with FuncDef p, if any. Except,
- * if this is not the last copy of the function, do not invoke it. Multiple
- * copies of a single function are created when create_function() is called
- * with SQL_ANY as the encoding.
- */
-static void
-functionDestroy(sql * db, FuncDef * p)
-{
-	FuncDestructor *pDestructor = p->u.pDestructor;
-	if (pDestructor) {
-		pDestructor->nRef--;
-		if (pDestructor->nRef == 0) {
-			pDestructor->xDestroy(pDestructor->pUserData);
-			sqlDbFree(db, pDestructor);
-		}
-	}
-}
-
 /*
  * Rollback all database files.  If tripCode is not 0, then
  * any write cursors are invalidated ("tripped" - as in "tripping a circuit
@@ -279,121 +257,6 @@ sqlRollbackAll(Vdbe * pVdbe)
 	}
 }
 
-/*
- * This function is exactly the same as sql_create_function(), except
- * that it is designed to be called by internal code. The difference is
- * that if a malloc() fails in sql_create_function(), an error code
- * is returned and the mallocFailed flag cleared.
- */
-int
-sqlCreateFunc(sql * db,
-		  const char *zFunctionName,
-		  enum field_type type,
-		  int nArg,
-		  int flags,
-		  void *pUserData,
-		  void (*xSFunc) (sql_context *, int, sql_value **),
-		  void (*xStep) (sql_context *, int, sql_value **),
-		  void (*xFinal) (sql_context *),
-		  FuncDestructor * pDestructor)
-{
-	FuncDef *p;
-	int extraFlags;
-
-	if (zFunctionName == 0 ||
-	    (xSFunc && (xFinal || xStep)) ||
-	    (!xSFunc && (xFinal && !xStep)) ||
-	    (!xSFunc && (!xFinal && xStep)) ||
-	    (nArg < -1 || nArg > SQL_MAX_FUNCTION_ARG) ||
-	    (255 < (sqlStrlen30(zFunctionName)))) {
-		diag_set(ClientError, ER_CREATE_FUNCTION, zFunctionName,
-			 "wrong function definition");
-		return -1;
-	}
-
-	assert(SQL_FUNC_CONSTANT == SQL_DETERMINISTIC);
-	extraFlags = flags & SQL_DETERMINISTIC;
-
-
-	/* Check if an existing function is being overridden or deleted. If so,
-	 * and there are active VMs, then return an error. If a function
-	 * is being overridden/deleted but there are no active VMs, allow the
-	 * operation to continue but invalidate all precompiled statements.
-	 */
-	p = sqlFindFunction(db, zFunctionName, nArg, 0);
-	if (p && p->nArg == nArg) {
-		if (db->nVdbeActive) {
-			diag_set(ClientError, ER_CREATE_FUNCTION, zFunctionName,
-				 "unable to create function due to active "\
-				 "statements");
-			return -1;
-		} else {
-			sqlExpirePreparedStatements(db);
-		}
-	}
-
-	p = sqlFindFunction(db, zFunctionName, nArg, 1);
-	assert(p || db->mallocFailed);
-	if (p == NULL)
-		return -1;
-
-	/* If an older version of the function with a configured destructor is
-	 * being replaced invoke the destructor function here.
-	 */
-	functionDestroy(db, p);
-
-	if (pDestructor) {
-		pDestructor->nRef++;
-	}
-	p->u.pDestructor = pDestructor;
-	p->funcFlags = extraFlags;
-	testcase(p->funcFlags & SQL_DETERMINISTIC);
-	p->xSFunc = xSFunc ? xSFunc : xStep;
-	p->xFinalize = xFinal;
-	p->pUserData = pUserData;
-	p->nArg = (u16) nArg;
-	p->ret_type = type;
-	return 0;
-}
-
-int
-sql_create_function_v2(sql * db,
-			   const char *zFunc,
-			   enum field_type type,
-			   int nArg,
-			   int flags,
-			   void *p,
-			   void (*xSFunc) (sql_context *, int,
-					   sql_value **),
-			   void (*xStep) (sql_context *, int,
-					  sql_value **),
-			   void (*xFinal) (sql_context *),
-			   void (*xDestroy) (void *))
-{
-	FuncDestructor *pArg = 0;
-
-	if (xDestroy) {
-		pArg =
-		    (FuncDestructor *) sqlDbMallocZero(db,
-							   sizeof
-							   (FuncDestructor));
-		if (!pArg) {
-			xDestroy(p);
-			return -1;
-		}
-		pArg->xDestroy = xDestroy;
-		pArg->pUserData = p;
-	}
-	int rc = sqlCreateFunc(db, zFunc, type, nArg, flags, p, xSFunc, xStep,
-			       xFinal, pArg);
-	if (pArg && pArg->nRef == 0) {
-		assert(rc != 0);
-		xDestroy(p);
-		sqlDbFree(db, pArg);
-	}
-	return rc;
-}
-
 /*
  * This array defines hard upper bounds on limit values.  The
  * initializer must be kept in sync with the SQL_LIMIT_*
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 207f57ba6..682bb9c8e 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -38,6 +38,9 @@
 #include "sqlInt.h"
 #include <stdlib.h>
 #include <string.h>
+#include "box/func.h"
+#include "box/func_def.h"
+#include "box/schema.h"
 
 /*
  * Walk the expression tree pExpr and increase the aggregate function
@@ -596,27 +599,28 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 			int is_agg = 0;	/* True if is an aggregate function */
 			int nId;	/* Number of characters in function name */
 			const char *zId;	/* The function name. */
-			FuncDef *pDef;	/* Information about the function */
 
 			assert(!ExprHasProperty(pExpr, EP_xIsSelect));
 			zId = pExpr->u.zToken;
 			nId = sqlStrlen30(zId);
-			pDef = sqlFindFunction(pParse->db, zId, n, 0);
-			if (pDef == 0) {
-				pDef =
-				    sqlFindFunction(pParse->db, zId, -2,0);
-				if (pDef == 0) {
+			struct func *func = sql_func_by_signature(zId, n);
+			if (func == NULL) {
+				func = func_by_name(zId, nId);
+				if (func == NULL || !func->def->exports.sql)
 					no_such_func = 1;
-				} else {
+				else
 					wrong_num_args = 1;
-				}
 			} else {
-				is_agg = pDef->xFinalize != 0;
-				pExpr->type = pDef->ret_type;
+				is_agg = func->def->language ==
+					 FUNC_LANGUAGE_SQL_BUILTIN &&
+					 func->def->aggregate ==
+					 FUNC_AGGREGATE_GROUP;
+				pExpr->type = func->def->returns;
 				const char *err =
 					"second argument to likelihood() must "\
 					"be a constant between 0.0 and 1.0";
-				if (pDef->funcFlags & SQL_FUNC_UNLIKELY) {
+				if (sql_func_flag_is_set(func,
+							 SQL_FUNC_UNLIKELY)) {
 					ExprSetProperty(pExpr,
 							EP_Unlikely | EP_Skip);
 					if (n == 2) {
@@ -643,19 +647,17 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 						 */
 						/* TUNING: unlikely() probability is 0.0625.  likely() is 0.9375 */
 						pExpr->iTable =
-						    pDef->zName[0] ==
-						    'u' ? 8388608 : 125829120;
+						    func->def->name[0] == 'u' ?
+						    8388608 : 125829120;
 					}
 				}
-				if ((pDef->funcFlags & SQL_FUNC_CONSTANT) != 0) {
+				if (func->def->is_deterministic) {
 					/* For the purposes of the EP_ConstFunc flag, date and time
 					 * functions and other functions that change slowly are considered
 					 * constant because they are constant for the duration of one query
 					 */
 					ExprSetProperty(pExpr, EP_ConstFunc);
-				}
-				if ((pDef->funcFlags & SQL_FUNC_CONSTANT) ==
-				    0) {
+				} else {
 					/* Date/time functions that use 'now', and other functions
 					 * that might change over time cannot be used
 					 * in an index.
@@ -698,18 +700,13 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 					pExpr->op2++;
 					pNC2 = pNC2->pNext;
 				}
-				assert(pDef != 0);
+				assert(func != NULL);
 				if (pNC2) {
-					assert(SQL_FUNC_MINMAX ==
-					       NC_MinMaxAgg);
-					testcase((pDef->
-						  funcFlags &
-						  SQL_FUNC_MINMAX) != 0);
-					pNC2->ncFlags |=
-					    NC_HasAgg | (pDef->
-							 funcFlags &
-							 SQL_FUNC_MINMAX);
-
+					pNC2->ncFlags |= NC_HasAgg;
+					if (sql_func_flag_is_set(func,
+							         SQL_FUNC_MIN |
+								 SQL_FUNC_MAX))
+						pNC2->ncFlags |= NC_MinMaxAgg;
 				}
 				pNC->ncFlags |= NC_AllowAgg;
 			}
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index ddb5de87e..b2ce868d6 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4381,7 +4381,9 @@ is_simple_count(struct Select *select, struct AggInfo *agg_info)
 		return NULL;
 	if (NEVER(agg_info->nFunc == 0))
 		return NULL;
-	if ((agg_info->aFunc->pFunc->funcFlags & SQL_FUNC_COUNT) == 0 ||
+	assert(agg_info->aFunc->func->def->language ==
+	       FUNC_LANGUAGE_SQL_BUILTIN);
+	if (!sql_func_flag_is_set(agg_info->aFunc->func, SQL_FUNC_COUNT) == 0 ||
 	    (agg_info->aFunc->pExpr->x.pList != NULL &&
 	     agg_info->aFunc->pExpr->x.pList->nExpr > 0))
 		return NULL;
@@ -5278,7 +5280,7 @@ finalizeAggFunctions(Parse * pParse, AggInfo * pAggInfo)
 		assert(!ExprHasProperty(pF->pExpr, EP_xIsSelect));
 		sqlVdbeAddOp2(v, OP_AggFinal, pF->iMem,
 				  pList ? pList->nExpr : 0);
-		sqlVdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
+		sqlVdbeAppendP4(v, pF->func, P4_FUNC);
 	}
 }
 
@@ -5319,7 +5321,7 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 			vdbe_insert_distinct(pParse, pF->iDistinct, pF->reg_eph,
 					     addrNext, 1, regAgg);
 		}
-		if (pF->pFunc->funcFlags & SQL_FUNC_NEEDCOLL) {
+		if (sql_func_flag_is_set(pF->func, SQL_FUNC_NEEDCOLL)) {
 			struct coll *coll = NULL;
 			struct ExprList_item *pItem;
 			int j;
@@ -5338,7 +5340,7 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 					  (char *)coll, P4_COLLSEQ);
 		}
 		sqlVdbeAddOp3(v, OP_AggStep0, 0, regAgg, pF->iMem);
-		sqlVdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
+		sqlVdbeAppendP4(v, pF->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/vdbe.c b/src/box/sql/vdbe.c
index 28552f64a..c5a4a0da9 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -41,6 +41,8 @@
  */
 #include "box/box.h"
 #include "box/error.h"
+#include "box/func.h"
+#include "box/func_def.h"
 #include "box/fk_constraint.h"
 #include "box/txn.h"
 #include "box/tuple.h"
@@ -1704,7 +1706,7 @@ case OP_BuiltinFunction0: {
 	int n;
 	sql_context *pCtx;
 
-	assert(pOp->p4type==P4_FUNCDEF);
+	assert(pOp->p4type == P4_FUNC);
 	n = pOp->p5;
 	assert(pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor));
 	assert(n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1));
@@ -1712,7 +1714,7 @@ case OP_BuiltinFunction0: {
 	pCtx = sqlDbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sql_value*));
 	if (pCtx==0) goto no_mem;
 	pCtx->pOut = 0;
-	pCtx->pFunc = pOp->p4.pFunc;
+	pCtx->func = pOp->p4.func;
 	pCtx->iOp = (int)(pOp - aOp);
 	pCtx->pVdbe = p;
 	pCtx->argc = n;
@@ -1747,7 +1749,9 @@ case OP_BuiltinFunction: {
 	}
 #endif
 	pCtx->is_aborted = false;
-	(*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */
+	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
+	func->call(pCtx, pCtx->argc, pCtx->argv);
 
 	/* If the function returned an error, throw an exception */
 	if (pCtx->is_aborted)
@@ -5005,7 +5009,7 @@ case OP_AggStep0: {
 	int n;
 	sql_context *pCtx;
 
-	assert(pOp->p4type==P4_FUNCDEF);
+	assert(pOp->p4type == P4_FUNC);
 	n = pOp->p5;
 	assert(pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor));
 	assert(n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1));
@@ -5013,7 +5017,7 @@ case OP_AggStep0: {
 	pCtx = sqlDbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sql_value*));
 	if (pCtx==0) goto no_mem;
 	pCtx->pMem = 0;
-	pCtx->pFunc = pOp->p4.pFunc;
+	pCtx->func = pOp->p4.func;
 	pCtx->iOp = (int)(pOp - aOp);
 	pCtx->pVdbe = p;
 	pCtx->argc = n;
@@ -5055,7 +5059,9 @@ case OP_AggStep: {
 	pCtx->pOut = &t;
 	pCtx->is_aborted = false;
 	pCtx->skipFlag = 0;
-	(pCtx->pFunc->xSFunc)(pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */
+	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
+	func->call(pCtx, pCtx->argc, pCtx->argv);
 	if (pCtx->is_aborted) {
 		sqlVdbeMemRelease(&t);
 		goto abort_due_to_error;
@@ -5087,7 +5093,7 @@ case OP_AggFinal: {
 	assert(pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor));
 	pMem = &aMem[pOp->p1];
 	assert((pMem->flags & ~(MEM_Null|MEM_Agg))==0);
-	if (sqlVdbeMemFinalize(pMem, pOp->p4.pFunc) != 0)
+	if (sql_vdbemem_finalize(pMem, pOp->p4.func) != 0)
 		goto abort_due_to_error;
 	UPDATE_MAX_BLOBSIZE(pMem);
 	if (sqlVdbeMemTooBig(pMem)) {
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index dfaff9365..bf40b44fa 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -477,17 +477,6 @@ sql_step(sql_stmt * pStmt)
 	return sqlStep(v);
 }
 
-/*
- * Extract the user data from a sql_context structure and return a
- * pointer to it.
- */
-void *
-sql_user_data(sql_context * p)
-{
-	assert(p && p->pFunc);
-	return p->pFunc->pUserData;
-}
-
 /*
  * Extract the user data from a sql_context structure and return a
  * pointer to it.
@@ -547,7 +536,7 @@ createAggContext(sql_context * p, int nByte)
 	} else {
 		sqlVdbeMemClearAndResize(pMem, nByte);
 		pMem->flags = MEM_Agg;
-		pMem->u.pDef = p->pFunc;
+		pMem->u.func = p->func;
 		if (pMem->z) {
 			memset(pMem->z, 0, nByte);
 		}
@@ -563,7 +552,9 @@ createAggContext(sql_context * p, int nByte)
 void *
 sql_aggregate_context(sql_context * p, int nByte)
 {
-	assert(p && p->pFunc && p->pFunc->xFinalize);
+	assert(p != NULL && p->func != NULL);
+	assert(p->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	assert(p->func->def->aggregate == FUNC_AGGREGATE_GROUP);
 	testcase(nByte < 0);
 	if ((p->pMem->flags & MEM_Agg) == 0) {
 		return createAggContext(p, nByte);
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index e7accc745..a44540b1d 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -648,24 +648,11 @@ sqlVdbeJumpHere(Vdbe * p, int addr)
 	sqlVdbeChangeP2(p, addr, p->nOp);
 }
 
-/*
- * If the input FuncDef structure is ephemeral, then free it.  If
- * the FuncDef is not ephermal, then do nothing.
- */
-static void
-freeEphemeralFunction(sql * db, FuncDef * pDef)
-{
-	if ((pDef->funcFlags & SQL_FUNC_EPHEM) != 0) {
-		sqlDbFree(db, pDef);
-	}
-}
-
 static void vdbeFreeOpArray(sql *, Op *, int);
 
 static SQL_NOINLINE void
 freeP4FuncCtx(sql * db, sql_context * p)
 {
-	freeEphemeralFunction(db, p->pFunc);
 	sqlDbFree(db, p);
 }
 
@@ -689,13 +676,11 @@ freeP4(sql * db, int p4type, void *p4)
 	case P4_KEYINFO:
 		sql_key_info_unref(p4);
 		break;
-	case P4_FUNCDEF:{
-			freeEphemeralFunction(db, (FuncDef *) p4);
-			break;
-		}
 	case P4_MEM:
 		sqlValueFree((sql_value *) p4);
 		break;
+	default:
+		break;
 	}
 }
 
@@ -1149,15 +1134,17 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
 				sqlXPrintf(&x, "(binary)");
 			break;
 		}
-	case P4_FUNCDEF:{
-			FuncDef *pDef = pOp->p4.pFunc;
-			sqlXPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg);
+	case P4_FUNC:{
+			struct func *func = pOp->p4.func;
+			sqlXPrintf(&x, "%s(%d)", func->def->name,
+				   func->def->param_count);
 			break;
 		}
 #if defined(SQL_DEBUG) || defined(VDBE_PROFILE)
 	case P4_FUNCCTX:{
-			FuncDef *pDef = pOp->p4.pCtx->pFunc;
-			sqlXPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg);
+			struct func *func = pOp->p4.func;
+			sqlXPrintf(&x, "%s(%d)", func->def->name,
+				   func->def->param_count);
 			break;
 		}
 #endif
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 5516d7fb1..ac0dfa333 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -312,37 +312,29 @@ sqlVdbeMemStringify(Mem * pMem)
 	return 0;
 }
 
-/*
- * Memory cell pMem contains the context of an aggregate function.
- * This routine calls the finalize method for that function.  The
- * result of the aggregate is stored back into pMem.
- *
- * Return -1 if the finalizer reports an error. 0 otherwise.
- */
 int
-sqlVdbeMemFinalize(Mem * pMem, FuncDef * pFunc)
+sql_vdbemem_finalize(struct Mem *mem, struct func *func)
 {
-	if (ALWAYS(pFunc && pFunc->xFinalize)) {
-		sql_context ctx;
-		Mem t;
-		assert((pMem->flags & MEM_Null) != 0 || pFunc == pMem->u.pDef);
-		memset(&ctx, 0, sizeof(ctx));
-		memset(&t, 0, sizeof(t));
-		t.flags = MEM_Null;
-		t.db = pMem->db;
-		t.field_type = field_type_MAX;
-		ctx.pOut = &t;
-		ctx.pMem = pMem;
-		ctx.pFunc = pFunc;
-		pFunc->xFinalize(&ctx);	/* IMP: R-24505-23230 */
-		assert((pMem->flags & MEM_Dyn) == 0);
-		if (pMem->szMalloc > 0)
-			sqlDbFree(pMem->db, pMem->zMalloc);
-		memcpy(pMem, &t, sizeof(t));
-		if (ctx.is_aborted)
-			return -1;
-	}
-	return 0;
+	assert(func != NULL);
+	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	assert(func->def->aggregate == FUNC_AGGREGATE_GROUP);
+	assert((mem->flags & MEM_Null) != 0 || func == mem->u.func);
+	sql_context ctx;
+	memset(&ctx, 0, sizeof(ctx));
+	Mem t;
+	memset(&t, 0, sizeof(t));
+	t.flags = MEM_Null;
+	t.db = mem->db;
+	t.field_type = field_type_MAX;
+	ctx.pOut = &t;
+	ctx.pMem = mem;
+	ctx.func = func;
+	((struct func_sql_builtin *)func)->finalize(&ctx);
+	assert((mem->flags & MEM_Dyn) == 0);
+	if (mem->szMalloc > 0)
+		sqlDbFree(mem->db, mem->zMalloc);
+	memcpy(mem, &t, sizeof(t));
+	return ctx.is_aborted ? -1 : 0;
 }
 
 /*
@@ -359,7 +351,7 @@ vdbeMemClearExternAndSetNull(Mem * p)
 {
 	assert(VdbeMemDynamic(p));
 	if (p->flags & MEM_Agg) {
-		sqlVdbeMemFinalize(p, p->u.pDef);
+		sql_vdbemem_finalize(p, p->u.func);
 		assert((p->flags & MEM_Agg) == 0);
 		testcase(p->flags & MEM_Dyn);
 	}
@@ -1289,7 +1281,6 @@ valueFromFunction(sql * db,	/* The database connection */
 	sql_context ctx;	/* Context object for function invocation */
 	sql_value **apVal = 0;	/* Function arguments */
 	int nVal = 0;		/* Size of apVal[] array */
-	FuncDef *pFunc = 0;	/* Function definition */
 	sql_value *pVal = 0;	/* New value */
 	int rc = 0;	/* Return code */
 	ExprList *pList = 0;	/* Function arguments */
@@ -1300,10 +1291,10 @@ valueFromFunction(sql * db,	/* The database connection */
 	pList = p->x.pList;
 	if (pList)
 		nVal = pList->nExpr;
-	pFunc = sqlFindFunction(db, p->u.zToken, nVal, 0);
-	assert(pFunc);
-	if ((pFunc->funcFlags & SQL_FUNC_CONSTANT) == 0 ||
-	    (pFunc->funcFlags & SQL_FUNC_NEEDCOLL))
+	struct func *func = sql_func_by_signature(p->u.zToken, nVal);
+	if (func == NULL || func->def->language != FUNC_LANGUAGE_SQL_BUILTIN ||
+	    !func->def->is_deterministic ||
+	    sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL))
 		return 0;
 
 	if (pList) {
@@ -1332,8 +1323,8 @@ valueFromFunction(sql * db,	/* The database connection */
 	assert(!pCtx->pParse->is_aborted);
 	memset(&ctx, 0, sizeof(ctx));
 	ctx.pOut = pVal;
-	ctx.pFunc = pFunc;
-	pFunc->xSFunc(&ctx, nVal, apVal);
+	ctx.func = func;
+	((struct func_sql_builtin *)func)->call(&ctx, nVal, apVal);
 	assert(!ctx.is_aborted);
 	sql_value_apply_type(pVal, type);
 	assert(rc == 0);
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index 8adf6a5f1..98615b118 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -273,7 +273,7 @@ like_optimization_is_valid(Parse *pParse, Expr *pExpr, Expr **ppPrefix,
 	/* Result code to return. */
 	int rc;
 
-	if (!sql_is_like_func(db, pExpr)) {
+	if (!sql_is_like_func(pExpr)) {
 		return 0;
 	}
 	pList = pExpr->x.pList;
diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 9bba37bcb..9d2fcea4b 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -130,7 +130,6 @@ add_library(box STATIC
     ${lua_sources}
     lua/init.c
     lua/call.c
-    lua/lua_sql.c
     lua/cfg.cc
     lua/console.c
     lua/tuple.c
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 4f2e34bf0..22d3040ef 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -2926,6 +2926,12 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 				  (unsigned) old_func->def->uid,
 				  "function has references");
 		}
+		/* Can't' drop a builtin function. */
+		if (old_func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
+			tnt_raise(ClientError, ER_DROP_FUNCTION,
+				  (unsigned) old_func->def->uid,
+				  "function is SQL builtin");
+		}
 		struct trigger *on_commit =
 			txn_alter_trigger_new(on_drop_func_commit, old_func);
 		struct trigger *on_rollback =
diff --git a/test/box/function1.result b/test/box/function1.result
index 5b091f72b..3eba37ab1 100644
--- a/test/box/function1.result
+++ b/test/box/function1.result
@@ -825,6 +825,10 @@ box.func.LUA:call({"return 1 + 1"})
 ---
 - 2
 ...
+box.schema.func.drop("SUM")
+---
+- error: 'Can''t drop function 1: function is SQL builtin'
+...
 -- Introduce function options
 box.schema.func.create('test', {body = "function(tuple) return tuple end", is_deterministic = true, opts = {is_multikey = true}})
 ---
diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua
index f894472f8..e9d3543b6 100644
--- a/test/box/function1.test.lua
+++ b/test/box/function1.test.lua
@@ -294,6 +294,8 @@ ok == true
 
 box.func.LUA:call({"return 1 + 1"})
 
+box.schema.func.drop("SUM")
+
 -- Introduce function options
 box.schema.func.create('test', {body = "function(tuple) return tuple end", is_deterministic = true, opts = {is_multikey = true}})
 box.func['test'].is_multikey == true
diff --git a/test/sql-tap/where2.test.lua b/test/sql-tap/where2.test.lua
index 4116ca913..f267be8e6 100755
--- a/test/sql-tap/where2.test.lua
+++ b/test/sql-tap/where2.test.lua
@@ -231,7 +231,7 @@ test:do_execsql_test(
         EXPLAIN SELECT * FROM x1, x2 WHERE x=1 ORDER BY random();
     ]], {
         -- <where2-2.5>
-        "/random/"
+        "/RANDOM/"
         -- </where2-2.5>
     })
 
@@ -254,7 +254,7 @@ test:do_execsql_test(
         EXPLAIN SELECT * FROM x1, x2 WHERE x=1 ORDER BY abs(5);
     ]], {
         -- <where2-2.6>
-        "~/abs/"
+        "~/ABS/"
         -- </where2-2.6>
     })
 
-- 
2.22.1

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

* [tarantool-patches] [PATCH v3 8/9] sql: get rid of box.internal.sql_function_create
  2019-08-16 13:26 [tarantool-patches] [PATCH v3 0/9] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
                   ` (6 preceding siblings ...)
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 7/9] sql: get rid of FuncDef function hash Kirill Shcherbatov
@ 2019-08-16 13:26 ` Kirill Shcherbatov
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 9/9] sql: better error messages on invalid arguments Kirill Shcherbatov
  8 siblings, 0 replies; 19+ messages in thread
From: Kirill Shcherbatov @ 2019-08-16 13:26 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

Closes #2200
Closes #4113
Closes #2233

@TarantoolBot document
Title: The box.internal.sql_function_create is forbidden
Legacy mechanism box.internal.sql_function_create to make some
Lua function available in SQL is forbidden now.

To make some function available in SQL you need to use
box.schema.func.create() mechanism: you need to specify
1) function language and language-specific options(e.g. you
   are able to define a persistent Lua function)
2) whether this function is_deterministic or not: deterministic
   functions allows to generate more efficient SQL VDBE bytecode
   so you better specify it when it is true
3) the function returns type: a Tarantool type string describes
   a type of value returned by function
4) param_list - a table of Tarantool's types strings desccribe
   function argument types
5) exports - a table of Tarantool's frontends where this function
   should be available ('LUA' by default). You need to specify
   {'LUA', 'SQL'} to make function available both in SQL requests
   and visible in box.func folder

Example:
-- Case1: C function
-- function1.so has int divide() symbol
box.schema.func.create("function1.divide", {language = 'C',
			returns = 'number',
			param_list = {'number', 'number'},
			is_deterministic = true,
			exports = {'LUA', 'SQL'}})
box.execute('SELECT "function1.divide"(6, 3)')
- metadata:
  - name: '"function1.divide"(6, 3)'
    type: number
  rows:
  - [2]
box.schema.func.drop("function1.divide")

-- Case2: Persistent Lua function
box.schema.func.create("SUMMARIZE", {language = 'LUA',
			returns = 'number',
			body = 'function (a, b) return a + b end',
			param_list = {'number', 'number'},
			is_deterministic = true,
			exports = {'LUA', 'SQL'}})
box.execute('SELECT summarize(1, 2)')
- metadata:
  - name: summarize(1, 2)
    type: number
  rows:
  - [3]
box.schema.func.drop("summarize")

Moreover there is a special predefined Lua function LUA that
allows to evaluate a custom Lua expressions in SQL.
You need to pass a string in form "return ...." to LUA function
that returns more than one value of any type.

Example:
box.execute('SELECT lua(\'return 1 + 1\')')
- metadata:
  - name: lua('return 1 + 1')
    type: any
  rows:
  - [2]
box.execute('SELECT lua(\'return box.cfg.memtx_memory\')')
- metadata:
  - name: lua('return box.cfg.memtx_memory')
    type: any
  rows:
  - [268435456]
---
 src/box/port.h                  |  17 ++
 src/box/sql/vdbe.h              |   2 +-
 src/lib/core/port.h             |  15 ++
 src/box/call.c                  |   1 +
 src/box/execute.c               |   1 +
 src/box/lua/call.c              |   4 +
 src/box/port.c                  |   4 +
 src/box/sql/expr.c              |   6 +-
 src/box/sql/func.c              | 278 ++++++++++++++++++++++++++++++++
 src/box/sql/vdbe.c              |  46 +++++-
 src/box/sql/vdbeaux.c           |   2 +-
 src/box/lua/schema.lua          |   3 +-
 test/box/function1.result       | 171 ++++++++++++++++++++
 test/box/function1.test.lua     |  60 +++++++
 test/sql-tap/alias.test.lua     |  11 +-
 test/sql-tap/check.test.lua     |  11 +-
 test/sql-tap/func5.test.lua     |  27 +++-
 test/sql-tap/lua_sql.test.lua   | 121 +++++++-------
 test/sql-tap/subquery.test.lua  |  21 ++-
 test/sql-tap/trigger9.test.lua  |   8 +-
 test/sql/errinj.result          |  26 ---
 test/sql/errinj.test.lua        |  10 --
 test/sql/func-recreate.result   |  41 ++++-
 test/sql/func-recreate.test.lua |  26 ++-
 24 files changed, 779 insertions(+), 133 deletions(-)

diff --git a/src/box/port.h b/src/box/port.h
index a7f5d81bd..9d3d02b3c 100644
--- a/src/box/port.h
+++ b/src/box/port.h
@@ -113,6 +113,23 @@ static_assert(sizeof(struct port_lua) <= sizeof(struct port),
 void
 port_lua_create(struct port *port, struct lua_State *L);
 
+struct sql_value;
+
+/** Port implementation used with vdbe memory variables. */
+struct port_vdbemem {
+    const struct port_vtab *vtab;
+    struct sql_value *mem;
+    uint32_t mem_count;
+};
+
+static_assert(sizeof(struct port_vdbemem) <= sizeof(struct port),
+	      "sizeof(struct port_vdbemem) must be <= sizeof(struct port)");
+
+/** Initialize a port to dump data in sql vdbe memory. */
+void
+port_vdbemem_create(struct port *base, struct sql_value *mem,
+		    uint32_t mem_count);
+
 void
 port_init(void);
 
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 4f639ec38..f8dbd0fa3 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -283,7 +283,7 @@ int sqlVdbeRecordCompare(struct sql *db, int key_count,
 			     const void *key1, UnpackedRecord *key2);
 UnpackedRecord *sqlVdbeAllocUnpackedRecord(struct sql *,
 					       struct key_def *);
-int sql_vdbe_mem_alloc_region(Mem *, uint32_t);
+int sql_vdbe_mem_alloc_blob_region(Mem *, uint32_t);
 
 void sqlVdbeLinkSubProgram(Vdbe *, SubProgram *);
 
diff --git a/src/lib/core/port.h b/src/lib/core/port.h
index 09a026df5..d61342287 100644
--- a/src/lib/core/port.h
+++ b/src/lib/core/port.h
@@ -98,6 +98,15 @@ struct port_vtab {
 	 * is responsible for cleaning up.
 	 **/
 	const char *(*get_msgpack)(struct port *port, uint32_t *size);
+	/**
+	 * Get the content of a port as a sequence of VDBE memory
+	 * cells. This API is used to pass VDBE variables to
+	 * user-defined Lua functions (see OP_Function in sql/vdbe.c).
+	 * The lifecycle of the returned value is the same as for
+	 * @get_msgpack method, i.e. it depends on particular
+	 * implementation
+	 */
+	struct sql_value *(*get_vdbemem)(struct port *port, uint32_t *size);
 	/** Destroy a port and release associated resources. */
 	void (*destroy)(struct port *port);
 };
@@ -150,6 +159,12 @@ port_get_msgpack(struct port *port, uint32_t *size)
 	return port->vtab->get_msgpack(port, size);
 }
 
+static inline struct sql_value *
+port_get_vdbemem(struct port *port, uint32_t *size)
+{
+	return port->vtab->get_vdbemem(port, size);
+}
+
 #if defined(__cplusplus)
 } /* extern "C" */
 #endif /* defined __cplusplus */
diff --git a/src/box/call.c b/src/box/call.c
index ac2bf3004..b459a9839 100644
--- a/src/box/call.c
+++ b/src/box/call.c
@@ -73,6 +73,7 @@ static const struct port_vtab port_msgpack_vtab = {
 	.dump_lua = port_msgpack_dump_lua,
 	.dump_plain = NULL,
 	.get_msgpack = port_msgpack_get_msgpack,
+	.get_vdbemem = NULL,
 	.destroy = NULL,
 };
 
diff --git a/src/box/execute.c b/src/box/execute.c
index 68e94e442..e8b012e5b 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -109,6 +109,7 @@ const struct port_vtab port_sql_vtab = {
 	/* .dump_lua = */ port_sql_dump_lua,
 	/* .dump_plain = */ NULL,
 	/* .get_msgpack = */ NULL,
+	/* .get_vdbemem = */ NULL,
 	/* .destroy = */ port_sql_destroy,
 };
 
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index 001578b5a..631003c84 100644
--- a/src/box/lua/call.c
+++ b/src/box/lua/call.c
@@ -510,12 +510,16 @@ port_lua_destroy(struct port *base)
 extern const char *
 port_lua_dump_plain(struct port *port, uint32_t *size);
 
+extern struct sql_value *
+port_lua_get_vdbemem(struct port *base, uint32_t *size);
+
 static const struct port_vtab port_lua_vtab = {
 	.dump_msgpack = port_lua_dump,
 	.dump_msgpack_16 = port_lua_dump_16,
 	.dump_lua = port_lua_dump_lua,
 	.dump_plain = port_lua_dump_plain,
 	.get_msgpack = port_lua_get_msgpack,
+	.get_vdbemem = port_lua_get_vdbemem,
 	.destroy = port_lua_destroy,
 };
 
diff --git a/src/box/port.c b/src/box/port.c
index 7f552bcfe..6e2fe3a6e 100644
--- a/src/box/port.c
+++ b/src/box/port.c
@@ -140,11 +140,15 @@ port_free(void)
 	mempool_destroy(&port_tuple_entry_pool);
 }
 
+extern struct sql_value *
+port_tuple_get_vdbemem(struct port *base, uint32_t *size);
+
 const struct port_vtab port_tuple_vtab = {
 	.dump_msgpack = port_tuple_dump_msgpack,
 	.dump_msgpack_16 = port_tuple_dump_msgpack_16,
 	.dump_lua = port_tuple_dump_lua,
 	.dump_plain = NULL,
 	.get_msgpack = NULL,
+	.get_vdbemem = port_tuple_get_vdbemem,
 	.destroy = port_tuple_destroy,
 };
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 7a354a00a..2b99f3188 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -4121,9 +4121,9 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				sqlVdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
 						  (char *)coll, P4_COLLSEQ);
 			}
-			assert(func->def->language ==
-			       FUNC_LANGUAGE_SQL_BUILTIN);
-			int op = OP_BuiltinFunction0;
+			int op = func->def->language ==
+				 FUNC_LANGUAGE_SQL_BUILTIN ?
+				 OP_BuiltinFunction0 : OP_Function;
 			sqlVdbeAddOp4(v, op, constMask, r1, target,
 				      (char *)func, P4_FUNC);
 			sqlVdbeChangeP5(v, (u8) nFarg);
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 3b24e4804..30f55cf1d 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -46,6 +46,10 @@
 #include <unicode/ucol.h>
 #include "box/coll_id_cache.h"
 #include "box/schema.h"
+#include "box/port.h"
+#include "box/tuple.h"
+#include "lua/utils.h"
+#include "mpstream.h"
 
 /*
  * Return the collating function associated with a function.
@@ -71,6 +75,280 @@ sqlSkipAccumulatorLoad(sql_context * context)
 	context->skipFlag = 1;
 }
 
+/**
+ * Allocate a sequence of initialized vdbe memory registers
+ * on region.
+ */
+static struct Mem *
+vdbemem_alloc_on_region(uint32_t count)
+{
+	struct region *region = &fiber()->gc;
+	struct Mem *ret = region_alloc(region, count * sizeof(*ret));
+	if (ret == NULL) {
+		diag_set(OutOfMemory, count * sizeof(*ret),
+			 "region_alloc", "ret");
+		return NULL;
+	}
+	memset(ret, 0, count * sizeof(*ret));
+	for (uint32_t i = 0; i < count; i++) {
+		sqlVdbeMemInit(&ret[i], sql_get(), MEM_Null);
+		assert(memIsValid(&ret[i]));
+	}
+	return ret;
+}
+
+static void
+port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
+{
+	(void) is_flat;
+	struct port_vdbemem *port = (struct port_vdbemem *) base;
+	assert(is_flat == true);
+	for (uint32_t i = 0; i < port->mem_count; i++) {
+		sql_value *param =
+			(sql_value *)((struct Mem *)port->mem + i);
+		switch (sql_value_type(param)) {
+		case MP_INT:
+			luaL_pushint64(L, sql_value_int64(param));
+			break;
+		case MP_UINT:
+			luaL_pushuint64(L, sql_value_uint64(param));
+			break;
+		case MP_DOUBLE:
+			lua_pushnumber(L, sql_value_double(param));
+			break;
+		case MP_STR:
+			lua_pushstring(L, (const char *) sql_value_text(param));
+			break;
+		case MP_BIN:
+			lua_pushlstring(L, sql_value_blob(param),
+					(size_t) sql_value_bytes(param));
+			break;
+		case MP_NIL:
+			lua_pushnil(L);
+			break;
+		case MP_BOOL:
+			lua_pushboolean(L, sql_value_boolean(param));
+			break;
+		default:
+			unreachable();
+		}
+	}
+}
+
+static const char *
+port_vdbemem_get_msgpack(struct port *base, uint32_t *size)
+{
+	struct port_vdbemem *port = (struct port_vdbemem *) base;
+	struct region *region = &fiber()->gc;
+	size_t region_svp = region_used(region);
+	bool is_error = false;
+	struct mpstream stream;
+	mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb,
+		      set_encode_error, &is_error);
+	mpstream_encode_array(&stream, port->mem_count);
+	for (uint32_t i = 0; i < port->mem_count && !is_error; i++) {
+		sql_value *param =
+			(sql_value *)((struct Mem *)port->mem + i);
+		switch (sql_value_type(param)) {
+		case MP_INT: {
+			sql_int64 val = sql_value_int64(param);
+			if (val < 0) {
+				mpstream_encode_int(&stream, val);
+				break;
+			}
+			FALLTHROUGH;
+		}
+		case MP_UINT: {
+			sql_int64 val = sql_value_int64(param);
+			mpstream_encode_uint(&stream, val);
+			break;
+		}
+		case MP_DOUBLE: {
+			mpstream_encode_double(&stream,
+					       sql_value_double(param));
+			break;
+		}
+		case MP_STR: {
+			const char *str = (const char *) sql_value_text(param);
+			mpstream_encode_str(&stream, str);
+			break;
+		}
+		case MP_BIN: {
+			mpstream_encode_binl(&stream, sql_value_bytes(param));
+			mpstream_memcpy(&stream, sql_value_blob(param),
+					sql_value_bytes(param));
+			break;
+		}
+		case MP_NIL: {
+			mpstream_encode_nil(&stream);
+			break;
+		}
+		case MP_BOOL: {
+			mpstream_encode_bool(&stream, sql_value_boolean(param));
+			break;
+		}
+		default:
+			unreachable();
+		}
+	}
+	mpstream_flush(&stream);
+	*size = region_used(region) - region_svp;
+	if (is_error)
+		goto error;
+	const char *ret = (char *)region_join(region, *size);
+	if (ret == NULL)
+		goto error;
+	return ret;
+error:
+	diag_set(OutOfMemory, *size, "region", "ret");
+	return NULL;
+}
+
+static const struct port_vtab port_vdbemem_vtab;
+
+void
+port_vdbemem_create(struct port *base, struct sql_value *mem,
+		    uint32_t mem_count)
+{
+	struct port_vdbemem *port = (struct port_vdbemem *) base;
+	port->vtab = &port_vdbemem_vtab;
+	port->mem = mem;
+	port->mem_count = mem_count;
+}
+
+static struct sql_value *
+port_vdbemem_get_vdbemem(struct port *base, uint32_t *mem_count)
+{
+	struct port_vdbemem *port = (struct port_vdbemem *) base;
+	assert(port->vtab == &port_vdbemem_vtab);
+	*mem_count = port->mem_count;
+	return port->mem;
+}
+
+static const struct port_vtab port_vdbemem_vtab = {
+	.dump_msgpack = NULL,
+	.dump_msgpack_16 = NULL,
+	.dump_lua = port_vdbemem_dump_lua,
+	.dump_plain = NULL,
+	.get_msgpack = port_vdbemem_get_msgpack,
+	.get_vdbemem = port_vdbemem_get_vdbemem,
+	.destroy = NULL,
+};
+
+struct sql_value *
+port_lua_get_vdbemem(struct port *base, uint32_t *size)
+{
+	struct port_lua *port = (struct port_lua *) base;
+	struct lua_State *L = port->L;
+	int argc = lua_gettop(L);
+	if (argc == 0) {
+		diag_set(ClientError, ER_SQL_EXECUTE,
+			 "No value was passed from Lua");
+		return NULL;
+	}
+	*size = argc;
+	struct region *region = &fiber()->gc;
+	size_t region_svp = region_used(region);
+	struct Mem *val = vdbemem_alloc_on_region(argc);
+	if (val == NULL)
+		return NULL;
+	for (int i = 0; i < argc; i++) {
+		switch(lua_type(L, -1 - i)) {
+		case LUA_TBOOLEAN:
+			mem_set_bool(&val[i], lua_toboolean(L, -1 - i));
+			break;
+		case LUA_TNUMBER:
+			sqlVdbeMemSetDouble(&val[i], lua_tonumber(L, -1 - i));
+			break;
+		case LUA_TSTRING:
+			if (sqlVdbeMemSetStr(&val[i], lua_tostring(L, -1 - i),
+					     -1, 1, SQL_TRANSIENT) != 0)
+				goto error;
+			break;
+		case LUA_TNIL:
+			sqlVdbeMemSetNull(&val[i]);
+			break;
+		default:
+			diag_set(ClientError, ER_SQL_EXECUTE,
+				 "Unsupported type passed from Lua");
+			goto error;
+		}
+	}
+	return (struct sql_value *)val;
+error:
+	for (int i = 0; i < argc; i++)
+		sqlVdbeMemRelease(&val[i]);
+	region_truncate(region, region_svp);
+	return NULL;
+}
+
+struct sql_value *
+port_tuple_get_vdbemem(struct port *base, uint32_t *size)
+{
+	struct port_tuple *port = (struct port_tuple *)base;
+	*size = port->size;
+	if (*size == 0) {
+		diag_set(ClientError, ER_SQL_EXECUTE,
+			 "No value was passed from C");
+		return NULL;
+	}
+	struct region *region = &fiber()->gc;
+	size_t region_svp = region_used(region);
+	struct Mem *val = vdbemem_alloc_on_region(port->size);
+	if (val == NULL)
+		return NULL;
+	int i = 0;
+	struct port_tuple_entry *pe;
+	for (pe = port->first; pe != NULL; pe = pe->next) {
+		const char *data = tuple_data(pe->tuple);
+		if (mp_typeof(*data) != MP_ARRAY ||
+			mp_decode_array(&data) != 1) {
+			diag_set(ClientError, ER_SQL_EXECUTE,
+				 "Unsupported type passed from C");
+			goto error;
+		}
+		uint32_t len;
+		const char *str;
+		switch (mp_typeof(*data)) {
+		case MP_BOOL:
+			mem_set_bool(&val[i], mp_decode_bool(&data));
+			break;
+		case MP_FLOAT:
+			sqlVdbeMemSetDouble(&val[i], mp_decode_float(&data));
+			break;
+		case MP_DOUBLE:
+			sqlVdbeMemSetDouble(&val[i], mp_decode_double(&data));
+			break;
+		case MP_INT:
+			mem_set_i64(val, mp_decode_int(&data));
+			break;
+		case MP_UINT:
+			mem_set_u64(val, mp_decode_uint(&data));
+			break;
+		case MP_STR:
+			str = mp_decode_str(&data, &len);
+			if (sqlVdbeMemSetStr(&val[i], str, len,
+					     1, SQL_TRANSIENT) != 0)
+				goto error;
+			break;
+		case MP_NIL:
+			sqlVdbeMemSetNull(val);
+			break;
+		default:
+			diag_set(ClientError, ER_SQL_EXECUTE,
+				 "Unsupported type passed from C");
+			goto error;
+		}
+		i++;
+	}
+	return (struct sql_value *) val;
+error:
+	for (int i = 0; i < port->size; i++)
+		sqlVdbeMemRelease(&val[i]);
+	region_truncate(region, region_svp);
+	return NULL;
+}
+
 /*
  * Implementation of the non-aggregate min() and max() functions
  */
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index c5a4a0da9..c2e49edb3 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -46,6 +46,7 @@
 #include "box/fk_constraint.h"
 #include "box/txn.h"
 #include "box/tuple.h"
+#include "box/port.h"
 #include "sqlInt.h"
 #include "vdbeInt.h"
 #include "tarantoolInt.h"
@@ -1767,6 +1768,49 @@ case OP_BuiltinFunction: {
 	break;
 }
 
+/* Opcode: Function P1 P2 P3 P4 P5
+ * Synopsis: r[P3]=func(r[P2@P5])
+ *
+ * Invoke a user function (P4 is a pointer to a function object
+ * that defines the function) with P5 arguments taken from
+ * register P2 and successors. The result of the function is
+ * stored in register P3.
+ * Register P3 must not be one of the function inputs.
+ */
+case OP_Function: {
+	struct func *func = pOp->p4.func;
+	int argc = pOp->p5;
+	struct Mem *argv = &aMem[pOp->p2];
+	struct port args, ret;
+
+	struct region *region = &fiber()->gc;
+	size_t region_svp = region_used(region);
+	port_vdbemem_create(&args, (struct sql_value *)argv, argc);
+	if (func_call(func, &args, &ret) != 0)
+		goto abort_due_to_error;
+
+	pOut = vdbe_prepare_null_out(p, pOp->p3);
+	uint32_t size;
+	struct Mem *mem = (struct Mem *)port_get_vdbemem(&ret, &size);
+	if (mem != NULL && size > 0)
+		*pOut = mem[0];
+	port_destroy(&ret);
+	region_truncate(region, region_svp);
+	if (mem == NULL)
+		goto abort_due_to_error;
+
+	/*
+	 * Copy the result of the function invocation into
+	 * register P3.
+	 */
+	if (pOut->flags & (MEM_Str | MEM_Blob))
+		if (sqlVdbeMemTooBig(pOut)) goto too_big;
+
+	REGISTER_TRACE(p, pOp->p3, pOut);
+	UPDATE_MAX_BLOBSIZE(pOut);
+	break;
+}
+
 /* Opcode: BitAnd P1 P2 P3 * *
  * Synopsis: r[P3]=r[P1]&r[P2]
  *
@@ -3907,7 +3951,7 @@ case OP_RowData: {
 	}
 	testcase( n==0);
 
-	if (sql_vdbe_mem_alloc_region(pOut, n) != 0)
+	if (sql_vdbe_mem_alloc_blob_region(pOut, n) != 0)
 		goto abort_due_to_error;
 	sqlCursorPayload(pCrsr, 0, n, pOut->z);
 	UPDATE_MAX_BLOBSIZE(pOut);
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index a44540b1d..3bd4ff433 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -2792,7 +2792,7 @@ sqlVdbeAllocUnpackedRecord(struct sql *db, struct key_def *key_def)
 
 /* Allocate memory for internal VDBE structure on region. */
 int
-sql_vdbe_mem_alloc_region(Mem *vdbe_mem, uint32_t size)
+sql_vdbe_mem_alloc_blob_region(Mem *vdbe_mem, uint32_t size)
 {
 	vdbe_mem->n = size;
 	vdbe_mem->z = region_alloc(&fiber()->gc, size);
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 65017d391..98067f795 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -2143,7 +2143,8 @@ box.schema.func.create = function(name, opts)
                               language = 'string', body = 'string',
                               is_deterministic = 'boolean',
                               is_sandboxed = 'boolean', comment = 'string',
-                              opts = 'table' })
+                              param_list = 'table', returns = 'string',
+                              exports = 'table', opts = 'table' })
     local _func = box.space[box.schema.FUNC_ID]
     local _vfunc = box.space[box.schema.VFUNC_ID]
     local func = _vfunc.index.name:get{name}
diff --git a/test/box/function1.result b/test/box/function1.result
index 3eba37ab1..ebd9be700 100644
--- a/test/box/function1.result
+++ b/test/box/function1.result
@@ -366,6 +366,177 @@ test_run:cmd("setopt delimiter ''");
 c:close()
 ---
 ...
+--
+-- gh-2233: Invoke Lua functions created outside SQL.
+--
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+box.schema.func.create("function1.divide", {language = 'C', returns = 'number',
+					    is_deterministic = true,
+					    exports = {'LUA'}})
+test_run:cmd("setopt delimiter ''");
+---
+...
+box.execute('SELECT "function1.divide"()')
+---
+- null
+- Function 'function1.divide' does not exist
+...
+box.func["function1.divide"]:drop()
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+box.schema.func.create("function1.divide", {language = 'C', returns = 'number',
+					    is_deterministic = true,
+					    exports = {'LUA', 'SQL'}})
+test_run:cmd("setopt delimiter ''");
+---
+...
+box.execute('SELECT "function1.divide"()')
+---
+- null
+- invalid argument
+...
+box.execute('SELECT "function1.divide"(6)')
+---
+- null
+- wrong number of arguments to function function1.divide()
+...
+box.execute('SELECT "function1.divide"(6, 3)')
+---
+- null
+- wrong number of arguments to function function1.divide()
+...
+box.func["function1.divide"]:drop()
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+box.schema.func.create("function1.divide", {language = 'C', returns = 'number',
+					    param_list = {'number', 'number'},
+					    is_deterministic = true,
+					    exports = {'LUA', 'SQL'}})
+test_run:cmd("setopt delimiter ''");
+---
+...
+box.execute('SELECT "function1.divide"()')
+---
+- null
+- wrong number of arguments to function function1.divide()
+...
+box.execute('SELECT "function1.divide"(6)')
+---
+- null
+- wrong number of arguments to function function1.divide()
+...
+box.execute('SELECT "function1.divide"(6, 3, 3)')
+---
+- null
+- wrong number of arguments to function function1.divide()
+...
+box.execute('SELECT "function1.divide"(6, 3)')
+---
+- metadata:
+  - name: '"function1.divide"(6, 3)'
+    type: number
+  rows:
+  - [2]
+...
+box.execute('SELECT "function1.divide"(5, 2)')
+---
+- metadata:
+  - name: '"function1.divide"(5, 2)'
+    type: number
+  rows:
+  - [2.5]
+...
+box.func["function1.divide"]:drop()
+---
+...
+function SUMMARIZE(a, b) return a + b end
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+box.schema.func.create("SUMMARIZE", {language = 'LUA', returns = 'number',
+				     is_deterministic = true,
+				     param_list = {'number', 'number'},
+				     exports = {'LUA', 'SQL'}})
+test_run:cmd("setopt delimiter ''");
+---
+...
+box.execute('SELECT summarize(1, 2)')
+---
+- metadata:
+  - name: summarize(1, 2)
+    type: number
+  rows:
+  - [3]
+...
+box.func.SUMMARIZE:drop()
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+box.schema.func.create("SUMMARIZE", {language = 'LUA', returns = 'number',
+				     body = 'function (a, b) return a + b end',
+				     is_deterministic = true,
+				     param_list = {'number', 'number'},
+				     exports = {'LUA', 'SQL'}})
+test_run:cmd("setopt delimiter ''");
+---
+...
+box.execute('SELECT summarize(1, 2)')
+---
+- metadata:
+  - name: summarize(1, 2)
+    type: number
+  rows:
+  - [3]
+...
+box.func.SUMMARIZE:drop()
+---
+...
+--
+-- gh-4113: Valid method to use Lua from SQL
+--
+box.execute('SELECT lua(\'return 1 + 1\')')
+---
+- metadata:
+  - name: lua('return 1 + 1')
+    type: any
+  rows:
+  - [2]
+...
+box.execute('SELECT lua(\'return box.cfg\')')
+---
+- null
+- 'Failed to execute SQL statement: Unsupported type passed from Lua'
+...
+box.execute('SELECT lua(\'return box.cfg()\')')
+---
+- null
+- 'Failed to execute SQL statement: No value was passed from Lua'
+...
+box.execute('SELECT lua(\'return box.cfg.memtx_memory\')')
+---
+- metadata:
+  - name: lua('return box.cfg.memtx_memory')
+    type: any
+  rows:
+  - [107374182]
+...
 -- Test registered functions interface.
 function divide(a, b) return a / b end
 ---
diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua
index e9d3543b6..0ee47cd37 100644
--- a/test/box/function1.test.lua
+++ b/test/box/function1.test.lua
@@ -128,6 +128,66 @@ identifier.run_test(
 test_run:cmd("setopt delimiter ''");
 c:close()
 
+--
+-- gh-2233: Invoke Lua functions created outside SQL.
+--
+test_run:cmd("setopt delimiter ';'")
+box.schema.func.create("function1.divide", {language = 'C', returns = 'number',
+					    is_deterministic = true,
+					    exports = {'LUA'}})
+test_run:cmd("setopt delimiter ''");
+box.execute('SELECT "function1.divide"()')
+box.func["function1.divide"]:drop()
+test_run:cmd("setopt delimiter ';'")
+box.schema.func.create("function1.divide", {language = 'C', returns = 'number',
+					    is_deterministic = true,
+					    exports = {'LUA', 'SQL'}})
+test_run:cmd("setopt delimiter ''");
+box.execute('SELECT "function1.divide"()')
+box.execute('SELECT "function1.divide"(6)')
+box.execute('SELECT "function1.divide"(6, 3)')
+box.func["function1.divide"]:drop()
+test_run:cmd("setopt delimiter ';'")
+box.schema.func.create("function1.divide", {language = 'C', returns = 'number',
+					    param_list = {'number', 'number'},
+					    is_deterministic = true,
+					    exports = {'LUA', 'SQL'}})
+test_run:cmd("setopt delimiter ''");
+box.execute('SELECT "function1.divide"()')
+box.execute('SELECT "function1.divide"(6)')
+box.execute('SELECT "function1.divide"(6, 3, 3)')
+box.execute('SELECT "function1.divide"(6, 3)')
+box.execute('SELECT "function1.divide"(5, 2)')
+box.func["function1.divide"]:drop()
+
+function SUMMARIZE(a, b) return a + b end
+test_run:cmd("setopt delimiter ';'")
+box.schema.func.create("SUMMARIZE", {language = 'LUA', returns = 'number',
+				     is_deterministic = true,
+				     param_list = {'number', 'number'},
+				     exports = {'LUA', 'SQL'}})
+test_run:cmd("setopt delimiter ''");
+box.execute('SELECT summarize(1, 2)')
+box.func.SUMMARIZE:drop()
+
+test_run:cmd("setopt delimiter ';'")
+box.schema.func.create("SUMMARIZE", {language = 'LUA', returns = 'number',
+				     body = 'function (a, b) return a + b end',
+				     is_deterministic = true,
+				     param_list = {'number', 'number'},
+				     exports = {'LUA', 'SQL'}})
+test_run:cmd("setopt delimiter ''");
+box.execute('SELECT summarize(1, 2)')
+box.func.SUMMARIZE:drop()
+
+--
+-- gh-4113: Valid method to use Lua from SQL
+--
+box.execute('SELECT lua(\'return 1 + 1\')')
+box.execute('SELECT lua(\'return box.cfg\')')
+box.execute('SELECT lua(\'return box.cfg()\')')
+box.execute('SELECT lua(\'return box.cfg.memtx_memory\')')
+
 -- Test registered functions interface.
 function divide(a, b) return a / b end
 box.schema.func.create("divide", {comment = 'Divide two values'})
diff --git a/test/sql-tap/alias.test.lua b/test/sql-tap/alias.test.lua
index f9e6fc9fa..75391b305 100755
--- a/test/sql-tap/alias.test.lua
+++ b/test/sql-tap/alias.test.lua
@@ -25,14 +25,13 @@ test:plan(9)
 --
 
 counter = 0
-sequence = function()
-    counter = counter + 1
-    return counter
-end
 
 -- Function is declared as deterministic deliberately.
 -- Otherwise it would be called as much as it occurs in a query.
-box.internal.sql_create_function("sequence", "INT", sequence, 0, true)
+box.schema.func.create('SEQUENCE', {language = 'Lua', is_deterministic = true,
+                       returns = 'unsigned',
+                       body = 'function() counter = counter + 1 return counter end',
+                       exports = {'LUA', 'SQL'}})
 
 test:do_test(
     "alias-1.1",
@@ -220,6 +219,6 @@ test:do_test(
 --         -- </alias-3.1>
 --     })
 
-
+box.func.SEQUENCE:drop()
 
 test:finish_test()
diff --git a/test/sql-tap/check.test.lua b/test/sql-tap/check.test.lua
index 6e4666cf2..c9b60bbf5 100755
--- a/test/sql-tap/check.test.lua
+++ b/test/sql-tap/check.test.lua
@@ -680,10 +680,11 @@ test:do_execsql_test(
 -- cannot be tested).
 --
 --reset_db()
-local function myfunc(x)
-    return x < 10
-end
-box.internal.sql_create_function("myfunc", "INT", myfunc)
+box.schema.func.create('MYFUNC', {language = 'Lua',
+                       is_deterministic = true,
+                       body = 'function(x) return x < 10 end',
+                       returns = 'boolean', param_list = {'number'},
+                       exports = {'LUA', 'SQL'}})
 
 test:do_execsql_test(
     7.1,
@@ -808,5 +809,7 @@ test:do_catchsql_test(
         -- </9.3>
     })
 
+box.func.MYFUNC:drop()
+
 test:finish_test()
 
diff --git a/test/sql-tap/func5.test.lua b/test/sql-tap/func5.test.lua
index 0b255e659..45994a352 100755
--- a/test/sql-tap/func5.test.lua
+++ b/test/sql-tap/func5.test.lua
@@ -71,13 +71,25 @@ test:do_execsql_test(
 
 global_counter = 0
 
-counter = function(str)
-    global_counter = global_counter + 1
-    return global_counter
-end
+box.schema.func.create('COUNTER1', {language = 'Lua', is_deterministic = false,
+                       param_list = {'any'}, returns = 'integer',
+                       exports = {'SQL', 'LUA'},
+                       body = [[
+                           function(str)
+                               global_counter = global_counter + 1
+                               return global_counter
+                           end
+                       ]]})
 
-box.internal.sql_create_function("counter1", "INT", counter, -1, false)
-box.internal.sql_create_function("counter2", "INT", counter, -1, true)
+box.schema.func.create('COUNTER2', {language = 'Lua', is_deterministic = true,
+                       param_list = {'any'}, returns = 'integer',
+                       exports = {'SQL', 'LUA'},
+                       body = [[
+                           function(str)
+                                   global_counter = global_counter + 1
+                                   return global_counter
+                               end
+                       ]]})
 
 test:do_execsql_test(
     "func5-2.2",
@@ -257,4 +269,7 @@ test:do_execsql_test(
         SELECT LEAST(false, 'STR', 1, 0.5);
     ]], { false } )
 
+box.func.COUNTER1:drop()
+box.func.COUNTER2:drop()
+
 test:finish_test()
diff --git a/test/sql-tap/lua_sql.test.lua b/test/sql-tap/lua_sql.test.lua
index b0ecccd52..67eff2d1b 100755
--- a/test/sql-tap/lua_sql.test.lua
+++ b/test/sql-tap/lua_sql.test.lua
@@ -1,19 +1,16 @@
 #!/usr/bin/env tarantool
 test = require("sqltester")
 NULL = require('msgpack').NULL
-test:plan(24)
-
-local function func1(a)
-    return a
-end
-local function allways_2(a)
-    return 2
-end
+test:plan(22)
 
 test:do_test(
     "lua_sql-1.0",
     function ()
-        box.internal.sql_create_function("func1", "INT", allways_2)
+        box.schema.func.create('FUNC1', {language = 'Lua',
+                         is_deterministic = true,
+                         body = 'function(a) return 2 end',
+                         param_list = {'any'}, returns = 'integer',
+                         exports = {'LUA', 'SQL'}})
         return test:execsql("select func1(1)")
     end,
     {2})
@@ -22,34 +19,16 @@ test:do_test(
 test:do_test(
     "lua_sql-1.1",
     function ()
-        box.internal.sql_create_function("func1", "INT", func1)
+        box.func.FUNC1:drop()
+        box.schema.func.create('FUNC1', {language = 'Lua',
+                        is_deterministic = true,
+                        body = 'function(a) return a end',
+                        param_list = {'scalar'}, returns = 'integer',
+                        exports = {'LUA', 'SQL'}})
         return test:execsql("select func1(1)")
     end,
     {1})
 
--- try to loose memory
-test:do_test(
-    "lua_sql-1.2",
-    function ()
-        for i = 1, 1000000, 1 do
-            box.internal.sql_create_function("func1", "INT", func1)
-        end
-        return test:execsql("select func1(1)")
-    end,
-    {1})
-
--- check sql polymorphism
-test:do_test(
-    "lua_sql-1.3",
-    function ()
-        box.internal.sql_create_function("allways_2", "INT", allways_2, 1) -- specify 1 arg
-        box.internal.sql_create_function("allways_2", "INT", func1)
-        box.internal.sql_create_function("allways_2", "INT", func1, 2)
-        box.internal.sql_create_function("allways_2", "INT", func1, 3)
-        return test:execsql("select allways_2(1)")
-    end,
-    {2})
-
 test:do_catchsql_test(
     "lua_sql-1.0",
     "select func3(1)",
@@ -72,7 +51,7 @@ for _, val in ipairs({
         {result})
 end
 
-local from_sql_to_lua = {
+from_sql_to_lua = {
     [1] = {1, 1},
     [2] = {"1", 1},
     [3] = {"1.5", 1.5},
@@ -81,14 +60,19 @@ local from_sql_to_lua = {
     [6] = {"x'0500'", "\u{0005}\u{0000}"},
     [7] = {"123123123123123", 123123123123123LL},
 }
-local json = require("json")
-local function check_from_sql_to_lua(i, arg)
-    if from_sql_to_lua[i][2] == arg then
-        return 1
-    end
-    return 0
-end
-box.internal.sql_create_function("check_from_sql_to_lua", "INT", check_from_sql_to_lua)
+
+box.schema.func.create('CHECK_FROM_SQL_TO_LUA', {language = 'Lua',
+                       is_deterministic = true,
+                       body = [[
+                           function(i, arg)
+                               if from_sql_to_lua[i][2] == arg then
+                                   return 1
+                               end
+                               return 0
+                           end
+                       ]],
+                       param_list = {'integer', 'scalar'}, returns = 'integer',
+                       exports = {'LUA', 'SQL'}})
 
 -- check for different types
 for i = 1, #from_sql_to_lua, 1 do
@@ -98,17 +82,23 @@ for i = 1, #from_sql_to_lua, 1 do
         {1})
 end
 
-local from_lua_to_sql = {
+from_lua_to_sql = {
     [1] = {1, 1},
     [2] = {"1.5", 1.5},
     [3] = {"'1'", "1"},
     [4] = {"true", true},
     [5] = {"false", false},
 }
-local function check_from_lua_to_sql(i)
-    return from_lua_to_sql[i][2]
-end
-box.internal.sql_create_function("check_from_lua_to_sql", "VARBINARY", check_from_lua_to_sql)
+
+box.schema.func.create('CHECK_FROM_LUA_TO_SQL', {language = 'Lua',
+                       is_deterministic = true,
+                       body = [[
+                           function(i)
+                               return from_lua_to_sql[i][2]
+                           end
+                       ]],
+                       param_list = {'integer'}, returns = 'scalar',
+                       exports = {'LUA', 'SQL'}})
 
 -- check for different types
 for i = 1, #from_lua_to_sql, 1 do
@@ -118,14 +108,20 @@ for i = 1, #from_lua_to_sql, 1 do
         {true})
 end
 
-local from_lua_to_sql_bad = {
+from_lua_to_sql_bad = {
     [1] = NULL,
     [2] = 12LL, -- it is possible to support this type
 }
-local function check_from_lua_to_sql_bad(i)
-    return from_lua_to_sql_bad[i]
-end
-box.internal.sql_create_function("check_from_lua_to_sql_bad", "VARBINARY", check_from_lua_to_sql_bad)
+
+box.schema.func.create('CHECK_FROM_LUA_TO_SQL_BAD', {language = 'Lua',
+                       is_deterministic = true,
+                       body = [[
+                           function(i)
+                               return from_lua_to_sql_bad[i]
+                           end
+                       ]],
+                       param_list = {'integer'}, returns = 'scalar',
+                       exports = {'LUA', 'SQL'}})
 
 for i = 1, #from_lua_to_sql_bad, 1 do
     test:do_catchsql_test(
@@ -134,16 +130,27 @@ for i = 1, #from_lua_to_sql_bad, 1 do
         {1, "/Unsupported/"})
 end
 
-local function allways_error()
-    error("my_error123")
-    return 1
-end
-box.internal.sql_create_function("allways_error", "INT", allways_error)
+box.schema.func.create('ALLWAYS_ERROR', {language = 'Lua',
+                       is_deterministic = true,
+                       body = [[
+                           function()
+                               error("my_error123")
+                               return 1
+                           end
+                       ]],
+                       param_list = {}, returns = 'integer',
+                       exports = {'LUA', 'SQL'}})
+
 
 test:do_catchsql_test(
     "lua_sql-2.6",
     "select allways_error()",
     {1, "/my_error123/"})
 
+box.func.FUNC1:drop()
+box.func.CHECK_FROM_SQL_TO_LUA:drop()
+box.func.CHECK_FROM_LUA_TO_SQL:drop()
+box.func.CHECK_FROM_LUA_TO_SQL_BAD:drop()
+box.func.ALLWAYS_ERROR:drop()
 
 test:finish_test()
diff --git a/test/sql-tap/subquery.test.lua b/test/sql-tap/subquery.test.lua
index 4e4b8d453..6bedf5879 100755
--- a/test/sql-tap/subquery.test.lua
+++ b/test/sql-tap/subquery.test.lua
@@ -710,17 +710,20 @@ test:do_execsql_test(
 -- for a matching column name did not cause an otherwise static subquery
 -- to become a dynamic (correlated) subquery.
 --
-local callcnt = 0
+callcnt = 0
 test:do_test(
     "subquery-5.1",
     function()
-        local function callcntproc(n)
-            callcnt = callcnt + 1
-            return n
-        end
-
-        callcnt = 0
-        box.internal.sql_create_function("callcnt", "INT", callcntproc)
+        box.schema.func.create('CALLCNT', {language = 'Lua',
+                               is_deterministic = true,
+                               param_list = {'integer'}, returns = 'integer',
+                               exports = {'LUA', 'SQL'},
+                               body = [[
+                                   function(n)
+                                           callcnt = callcnt + 1
+                                           return n
+                                   end
+                               ]]})
         return test:execsql [[
             CREATE TABLE t4(x TEXT,y  INT PRIMARY KEY);
             INSERT INTO t4 VALUES('one',1);
@@ -791,6 +794,8 @@ test:do_test(
         return callcnt
     end, 1)
 
+box.func.CALLCNT:drop()
+
 --############  was disable until we get #2652 fixed
 -- Ticket #2652.  Allow aggregate functions of outer queries inside
 -- a non-aggregate subquery.
diff --git a/test/sql-tap/trigger9.test.lua b/test/sql-tap/trigger9.test.lua
index e7e170b3d..e39a31384 100755
--- a/test/sql-tap/trigger9.test.lua
+++ b/test/sql-tap/trigger9.test.lua
@@ -46,7 +46,11 @@ local function has_rowdata(sql)
 --     X(41, "X!cmd", [=[["expr","[lsearch [execsql \"explain $sql\"] RowData]>=0"]]=])
 end
 
-box.internal.sql_create_function('randstr', 'TEXT', test.randstr, 1)
+box.schema.func.create('RANDSTR', {language = 'Lua',
+                   body = 'function(n) return test.randstr(n) end',
+                   param_list = {'integer'}, returns = 'string',
+                   exports = {'LUA', 'SQL'}})
+
 
 -- MUST_WORK_TEST
 test:do_execsql_test(
@@ -171,6 +175,8 @@ test:do_execsql_test(
         -- </trigger9-1.4.3>
     })
 
+box.func.RANDSTR:drop()
+
 test:do_execsql_test(
     "trigger9-1.5.1",
     [[
diff --git a/test/sql/errinj.result b/test/sql/errinj.result
index ea570f0dc..ecc194fb8 100644
--- a/test/sql/errinj.result
+++ b/test/sql/errinj.result
@@ -397,32 +397,6 @@ box.execute("DROP TABLE t3;")
 - row_count: 1
 ...
 --
--- gh-3931: Store regular identifiers in case-normal form
---
-errinj = box.error.injection
----
-...
-errinj.set("ERRINJ_SQL_NAME_NORMALIZATION", true)
----
-- ok
-...
-box.execute("CREATE TABLE hello (id INT primary key,x INT,y INT);")
----
-- null
-- Failed to allocate 6 bytes in sqlDbMallocRawNN for res
-...
-dummy_f = function(int) return 1 end
----
-...
-box.internal.sql_create_function("counter1", "INT", dummy_f, -1, false)
----
-- error: Failed to allocate 9 bytes in region_alloc for res
-...
-errinj.set("ERRINJ_SQL_NAME_NORMALIZATION", false)
----
-- ok
-...
---
 -- Tests which are aimed at verifying work of commit/rollback
 -- triggers on _ck_constraint space.
 --
diff --git a/test/sql/errinj.test.lua b/test/sql/errinj.test.lua
index 552860a38..2b4f74a82 100644
--- a/test/sql/errinj.test.lua
+++ b/test/sql/errinj.test.lua
@@ -118,16 +118,6 @@ box.execute("INSERT INTO t3 VALUES(1, 1, 3);")
 errinj.set("ERRINJ_WAL_IO", false)
 box.execute("DROP TABLE t3;")
 
---
--- gh-3931: Store regular identifiers in case-normal form
---
-errinj = box.error.injection
-errinj.set("ERRINJ_SQL_NAME_NORMALIZATION", true)
-box.execute("CREATE TABLE hello (id INT primary key,x INT,y INT);")
-dummy_f = function(int) return 1 end
-box.internal.sql_create_function("counter1", "INT", dummy_f, -1, false)
-errinj.set("ERRINJ_SQL_NAME_NORMALIZATION", false)
-
 --
 -- Tests which are aimed at verifying work of commit/rollback
 -- triggers on _ck_constraint space.
diff --git a/test/sql/func-recreate.result b/test/sql/func-recreate.result
index 73fb03cc4..1f6966431 100644
--- a/test/sql/func-recreate.result
+++ b/test/sql/func-recreate.result
@@ -12,7 +12,15 @@ box.execute('pragma sql_default_engine=\''..engine..'\'')
 fiber = require('fiber')
 ---
 ...
-box.internal.sql_create_function('WAITFOR', 'INT', function (n) fiber.sleep(n) return n end)
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+box.schema.func.create('WAITFOR', {language = 'Lua',
+                       body = 'function (n) fiber.sleep(n) return n end',
+                       param_list = {'integer'}, returns = 'integer',
+                       exports = {'LUA', 'SQL'}})
+test_run:cmd("setopt delimiter ''");
 ---
 ...
 ch = fiber.channel(1)
@@ -24,10 +32,19 @@ _ = fiber.create(function () ch:put(box.execute('select WAITFOR(0.2)')) end)
 fiber.sleep(0.1)
 ---
 ...
-box.internal.sql_create_function('WAITFOR', 'INT', function (n) require('fiber').sleep(n) return n end)
+box.func.WAITFOR:drop()
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+box.schema.func.create('WAITFOR', {language = 'Lua',
+                       body = 'function (n) fiber.sleep(n) return n end',
+                       param_list = {'integer'}, returns = 'integer',
+                       exports = {'LUA', 'SQL'}})
+test_run:cmd("setopt delimiter ''");
 ---
-- error: 'Failed to create function ''WAITFOR'': unable to create function due to
-    active statements'
 ...
 ch:get()
 ---
@@ -37,6 +54,20 @@ ch:get()
   rows:
   - [0.2]
 ...
-box.internal.sql_create_function('WAITFOR', 'INT', function (n) require('fiber').sleep(n) return n end)
+box.func.WAITFOR:drop()
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+box.schema.func.create('WAITFOR', {language = 'Lua',
+                   body = 'function (n) fiber.sleep(n) return n end',
+                   param_list = {'integer'}, returns = 'integer',
+                   exports = {'LUA', 'SQL'}})
+test_run:cmd("setopt delimiter ''");
+---
+...
+box.func.WAITFOR:drop()
 ---
 ...
diff --git a/test/sql/func-recreate.test.lua b/test/sql/func-recreate.test.lua
index 753e9ca4d..5496baf6e 100644
--- a/test/sql/func-recreate.test.lua
+++ b/test/sql/func-recreate.test.lua
@@ -4,14 +4,34 @@ box.execute('pragma sql_default_engine=\''..engine..'\'')
 
 -- Check errors during function create process
 fiber = require('fiber')
-box.internal.sql_create_function('WAITFOR', 'INT', function (n) fiber.sleep(n) return n end)
+test_run:cmd("setopt delimiter ';'")
+box.schema.func.create('WAITFOR', {language = 'Lua',
+                       body = 'function (n) fiber.sleep(n) return n end',
+                       param_list = {'integer'}, returns = 'integer',
+                       exports = {'LUA', 'SQL'}})
+test_run:cmd("setopt delimiter ''");
 
 ch = fiber.channel(1)
 
 _ = fiber.create(function () ch:put(box.execute('select WAITFOR(0.2)')) end)
 fiber.sleep(0.1)
 
-box.internal.sql_create_function('WAITFOR', 'INT', function (n) require('fiber').sleep(n) return n end)
+box.func.WAITFOR:drop()
+
+test_run:cmd("setopt delimiter ';'")
+box.schema.func.create('WAITFOR', {language = 'Lua',
+                       body = 'function (n) fiber.sleep(n) return n end',
+                       param_list = {'integer'}, returns = 'integer',
+                       exports = {'LUA', 'SQL'}})
+test_run:cmd("setopt delimiter ''");
 ch:get()
-box.internal.sql_create_function('WAITFOR', 'INT', function (n) require('fiber').sleep(n) return n end)
+box.func.WAITFOR:drop()
 
+test_run:cmd("setopt delimiter ';'")
+box.schema.func.create('WAITFOR', {language = 'Lua',
+                   body = 'function (n) fiber.sleep(n) return n end',
+                   param_list = {'integer'}, returns = 'integer',
+                   exports = {'LUA', 'SQL'}})
+test_run:cmd("setopt delimiter ''");
+
+box.func.WAITFOR:drop()
-- 
2.22.1

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

* [tarantool-patches] [PATCH v3 9/9] sql: better error messages on invalid arguments
  2019-08-16 13:26 [tarantool-patches] [PATCH v3 0/9] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
                   ` (7 preceding siblings ...)
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 8/9] sql: get rid of box.internal.sql_function_create Kirill Shcherbatov
@ 2019-08-16 13:26 ` Kirill Shcherbatov
  8 siblings, 0 replies; 19+ messages in thread
From: Kirill Shcherbatov @ 2019-08-16 13:26 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

Previous error message was not informative enough:
"wrong number of arguments to function".

An updated code provide more information in such case, when
possible:
"invalid number of arguments is passed to FUNCTION_NAME: expected
 n, got m".
---
 src/box/sql/resolve.c           | 14 +++++++++++---
 test/box/function1.result       | 10 +++++-----
 test/sql-tap/func.test.lua      | 22 +++++++++++-----------
 test/sql-tap/func2.test.lua     | 18 +++++++++---------
 test/sql-tap/limit.test.lua     |  4 ++--
 test/sql-tap/select1.test.lua   | 12 ++++++------
 test/sql/icu-upper-lower.result |  4 ++--
 7 files changed, 46 insertions(+), 38 deletions(-)

diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 682bb9c8e..2c037a5a4 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -678,10 +678,18 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 				pParse->is_aborted = true;
 				pNC->nErr++;
 			} else if (wrong_num_args) {
-				const char *err = "wrong number of arguments "\
-						  "to function %.*s()";
+				const char *err;
+				if (func->def->param_count >= 0) {
+					err = "invalid number of arguments is "
+					      "passed to %.*s(): expected %d, "
+					      "got %d";
+				} else {
+					err = "invalid number of arguments is "
+					      "passed to %.*s()";
+				}
 				diag_set(ClientError, ER_SQL_PARSER_GENERIC,
-					 tt_sprintf(err, nId, zId));
+					 tt_sprintf(err, nId, zId,
+						    func->def->param_count, n));
 				pParse->is_aborted = true;
 				pNC->nErr++;
 			}
diff --git a/test/box/function1.result b/test/box/function1.result
index ebd9be700..25c470d0e 100644
--- a/test/box/function1.result
+++ b/test/box/function1.result
@@ -405,12 +405,12 @@ box.execute('SELECT "function1.divide"()')
 box.execute('SELECT "function1.divide"(6)')
 ---
 - null
-- wrong number of arguments to function function1.divide()
+- 'invalid number of arguments is passed to function1.divide(): expected 0, got 1'
 ...
 box.execute('SELECT "function1.divide"(6, 3)')
 ---
 - null
-- wrong number of arguments to function function1.divide()
+- 'invalid number of arguments is passed to function1.divide(): expected 0, got 2'
 ...
 box.func["function1.divide"]:drop()
 ---
@@ -429,17 +429,17 @@ test_run:cmd("setopt delimiter ''");
 box.execute('SELECT "function1.divide"()')
 ---
 - null
-- wrong number of arguments to function function1.divide()
+- 'invalid number of arguments is passed to function1.divide(): expected 2, got 0'
 ...
 box.execute('SELECT "function1.divide"(6)')
 ---
 - null
-- wrong number of arguments to function function1.divide()
+- 'invalid number of arguments is passed to function1.divide(): expected 2, got 1'
 ...
 box.execute('SELECT "function1.divide"(6, 3, 3)')
 ---
 - null
-- wrong number of arguments to function function1.divide()
+- 'invalid number of arguments is passed to function1.divide(): expected 2, got 3'
 ...
 box.execute('SELECT "function1.divide"(6, 3)')
 ---
diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
index ec06c903d..ae87bf61b 100755
--- a/test/sql-tap/func.test.lua
+++ b/test/sql-tap/func.test.lua
@@ -69,7 +69,7 @@ test:do_catchsql_test(
         SELECT length(*) FROM tbl1 ORDER BY t1
     ]], {
         -- <func-1.1>
-        1, "wrong number of arguments to function LENGTH()"
+        1, "invalid number of arguments is passed to LENGTH(): expected 1, got 0"
         -- </func-1.1>
     })
 
@@ -79,7 +79,7 @@ test:do_catchsql_test(
         SELECT length(t1,5) FROM tbl1 ORDER BY t1
     ]], {
         -- <func-1.2>
-        1, "wrong number of arguments to function LENGTH()"
+        1, "invalid number of arguments is passed to LENGTH(): expected 1, got 2"
         -- </func-1.2>
     })
 
@@ -365,7 +365,7 @@ test:do_test(
         return test:catchsql("SELECT abs(a,b) FROM t1")
     end, {
         -- <func-4.1>
-        1, "wrong number of arguments to function ABS()"
+        1, "invalid number of arguments is passed to ABS(): expected 1, got 2"
         -- </func-4.1>
     })
 
@@ -377,7 +377,7 @@ test:do_catchsql_test(
         SELECT abs() FROM t1
     ]], {
         -- <func-4.2>
-        1, "wrong number of arguments to function ABS()"
+        1, "invalid number of arguments is passed to ABS(): expected 1, got 0"
         -- </func-4.2>
     })
 
@@ -428,7 +428,7 @@ test:do_catchsql_test(
         SELECT round(a,b,c) FROM t1
     ]], {
         -- <func-4.5>
-        1, "wrong number of arguments to function ROUND()"
+        1, "invalid number of arguments is passed to ROUND()"
         -- </func-4.5>
     })
 
@@ -488,7 +488,7 @@ test:do_catchsql_test(
         SELECT round() FROM t1 ORDER BY a
     ]], {
         -- <func-4.11>
-        1, "wrong number of arguments to function ROUND()"
+        1, "invalid number of arguments is passed to ROUND()"
         -- </func-4.11>
     })
 
@@ -778,7 +778,7 @@ test:do_catchsql_test(
         SELECT upper(*) FROM t2
     ]], {
         -- <func-5.5>
-        1, "wrong number of arguments to function UPPER()"
+        1, "invalid number of arguments is passed to UPPER(): expected 1, got 0"
         -- </func-5.5>
     })
 
@@ -1782,7 +1782,7 @@ test:do_catchsql_test(
         SELECT replace(1,2);
     ]], {
         -- <func-21.1>
-        1, "wrong number of arguments to function REPLACE()"
+        1, "invalid number of arguments is passed to REPLACE(): expected 3, got 2"
         -- </func-21.1>
     })
 
@@ -1792,7 +1792,7 @@ test:do_catchsql_test(
         SELECT replace(1,2,3,4);
     ]], {
         -- <func-21.2>
-        1, "wrong number of arguments to function REPLACE()"
+        1, "invalid number of arguments is passed to REPLACE(): expected 3, got 4"
         -- </func-21.2>
     })
 
@@ -2540,7 +2540,7 @@ test:do_catchsql_test(
         SELECT coalesce()
     ]], {
         -- <func-27.1>
-        1, "wrong number of arguments to function COALESCE()"
+        1, "invalid number of arguments is passed to COALESCE()"
         -- </func-27.1>
     })
 
@@ -2550,7 +2550,7 @@ test:do_catchsql_test(
         SELECT coalesce(1)
     ]], {
         -- <func-27.2>
-        1, "wrong number of arguments to function COALESCE()"
+        1, "invalid number of arguments is passed to COALESCE()"
         -- </func-27.2>
     })
 
diff --git a/test/sql-tap/func2.test.lua b/test/sql-tap/func2.test.lua
index b70197d09..8ae565040 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 to function SUBSTR()"
+        1, "invalid number of arguments is passed to SUBSTR()"
         -- </func2-1.2.1>
     })
 
@@ -60,7 +60,7 @@ test:do_catchsql_test(
         SELECT SUBSTR('Supercalifragilisticexpialidocious')
     ]], {
         -- <func2-1.2.2>
-        1, "wrong number of arguments to function SUBSTR()"
+        1, "invalid number of arguments is passed to SUBSTR()"
         -- </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 to function SUBSTR()"
+        1, "invalid number of arguments is passed to SUBSTR()"
         -- </func2-1.2.3>
     })
 
@@ -673,7 +673,7 @@ if ("ሴ" ~= "u1234")
             SELECT SUBSTR()
         ]], {
             -- <func2-2.1.2>
-            1, "wrong number of arguments to function SUBSTR()"
+            1, "invalid number of arguments is passed to SUBSTR()"
             -- </func2-2.1.2>
         })
 
@@ -683,7 +683,7 @@ if ("ሴ" ~= "u1234")
             SELECT SUBSTR('hiሴho')
         ]], {
             -- <func2-2.1.3>
-            1, "wrong number of arguments to function SUBSTR()"
+            1, "invalid number of arguments is passed to SUBSTR()"
             -- </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 to function SUBSTR()"
+            1, "invalid number of arguments is passed to SUBSTR()"
             -- </func2-2.1.4>
         })
 
@@ -1038,7 +1038,7 @@ test:do_catchsql_test(
         SELECT SUBSTR()
     ]], {
         -- <func2-3.1.2>
-        1, "wrong number of arguments to function SUBSTR()"
+        1, "invalid number of arguments is passed to SUBSTR()"
         -- </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 to function SUBSTR()"
+        1, "invalid number of arguments is passed to SUBSTR()"
         -- </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 to function SUBSTR()"
+        1, "invalid number of arguments is passed to SUBSTR()"
         -- </func2-3.1.4>
     })
 
diff --git a/test/sql-tap/limit.test.lua b/test/sql-tap/limit.test.lua
index 8445ab18e..76c5cba4d 100755
--- a/test/sql-tap/limit.test.lua
+++ b/test/sql-tap/limit.test.lua
@@ -771,7 +771,7 @@ test:do_catchsql_test(
         SELECT * FROM t1 LIMIT replace(1)
     ]], {
         -- <limit-12.1>
-        1, "wrong number of arguments to function REPLACE()"
+        1, "invalid number of arguments is passed to REPLACE(): expected 3, got 1"
         -- </limit-12.1>
     })
 
@@ -781,7 +781,7 @@ test:do_catchsql_test(
         SELECT * FROM t1 LIMIT 5 OFFSET replace(1)
     ]], {
         -- <limit-12.2>
-        1, 'wrong number of arguments to function REPLACE()'
+        1, 'invalid number of arguments is passed to REPLACE(): expected 3, got 1'
         -- </limit-12.2>
     })
 
diff --git a/test/sql-tap/select1.test.lua b/test/sql-tap/select1.test.lua
index 924c0ccb1..096bd23c1 100755
--- a/test/sql-tap/select1.test.lua
+++ b/test/sql-tap/select1.test.lua
@@ -244,7 +244,7 @@ test:do_catchsql_test(
         SELECT count(f1,f2) FROM test1
     ]], {
         -- <select1-2.1>
-        1, "wrong number of arguments to function COUNT()"
+        1, "invalid number of arguments is passed to COUNT()"
         -- </select1-2.1>
     })
 
@@ -324,7 +324,7 @@ test:do_catchsql_test(
         SELECT min(*) FROM test1
     ]], {
         -- <select1-2.6>
-        1, "wrong number of arguments to function MIN()"
+        1, "invalid number of arguments is passed to MIN(): expected 1, got 0"
         -- </select1-2.6>
     })
 
@@ -389,7 +389,7 @@ test:do_catchsql_test(
         SELECT MAX(*) FROM test1
     ]], {
         -- <select1-2.9>
-        1, "wrong number of arguments to function MAX()"
+        1, "invalid number of arguments is passed to MAX(): expected 1, got 0"
         -- </select1-2.9>
     })
 
@@ -469,7 +469,7 @@ test:do_catchsql_test(
         SELECT SUM(*) FROM test1
     ]], {
         -- <select1-2.14>
-        1, "wrong number of arguments to function SUM()"
+        1, "invalid number of arguments is passed to SUM(): expected 1, got 0"
         -- </select1-2.14>
     })
 
@@ -489,7 +489,7 @@ test:do_catchsql_test(
         SELECT sum(f1,f2) FROM test1
     ]], {
         -- <select1-2.16>
-        1, "wrong number of arguments to function SUM()"
+        1, "invalid number of arguments is passed to SUM(): expected 1, got 2"
         -- </select1-2.16>
     })
 
@@ -691,7 +691,7 @@ test:do_catchsql_test(
         SELECT f1 FROM test1 WHERE count(f1,f2)!=11
     ]], {
         -- <select1-3.9>
-        1, "wrong number of arguments to function COUNT()"
+        1, "invalid number of arguments is passed to COUNT()"
         -- </select1-3.9>
     })
 
diff --git a/test/sql/icu-upper-lower.result b/test/sql/icu-upper-lower.result
index 88266c8c5..e853c25e4 100644
--- a/test/sql/icu-upper-lower.result
+++ b/test/sql/icu-upper-lower.result
@@ -277,7 +277,7 @@ test_run:cmd("setopt delimiter ''");
 box.execute("select upper('1', 2)")
 ---
 - null
-- wrong number of arguments to function UPPER()
+- 'invalid number of arguments is passed to UPPER(): expected 1, got 2'
 ...
 box.execute("select upper(\"1\")")
 ---
@@ -287,5 +287,5 @@ box.execute("select upper(\"1\")")
 box.execute("select upper()")
 ---
 - null
-- wrong number of arguments to function UPPER()
+- 'invalid number of arguments is passed to UPPER(): expected 1, got 0'
 ...
-- 
2.22.1

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

* [tarantool-patches] Re: [PATCH v3 7/9] sql: get rid of FuncDef function hash
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 7/9] sql: get rid of FuncDef function hash Kirill Shcherbatov
@ 2019-08-16 14:09   ` Konstantin Osipov
  2019-08-16 18:59     ` n.pettik
  2019-08-19 15:51     ` Kirill Shcherbatov
  0 siblings, 2 replies; 19+ messages in thread
From: Konstantin Osipov @ 2019-08-16 14:09 UTC (permalink / raw)
  To: tarantool-patches; +Cc: korablev, Kirill Shcherbatov

* Kirill Shcherbatov <kshcherbatov@tarantool.org> [19/08/16 16:29]:
> --- a/src/box/func.c
> +++ b/src/box/func.c
> @@ -39,6 +39,7 @@
>  #include "port.h"
>  #include "schema.h"
>  #include "session.h"
> +#include "sql.h"
>  #include <dlfcn.h>

Why?

> +struct func *
> +sql_func_by_signature(const char *name, int argc)
> +{
> +	struct func *func = func_by_name(name, strlen(name));
> +	if (func == NULL || !func->def->exports.sql)
> +		return NULL;
> +	if (func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
> +		if (strcmp(func->def->name, "COALESCE") == 0) {
> +			if (argc == 0 || argc == 1)
> +				return NULL;
> +			argc = func->def->param_count;
> +		} else if (strcmp(func->def->name, "COUNT") == 0) {
> +			if (argc != 0 && argc != 1)
> +				return NULL;
> +			argc = func->def->param_count;
> +		} else if (strcmp(func->def->name, "GROUP_CONCAT") == 0) {
> +			if (argc != 1 && argc != 2)
> +				return NULL;
> +			argc = func->def->param_count;
> +		} else if (strcmp(func->def->name, "LIKE") == 0) {
> +			if (argc != 2 && argc != 3)
> +				return NULL;
> +			argc = func->def->param_count;
> +		} else if (strcmp(func->def->name, "ROUND") == 0) {
> +			if (argc != 1 && argc != 2)
> +				return NULL;
> +			argc = func->def->param_count;
> +		} else if (strcmp(func->def->name, "SUBSTR") == 0) {
> +			if (argc != 2 && argc != 3)
> +				return NULL;
> +			argc = func->def->param_count;
> +		} else if (strcmp(func->def->name, "TRIM") == 0) {
> +			if (argc != 1 && argc != 2 && argc != 3)
> +				return NULL;
> +			argc = func->def->param_count;
> +		}
> +	}
> +	if (func->def->param_count != -1 && argc != func->def->param_count)
> +		return NULL;
> +	return func;
> +}

Why can't you have this as a check() method of sql_builtin_func, set for the specific functions which nee
this check, why do you have to have a switch + strcmp during runtime?


overall, this patch is huge. It is frustrating that the previous
patches on the stack are tacking so long to push. They should have
been finished & pushed so that this patch could be split into
pieces and worked on faster.

Nikita, could you please finish your review of all the patches up
to this one?

-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v3 6/9] sql: remove SQL_FUNC_SLOCHNG flag
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 6/9] sql: remove SQL_FUNC_SLOCHNG flag Kirill Shcherbatov
@ 2019-08-16 18:54   ` n.pettik
  0 siblings, 0 replies; 19+ messages in thread
From: n.pettik @ 2019-08-16 18:54 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov

lgtm

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

* [tarantool-patches] Re: [PATCH v3 4/9] sql: rework SQL_FUNC_COUNT flag semantics
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 4/9] sql: rework SQL_FUNC_COUNT flag semantics Kirill Shcherbatov
@ 2019-08-16 18:55   ` n.pettik
  0 siblings, 0 replies; 19+ messages in thread
From: n.pettik @ 2019-08-16 18:55 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov

lgtm

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

* [tarantool-patches] Re: [PATCH v3 2/9] sql: GREATEST, LEAST instead of MIN/MAX overload
  2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 2/9] sql: GREATEST, LEAST instead of MIN/MAX overload Kirill Shcherbatov
@ 2019-08-16 18:57   ` n.pettik
  0 siblings, 0 replies; 19+ messages in thread
From: n.pettik @ 2019-08-16 18:57 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov

lgtm

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

* [tarantool-patches] Re: [PATCH v3 7/9] sql: get rid of FuncDef function hash
  2019-08-16 14:09   ` [tarantool-patches] " Konstantin Osipov
@ 2019-08-16 18:59     ` n.pettik
  2019-08-19 15:51     ` Kirill Shcherbatov
  1 sibling, 0 replies; 19+ messages in thread
From: n.pettik @ 2019-08-16 18:59 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Konstantin Osipov, Kirill Shcherbatov


> Nikita, could you please finish your review of all the patches up
> to this one?
> 

Yep, now they seem to be ok. You can push them.

> -- 
> Konstantin Osipov, Moscow, Russia
> 

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

* [tarantool-patches] Re: [PATCH v3 7/9] sql: get rid of FuncDef function hash
  2019-08-16 14:09   ` [tarantool-patches] " Konstantin Osipov
  2019-08-16 18:59     ` n.pettik
@ 2019-08-19 15:51     ` Kirill Shcherbatov
  2019-08-20 13:47       ` Konstantin Osipov
  2019-08-20 19:04       ` n.pettik
  1 sibling, 2 replies; 19+ messages in thread
From: Kirill Shcherbatov @ 2019-08-19 15:51 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Konstantin Osipov

Tnx to Kostya for a brilliant idea with check_param_count virtual method.
I've implemented it.
I've decided to implement it in func_sql_builtin class, but not in func.vtab -
discussed verbally with Vova and Nikita.

==============================================

Now it is possible to move all SQL builtin functions to
Tarantool's function hash. An existent FuncDef function
representation was replaced with func_sql_builtin class.
It has a sql-specific method :call and :finalize, while
port API call is not supported and protected with stubs.

This patch removes FuncDef hash and sql_function_create endpoint,
but doesn't introduce something instead. Therefore few affected
tests are disabled. A required functionality would be fixed in
the next patch.

Following tests using sql_create_function are broken now.
They are going to be fixed in the next commit:
 sql-tap/alias.test.lua sql-tap/check.test.lua
 sql-tap/func5.test.lua sql-tap/lua_sql.test.lua
 sql-tap/subquery.test.lua sql-tap/trigger9.test.lua
 sql/errinj.result sql/errinj.test.lua
 sql/func-recreate.test.lua

Part of #2200, #4113, #2233
---
 src/box/lua/lua_sql.h        |  39 ---
 src/box/sql.h                |   5 +
 src/box/sql/sqlInt.h         | 240 ++++---------
 src/box/sql/vdbe.h           |   9 +-
 src/box/sql/vdbeInt.h        |  23 +-
 src/box/func.c               |  34 +-
 src/box/lua/call.c           |   2 -
 src/box/lua/lua_sql.c        | 205 -----------
 src/box/sql/analyze.c        |  34 +-
 src/box/sql/callback.c       | 204 -----------
 src/box/sql/date.c           |  28 --
 src/box/sql/expr.c           |  65 ++--
 src/box/sql/func.c           | 656 ++++++++++++++++++++++++++++-------
 src/box/sql/global.c         |   7 -
 src/box/sql/main.c           | 137 --------
 src/box/sql/resolve.c        |  53 ++-
 src/box/sql/select.c         |  10 +-
 src/box/sql/vdbe.c           |  20 +-
 src/box/sql/vdbeapi.c        |  17 +-
 src/box/sql/vdbeaux.c        |  31 +-
 src/box/sql/vdbemem.c        |  65 ++--
 src/box/sql/whereexpr.c      |   2 +-
 src/box/CMakeLists.txt       |   1 -
 src/box/alter.cc             |   6 +
 test/box/function1.result    |   4 +
 test/box/function1.test.lua  |   2 +
 test/sql-tap/where2.test.lua |   4 +-
 27 files changed, 780 insertions(+), 1123 deletions(-)
 delete mode 100644 src/box/lua/lua_sql.h
 delete mode 100644 src/box/lua/lua_sql.c

diff --git a/src/box/lua/lua_sql.h b/src/box/lua/lua_sql.h
deleted file mode 100644
index b81093eca..000000000
--- a/src/box/lua/lua_sql.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * 1. Redistributions of source code must retain the above
- *    copyright notice, this list of conditions and the
- *    following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above
- *    copyright notice, this list of conditions and the following
- *    disclaimer in the documentation and/or other materials
- *    provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
- * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#ifndef TARANTOOL_LUA_SQL_H
-#define TARANTOOL_LUA_SQL_H
-
-int
-lbox_sql_create_function(struct lua_State *L);
-
-#endif //TARANTOOL_LUA_SQL_H
-
diff --git a/src/box/sql.h b/src/box/sql.h
index 9ccecf28c..6eaf5ba3a 100644
--- a/src/box/sql.h
+++ b/src/box/sql.h
@@ -68,6 +68,7 @@ struct Expr;
 struct Parse;
 struct Select;
 struct Table;
+struct func_def;
 struct sql_trigger;
 struct space_def;
 
@@ -348,6 +349,10 @@ sql_src_list_entry_name(const struct SrcList *list, int i);
 void
 sqlSrcListDelete(struct sql *db, struct SrcList *list);
 
+/** Construct a SQL builtin function object. */
+struct func *
+func_sql_builtin_new(struct func_def *def);
+
 /**
  * Auxiliary VDBE structure to speed-up tuple data field access.
  * A memory allocation that manage this structure must have
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 5a3e8f1c1..91a640816 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -70,6 +70,8 @@
 #include "box/column_mask.h"
 #include "parse_def.h"
 #include "box/field_def.h"
+#include "box/func.h"
+#include "box/func_def.h"
 #include "box/sql.h"
 #include "box/txn.h"
 #include "trivia/util.h"
@@ -541,9 +543,6 @@ void
 sql_row_count(struct sql_context *context, MAYBE_UNUSED int unused1,
 	      MAYBE_UNUSED sql_value **unused2);
 
-void *
-sql_user_data(sql_context *);
-
 void *
 sql_aggregate_context(sql_context *,
 			  int nBytes);
@@ -566,26 +565,6 @@ sql_initialize(void);
 #define SQL_TRACE_ROW        0x04
 #define SQL_TRACE_CLOSE      0x08
 
-#define SQL_DETERMINISTIC    0x800
-
-int
-sql_create_function_v2(sql * db,
-			   const char *zFunctionName,
-			   enum field_type type,
-			   int nArg,
-			   int flags,
-			   void *pApp,
-			   void (*xFunc) (sql_context *,
-					  int,
-					  sql_value **),
-			   void (*xStep) (sql_context *,
-					  int,
-					  sql_value **),
-			   void (*xFinal)
-			   (sql_context *),
-			   void (*xDestroy) (void *)
-	);
-
 #define SQL_OPEN_READONLY         0x00000001	/* Ok for sql_open_v2() */
 #define SQL_OPEN_READWRITE        0x00000002	/* Ok for sql_open_v2() */
 #define SQL_OPEN_CREATE           0x00000004	/* Ok for sql_open_v2() */
@@ -1039,9 +1018,6 @@ typedef struct Column Column;
 typedef struct Expr Expr;
 typedef struct ExprList ExprList;
 typedef struct ExprSpan ExprSpan;
-typedef struct FuncDestructor FuncDestructor;
-typedef struct FuncDef FuncDef;
-typedef struct FuncDefHash FuncDefHash;
 typedef struct IdList IdList;
 typedef struct KeyClass KeyClass;
 typedef struct Lookaside Lookaside;
@@ -1123,18 +1099,6 @@ struct LookasideSlot {
 	LookasideSlot *pNext;	/* Next buffer in the list of free buffers */
 };
 
-/*
- * A hash table for built-in function definitions.  (Application-defined
- * functions use a regular table table from hash.h.)
- *
- * Hash each FuncDef structure into one of the FuncDefHash.a[] slots.
- * Collisions are on the FuncDef.u.pHash chain.
- */
-#define SQL_FUNC_HASH_SZ 23
-struct FuncDefHash {
-	FuncDef *a[SQL_FUNC_HASH_SZ];	/* Hash table for functions */
-};
-
 /*
  * Each database connection is an instance of the following structure.
  */
@@ -1244,65 +1208,11 @@ struct type_def {
 };
 
 /*
- * Each SQL function is defined by an instance of the following
- * structure.  For global built-in functions (ex: substr(), max(), count())
- * a pointer to this structure is held in the sqlBuiltinFunctions object.
- * For per-connection application-defined functions, a pointer to this
- * structure is held in the db->aHash hash table.
- *
- * The u.pHash field is used by the global built-ins.  The u.pDestructor
- * field is used by per-connection app-def functions.
- */
-struct FuncDef {
-	i8 nArg;		/* Number of arguments.  -1 means unlimited */
-	u16 funcFlags;		/* Some combination of sql_FUNC_* */
-	void *pUserData;	/* User data parameter */
-	FuncDef *pNext;		/* Next function with same name */
-	void (*xSFunc) (sql_context *, int, sql_value **);	/* func or agg-step */
-	void (*xFinalize) (sql_context *);	/* Agg finalizer */
-	const char *zName;	/* SQL name of the function. */
-	union {
-		FuncDef *pHash;	/* Next with a different name but the same hash */
-		FuncDestructor *pDestructor;	/* Reference counted destructor function */
-	} u;
-	/* Return type. */
-	enum field_type ret_type;
-};
-
-/*
- * This structure encapsulates a user-function destructor callback (as
- * configured using create_function_v2()) and a reference counter. When
- * create_function_v2() is called to create a function with a destructor,
- * a single object of this type is allocated. FuncDestructor.nRef is set to
- * the number of FuncDef objects created (either 1 or 3, depending on whether
- * or not the specified encoding is sql_ANY). The FuncDef.pDestructor
- * member of each of the new FuncDef objects is set to point to the allocated
- * FuncDestructor.
- *
- * Thereafter, when one of the FuncDef objects is deleted, the reference
- * count on this object is decremented. When it reaches 0, the destructor
- * is invoked and the FuncDestructor structure freed.
- */
-struct FuncDestructor {
-	int nRef;
-	void (*xDestroy) (void *);
-	void *pUserData;
-};
-
-/*
- * Possible values for FuncDef.flags.  Note that the _LENGTH and _TYPEOF
- * values must correspond to OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG.  And
- * sql_FUNC_CONSTANT must be the same as sql_DETERMINISTIC.  There
- * are assert() statements in the code to verify this.
- *
  * Value constraints (enforced via assert()):
- *     SQL_FUNC_MINMAX    ==  NC_MinMaxAgg      == SF_MinMaxAgg
  *     SQL_FUNC_LENGTH    ==  OPFLAG_LENGTHARG
  *     SQL_FUNC_TYPEOF    ==  OPFLAG_TYPEOFARG
- *     SQL_FUNC_CONSTANT  ==  sql_DETERMINISTIC from the API
  */
 #define SQL_FUNC_LIKE     0x0004	/* Candidate for the LIKE optimization */
-#define SQL_FUNC_EPHEM    0x0010	/* Ephemeral.  Delete with VDBE */
 #define SQL_FUNC_NEEDCOLL 0x0020	/* sqlGetFuncCollSeq() might be called.
 					 * The flag is set when the collation
 					 * of function arguments should be
@@ -1316,9 +1226,10 @@ struct FuncDestructor {
 #define SQL_FUNC_COUNT    0x0100
 #define SQL_FUNC_COALESCE 0x0200	/* Built-in coalesce() or ifnull() */
 #define SQL_FUNC_UNLIKELY 0x0400	/* Built-in unlikely() function */
-#define SQL_FUNC_CONSTANT 0x0800	/* Constant inputs give a constant output */
-#define SQL_FUNC_MINMAX   0x1000	/* True for min() and max() aggregates */
-
+/** Built-in min() or least() function. */
+#define SQL_FUNC_MIN      0x1000
+/** Built-in max() or greatest() function. */
+#define SQL_FUNC_MAX   0x2000
 /**
  * If function returns string, it may require collation to be
  * applied on its result. For instance, result of substr()
@@ -1338,61 +1249,6 @@ enum trim_side_mask {
 	TRIM_BOTH = TRIM_LEADING | TRIM_TRAILING
 };
 
-/*
- * The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are
- * used to create the initializers for the FuncDef structures.
- *
- *   FUNCTION(zName, nArg, iArg, bNC, xFunc)
- *     Used to create a scalar function definition of a function zName
- *     implemented by C function xFunc that accepts nArg arguments. The
- *     value passed as iArg is cast to a (void*) and made available
- *     as the user-data (sql_user_data()) for the function. If
- *     argument bNC is true, then the sql_FUNC_NEEDCOLL flag is set.
- *
- *   FUNCTION_COLL
- *     Like FUNCTION except it assumes that function returns
- *     STRING which collation should be derived from first
- *     argument (trim, substr etc).
- *
- *   VFUNCTION(zName, nArg, iArg, bNC, xFunc)
- *     Like FUNCTION except it omits the sql_FUNC_CONSTANT flag.
- *
- *   AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal)
- *     Used to create an aggregate function definition implemented by
- *     the C functions xStep and xFinal. The first four parameters
- *     are interpreted in the same way as the first 4 parameters to
- *     FUNCTION().
- *
- *   LIKEFUNC(zName, nArg, pArg, flags)
- *     Used to create a scalar function definition of a function zName
- *     that accepts nArg arguments and is implemented by a call to C
- *     function likeFunc. Argument pArg is cast to a (void *) and made
- *     available as the function user-data (sql_user_data()). The
- *     FuncDef.flags variable is set to the value passed as the flags
- *     parameter.
- */
-#define FUNCTION(zName, nArg, iArg, bNC, xFunc, type) \
-  {nArg, SQL_FUNC_CONSTANT|(bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define FUNCTION_COLL(zName, nArg, iArg, bNC, xFunc) \
-  {nArg, SQL_FUNC_CONSTANT|SQL_FUNC_DERIVEDCOLL|(bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, FIELD_TYPE_STRING}
-#define VFUNCTION(zName, nArg, iArg, bNC, xFunc, type) \
-  {nArg, (bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags, type) \
-  {nArg,SQL_FUNC_CONSTANT|(bNC*SQL_FUNC_NEEDCOLL)|extraFlags,\
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define LIKEFUNC(zName, nArg, arg, flags, type) \
-  {nArg, SQL_FUNC_NEEDCOLL|SQL_FUNC_CONSTANT|flags, \
-   (void *)(SQL_INT_TO_PTR(arg)), 0, likeFunc, 0, #zName, {0}, type}
-#define AGGREGATE(zName, nArg, arg, nc, xStep, xFinal, type) \
-  {nArg, (nc*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}, type}
-#define AGGREGATE2(zName, nArg, arg, nc, xStep, xFinal, extraFlags, type) \
-  {nArg, (nc*SQL_FUNC_NEEDCOLL)|extraFlags, \
-   SQL_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}, type}
-
 /*
  * All current savepoints are stored in a linked list starting at
  * sql.pSavepoint. The first element in the list is the most recently
@@ -1585,7 +1441,8 @@ struct AggInfo {
 				 */
 	struct AggInfo_func {	/* For each aggregate function */
 		Expr *pExpr;	/* Expression encoding the function */
-		FuncDef *pFunc;	/* The aggregate function implementation */
+		/** The aggregate function implementation. */
+		struct func *func;
 		int iMem;	/* Memory location that acts as accumulator */
 		int iDistinct;	/* Ephemeral table used to enforce DISTINCT */
 		/**
@@ -2014,7 +1871,7 @@ struct NameContext {
  *
  * Value constraints (all checked via assert()):
  *    NC_HasAgg    == SF_HasAgg
- *    NC_MinMaxAgg == SF_MinMaxAgg == sql_FUNC_MINMAX
+ *    NC_MinMaxAgg == SF_MinMaxAgg
  *
  */
 #define NC_AllowAgg  0x0001	/* Aggregate functions are allowed here */
@@ -2072,7 +1929,7 @@ struct Select {
  *
  * Value constraints (all checked via assert())
  *     SF_HasAgg     == NC_HasAgg
- *     SF_MinMaxAgg  == NC_MinMaxAgg     == sql_FUNC_MINMAX
+ *     SF_MinMaxAgg  == NC_MinMaxAgg
  *     SF_FixedLimit == WHERE_USE_LIMIT
  */
 #define SF_Distinct       0x00001	/* Output should be DISTINCT */
@@ -3533,10 +3390,6 @@ void sqlSelectSetName(Select *, const char *);
 #else
 #define sqlSelectSetName(A,B)
 #endif
-void sqlInsertBuiltinFuncs(FuncDef *, int);
-FuncDef *sqlFindFunction(sql *, const char *, int, u8);
-void sqlRegisterBuiltinFunctions(void);
-void sqlRegisterDateTimeFunctions(void);
 
 /**
  * Evaluate a view and store its result in an ephemeral table.
@@ -4109,7 +3962,6 @@ extern const unsigned char sqlUpperToLower[];
 extern const unsigned char sqlCtypeMap[];
 extern const Token sqlIntTokens[];
 extern SQL_WSD struct sqlConfig sqlConfig;
-extern FuncDefHash sqlBuiltinFunctions;
 extern int sqlPendingByte;
 
 /**
@@ -4245,21 +4097,13 @@ sql_key_info_to_key_def(struct sql_key_info *key_info);
  * Check if the function implements LIKE-style comparison & if it
  * is appropriate to apply a LIKE query optimization.
  *
- * @param db database structure.
  * @param pExpr pointer to a function-implementing expression.
  * @param[out] is_like_ci true if LIKE is case insensitive.
  *
  * @retval 1 if LIKE optimization can be used, 0 otherwise.
  */
 int
-sql_is_like_func(struct sql *db, struct Expr *expr);
-
-int sqlCreateFunc(sql *, const char *, enum field_type,
-		      int, int, void *,
-		      void (*)(sql_context *, int, sql_value **),
-		      void (*)(sql_context *, int, sql_value **),
-		      void (*)(sql_context *),
-		      FuncDestructor * pDestructor);
+sql_is_like_func(struct Expr *expr);
 
 /** Set OOM error flag. */
 static inline void
@@ -4296,7 +4140,6 @@ sql_expr_new_column(struct sql *db, struct SrcList *src_list, int src_idx,
 
 int sqlExprCheckIN(Parse *, Expr *);
 
-void sqlAnalyzeFunctions(void);
 int sqlStat4ProbeSetValue(Parse *, struct index_def *, UnpackedRecord **, Expr *, int,
 			      int, int *);
 int sqlStat4ValueFromExpr(Parse *, Expr *, enum field_type type,
@@ -4487,9 +4330,64 @@ Expr *sqlExprForVectorField(Parse *, Expr *, int);
  */
 extern int sqlSubProgramsRemaining;
 
-/** Register built-in functions to work with ANALYZE data. */
-void
-sql_register_analyze_builtins(void);
+struct func_sql_builtin {
+	/** Function object base class. */
+	struct func base;
+	/** A bitmask of SQL flags. */
+	uint16_t flags;
+	/** User data to pass in call method. */
+	void *user_data;
+	/**
+	 * A VDBE-memory-compatible call method for a function.
+	 * SQL Builting functions doesn't use base class call
+	 * method to gain the best possible performance for SQL
+	 * requests. As builtin functions are predefined and
+	 * non of them modifie schema, access checks are
+	 * redundant, functions have the same execution
+	 * privileges as SQL.
+	 */
+	void (*call)(sql_context *ctx, int argc, sql_value **argv);
+	/**
+	 * Check whether do the function support a specified
+	 * number of input arguments.
+	 */
+	int (*check_param_count)(struct func_sql_builtin *func, int argc);
+	/**
+	 * A VDBE-memory-compatible finalize method
+	 * (is valid only for aggregate function).
+	 */
+	void (*finalize)(sql_context *ctx);
+};
+
+/**
+ * Test whether SQL-specific flag is set for given function.
+ * Currently only SQL Builtin Functions have such hint flags,
+ * so function returns false for other functions. Such approach
+ * decreases code complexity and allows do not distinguish
+ * functions by implementation details where it is unnecessary.
+ *
+ * Returns true when given flag is set for a given function and
+ * false otherwise.
+ */
+static inline bool
+sql_func_flag_is_set(struct func *func, uint16_t flag)
+{
+	if (func->def->language != FUNC_LANGUAGE_SQL_BUILTIN)
+		return false;
+	return (((struct func_sql_builtin *)func)->flags & flag) != 0;
+}
+
+/**
+ * A SQL method to find a function in a hash by its name and
+ * count of arguments. Only functions that have 'SQL' engine
+ * export field set true and have exactly the same signature
+ * are returned.
+ *
+ * Returns not NULL function pointer when a valid and exported
+ * to SQL engine function is found and NULL otherwise.
+ */
+struct func *
+sql_func_by_signature(const char *name, int argc);
 
 /**
  * Generate VDBE code to halt execution with correct error if
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 8f16202ba..4f639ec38 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -72,7 +72,11 @@ struct VdbeOp {
 		char *z;	/* Pointer to data for string (char array) types */
 		i64 *pI64;	/* Used when p4type is P4_INT64/UINT64 */
 		double *pReal;	/* Used when p4type is P4_REAL */
-		FuncDef *pFunc;	/* Used when p4type is P4_FUNCDEF */
+		/**
+		 * A pointer to function implementation.
+		 * Used when p4type is P4_FUNC.
+		 */
+		struct func *func;
 		sql_context *pCtx;	/* Used when p4type is P4_FUNCCTX */
 		struct coll *pColl;	/* Used when p4type is P4_COLLSEQ */
 		Mem *pMem;	/* Used when p4type is P4_MEM */
@@ -122,7 +126,8 @@ struct SubProgram {
 #define P4_DYNAMIC  (-1)	/* Pointer to a string obtained from sqlMalloc() */
 #define P4_STATIC   (-2)	/* Pointer to a static string */
 #define P4_COLLSEQ  (-3)	/* P4 is a pointer to a CollSeq structure */
-#define P4_FUNCDEF  (-4)	/* P4 is a pointer to a FuncDef structure */
+/** P4 is a pointer to a func structure. */
+#define P4_FUNC     (-4)
 #define P4_MEM      (-7)	/* P4 is a pointer to a Mem*    structure */
 #define P4_TRANSIENT  0		/* P4 is a pointer to a transient string */
 #define P4_REAL     (-9)	/* P4 is a 64-bit floating point value */
diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
index f77c019fb..d0c15d1fe 100644
--- a/src/box/sql/vdbeInt.h
+++ b/src/box/sql/vdbeInt.h
@@ -46,6 +46,8 @@
  */
 typedef struct VdbeOp Op;
 
+struct func;
+
 /*
  * Boolean values
  */
@@ -168,7 +170,11 @@ struct Mem {
 		bool b;         /* Boolean value used when MEM_Bool is set in flags */
 		int nZero;	/* Used when bit MEM_Zero is set in flags */
 		void *p;	/* Generic pointer */
-		FuncDef *pDef;	/* Used only when flags==MEM_Agg */
+		/**
+		 * A pointer to function implementation.
+		 * Used only when flags==MEM_Agg.
+		 */
+		struct func *func;
 		VdbeFrame *pFrame;	/* Used when flags==MEM_Frame */
 	} u;
 	u32 flags;		/* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
@@ -309,7 +315,8 @@ mem_apply_numeric_type(struct Mem *record);
  */
 struct sql_context {
 	Mem *pOut;		/* The return value is stored here */
-	FuncDef *pFunc;		/* Pointer to function information */
+	/* A pointer to function implementation. */
+	struct func *func;
 	Mem *pMem;		/* Memory cell used to store aggregate context */
 	Vdbe *pVdbe;		/* The VM that owns this context */
 	/** Instruction number of OP_BuiltinFunction0. */
@@ -516,7 +523,17 @@ int sqlVdbeMemNumerify(Mem *);
 int sqlVdbeMemCast(Mem *, enum field_type type);
 int sqlVdbeMemFromBtree(BtCursor *, u32, u32, Mem *);
 void sqlVdbeMemRelease(Mem * p);
-int sqlVdbeMemFinalize(Mem *, FuncDef *);
+
+/**
+ * Memory cell pMem contains the context of an aggregate function.
+ * This routine calls the finalize method for that function. The
+ * result of the aggregate is stored back into mem.
+ *
+ * Returns -1 if the finalizer reports an error. 0 otherwise.
+ */
+int
+sql_vdbemem_finalize(struct Mem *mem, struct func *func);
+
 const char *sqlOpcodeName(int);
 int sqlVdbeMemGrow(Mem * pMem, int n, int preserve);
 int sqlVdbeMemClearAndResize(Mem * pMem, int n);
diff --git a/src/box/func.c b/src/box/func.c
index b35d05dca..12e603b3f 100644
--- a/src/box/func.c
+++ b/src/box/func.c
@@ -39,6 +39,7 @@
 #include "port.h"
 #include "schema.h"
 #include "session.h"
 #include <dlfcn.h>
 
 /**
@@ -381,39 +382,6 @@ restore:
 static struct func *
 func_c_new(struct func_def *def);
 
-/** A stub object for SQL builtins to avoid name clash with UDF. */
-static struct func_vtab func_sql_builtin_vtab;
-
-/** Construct a SQL builtin function object. */
-struct func *
-func_sql_builtin_new(struct func_def *def)
-{
-	assert(def->language == FUNC_LANGUAGE_SQL_BUILTIN);
-	struct func *func =
-		(struct func *) malloc(sizeof(*func));
-	if (func == NULL) {
-		diag_set(OutOfMemory, sizeof(*func), "malloc", "func");
-		return NULL;
-	}
-	/** Don't export SQL builtins in Lua for now. */
-	def->exports.lua = false;
-	func->vtab = &func_sql_builtin_vtab;
-	return func;
-}
-
-static void
-func_sql_builtin_destroy(struct func *func)
-{
-	assert(func->vtab == &func_sql_builtin_vtab);
-	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
-	free(func);
-}
-
-static struct func_vtab func_sql_builtin_vtab = {
-	.call = NULL,
-	.destroy = func_sql_builtin_destroy,
-};
-
 struct func *
 func_new(struct func_def *def)
 {
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index 0ac2eb7a6..001578b5a 100644
--- a/src/box/lua/call.c
+++ b/src/box/lua/call.c
@@ -44,7 +44,6 @@
 #include "box/port.h"
 #include "box/lua/tuple.h"
 #include "small/obuf.h"
-#include "lua_sql.h"
 #include "trivia/util.h"
 #include "mpstream.h"
 
@@ -968,7 +967,6 @@ static struct trigger on_alter_func_in_lua = {
 
 static const struct luaL_Reg boxlib_internal[] = {
 	{"call_loadproc",  lbox_call_loadproc},
-	{"sql_create_function",  lbox_sql_create_function},
 	{"module_reload", lbox_module_reload},
 	{"func_call", lbox_func_call},
 	{NULL, NULL}
diff --git a/src/box/lua/lua_sql.c b/src/box/lua/lua_sql.c
deleted file mode 100644
index 67a51a82c..000000000
--- a/src/box/lua/lua_sql.c
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * 1. Redistributions of source code must retain the above
- *    copyright notice, this list of conditions and the
- *    following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above
- *    copyright notice, this list of conditions and the following
- *    disclaimer in the documentation and/or other materials
- *    provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
- * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include "lua.h"
-#include "lua/utils.h"
-
-#include "box/lua/call.h"
-#include "box/sql/sqlInt.h"
-#include "box/sql/vdbeInt.h"
-
-struct lua_sql_func_info {
-	int func_ref;
-};
-
-/**
- * This function is callback which is called by sql engine.
- *
- * Purpose of this function is to call lua func from sql.
- * Lua func should be previously registered in sql
- * (see lbox_sql_create_function).
- */
-static void
-lua_sql_call(sql_context *pCtx, int nVal, sql_value **apVal) {
-	lua_State *L = lua_newthread(tarantool_L);
-	int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX);
-	struct lua_sql_func_info *func_info = sql_user_data(pCtx);
-
-	lua_rawgeti(L, LUA_REGISTRYINDEX, func_info->func_ref);
-	for (int i = 0; i < nVal; i++) {
-		sql_value *param = apVal[i];
-		switch (sql_value_type(param)) {
-		case MP_INT:
-			luaL_pushint64(L, sql_value_int64(param));
-			break;
-		case MP_UINT:
-			luaL_pushuint64(L, sql_value_uint64(param));
-			break;
-		case MP_DOUBLE:
-			lua_pushnumber(L, sql_value_double(param));
-			break;
-		case MP_STR:
-			lua_pushstring(L, (const char *) sql_value_text(param));
-			break;
-		case MP_BIN:
-			lua_pushlstring(L, sql_value_blob(param),
-					(size_t) sql_value_bytes(param));
-			break;
-		case MP_NIL:
-			lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_nil_ref);
-			break;
-		case MP_BOOL:
-			lua_pushboolean(L, sql_value_boolean(param));
-			break;
-		default:
-			diag_set(ClientError, ER_SQL_EXECUTE, "Unsupported "\
-				 "type passed to Lua");
-			pCtx->is_aborted = true;
-			goto error;
-		}
-	}
-	if (lua_pcall(L, lua_gettop(L) - 1, 1, 0) != 0){
-		diag_set(ClientError, ER_SQL_EXECUTE, lua_tostring(L, -1));
-		pCtx->is_aborted = true;
-		goto error;
-	}
-	switch(lua_type(L, -1)) {
-	case LUA_TBOOLEAN:
-		sql_result_bool(pCtx, lua_toboolean(L, -1));
-		break;
-	case LUA_TNUMBER:
-		sql_result_double(pCtx, lua_tonumber(L, -1));
-		break;
-	case LUA_TSTRING:
-		sql_result_text(pCtx, lua_tostring(L, -1), -1,
-				    SQL_TRANSIENT);
-		break;
-	case LUA_TNIL:
-		sql_result_null(pCtx);
-		break;
-	default:
-		diag_set(ClientError, ER_SQL_EXECUTE, "Unsupported type "\
-			 "passed from Lua");
-		pCtx->is_aborted = true;
-		goto error;
-	}
-error:
-	luaL_unref(tarantool_L, LUA_REGISTRYINDEX, coro_ref);
-	return;
-}
-
-static void
-lua_sql_destroy(void *p)
-{
-	struct lua_sql_func_info *func_info = p;
-	luaL_unref(tarantool_L, LUA_REGISTRYINDEX, func_info->func_ref);
-	free(func_info);
-	return;
-}
-
-/**
- * A helper to register lua function in SQL during runtime.
- * It makes available queries like this: "SELECT lua_func(arg);"
- *
- * sql_create_function *p argument is used to store func ref
- * to lua function (it identifies actual lua func to call if there
- * are many of them). SQL function must have name and type of
- * returning value. Additionally, it can feature number of
- * arguments and deterministic flag.
- */
-int
-lbox_sql_create_function(struct lua_State *L)
-{
-	struct sql *db = sql_get();
-	if (db == NULL)
-		return luaL_error(L, "Please call box.cfg{} first");
-	int argc = lua_gettop(L);
-	/*
-	 * Three function prototypes are possible:
-	 * 1. sql_create_function("func_name", "type", func);
-	 * 2. sql_create_function("func_name", "type", func,
-	 *                        func_arg_num);
-	 * 3. sql_create_function("func_name", "type", func,
-	 *                        func_arg_num, is_deterministic);
-	 */
-	if (!(argc == 3 && lua_isstring(L, 1) && lua_isstring(L, 2) &&
-	    lua_isfunction(L, 3)) &&
-	    !(argc == 4 && lua_isstring(L, 1) && lua_isstring(L, 2) &&
-	      lua_isfunction(L, 3) && lua_isnumber(L, 4)) &&
-	    !(argc == 5 && lua_isstring(L, 1) && lua_isstring(L, 2) &&
-	      lua_isfunction(L, 3) && lua_isnumber(L, 4) &&
-	      lua_isboolean(L, 5)))
-		return luaL_error(L, "Invalid arguments");
-	enum field_type type;
-	const char *type_arg = lua_tostring(L, 2);
-	if (strcmp(type_arg, "INT") == 0 || strcmp(type_arg, "INTEGER") == 0)
-		type = FIELD_TYPE_INTEGER;
-	else if (strcmp(type_arg, "TEXT") == 0)
-		type = FIELD_TYPE_STRING;
-	else if (strcmp(type_arg, "NUMBER") == 0)
-		type = FIELD_TYPE_NUMBER;
-	else if (strcmp(type_arg, "VARBINARY") == 0)
-		type = FIELD_TYPE_SCALAR;
-	else if (strcmp(type_arg, "BOOL") == 0 ||
-		 strcmp(type_arg, "BOOLEAN") == 0)
-		type = FIELD_TYPE_BOOLEAN;
-	else
-		return luaL_error(L, "Unknown type");
-	/* -1 indicates any number of arguments. */
-	int func_arg_num = -1;
-	bool is_deterministic = false;
-	if (argc == 4) {
-		func_arg_num = lua_tointeger(L, 4);
-		lua_pop(L, 1);
-	} else if (argc == 5) {
-		is_deterministic = lua_toboolean(L, 5);
-		func_arg_num = lua_tointeger(L, 4);
-		lua_pop(L, 2);
-	}
-	size_t name_len;
-	const char *name = lua_tolstring(L, 1, &name_len);
-	char *normalized_name =
-		sql_normalized_name_region_new(&fiber()->gc, name, name_len);
-	if (normalized_name == NULL)
-		return luaT_error(L);
-	struct lua_sql_func_info *func_info =
-		(struct lua_sql_func_info *) malloc(sizeof(*func_info));
-	if (func_info == NULL)
-		return luaL_error(L, "out of memory");
-	func_info->func_ref = luaL_ref(L, LUA_REGISTRYINDEX);
-	int rc = sql_create_function_v2(db, normalized_name, type, func_arg_num,
-					   is_deterministic ? SQL_DETERMINISTIC : 0,
-					   func_info, lua_sql_call, NULL, NULL,
-					   lua_sql_destroy);
-	if (rc != 0)
-		return luaT_error(L);
-	return 0;
-}
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index bd52d12df..b9858c8d6 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -263,7 +263,7 @@ stat4Destructor(void *pOld)
  * return value is BLOB, but it is really just a pointer to the Stat4Accum
  * object.
  */
-static void
+MAYBE_UNUSED static void
 statInit(sql_context * context, int argc, sql_value ** argv)
 {
 	Stat4Accum *p;
@@ -535,7 +535,7 @@ samplePushPrevious(Stat4Accum * p, int iChng)
  *
  * The R parameter is only used for STAT4
  */
-static void
+MAYBE_UNUSED static void
 statPush(sql_context * context, int argc, sql_value ** argv)
 {
 	int i;
@@ -608,7 +608,7 @@ statPush(sql_context * context, int argc, sql_value ** argv)
  * The content to returned is determined by the parameter J
  * which is one of the STAT_GET_xxxx values defined above.
  */
-static void
+MAYBE_UNUSED static void
 statGet(sql_context * context, int argc, sql_value ** argv)
 {
 	Stat4Accum *p = (Stat4Accum *) sql_value_blob(argv[0]);
@@ -715,11 +715,10 @@ callStatGet(Vdbe * v, int regStat4, int iParam, int regOut)
 {
 	assert(regOut != regStat4 && regOut != regStat4 + 1);
 	sqlVdbeAddOp2(v, OP_Integer, iParam, regStat4 + 1);
-	struct FuncDef *func =
-		sqlFindFunction(sql_get(), "_sql_stat_get", 2, 0);
+	struct func *func = sql_func_by_signature("_sql_stat_get", 2);
 	assert(func != NULL);
 	sqlVdbeAddOp4(v, OP_BuiltinFunction0, 0, regStat4, regOut,
-		      (char *)func, P4_FUNCDEF);
+		      (char *)func, P4_FUNC);
 	sqlVdbeChangeP5(v, 2);
 }
 
@@ -855,11 +854,11 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
 		sqlVdbeAddOp2(v, OP_Count, idx_cursor, stat4_reg + 3);
 		sqlVdbeAddOp2(v, OP_Integer, part_count, stat4_reg + 1);
 		sqlVdbeAddOp2(v, OP_Integer, part_count, stat4_reg + 2);
-		struct FuncDef *init_func =
-			sqlFindFunction(sql_get(), "_sql_stat_init", 3, 0);
+		struct func *init_func =
+			sql_func_by_signature("_sql_stat_init", 3);
 		assert(init_func != NULL);
 		sqlVdbeAddOp4(v, OP_BuiltinFunction0, 0, stat4_reg + 1,
-			      stat4_reg, (char *)init_func, P4_FUNCDEF);
+			      stat4_reg, (char *)init_func, P4_FUNC);
 		sqlVdbeChangeP5(v, 3);
 		/*
 		 * Implementation of the following:
@@ -956,11 +955,11 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
 		sqlVdbeAddOp3(v, OP_MakeRecord, stat_key_reg,
 				  pk_part_count, key_reg);
 		assert(chng_reg == (stat4_reg + 1));
-		struct FuncDef *push_func =
-			sqlFindFunction(sql_get(), "_sql_stat_push", 3, 0);
+		struct func *push_func =
+			sql_func_by_signature("_sql_stat_push", 3);
 		assert(push_func != NULL);
 		sqlVdbeAddOp4(v, OP_BuiltinFunction0, 1, stat4_reg, tmp_reg,
-			      (char *)push_func, P4_FUNCDEF);
+			      (char *)push_func, P4_FUNC);
 		sqlVdbeChangeP5(v, 3);
 		sqlVdbeAddOp2(v, OP_Next, idx_cursor, next_row_addr);
 		/* Add the entry to the stat1 table. */
@@ -1746,14 +1745,3 @@ fail:
 	box_txn_rollback();
 	return -1;
 }
-
-void
-sql_register_analyze_builtins(void)
-{
-	static FuncDef funcs[] = {
-		FUNCTION(_sql_stat_get, 2, 0, 0, statGet, FIELD_TYPE_ANY),
-		FUNCTION(_sql_stat_push, 3, 0, 0, statPush, FIELD_TYPE_ANY),
-		FUNCTION(_sql_stat_init, 3, 0, 0, statInit, FIELD_TYPE_ANY),
-	};
-	sqlInsertBuiltinFuncs(funcs, nelem(funcs));
-}
diff --git a/src/box/sql/callback.c b/src/box/sql/callback.c
index 6c272de71..290363db6 100644
--- a/src/box/sql/callback.c
+++ b/src/box/sql/callback.c
@@ -56,207 +56,3 @@ sql_get_coll_seq(Parse *parser, const char *name, uint32_t *coll_id)
 		return p->coll;
 	}
 }
-
-/* During the search for the best function definition, this procedure
- * is called to test how well the function passed as the first argument
- * matches the request for a function with nArg arguments in a system
- * that uses encoding enc. The value returned indicates how well the
- * request is matched. A higher value indicates a better match.
- *
- * If nArg is -1 that means to only return a match (non-zero) if p->nArg
- * is also -1.  In other words, we are searching for a function that
- * takes a variable number of arguments.
- *
- * If nArg is -2 that means that we are searching for any function
- * regardless of the number of arguments it uses, so return a positive
- * match score for any
- *
- * The returned value is always between 0 and 6, as follows:
- *
- * 0: Not a match.
- * 1: UTF8/16 conversion required and function takes any number of arguments.
- * 2: UTF16 byte order change required and function takes any number of args.
- * 3: encoding matches and function takes any number of arguments
- * 4: UTF8/16 conversion required - argument count matches exactly
- * 5: UTF16 byte order conversion required - argument count matches exactly
- * 6: Perfect match:  encoding and argument count match exactly.
- *
- * If nArg==(-2) then any function with a non-null xSFunc is
- * a perfect match and any function with xSFunc NULL is
- * a non-match.
- */
-#define FUNC_PERFECT_MATCH 4	/* The score for a perfect match */
-static int
-matchQuality(FuncDef * p,	/* The function we are evaluating for match quality */
-	     int nArg		/* Desired number of arguments.  (-1)==any */
-    )
-{
-	int match;
-
-	/* nArg of -2 is a special case */
-	if (nArg == (-2))
-		return (p->xSFunc == 0) ? 0 : FUNC_PERFECT_MATCH;
-
-	/* Wrong number of arguments means "no match" */
-	if (p->nArg != nArg && p->nArg >= 0)
-		return 0;
-
-	/* Give a better score to a function with a specific number of arguments
-	 * than to function that accepts any number of arguments.
-	 */
-	if (p->nArg == nArg) {
-		match = 4;
-	} else {
-		match = 1;
-	}
-
-	return match;
-}
-
-/*
- * Search a FuncDefHash for a function with the given name.  Return
- * a pointer to the matching FuncDef if found, or 0 if there is no match.
- */
-static FuncDef *
-functionSearch(int h,		/* Hash of the name */
-	       const char *zFunc	/* Name of function */
-    )
-{
-	FuncDef *p;
-	for (p = sqlBuiltinFunctions.a[h]; p; p = p->u.pHash) {
-		if (sqlStrICmp(p->zName, zFunc) == 0) {
-			return p;
-		}
-	}
-	return 0;
-}
-
-/*
- * Insert a new FuncDef into a FuncDefHash hash table.
- */
-void
-sqlInsertBuiltinFuncs(FuncDef * aDef,	/* List of global functions to be inserted */
-			  int nDef	/* Length of the apDef[] list */
-    )
-{
-	int i;
-	for (i = 0; i < nDef; i++) {
-		FuncDef *pOther;
-		const char *zName = aDef[i].zName;
-		int nName = sqlStrlen30(zName);
-		int h =
-		    (sqlUpperToLower[(u8) zName[0]] +
-		     nName) % SQL_FUNC_HASH_SZ;
-		pOther = functionSearch(h, zName);
-		if (pOther) {
-			assert(pOther != &aDef[i] && pOther->pNext != &aDef[i]);
-			aDef[i].pNext = pOther->pNext;
-			pOther->pNext = &aDef[i];
-		} else {
-			aDef[i].pNext = 0;
-			aDef[i].u.pHash = sqlBuiltinFunctions.a[h];
-			sqlBuiltinFunctions.a[h] = &aDef[i];
-		}
-	}
-}
-
-/*
- * Locate a user function given a name, a number of arguments and a flag
- * indicating whether the function prefers UTF-16 over UTF-8.  Return a
- * pointer to the FuncDef structure that defines that function, or return
- * NULL if the function does not exist.
- *
- * If the createFlag argument is true, then a new (blank) FuncDef
- * structure is created and liked into the "db" structure if a
- * no matching function previously existed.
- *
- * If nArg is -2, then the first valid function found is returned.  A
- * function is valid if xSFunc is non-zero.  The nArg==(-2)
- * case is used to see if zName is a valid function name for some number
- * of arguments.  If nArg is -2, then createFlag must be 0.
- *
- * If createFlag is false, then a function with the required name and
- * number of arguments may be returned even if the eTextRep flag does not
- * match that requested.
- */
-FuncDef *
-sqlFindFunction(sql * db,	/* An open database */
-		    const char *zName,	/* Name of the function.  zero-terminated */
-		    int nArg,	/* Number of arguments.  -1 means any number */
-		    u8 createFlag	/* Create new entry if true and does not otherwise exist */
-    )
-{
-	FuncDef *p;		/* Iterator variable */
-	FuncDef *pBest = 0;	/* Best match found so far */
-	int bestScore = 0;	/* Score of best match */
-	int h;			/* Hash value */
-	int nName;		/* Length of the name */
-
-	assert(nArg >= (-2));
-	assert(nArg >= (-1) || createFlag == 0);
-	nName = sqlStrlen30(zName);
-
-	/* First search for a match amongst the application-defined functions.
-	 */
-	p = (FuncDef *) sqlHashFind(&db->aFunc, zName);
-	while (p) {
-		int score = matchQuality(p, nArg);
-		if (score > bestScore) {
-			pBest = p;
-			bestScore = score;
-		}
-		p = p->pNext;
-	}
-
-	/* If no match is found, search the built-in functions.
-	 *
-	 * Except, if createFlag is true, that means that we are trying to
-	 * install a new function.  Whatever FuncDef structure is returned it will
-	 * have fields overwritten with new information appropriate for the
-	 * new function.  But the FuncDefs for built-in functions are read-only.
-	 * So we must not search for built-ins when creating a new function.
-	 */
-	if (!createFlag && (pBest == NULL)) {
-		bestScore = 0;
-		h = (sqlUpperToLower[(u8) zName[0]] +
-		     nName) % SQL_FUNC_HASH_SZ;
-		p = functionSearch(h, zName);
-		while (p) {
-			int score = matchQuality(p, nArg);
-			if (score > bestScore) {
-				pBest = p;
-				bestScore = score;
-			}
-			p = p->pNext;
-		}
-	}
-
-	/* If the createFlag parameter is true and the search did not reveal an
-	 * exact match for the name, number of arguments and encoding, then add a
-	 * new entry to the hash table and return it.
-	 */
-	if (createFlag && bestScore < FUNC_PERFECT_MATCH &&
-	    (pBest =
-	     sqlDbMallocZero(db, sizeof(*pBest) + nName + 1)) != 0) {
-		FuncDef *pOther;
-		pBest->zName = (const char *)&pBest[1];
-		pBest->nArg = (u16) nArg;
-		pBest->funcFlags = 0;
-		memcpy((char *)&pBest[1], zName, nName + 1);
-		pOther =
-		    (FuncDef *) sqlHashInsert(&db->aFunc, pBest->zName,
-						  pBest);
-		if (pOther == pBest) {
-			sqlDbFree(db, pBest);
-			sqlOomFault(db);
-			return 0;
-		} else {
-			pBest->pNext = pOther;
-		}
-	}
-
-	if (pBest && (pBest->xSFunc || createFlag)) {
-		return pBest;
-	}
-	return 0;
-}
diff --git a/src/box/sql/date.c b/src/box/sql/date.c
index 2e2a71ad2..dffc23616 100644
--- a/src/box/sql/date.c
+++ b/src/box/sql/date.c
@@ -1290,31 +1290,3 @@ currentTimeFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 }
 #endif
-
-/*
- * This function registered all of the above C functions as SQL
- * functions.  This should be the only routine in this file with
- * external linkage.
- */
-void
-sqlRegisterDateTimeFunctions(void)
-{
-	static FuncDef aDateTimeFuncs[] = {
-#if 0
-		DFUNCTION(julianday, -1, 0, 0, juliandayFunc, FIELD_TYPE_NUMBER),
-		DFUNCTION(date, -1, 0, 0, dateFunc, FIELD_TYPE_STRING),
-		DFUNCTION(time, -1, 0, 0, timeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(datetime, -1, 0, 0, datetimeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(strftime, -1, 0, 0, strftimeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(current_time, 0, 0, 0, ctimeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc,
-			  FIELD_TYPE_STRING),
-		DFUNCTION(current_date, 0, 0, 0, cdateFunc, FIELD_TYPE_STRING),
-		STR_FUNCTION(current_time, 0, "%H:%M:%S", 0, currentTimeFunc),
-		STR_FUNCTION(current_date, 0, "%Y-%m-%d", 0, currentTimeFunc),
-		STR_FUNCTION(current_timestamp, 0, "%Y-%m-%d %H:%M:%S", 0,
-			     currentTimeFunc),
-#endif
-	};
-	sqlInsertBuiltinFuncs(aDateTimeFuncs, ArraySize(aDateTimeFuncs));
-}
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 1f9d91705..7a354a00a 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -328,12 +328,11 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
 		if (op == TK_FUNCTION) {
 			uint32_t arg_count = p->x.pList == NULL ? 0 :
 					     p->x.pList->nExpr;
-			struct FuncDef *func = sqlFindFunction(parse->db,
-							       p->u.zToken,
-							       arg_count, 0);
+			struct func *func =
+				sql_func_by_signature(p->u.zToken, arg_count);
 			if (func == NULL)
 				break;
-			if ((func->funcFlags & SQL_FUNC_DERIVEDCOLL) != 0) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL)) {
 				/*
 				 * Now we use quite straightforward
 				 * approach assuming that resulting
@@ -342,7 +341,7 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
 				 * built-in functions: trim, upper,
 				 * lower, replace, substr.
 				 */
-				assert(func->ret_type == FIELD_TYPE_STRING);
+				assert(func->def->returns == FIELD_TYPE_STRING);
 				p = p->x.pList->a->pExpr;
 				continue;
 			}
@@ -3975,11 +3974,9 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 	case TK_FUNCTION:{
 			ExprList *pFarg;	/* List of function arguments */
 			int nFarg;	/* Number of function arguments */
-			FuncDef *pDef;	/* The function definition object */
 			const char *zId;	/* The function name */
 			u32 constMask = 0;	/* Mask of function arguments that are constant */
 			int i;	/* Loop counter */
-			sql *db = pParse->db;	/* The database connection */
 			struct coll *coll = NULL;
 
 			assert(!ExprHasProperty(pExpr, EP_xIsSelect));
@@ -3991,8 +3988,8 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			nFarg = pFarg ? pFarg->nExpr : 0;
 			assert(!ExprHasProperty(pExpr, EP_IntValue));
 			zId = pExpr->u.zToken;
-			pDef = sqlFindFunction(db, zId, nFarg, 0);
-			if (pDef == 0 || pDef->xFinalize != 0) {
+			struct func *func = sql_func_by_signature(zId, nFarg);
+			if (func == NULL) {
 				diag_set(ClientError, ER_NO_SUCH_FUNCTION,
 					 zId);
 				pParse->is_aborted = true;
@@ -4002,7 +3999,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			 * IFNULL() functions.  This avoids unnecessary evaluation of
 			 * arguments past the first non-NULL argument.
 			 */
-			if (pDef->funcFlags & SQL_FUNC_COALESCE) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_COALESCE)) {
 				int endCoalesce = sqlVdbeMakeLabel(v);
 				assert(nFarg >= 2);
 				sqlExprCode(pParse, pFarg->a[0].pExpr,
@@ -4026,7 +4023,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			/* The UNLIKELY() function is a no-op.  The result is the value
 			 * of the first argument.
 			 */
-			if (pDef->funcFlags & SQL_FUNC_UNLIKELY) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_UNLIKELY)) {
 				assert(nFarg >= 1);
 				return sqlExprCodeTarget(pParse,
 							     pFarg->a[0].pExpr,
@@ -4049,7 +4046,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			 * is done using ANSI rules from
 			 * collations_check_compatibility().
 			 */
-			if ((pDef->funcFlags & SQL_FUNC_NEEDCOLL) != 0) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) {
 				struct coll *unused = NULL;
 				uint32_t curr_id = COLL_NONE;
 				bool is_curr_forced = false;
@@ -4096,9 +4093,8 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				 * or OPFLAG_TYPEOFARG respectively, to avoid unnecessary data
 				 * loading.
 				 */
-				if ((pDef->
-				     funcFlags & (SQL_FUNC_LENGTH |
-						  SQL_FUNC_TYPEOF)) != 0) {
+				if (sql_func_flag_is_set(func, SQL_FUNC_LENGTH |
+							       SQL_FUNC_TYPEOF)) {
 					u8 exprOp;
 					assert(nFarg == 1);
 					assert(pFarg->a[0].pExpr != 0);
@@ -4109,14 +4105,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 						       OPFLAG_LENGTHARG);
 						assert(SQL_FUNC_TYPEOF ==
 						       OPFLAG_TYPEOFARG);
-						testcase(pDef->
-							 funcFlags &
-							 OPFLAG_LENGTHARG);
-						pFarg->a[0].pExpr->op2 =
-						    pDef->
-						    funcFlags &
-						    (OPFLAG_LENGTHARG |
-						     OPFLAG_TYPEOFARG);
+						pFarg->a[0].pExpr->op2 = true;
 					}
 				}
 
@@ -4128,12 +4117,15 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			} else {
 				r1 = 0;
 			}
-			if (pDef->funcFlags & SQL_FUNC_NEEDCOLL) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) {
 				sqlVdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
 						  (char *)coll, P4_COLLSEQ);
 			}
-			sqlVdbeAddOp4(v, OP_BuiltinFunction0, constMask, r1,
-					  target, (char *)pDef, P4_FUNCDEF);
+			assert(func->def->language ==
+			       FUNC_LANGUAGE_SQL_BUILTIN);
+			int op = OP_BuiltinFunction0;
+			sqlVdbeAddOp4(v, op, constMask, r1, target,
+				      (char *)func, P4_FUNC);
 			sqlVdbeChangeP5(v, (u8) nFarg);
 			if (nFarg && constMask == 0) {
 				sqlReleaseTempRange(pParse, r1, nFarg);
@@ -5441,12 +5433,21 @@ analyzeAggregate(Walker * pWalker, Expr * pExpr)
 						pItem->iMem = ++pParse->nMem;
 						assert(!ExprHasProperty
 						       (pExpr, EP_IntValue));
-						pItem->pFunc = sqlFindFunction(
-							pParse->db,
-							pExpr->u.zToken,
-							pExpr->x.pList ?
-							pExpr->x.pList->nExpr : 0,
-							0);
+						const char *name =
+							pExpr->u.zToken;
+						uint32_t argc =
+							pExpr->x.pList != NULL ?
+							pExpr->x.pList->nExpr : 0;
+						pItem->func =
+							sql_func_by_signature(
+								name, argc);
+						assert(pItem->func != NULL);
+						assert(pItem->func->def->
+						       language ==
+						       FUNC_LANGUAGE_SQL_BUILTIN &&
+						       pItem->func->def->
+						       aggregate ==
+						       FUNC_AGGREGATE_GROUP);
 						if (pExpr->flags & EP_Distinct) {
 							pItem->iDistinct =
 								pParse->nTab++;
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 07c019db9..b583b60c9 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -45,6 +45,8 @@
 #include <unicode/uchar.h>
 #include <unicode/ucol.h>
 #include "box/coll_id_cache.h"
+#include "box/schema.h"
+#include "box/func.h"
 
 /*
  * Return the collating function associated with a function.
@@ -77,12 +79,13 @@ static void
 minmaxFunc(sql_context * context, int argc, sql_value ** argv)
 {
 	int i;
-	int mask;		/* 0 for min() or 0xffffffff for max() */
 	int iBest;
 	struct coll *pColl;
+	struct func_sql_builtin *func =
+		(struct func_sql_builtin *)context->func;
 
 	assert(argc > 1);
-	mask = sql_user_data(context) == 0 ? 0 : -1;
+	int mask = (func->flags & SQL_FUNC_MAX) != 0 ? -1 : 0;
 	pColl = sqlGetFuncCollSeq(context);
 	assert(mask == -1 || mask == 0);
 	iBest = 0;
@@ -498,6 +501,13 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 }
 
+static int
+sql_builtin_substr_check_param_count(struct func_sql_builtin *func, int argc)
+{
+	(void) func;
+	return argc != 2 && argc != 3 ? -1 : 0;
+}
+
 /*
  * Implementation of the round() function
  */
@@ -538,6 +548,13 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 	sql_result_double(context, r);
 }
 
+static int
+sql_builtin_round_check_param_count(struct func_sql_builtin *func, int argc)
+{
+	(void) func;
+	return argc != 1 && argc != 2 ? -1 : 0;
+}
+
 /*
  * Allocate nByte bytes of space using sqlMalloc(). If the
  * allocation fails, return NULL. If nByte is larger than the
@@ -968,6 +985,13 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 	sql_result_bool(context, res == MATCH);
 }
 
+static int
+sql_builtin_like_check_param_count(struct func_sql_builtin *func, int argc)
+{
+	(void) func;
+	return argc != 2 && argc != 3 ? -1 : 0;
+}
+
 /*
  * Implementation of the NULLIF(x,y) function.  The result is the first
  * argument if the arguments are different.  The result is NULL if the
@@ -1514,6 +1538,13 @@ trim_func(struct sql_context *context, int argc, sql_value **argv)
 	}
 }
 
+static int
+sql_builtin_trim_check_param_count(struct func_sql_builtin *func, int argc)
+{
+	(void) func;
+	return argc != 1 && argc != 2 && argc != 3 ? -1 : 0;
+}
+
 /*
  * Compute the soundex encoding of a word.
  *
@@ -1703,6 +1734,13 @@ countFinalize(sql_context * context)
 	sql_result_uint(context, p ? p->n : 0);
 }
 
+static int
+sql_builtin_count_check_param_count(struct func_sql_builtin *func, int argc)
+{
+	(void) func;
+	return argc != 0 && argc != 1 ? -1 : 0;
+}
+
 /*
  * Routines to implement min() and max() aggregate functions.
  */
@@ -1713,6 +1751,8 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 	Mem *pBest;
 	UNUSED_PARAMETER(NotUsed);
 
+	struct func_sql_builtin *func =
+		(struct func_sql_builtin *)context->func;
 	pBest = (Mem *) sql_aggregate_context(context, sizeof(*pBest));
 	if (!pBest)
 		return;
@@ -1721,20 +1761,17 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 		if (pBest->flags)
 			sqlSkipAccumulatorLoad(context);
 	} else if (pBest->flags) {
-		int max;
 		int cmp;
 		struct coll *pColl = sqlGetFuncCollSeq(context);
-		/* This step function is used for both the min() and max() aggregates,
-		 * the only difference between the two being that the sense of the
-		 * comparison is inverted. For the max() aggregate, the
-		 * sql_user_data() function returns (void *)-1. For min() it
-		 * returns (void *)db, where db is the sql* database pointer.
-		 * Therefore the next statement sets variable 'max' to 1 for the max()
-		 * aggregate, or 0 for min().
+		/*
+		 * This step function is used for both the min()
+		 * and max() aggregates, the only difference
+		 * between the two being that the sense of the
+		 * comparison is inverted.
 		 */
-		max = sql_user_data(context) != 0;
+		bool is_max = (func->flags & SQL_FUNC_MAX) != 0;
 		cmp = sqlMemCompare(pBest, pArg, pColl);
-		if ((max && cmp < 0) || (!max && cmp > 0)) {
+		if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
 			sqlVdbeMemCopy(pBest, pArg);
 		} else {
 			sqlSkipAccumulatorLoad(context);
@@ -1816,128 +1853,503 @@ groupConcatFinalize(sql_context * context)
 	}
 }
 
+static int
+sql_builtin_group_concat_check_param_count(struct func_sql_builtin *func,
+					   int argc)
+{
+	(void) func;
+	return argc != 1 && argc != 2 ? -1 : 0;
+}
+
 int
-sql_is_like_func(struct sql *db, struct Expr *expr)
+sql_is_like_func(struct Expr *expr)
 {
 	if (expr->op != TK_FUNCTION || !expr->x.pList ||
 	    expr->x.pList->nExpr != 2)
 		return 0;
 	assert(!ExprHasProperty(expr, EP_xIsSelect));
-	struct FuncDef *func = sqlFindFunction(db, expr->u.zToken, 2, 0);
-	assert(func != NULL);
-	if ((func->funcFlags & SQL_FUNC_LIKE) == 0)
+	struct func *func = sql_func_by_signature(expr->u.zToken, 2);
+	if (func == NULL || !sql_func_flag_is_set(func, SQL_FUNC_LIKE))
 		return 0;
 	return 1;
 }
 
-/*
- * All of the FuncDef structures in the aBuiltinFunc[] array above
- * to the global function hash table.  This occurs at start-time (as
- * a consequence of calling sql_initialize()).
- *
- * After this routine runs
+struct func *
+sql_func_by_signature(const char *name, int argc)
+{
+	struct func *base = func_by_name(name, strlen(name));
+	if (base == NULL || !base->def->exports.sql)
+		return NULL;
+	if (base->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
+		struct func_sql_builtin *func =
+			(struct func_sql_builtin *) base;
+		if (func->check_param_count(func, argc) != 0)
+			return NULL;
+	} else {
+		if (base->def->param_count != -1 &&
+		    base->def->param_count != argc)
+			return NULL;
+	}
+	return base;
+}
+
+
+static int
+sql_builtin_call_stub(struct func *func, struct port *args, struct port *ret)
+{
+	(void) func; (void) args; (void) ret;
+	diag_set(ClientError, ER_UNSUPPORTED,
+		 "sql builtin function", "Lua frontend");
+	return -1;
+}
+
+static void
+sql_builtin_stub(sql_context *ctx, int argc, sql_value **argv)
+{
+	(void) argc; (void) argv;
+	diag_set(ClientError, ER_SQL_EXECUTE,
+		 tt_sprintf("function '%s' is not implemented",
+			    ctx->func->def->name));
+	ctx->is_aborted = true;
+}
+
+static int
+sql_builtin_default_check_param_count(struct func_sql_builtin *func, int argc)
+{
+	return (func->base.def->param_count != -1 &&
+		argc != func->base.def->param_count) ? -1 : 0;
+}
+
+static int
+sql_builtin_coalesce_check_param_count(struct func_sql_builtin *func, int argc)
+{
+	(void) func;
+	return argc == 0 || argc == 1 ? -1 : 0;
+}
+
+/**
+ * A sequence of SQL builtins definitions in
+ * lexicographic order.
  */
-void
-sqlRegisterBuiltinFunctions(void)
+static struct {
+	const char *name;
+	int param_count;
+	enum field_type returns;
+	enum func_aggregate aggregate;
+	bool is_deterministic;
+	uint16_t flags;
+	void (*call)(sql_context *ctx, int argc, sql_value **argv);
+	int (*check_param_count)(struct func_sql_builtin *func, int argc);
+	void (*finalize)(sql_context *ctx);
+} sql_builtins[] = {
+	{.name = "ABS",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = absFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "AVG",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .is_deterministic = false,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .flags = 0,
+	 .call = sum_step,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = avgFinalize},
+	{.name = "CHAR",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .is_deterministic = true,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .flags = 0,
+	 .call = charFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "CHARACTER_LENGTH",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = lengthFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "CHAR_LENGTH",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = lengthFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "COALESCE",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_COALESCE,
+	 .call = sql_builtin_stub,
+	 .check_param_count = sql_builtin_coalesce_check_param_count,
+	 .finalize = NULL},
+	{.name = "COUNT",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = countStep,
+	 .check_param_count = sql_builtin_count_check_param_count,
+	 .finalize = countFinalize},
+	{.name = "GREATEST",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MAX,
+	 .call = minmaxFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "GROUP_CONCAT",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = groupConcatStep,
+	 .check_param_count = sql_builtin_group_concat_check_param_count,
+	 .finalize = groupConcatFinalize},
+	{.name = "HEX",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = hexFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "IFNULL",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_COALESCE,
+	 .call = sql_builtin_stub,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "LEAST",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MIN,
+	 .call = minmaxFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "LENGTH",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_LENGTH,
+	 .call = lengthFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "LIKE",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_LIKE,
+	 .call = likeFunc,
+	 .check_param_count = sql_builtin_like_check_param_count,
+	 .finalize = NULL},
+	{.name = "LIKELIHOOD",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_BOOLEAN,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_UNLIKELY,
+	 .call = sql_builtin_stub,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "LIKELY",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_BOOLEAN,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_UNLIKELY,
+	 .call = sql_builtin_stub,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "LOWER",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
+	 .call = LowerICUFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "MAX",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MAX,
+	 .call = minmaxStep,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = minMaxFinalize},
+	{.name = "MIN",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MIN,
+	 .call = minmaxStep,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = minMaxFinalize},
+	{.name = "NULLIF",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL,
+	 .call = nullifFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "POSITION",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL,
+	 .call = position_func,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "PRINTF",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = printfFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "QUOTE",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = quoteFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "RANDOM",
+	 .param_count = 0,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = randomFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "RANDOMBLOB",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_VARBINARY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = randomBlob,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "REPLACE",
+	 .param_count = 3,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL,
+	 .call = replaceFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "ROUND",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = roundFunc,
+	 .check_param_count = sql_builtin_round_check_param_count,
+	 .finalize = NULL},
+	{.name = "ROW_COUNT",
+	 .param_count = 0,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = sql_row_count,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "SOUNDEX",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = soundexFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "SUBSTR",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL,
+	 .call = substrFunc,
+	 .check_param_count = sql_builtin_substr_check_param_count,
+	 .finalize = NULL},
+	{.name = "SUM",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = sum_step,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = sumFinalize},
+	{.name = "TOTAL",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = sum_step,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = totalFinalize},
+	{.name = "TRIM",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL,
+	 .call = trim_func,
+	 .check_param_count = sql_builtin_trim_check_param_count,
+	 .finalize = NULL},
+	{.name = "TYPEOF",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_TYPEOF,
+	 .call = typeofFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "UNICODE",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = unicodeFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "UNLIKELY",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_BOOLEAN,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_UNLIKELY,
+	 .call = sql_builtin_stub,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "UPPER",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
+	 .call = UpperICUFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "VERSION",
+	 .param_count = 0,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = sql_func_version,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+	{.name = "ZEROBLOB",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_VARBINARY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = zeroblobFunc,
+	 .check_param_count = sql_builtin_default_check_param_count,
+	 .finalize = NULL},
+};
+
+
+static struct func_vtab func_sql_builtin_vtab;
+
+struct func *
+func_sql_builtin_new(struct func_def *def)
 {
-	/*
-	 * The following array holds FuncDef structures for all of the functions
-	 * defined in this file.
-	 *
-	 * The array cannot be constant since changes are made to the
-	 * FuncDef.pHash elements at start-time.  The elements of this array
-	 * are read-only after initialization is complete.
-	 *
-	 * For peak efficiency, put the most frequently used function last.
-	 */
-	static FuncDef aBuiltinFunc[] = {
-		FUNCTION(soundex, 1, 0, 0, soundexFunc, FIELD_TYPE_STRING),
-		FUNCTION2(unlikely, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION2(likelihood, 2, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION2(likely, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION_COLL(trim, 1, 3, 0, trim_func),
-		FUNCTION_COLL(trim, 2, 3, 0, trim_func),
-		FUNCTION_COLL(trim, 3, 3, 0, trim_func),
-		FUNCTION(least, -1, 0, 1, minmaxFunc, FIELD_TYPE_SCALAR),
-		AGGREGATE2(min, 1, 0, 1, minmaxStep, minMaxFinalize,
-			   SQL_FUNC_MINMAX, FIELD_TYPE_SCALAR),
-		FUNCTION(greatest, -1, 1, 1, minmaxFunc, FIELD_TYPE_SCALAR),
-		AGGREGATE2(max, 1, 1, 1, minmaxStep, minMaxFinalize,
-			   SQL_FUNC_MINMAX, FIELD_TYPE_SCALAR),
-		FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQL_FUNC_TYPEOF,
-			  FIELD_TYPE_STRING),
-		FUNCTION2(length, 1, 0, 0, lengthFunc, SQL_FUNC_LENGTH,
-			  FIELD_TYPE_INTEGER),
-		FUNCTION(char_length, 1, 0, 0, lengthFunc, FIELD_TYPE_INTEGER),
-		FUNCTION(character_length, 1, 0, 0, lengthFunc,
-			 FIELD_TYPE_INTEGER),
-		FUNCTION(position, 2, 0, 1, position_func, FIELD_TYPE_INTEGER),
-		FUNCTION(printf, -1, 0, 0, printfFunc, FIELD_TYPE_STRING),
-		FUNCTION(unicode, 1, 0, 0, unicodeFunc, FIELD_TYPE_STRING),
-		FUNCTION(char, -1, 0, 0, charFunc, FIELD_TYPE_STRING),
-		FUNCTION(abs, 1, 0, 0, absFunc, FIELD_TYPE_NUMBER),
-		FUNCTION(round, 1, 0, 0, roundFunc, FIELD_TYPE_INTEGER),
-		FUNCTION(round, 2, 0, 0, roundFunc, FIELD_TYPE_INTEGER),
-		FUNCTION_COLL(upper, 1, 0, 1, UpperICUFunc),
-		FUNCTION_COLL(lower, 1, 0, 1, LowerICUFunc),
-		FUNCTION(hex, 1, 0, 0, hexFunc, FIELD_TYPE_STRING),
-		FUNCTION2(ifnull, 2, 0, 0, noopFunc, SQL_FUNC_COALESCE,
-			  FIELD_TYPE_INTEGER),
-		VFUNCTION(random, 0, 0, 0, randomFunc, FIELD_TYPE_INTEGER),
-		VFUNCTION(randomblob, 1, 0, 0, randomBlob, FIELD_TYPE_VARBINARY),
-		FUNCTION(nullif, 2, 0, 1, nullifFunc, FIELD_TYPE_SCALAR),
-		FUNCTION(version, 0, 0, 0, sql_func_version, FIELD_TYPE_STRING),
-		FUNCTION(quote, 1, 0, 0, quoteFunc, FIELD_TYPE_STRING),
-		VFUNCTION(row_count, 0, 0, 0, sql_row_count, FIELD_TYPE_INTEGER),
-		FUNCTION_COLL(replace, 3, 0, 0, replaceFunc),
-		FUNCTION(zeroblob, 1, 0, 0, zeroblobFunc, FIELD_TYPE_VARBINARY),
-		FUNCTION_COLL(substr, 2, 0, 0, substrFunc),
-		FUNCTION_COLL(substr, 3, 0, 0, substrFunc),
-		AGGREGATE(sum, 1, 0, 0, sum_step, sumFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE(total, 1, 0, 0, sum_step, totalFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE(avg, 1, 0, 0, sum_step, avgFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE2(count, 0, 0, 0, countStep, countFinalize,
-			  SQL_FUNC_COUNT, FIELD_TYPE_INTEGER),
-		AGGREGATE2(count, 1, 0, 0, countStep, countFinalize,
-			   SQL_FUNC_COUNT, FIELD_TYPE_INTEGER),
-		AGGREGATE(group_concat, 1, 0, 0, groupConcatStep,
-			  groupConcatFinalize, FIELD_TYPE_STRING),
-		AGGREGATE(group_concat, 2, 0, 0, groupConcatStep,
-			  groupConcatFinalize, FIELD_TYPE_STRING),
-
-		LIKEFUNC(like, 2, 1, SQL_FUNC_LIKE,
-			 FIELD_TYPE_INTEGER),
-		LIKEFUNC(like, 3, 1, SQL_FUNC_LIKE,
-			 FIELD_TYPE_INTEGER),
-		FUNCTION(coalesce, 1, 0, 0, 0, FIELD_TYPE_SCALAR),
-		FUNCTION(coalesce, 0, 0, 0, 0, FIELD_TYPE_SCALAR),
-		FUNCTION2(coalesce, -1, 0, 0, noopFunc, SQL_FUNC_COALESCE,
-			  FIELD_TYPE_SCALAR),
-	};
-	sql_register_analyze_builtins();
-	sqlRegisterDateTimeFunctions();
-	sqlInsertBuiltinFuncs(aBuiltinFunc, ArraySize(aBuiltinFunc));
-
-#if 0				/* Enable to print out how the built-in functions are hashed */
-	{
-		int i;
-		FuncDef *p;
-		for (i = 0; i < SQL_FUNC_HASH_SZ; i++) {
-			printf("FUNC-HASH %02d:", i);
-			for (p = sqlBuiltinFunctions.a[i]; p;
-			     p = p->u.pHash) {
-				int n = sqlStrlen30(p->zName);
-				int h = p->zName[0] + n;
-				printf(" %s(%d)", p->zName, h);
-			}
-			printf("\n");
+	assert(def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	struct func_sql_builtin *func =
+		(struct func_sql_builtin *) calloc(1, sizeof(*func));
+	if (func == NULL) {
+		diag_set(OutOfMemory, sizeof(*func), "malloc", "func");
+		return NULL;
+	}
+	func->base.def = def;
+	func->base.vtab = &func_sql_builtin_vtab;
+	func->call = sql_builtin_stub;
+	/** Binary search for corresponding builtin entry. */
+	int idx = -1, left = 0, right = nelem(sql_builtins) - 1;
+	while (left <= right) {
+		uint32_t mid = (left + right) / 2;
+		int rc = strcmp(def->name, sql_builtins[mid].name);
+		if (rc == 0) {
+			idx = mid;
+			break;
 		}
+		if (rc < 0)
+			right = mid - 1;
+		else
+			left = mid + 1;
 	}
-#endif
+	/* Some builtins are not implemented yet. */
+	if (idx == -1)
+		goto end;
+	func->flags = sql_builtins[idx].flags;
+	func->call = sql_builtins[idx].call;
+	func->check_param_count = sql_builtins[idx].check_param_count;
+	func->finalize = sql_builtins[idx].finalize;
+	def->param_count = sql_builtins[idx].param_count;
+	def->is_deterministic = sql_builtins[idx].is_deterministic;
+	def->returns = sql_builtins[idx].returns;
+	def->aggregate = sql_builtins[idx].aggregate;
+	def->exports.sql = true;
+end:
+	return &func->base;
 }
+
+static void
+func_sql_builtin_destroy(struct func *func)
+{
+	assert(func->vtab == &func_sql_builtin_vtab);
+	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	free(func);
+}
+
+static struct func_vtab func_sql_builtin_vtab = {
+	.call = sql_builtin_call_stub,
+	.destroy = func_sql_builtin_destroy,
+};
diff --git a/src/box/sql/global.c b/src/box/sql/global.c
index 6cadef809..c25b83de1 100644
--- a/src/box/sql/global.c
+++ b/src/box/sql/global.c
@@ -162,13 +162,6 @@ SQL_WSD struct sqlConfig sqlConfig = {
 	0x7ffffffe		/* iOnceResetThreshold */
 };
 
-/*
- * Hash table for global functions - functions common to all
- * database connections.  After initialization, this table is
- * read-only.
- */
-FuncDefHash sqlBuiltinFunctions;
-
 /*
  * The value of the "pending" byte must be 0x40000000 (1 byte past the
  * 1-gibabyte boundary) in a compatible database.  sql never uses
diff --git a/src/box/sql/main.c b/src/box/sql/main.c
index 748dc1b8e..0b20f2132 100644
--- a/src/box/sql/main.c
+++ b/src/box/sql/main.c
@@ -131,9 +131,6 @@ sql_initialize(void)
 	if (sqlGlobalConfig.isInit == 0
 	    && sqlGlobalConfig.inProgress == 0) {
 		sqlGlobalConfig.inProgress = 1;
-		memset(&sqlBuiltinFunctions, 0,
-		       sizeof(sqlBuiltinFunctions));
-		sqlRegisterBuiltinFunctions();
 		sql_os_init();
 		sqlGlobalConfig.isInit = 1;
 		sqlGlobalConfig.inProgress = 0;
@@ -242,25 +239,6 @@ sqlCloseSavepoints(Vdbe * pVdbe)
 	pVdbe->anonymous_savepoint = NULL;
 }
 
-/*
- * Invoke the destructor function associated with FuncDef p, if any. Except,
- * if this is not the last copy of the function, do not invoke it. Multiple
- * copies of a single function are created when create_function() is called
- * with SQL_ANY as the encoding.
- */
-static void
-functionDestroy(sql * db, FuncDef * p)
-{
-	FuncDestructor *pDestructor = p->u.pDestructor;
-	if (pDestructor) {
-		pDestructor->nRef--;
-		if (pDestructor->nRef == 0) {
-			pDestructor->xDestroy(pDestructor->pUserData);
-			sqlDbFree(db, pDestructor);
-		}
-	}
-}
-
 /*
  * Rollback all database files.  If tripCode is not 0, then
  * any write cursors are invalidated ("tripped" - as in "tripping a circuit
@@ -279,121 +257,6 @@ sqlRollbackAll(Vdbe * pVdbe)
 	}
 }
 
-/*
- * This function is exactly the same as sql_create_function(), except
- * that it is designed to be called by internal code. The difference is
- * that if a malloc() fails in sql_create_function(), an error code
- * is returned and the mallocFailed flag cleared.
- */
-int
-sqlCreateFunc(sql * db,
-		  const char *zFunctionName,
-		  enum field_type type,
-		  int nArg,
-		  int flags,
-		  void *pUserData,
-		  void (*xSFunc) (sql_context *, int, sql_value **),
-		  void (*xStep) (sql_context *, int, sql_value **),
-		  void (*xFinal) (sql_context *),
-		  FuncDestructor * pDestructor)
-{
-	FuncDef *p;
-	int extraFlags;
-
-	if (zFunctionName == 0 ||
-	    (xSFunc && (xFinal || xStep)) ||
-	    (!xSFunc && (xFinal && !xStep)) ||
-	    (!xSFunc && (!xFinal && xStep)) ||
-	    (nArg < -1 || nArg > SQL_MAX_FUNCTION_ARG) ||
-	    (255 < (sqlStrlen30(zFunctionName)))) {
-		diag_set(ClientError, ER_CREATE_FUNCTION, zFunctionName,
-			 "wrong function definition");
-		return -1;
-	}
-
-	assert(SQL_FUNC_CONSTANT == SQL_DETERMINISTIC);
-	extraFlags = flags & SQL_DETERMINISTIC;
-
-
-	/* Check if an existing function is being overridden or deleted. If so,
-	 * and there are active VMs, then return an error. If a function
-	 * is being overridden/deleted but there are no active VMs, allow the
-	 * operation to continue but invalidate all precompiled statements.
-	 */
-	p = sqlFindFunction(db, zFunctionName, nArg, 0);
-	if (p && p->nArg == nArg) {
-		if (db->nVdbeActive) {
-			diag_set(ClientError, ER_CREATE_FUNCTION, zFunctionName,
-				 "unable to create function due to active "\
-				 "statements");
-			return -1;
-		} else {
-			sqlExpirePreparedStatements(db);
-		}
-	}
-
-	p = sqlFindFunction(db, zFunctionName, nArg, 1);
-	assert(p || db->mallocFailed);
-	if (p == NULL)
-		return -1;
-
-	/* If an older version of the function with a configured destructor is
-	 * being replaced invoke the destructor function here.
-	 */
-	functionDestroy(db, p);
-
-	if (pDestructor) {
-		pDestructor->nRef++;
-	}
-	p->u.pDestructor = pDestructor;
-	p->funcFlags = extraFlags;
-	testcase(p->funcFlags & SQL_DETERMINISTIC);
-	p->xSFunc = xSFunc ? xSFunc : xStep;
-	p->xFinalize = xFinal;
-	p->pUserData = pUserData;
-	p->nArg = (u16) nArg;
-	p->ret_type = type;
-	return 0;
-}
-
-int
-sql_create_function_v2(sql * db,
-			   const char *zFunc,
-			   enum field_type type,
-			   int nArg,
-			   int flags,
-			   void *p,
-			   void (*xSFunc) (sql_context *, int,
-					   sql_value **),
-			   void (*xStep) (sql_context *, int,
-					  sql_value **),
-			   void (*xFinal) (sql_context *),
-			   void (*xDestroy) (void *))
-{
-	FuncDestructor *pArg = 0;
-
-	if (xDestroy) {
-		pArg =
-		    (FuncDestructor *) sqlDbMallocZero(db,
-							   sizeof
-							   (FuncDestructor));
-		if (!pArg) {
-			xDestroy(p);
-			return -1;
-		}
-		pArg->xDestroy = xDestroy;
-		pArg->pUserData = p;
-	}
-	int rc = sqlCreateFunc(db, zFunc, type, nArg, flags, p, xSFunc, xStep,
-			       xFinal, pArg);
-	if (pArg && pArg->nRef == 0) {
-		assert(rc != 0);
-		xDestroy(p);
-		sqlDbFree(db, pArg);
-	}
-	return rc;
-}
-
 /*
  * This array defines hard upper bounds on limit values.  The
  * initializer must be kept in sync with the SQL_LIMIT_*
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 207f57ba6..682bb9c8e 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -38,6 +38,9 @@
 #include "sqlInt.h"
 #include <stdlib.h>
 #include <string.h>
+#include "box/func.h"
+#include "box/func_def.h"
+#include "box/schema.h"
 
 /*
  * Walk the expression tree pExpr and increase the aggregate function
@@ -596,27 +599,28 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 			int is_agg = 0;	/* True if is an aggregate function */
 			int nId;	/* Number of characters in function name */
 			const char *zId;	/* The function name. */
-			FuncDef *pDef;	/* Information about the function */
 
 			assert(!ExprHasProperty(pExpr, EP_xIsSelect));
 			zId = pExpr->u.zToken;
 			nId = sqlStrlen30(zId);
-			pDef = sqlFindFunction(pParse->db, zId, n, 0);
-			if (pDef == 0) {
-				pDef =
-				    sqlFindFunction(pParse->db, zId, -2,0);
-				if (pDef == 0) {
+			struct func *func = sql_func_by_signature(zId, n);
+			if (func == NULL) {
+				func = func_by_name(zId, nId);
+				if (func == NULL || !func->def->exports.sql)
 					no_such_func = 1;
-				} else {
+				else
 					wrong_num_args = 1;
-				}
 			} else {
-				is_agg = pDef->xFinalize != 0;
-				pExpr->type = pDef->ret_type;
+				is_agg = func->def->language ==
+					 FUNC_LANGUAGE_SQL_BUILTIN &&
+					 func->def->aggregate ==
+					 FUNC_AGGREGATE_GROUP;
+				pExpr->type = func->def->returns;
 				const char *err =
 					"second argument to likelihood() must "\
 					"be a constant between 0.0 and 1.0";
-				if (pDef->funcFlags & SQL_FUNC_UNLIKELY) {
+				if (sql_func_flag_is_set(func,
+							 SQL_FUNC_UNLIKELY)) {
 					ExprSetProperty(pExpr,
 							EP_Unlikely | EP_Skip);
 					if (n == 2) {
@@ -643,19 +647,17 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 						 */
 						/* TUNING: unlikely() probability is 0.0625.  likely() is 0.9375 */
 						pExpr->iTable =
-						    pDef->zName[0] ==
-						    'u' ? 8388608 : 125829120;
+						    func->def->name[0] == 'u' ?
+						    8388608 : 125829120;
 					}
 				}
-				if ((pDef->funcFlags & SQL_FUNC_CONSTANT) != 0) {
+				if (func->def->is_deterministic) {
 					/* For the purposes of the EP_ConstFunc flag, date and time
 					 * functions and other functions that change slowly are considered
 					 * constant because they are constant for the duration of one query
 					 */
 					ExprSetProperty(pExpr, EP_ConstFunc);
-				}
-				if ((pDef->funcFlags & SQL_FUNC_CONSTANT) ==
-				    0) {
+				} else {
 					/* Date/time functions that use 'now', and other functions
 					 * that might change over time cannot be used
 					 * in an index.
@@ -698,18 +700,13 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 					pExpr->op2++;
 					pNC2 = pNC2->pNext;
 				}
-				assert(pDef != 0);
+				assert(func != NULL);
 				if (pNC2) {
-					assert(SQL_FUNC_MINMAX ==
-					       NC_MinMaxAgg);
-					testcase((pDef->
-						  funcFlags &
-						  SQL_FUNC_MINMAX) != 0);
-					pNC2->ncFlags |=
-					    NC_HasAgg | (pDef->
-							 funcFlags &
-							 SQL_FUNC_MINMAX);
-
+					pNC2->ncFlags |= NC_HasAgg;
+					if (sql_func_flag_is_set(func,
+							         SQL_FUNC_MIN |
+								 SQL_FUNC_MAX))
+						pNC2->ncFlags |= NC_MinMaxAgg;
 				}
 				pNC->ncFlags |= NC_AllowAgg;
 			}
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index ddb5de87e..b2ce868d6 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4381,7 +4381,9 @@ is_simple_count(struct Select *select, struct AggInfo *agg_info)
 		return NULL;
 	if (NEVER(agg_info->nFunc == 0))
 		return NULL;
-	if ((agg_info->aFunc->pFunc->funcFlags & SQL_FUNC_COUNT) == 0 ||
+	assert(agg_info->aFunc->func->def->language ==
+	       FUNC_LANGUAGE_SQL_BUILTIN);
+	if (!sql_func_flag_is_set(agg_info->aFunc->func, SQL_FUNC_COUNT) == 0 ||
 	    (agg_info->aFunc->pExpr->x.pList != NULL &&
 	     agg_info->aFunc->pExpr->x.pList->nExpr > 0))
 		return NULL;
@@ -5278,7 +5280,7 @@ finalizeAggFunctions(Parse * pParse, AggInfo * pAggInfo)
 		assert(!ExprHasProperty(pF->pExpr, EP_xIsSelect));
 		sqlVdbeAddOp2(v, OP_AggFinal, pF->iMem,
 				  pList ? pList->nExpr : 0);
-		sqlVdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
+		sqlVdbeAppendP4(v, pF->func, P4_FUNC);
 	}
 }
 
@@ -5319,7 +5321,7 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 			vdbe_insert_distinct(pParse, pF->iDistinct, pF->reg_eph,
 					     addrNext, 1, regAgg);
 		}
-		if (pF->pFunc->funcFlags & SQL_FUNC_NEEDCOLL) {
+		if (sql_func_flag_is_set(pF->func, SQL_FUNC_NEEDCOLL)) {
 			struct coll *coll = NULL;
 			struct ExprList_item *pItem;
 			int j;
@@ -5338,7 +5340,7 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 					  (char *)coll, P4_COLLSEQ);
 		}
 		sqlVdbeAddOp3(v, OP_AggStep0, 0, regAgg, pF->iMem);
-		sqlVdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
+		sqlVdbeAppendP4(v, pF->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/vdbe.c b/src/box/sql/vdbe.c
index 28552f64a..c5a4a0da9 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -41,6 +41,8 @@
  */
 #include "box/box.h"
 #include "box/error.h"
+#include "box/func.h"
+#include "box/func_def.h"
 #include "box/fk_constraint.h"
 #include "box/txn.h"
 #include "box/tuple.h"
@@ -1704,7 +1706,7 @@ case OP_BuiltinFunction0: {
 	int n;
 	sql_context *pCtx;
 
-	assert(pOp->p4type==P4_FUNCDEF);
+	assert(pOp->p4type == P4_FUNC);
 	n = pOp->p5;
 	assert(pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor));
 	assert(n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1));
@@ -1712,7 +1714,7 @@ case OP_BuiltinFunction0: {
 	pCtx = sqlDbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sql_value*));
 	if (pCtx==0) goto no_mem;
 	pCtx->pOut = 0;
-	pCtx->pFunc = pOp->p4.pFunc;
+	pCtx->func = pOp->p4.func;
 	pCtx->iOp = (int)(pOp - aOp);
 	pCtx->pVdbe = p;
 	pCtx->argc = n;
@@ -1747,7 +1749,9 @@ case OP_BuiltinFunction: {
 	}
 #endif
 	pCtx->is_aborted = false;
-	(*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */
+	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
+	func->call(pCtx, pCtx->argc, pCtx->argv);
 
 	/* If the function returned an error, throw an exception */
 	if (pCtx->is_aborted)
@@ -5005,7 +5009,7 @@ case OP_AggStep0: {
 	int n;
 	sql_context *pCtx;
 
-	assert(pOp->p4type==P4_FUNCDEF);
+	assert(pOp->p4type == P4_FUNC);
 	n = pOp->p5;
 	assert(pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor));
 	assert(n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1));
@@ -5013,7 +5017,7 @@ case OP_AggStep0: {
 	pCtx = sqlDbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sql_value*));
 	if (pCtx==0) goto no_mem;
 	pCtx->pMem = 0;
-	pCtx->pFunc = pOp->p4.pFunc;
+	pCtx->func = pOp->p4.func;
 	pCtx->iOp = (int)(pOp - aOp);
 	pCtx->pVdbe = p;
 	pCtx->argc = n;
@@ -5055,7 +5059,9 @@ case OP_AggStep: {
 	pCtx->pOut = &t;
 	pCtx->is_aborted = false;
 	pCtx->skipFlag = 0;
-	(pCtx->pFunc->xSFunc)(pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */
+	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
+	func->call(pCtx, pCtx->argc, pCtx->argv);
 	if (pCtx->is_aborted) {
 		sqlVdbeMemRelease(&t);
 		goto abort_due_to_error;
@@ -5087,7 +5093,7 @@ case OP_AggFinal: {
 	assert(pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor));
 	pMem = &aMem[pOp->p1];
 	assert((pMem->flags & ~(MEM_Null|MEM_Agg))==0);
-	if (sqlVdbeMemFinalize(pMem, pOp->p4.pFunc) != 0)
+	if (sql_vdbemem_finalize(pMem, pOp->p4.func) != 0)
 		goto abort_due_to_error;
 	UPDATE_MAX_BLOBSIZE(pMem);
 	if (sqlVdbeMemTooBig(pMem)) {
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index dfaff9365..bf40b44fa 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -477,17 +477,6 @@ sql_step(sql_stmt * pStmt)
 	return sqlStep(v);
 }
 
-/*
- * Extract the user data from a sql_context structure and return a
- * pointer to it.
- */
-void *
-sql_user_data(sql_context * p)
-{
-	assert(p && p->pFunc);
-	return p->pFunc->pUserData;
-}
-
 /*
  * Extract the user data from a sql_context structure and return a
  * pointer to it.
@@ -547,7 +536,7 @@ createAggContext(sql_context * p, int nByte)
 	} else {
 		sqlVdbeMemClearAndResize(pMem, nByte);
 		pMem->flags = MEM_Agg;
-		pMem->u.pDef = p->pFunc;
+		pMem->u.func = p->func;
 		if (pMem->z) {
 			memset(pMem->z, 0, nByte);
 		}
@@ -563,7 +552,9 @@ createAggContext(sql_context * p, int nByte)
 void *
 sql_aggregate_context(sql_context * p, int nByte)
 {
-	assert(p && p->pFunc && p->pFunc->xFinalize);
+	assert(p != NULL && p->func != NULL);
+	assert(p->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	assert(p->func->def->aggregate == FUNC_AGGREGATE_GROUP);
 	testcase(nByte < 0);
 	if ((p->pMem->flags & MEM_Agg) == 0) {
 		return createAggContext(p, nByte);
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index e7accc745..a44540b1d 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -648,24 +648,11 @@ sqlVdbeJumpHere(Vdbe * p, int addr)
 	sqlVdbeChangeP2(p, addr, p->nOp);
 }
 
-/*
- * If the input FuncDef structure is ephemeral, then free it.  If
- * the FuncDef is not ephermal, then do nothing.
- */
-static void
-freeEphemeralFunction(sql * db, FuncDef * pDef)
-{
-	if ((pDef->funcFlags & SQL_FUNC_EPHEM) != 0) {
-		sqlDbFree(db, pDef);
-	}
-}
-
 static void vdbeFreeOpArray(sql *, Op *, int);
 
 static SQL_NOINLINE void
 freeP4FuncCtx(sql * db, sql_context * p)
 {
-	freeEphemeralFunction(db, p->pFunc);
 	sqlDbFree(db, p);
 }
 
@@ -689,13 +676,11 @@ freeP4(sql * db, int p4type, void *p4)
 	case P4_KEYINFO:
 		sql_key_info_unref(p4);
 		break;
-	case P4_FUNCDEF:{
-			freeEphemeralFunction(db, (FuncDef *) p4);
-			break;
-		}
 	case P4_MEM:
 		sqlValueFree((sql_value *) p4);
 		break;
+	default:
+		break;
 	}
 }
 
@@ -1149,15 +1134,17 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
 				sqlXPrintf(&x, "(binary)");
 			break;
 		}
-	case P4_FUNCDEF:{
-			FuncDef *pDef = pOp->p4.pFunc;
-			sqlXPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg);
+	case P4_FUNC:{
+			struct func *func = pOp->p4.func;
+			sqlXPrintf(&x, "%s(%d)", func->def->name,
+				   func->def->param_count);
 			break;
 		}
 #if defined(SQL_DEBUG) || defined(VDBE_PROFILE)
 	case P4_FUNCCTX:{
-			FuncDef *pDef = pOp->p4.pCtx->pFunc;
-			sqlXPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg);
+			struct func *func = pOp->p4.func;
+			sqlXPrintf(&x, "%s(%d)", func->def->name,
+				   func->def->param_count);
 			break;
 		}
 #endif
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 5516d7fb1..ac0dfa333 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -312,37 +312,29 @@ sqlVdbeMemStringify(Mem * pMem)
 	return 0;
 }
 
-/*
- * Memory cell pMem contains the context of an aggregate function.
- * This routine calls the finalize method for that function.  The
- * result of the aggregate is stored back into pMem.
- *
- * Return -1 if the finalizer reports an error. 0 otherwise.
- */
 int
-sqlVdbeMemFinalize(Mem * pMem, FuncDef * pFunc)
+sql_vdbemem_finalize(struct Mem *mem, struct func *func)
 {
-	if (ALWAYS(pFunc && pFunc->xFinalize)) {
-		sql_context ctx;
-		Mem t;
-		assert((pMem->flags & MEM_Null) != 0 || pFunc == pMem->u.pDef);
-		memset(&ctx, 0, sizeof(ctx));
-		memset(&t, 0, sizeof(t));
-		t.flags = MEM_Null;
-		t.db = pMem->db;
-		t.field_type = field_type_MAX;
-		ctx.pOut = &t;
-		ctx.pMem = pMem;
-		ctx.pFunc = pFunc;
-		pFunc->xFinalize(&ctx);	/* IMP: R-24505-23230 */
-		assert((pMem->flags & MEM_Dyn) == 0);
-		if (pMem->szMalloc > 0)
-			sqlDbFree(pMem->db, pMem->zMalloc);
-		memcpy(pMem, &t, sizeof(t));
-		if (ctx.is_aborted)
-			return -1;
-	}
-	return 0;
+	assert(func != NULL);
+	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	assert(func->def->aggregate == FUNC_AGGREGATE_GROUP);
+	assert((mem->flags & MEM_Null) != 0 || func == mem->u.func);
+	sql_context ctx;
+	memset(&ctx, 0, sizeof(ctx));
+	Mem t;
+	memset(&t, 0, sizeof(t));
+	t.flags = MEM_Null;
+	t.db = mem->db;
+	t.field_type = field_type_MAX;
+	ctx.pOut = &t;
+	ctx.pMem = mem;
+	ctx.func = func;
+	((struct func_sql_builtin *)func)->finalize(&ctx);
+	assert((mem->flags & MEM_Dyn) == 0);
+	if (mem->szMalloc > 0)
+		sqlDbFree(mem->db, mem->zMalloc);
+	memcpy(mem, &t, sizeof(t));
+	return ctx.is_aborted ? -1 : 0;
 }
 
 /*
@@ -359,7 +351,7 @@ vdbeMemClearExternAndSetNull(Mem * p)
 {
 	assert(VdbeMemDynamic(p));
 	if (p->flags & MEM_Agg) {
-		sqlVdbeMemFinalize(p, p->u.pDef);
+		sql_vdbemem_finalize(p, p->u.func);
 		assert((p->flags & MEM_Agg) == 0);
 		testcase(p->flags & MEM_Dyn);
 	}
@@ -1289,7 +1281,6 @@ valueFromFunction(sql * db,	/* The database connection */
 	sql_context ctx;	/* Context object for function invocation */
 	sql_value **apVal = 0;	/* Function arguments */
 	int nVal = 0;		/* Size of apVal[] array */
-	FuncDef *pFunc = 0;	/* Function definition */
 	sql_value *pVal = 0;	/* New value */
 	int rc = 0;	/* Return code */
 	ExprList *pList = 0;	/* Function arguments */
@@ -1300,10 +1291,10 @@ valueFromFunction(sql * db,	/* The database connection */
 	pList = p->x.pList;
 	if (pList)
 		nVal = pList->nExpr;
-	pFunc = sqlFindFunction(db, p->u.zToken, nVal, 0);
-	assert(pFunc);
-	if ((pFunc->funcFlags & SQL_FUNC_CONSTANT) == 0 ||
-	    (pFunc->funcFlags & SQL_FUNC_NEEDCOLL))
+	struct func *func = sql_func_by_signature(p->u.zToken, nVal);
+	if (func == NULL || func->def->language != FUNC_LANGUAGE_SQL_BUILTIN ||
+	    !func->def->is_deterministic ||
+	    sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL))
 		return 0;
 
 	if (pList) {
@@ -1332,8 +1323,8 @@ valueFromFunction(sql * db,	/* The database connection */
 	assert(!pCtx->pParse->is_aborted);
 	memset(&ctx, 0, sizeof(ctx));
 	ctx.pOut = pVal;
-	ctx.pFunc = pFunc;
-	pFunc->xSFunc(&ctx, nVal, apVal);
+	ctx.func = func;
+	((struct func_sql_builtin *)func)->call(&ctx, nVal, apVal);
 	assert(!ctx.is_aborted);
 	sql_value_apply_type(pVal, type);
 	assert(rc == 0);
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index 8adf6a5f1..98615b118 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -273,7 +273,7 @@ like_optimization_is_valid(Parse *pParse, Expr *pExpr, Expr **ppPrefix,
 	/* Result code to return. */
 	int rc;
 
-	if (!sql_is_like_func(db, pExpr)) {
+	if (!sql_is_like_func(pExpr)) {
 		return 0;
 	}
 	pList = pExpr->x.pList;
diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 9bba37bcb..9d2fcea4b 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -130,7 +130,6 @@ add_library(box STATIC
     ${lua_sources}
     lua/init.c
     lua/call.c
-    lua/lua_sql.c
     lua/cfg.cc
     lua/console.c
     lua/tuple.c
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 4f2e34bf0..22d3040ef 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -2926,6 +2926,12 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 				  (unsigned) old_func->def->uid,
 				  "function has references");
 		}
+		/* Can't' drop a builtin function. */
+		if (old_func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
+			tnt_raise(ClientError, ER_DROP_FUNCTION,
+				  (unsigned) old_func->def->uid,
+				  "function is SQL builtin");
+		}
 		struct trigger *on_commit =
 			txn_alter_trigger_new(on_drop_func_commit, old_func);
 		struct trigger *on_rollback =
diff --git a/test/box/function1.result b/test/box/function1.result
index 5b091f72b..3eba37ab1 100644
--- a/test/box/function1.result
+++ b/test/box/function1.result
@@ -825,6 +825,10 @@ box.func.LUA:call({"return 1 + 1"})
 ---
 - 2
 ...
+box.schema.func.drop("SUM")
+---
+- error: 'Can''t drop function 1: function is SQL builtin'
+...
 -- Introduce function options
 box.schema.func.create('test', {body = "function(tuple) return tuple end", is_deterministic = true, opts = {is_multikey = true}})
 ---
diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua
index f894472f8..e9d3543b6 100644
--- a/test/box/function1.test.lua
+++ b/test/box/function1.test.lua
@@ -294,6 +294,8 @@ ok == true
 
 box.func.LUA:call({"return 1 + 1"})
 
+box.schema.func.drop("SUM")
+
 -- Introduce function options
 box.schema.func.create('test', {body = "function(tuple) return tuple end", is_deterministic = true, opts = {is_multikey = true}})
 box.func['test'].is_multikey == true
diff --git a/test/sql-tap/where2.test.lua b/test/sql-tap/where2.test.lua
index 4116ca913..f267be8e6 100755
--- a/test/sql-tap/where2.test.lua
+++ b/test/sql-tap/where2.test.lua
@@ -231,7 +231,7 @@ test:do_execsql_test(
         EXPLAIN SELECT * FROM x1, x2 WHERE x=1 ORDER BY random();
     ]], {
         -- <where2-2.5>
-        "/random/"
+        "/RANDOM/"
         -- </where2-2.5>
     })
 
@@ -254,7 +254,7 @@ test:do_execsql_test(
         EXPLAIN SELECT * FROM x1, x2 WHERE x=1 ORDER BY abs(5);
     ]], {
         -- <where2-2.6>
-        "~/abs/"
+        "~/ABS/"
         -- </where2-2.6>
     })
 
-- 
2.22.1

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

* [tarantool-patches] Re: [PATCH v3 7/9] sql: get rid of FuncDef function hash
  2019-08-19 15:51     ` Kirill Shcherbatov
@ 2019-08-20 13:47       ` Konstantin Osipov
  2019-08-20 19:04       ` n.pettik
  1 sibling, 0 replies; 19+ messages in thread
From: Konstantin Osipov @ 2019-08-20 13:47 UTC (permalink / raw)
  To: Kirill Shcherbatov; +Cc: tarantool-patches, korablev

* Kirill Shcherbatov <kshcherbatov@tarantool.org> [19/08/19 18:55]:
>  src/box/alter.cc             |   6 +
> index 4f2e34bf0..22d3040ef 100644
> --- a/src/box/alter.cc
> +++ b/src/box/alter.cc
> @@ -2926,6 +2926,12 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
>  				  (unsigned) old_func->def->uid,
>  				  "function has references");
>  		}
> +		/* Can't' drop a builtin function. */
> +		if (old_func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
> +			tnt_raise(ClientError, ER_DROP_FUNCTION,
> +				  (unsigned) old_func->def->uid,
> +				  "function is SQL builtin");
> +		}

I remember we discussed a safety net which adds a flag
"is_persistent" to sql_func_builtin, asserts this flag is set
whenever the function is used in SQL, and sets this flag in
alter.cc on_replace trigger.

this safety net can go in a separate patch.

Otherwise lgtm.


-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v3 7/9] sql: get rid of FuncDef function hash
  2019-08-19 15:51     ` Kirill Shcherbatov
  2019-08-20 13:47       ` Konstantin Osipov
@ 2019-08-20 19:04       ` n.pettik
  2019-08-20 19:12         ` Konstantin Osipov
  1 sibling, 1 reply; 19+ messages in thread
From: n.pettik @ 2019-08-20 19:04 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov, Konstantin Osipov


> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index 5a3e8f1c1..91a640816 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> 
> @@ -4487,9 +4330,64 @@ Expr *sqlExprForVectorField(Parse *, Expr *, int);
>  */
> extern int sqlSubProgramsRemaining;
> 
> -/** Register built-in functions to work with ANALYZE data. */
> -void
> -sql_register_analyze_builtins(void);
> +struct func_sql_builtin {
> +	/** Function object base class. */
> +	struct func base;
> +	/** A bitmask of SQL flags. */
> +	uint16_t flags;
> +	/** User data to pass in call method. */
> +	void *user_data;

‘user_data' is not used anymore, please remove.

> +	/**
> +	 * A VDBE-memory-compatible call method for a function.
> +	 * SQL Builting functions doesn't use base class call
> +	 * method to gain the best possible performance for SQL
> +	 * requests. As builtin functions are predefined and
> +	 * non of them modifie schema, access checks are
> +	 * redundant, functions have the same execution
> +	 * privileges as SQL.
> +	 */
> +	void (*call)(sql_context *ctx, int argc, sql_value **argv);
> +	/**
> +	 * Check whether do the function support a specified
> +	 * number of input arguments.

-> Check whether function supports given number of arguments.
For instance, trim() function can accept one, two or three arguments.

> +	 */
> +	int (*check_param_count)(struct func_sql_builtin *func, int argc);
> 
> 
> diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
> index f77c019fb..d0c15d1fe 100644
> --- a/src/box/sql/vdbeInt.h
> +++ b/src/box/sql/vdbeInt.h
> @@ -46,6 +46,8 @@
>  */
> typedef struct VdbeOp Op;
> 
> +struct func;
> +
> /*
>  * Boolean values
>  */
> 
> @@ -516,7 +523,17 @@ int sqlVdbeMemNumerify(Mem *);
> int sqlVdbeMemCast(Mem *, enum field_type type);
> int sqlVdbeMemFromBtree(BtCursor *, u32, u32, Mem *);
> void sqlVdbeMemRelease(Mem * p);
> -int sqlVdbeMemFinalize(Mem *, FuncDef *);
> +
> +/**
> + * Memory cell pMem contains the context of an aggregate function.

Again: pMem -> mem.

> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 07c019db9..b583b60c9 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> 
> @@ -498,6 +501,13 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
> 	}
> }
> 
> +static int
> +sql_builtin_substr_check_param_count(struct func_sql_builtin *func, int argc)
> +{
> +	(void) func;
> +	return argc != 2 && argc != 3 ? -1 : 0;
> +}
> +
> /*
>  * Implementation of the round() function
>  */
> @@ -538,6 +548,13 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
> 	sql_result_double(context, r);
> }
> 
> +static int
> +sql_builtin_round_check_param_count(struct func_sql_builtin *func, int argc)
> +{
> +	(void) func;
> +	return argc != 1 && argc != 2 ? -1 : 0;
> +}
> +
> /*
>  * Allocate nByte bytes of space using sqlMalloc(). If the
>  * allocation fails, return NULL. If nByte is larger than the
> @@ -968,6 +985,13 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
> 	sql_result_bool(context, res == MATCH);
> }
> 
> +static int
> +sql_builtin_like_check_param_count(struct func_sql_builtin *func, int argc)
> +{
> +	(void) func;
> +	return argc != 2 && argc != 3 ? -1 : 0;
> +}
> +
> /*
>  * Implementation of the NULLIF(x,y) function.  The result is the first
>  * argument if the arguments are different.  The result is NULL if the
> @@ -1514,6 +1538,13 @@ trim_func(struct sql_context *context, int argc, sql_value **argv)
> 	}
> }
> 
> +static int
> +sql_builtin_trim_check_param_count(struct func_sql_builtin *func, int argc)
> +{
> +	(void) func;
> +	return argc != 1 && argc != 2 && argc != 3 ? -1 : 0;
> +}
> +
> /*
>  * Compute the soundex encoding of a word.
>  *
> @@ -1703,6 +1734,13 @@ countFinalize(sql_context * context)
> 	sql_result_uint(context, p ? p->n : 0);
> }
> 
> +static int
> +sql_builtin_count_check_param_count(struct func_sql_builtin *func, int argc)
> +{
> +	(void) func;
> +	return argc != 0 && argc != 1 ? -1 : 0;
> +}
> +

To be honest, all these stubs and idea with count_check
function at all now seems to be not as good as it used to be.
I’ve removed this check and instead added several runtime
ones and IMHO it looks way much better:

np/sql-builtins

diff --git a/src/box/errcode.h b/src/box/errcode.h
index 817275b97..d4aca2caa 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -253,6 +253,7 @@ struct errcode_record {
        /*198 */_(ER_FUNC_INDEX_FUNC,           "Failed to build a key for functional index '%s' of space '%s': %s") \
        /*199 */_(ER_FUNC_INDEX_FORMAT,         "Key format doesn't match one defined in functional index '%s' of space '%s': %s") \
        /*200 */_(ER_FUNC_INDEX_PARTS,          "Wrong functional index definition: %s") \
+       /*200 */_(ER_FUNC_WRONG_ARG_COUNT,      "Wrong number of arguments is passed to %s: expected %s, got %d")
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 74f434346..9f5dffd73 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -691,7 +691,12 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
        i64 p1, p2;
        int negP2 = 0;
 
-       assert(argc == 3 || argc == 2);
+       if (argc != 2 && argc != 3) {
+               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "SUBSTR",
+                        "1 or 2", argc);
+               context->is_aborted = true;
+               return;
+       }
        if (sql_value_is_null(argv[1])
            || (argc == 3 && sql_value_is_null(argv[2]))
            ) {
@@ -779,13 +784,6 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
        }
 }
 
-static int
-sql_builtin_substr_check_param_count(struct func_sql_builtin *func, int argc)
-{
-       (void) func;
-       return argc != 2 && argc != 3 ? -1 : 0;
-}
-
 /*
  * Implementation of the round() function
  */
@@ -794,7 +792,12 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 {
        int n = 0;
        double r;
-       assert(argc == 1 || argc == 2);
+       if (argc != 1 && argc != 2) {
+               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ROUND",
+                        "1 or 2", argc);
+               context->is_aborted = true;
+               return;
+       }
        if (argc == 2) {
                if (sql_value_is_null(argv[1]))
                        return;
@@ -826,13 +829,6 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
        sql_result_double(context, r);
 }
 
-static int
-sql_builtin_round_check_param_count(struct func_sql_builtin *func, int argc)
-{
-       (void) func;
-       return argc != 1 && argc != 2 ? -1 : 0;
-}
-
 /*
  * Allocate nByte bytes of space using sqlMalloc(). If the
  * allocation fails, return NULL. If nByte is larger than the
@@ -1193,6 +1189,12 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 {
        u32 escape = SQL_END_OF_STRING;
        int nPat;
+       if (argc != 2 && argc != 3) {
+               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+                        "LIKE", "2 or 3", argc);
+               context->is_aborted = true;
+               return;
+       }
        sql *db = sql_context_db_handle(context);
        int rhs_type = sql_value_type(argv[0]);
        int lhs_type = sql_value_type(argv[1]);
@@ -1263,13 +1265,6 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
        sql_result_bool(context, res == MATCH);
 }
 
-static int
-sql_builtin_like_check_param_count(struct func_sql_builtin *func, int argc)
-{
-       (void) func;
-       return argc != 2 && argc != 3 ? -1 : 0;
-}
-
 /*
  * Implementation of the NULLIF(x,y) function.  The result is the first
  * argument if the arguments are different.  The result is NULL if the
@@ -1812,17 +1807,12 @@ trim_func(struct sql_context *context, int argc, sql_value **argv)
                trim_func_three_args(context, argv[0], argv[1], argv[2]);
                break;
        default:
-               unreachable();
+               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "TRIM",
+                        "1 or 2 or 3", argc);
+               context->is_aborted = true;
        }
 }
 
-static int
-sql_builtin_trim_check_param_count(struct func_sql_builtin *func, int argc)
-{
-       (void) func;
-       return argc != 1 && argc != 2 && argc != 3 ? -1 : 0;
-}
-
 /*
  * Compute the soundex encoding of a word.
  *
@@ -1998,6 +1988,12 @@ static void
 countStep(sql_context * context, int argc, sql_value ** argv)
 {
        CountCtx *p;
+       if (argc != 0 && argc != 1) {
+               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+                        "COUNT", "0 or 1", argc);
+               context->is_aborted = true;
+               return;
+       }
        p = sql_aggregate_context(context, sizeof(*p));
        if ((argc == 0 || ! sql_value_is_null(argv[0])) && p) {
                p->n++;
@@ -2012,13 +2008,6 @@ countFinalize(sql_context * context)
        sql_result_uint(context, p ? p->n : 0);
 }
 
-static int
-sql_builtin_count_check_param_count(struct func_sql_builtin *func, int argc)
-{
-       (void) func;
-       return argc != 0 && argc != 1 ? -1 : 0;
-}
-
 /*
  * Routines to implement min() and max() aggregate functions.
  */
@@ -2083,7 +2072,12 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
        StrAccum *pAccum;
        const char *zSep;
        int nVal, nSep;
-       assert(argc == 1 || argc == 2);
+       if (argc != 1 && argc != 2) {
+               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+                        "GROUP_CONCAT", "1 or 2", argc);
+               context->is_aborted = true;
+               return;
+       }
        if (sql_value_is_null(argv[0]))
                return;
        pAccum =
@@ -2131,14 +2125,6 @@ groupConcatFinalize(sql_context * context)
        }
 }
 
-static int
-sql_builtin_group_concat_check_param_count(struct func_sql_builtin *func,
-                                          int argc)
-{
-       (void) func;
-       return argc != 1 && argc != 2 ? -1 : 0;
-}
-
 int
 sql_is_like_func(struct Expr *expr)
 {
@@ -2158,16 +2144,9 @@ sql_func_by_signature(const char *name, int argc)
        struct func *base = func_by_name(name, strlen(name));
        if (base == NULL || !base->def->exports.sql)
                return NULL;
-       if (base->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
-               struct func_sql_builtin *func =
-                       (struct func_sql_builtin *) base;
-               if (func->check_param_count(func, argc) != 0)
-                       return NULL;
-       } else {
-               if (base->def->param_count != -1 &&
-                   base->def->param_count != argc)
-                       return NULL;
-       }
+
+       if (base->def->param_count != -1 && base->def->param_count != argc)
+               return NULL;
        return base;
 }
 
@@ -2190,34 +2169,25 @@ sql_builtin_stub(sql_context *ctx, int argc, sql_value **argv)
        ctx->is_aborted = true;
 }
 
-static int
-sql_builtin_default_check_param_count(struct func_sql_builtin *func, int argc)
-{
-       return (func->base.def->param_count != -1 &&
-               argc != func->base.def->param_count) ? -1 : 0;
-}
-
-static int
-sql_builtin_coalesce_check_param_count(struct func_sql_builtin *func, int argc)
-{
-       (void) func;
-       return argc == 0 || argc == 1 ? -1 : 0;
-}
-
 /**
  * A sequence of SQL builtins definitions in
  * lexicographic order.
  */
 static struct {
+       /**
+        * Name is used to find corresponding entry in array
+        * sql_builtins applying binary search.
+        */
        const char *name;
-       int param_count;
-       enum field_type returns;
-       enum func_aggregate aggregate;
-       bool is_deterministic;
+       /** Members below are related to struct func_sql_builtin. */
        uint16_t flags;
        void (*call)(sql_context *ctx, int argc, sql_value **argv);
-       int (*check_param_count)(struct func_sql_builtin *func, int argc);
        void (*finalize)(sql_context *ctx);
+       /** Members below are related to struct func_def. */
+       bool is_deterministic;
+       int param_count;
+       enum field_type returns;
+       enum func_aggregate aggregate;
 } sql_builtins[] = {
        {.name = "ABS",
         .param_count = 1,
@@ -2226,7 +2196,6 @@ static struct {
         .is_deterministic = true,
         .flags = 0,
         .call = absFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "AVG",
         .param_count = 1,
@@ -2235,7 +2204,6 @@ static struct {
         .aggregate = FUNC_AGGREGATE_GROUP,
         .flags = 0,
         .call = sum_step,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = avgFinalize},
        {.name = "CHAR",
         .param_count = -1,
@@ -2244,7 +2212,6 @@ static struct {
         .aggregate = FUNC_AGGREGATE_NONE,
         .flags = 0,
         .call = charFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "CHARACTER_LENGTH",
         .param_count = 1,
@@ -2253,7 +2220,6 @@ static struct {
         .is_deterministic = true,
         .flags = 0,
         .call = lengthFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "CHAR_LENGTH",
         .param_count = 1,
@@ -2262,7 +2228,6 @@ static struct {
         .is_deterministic = true,
         .flags = 0,
         .call = lengthFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "COALESCE",
         .param_count = -1,
@@ -2271,7 +2236,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_COALESCE,
         .call = sql_builtin_stub,
-        .check_param_count = sql_builtin_coalesce_check_param_count,
         .finalize = NULL},
        {.name = "COUNT",
         .param_count = -1,
@@ -2280,7 +2244,6 @@ static struct {
         .is_deterministic = false,
         .flags = 0,
         .call = countStep,
-        .check_param_count = sql_builtin_count_check_param_count,
         .finalize = countFinalize},
        {.name = "GREATEST",
         .param_count = -1,
@@ -2289,7 +2252,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MAX,
         .call = minmaxFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "GROUP_CONCAT",
         .param_count = -1,
@@ -2298,7 +2260,6 @@ static struct {
         .is_deterministic = false,
         .flags = 0,
         .call = groupConcatStep,
-        .check_param_count = sql_builtin_group_concat_check_param_count,
         .finalize = groupConcatFinalize},
        {.name = "HEX",
         .param_count = 1,
@@ -2307,7 +2268,6 @@ static struct {
         .is_deterministic = true,
         .flags = 0,
         .call = hexFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "IFNULL",
         .param_count = 2,
@@ -2316,7 +2276,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_COALESCE,
         .call = sql_builtin_stub,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "LEAST",
         .param_count = -1,
@@ -2325,7 +2284,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MIN,
         .call = minmaxFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "LENGTH",
         .param_count = 1,
@@ -2334,7 +2292,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_LENGTH,
         .call = lengthFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "LIKE",
         .param_count = -1,
@@ -2343,7 +2300,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_LIKE,
         .call = likeFunc,
-        .check_param_count = sql_builtin_like_check_param_count,
         .finalize = NULL},
        {.name = "LIKELIHOOD",
         .param_count = 2,
@@ -2352,7 +2308,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_UNLIKELY,
         .call = sql_builtin_stub,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "LIKELY",
         .param_count = 1,
@@ -2361,7 +2316,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_UNLIKELY,
         .call = sql_builtin_stub,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "LOWER",
         .param_count = 1,
@@ -2370,7 +2324,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
         .call = LowerICUFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "MAX",
         .param_count = 1,
@@ -2379,7 +2332,6 @@ static struct {
         .is_deterministic = false,
         .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MAX,
         .call = minmaxStep,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = minMaxFinalize},
        {.name = "MIN",
         .param_count = 1,
@@ -2388,7 +2340,6 @@ static struct {
         .is_deterministic = false,
         .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MIN,
         .call = minmaxStep,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = minMaxFinalize},
        {.name = "NULLIF",
         .param_count = 2,
@@ -2397,7 +2348,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_NEEDCOLL,
         .call = nullifFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "POSITION",
         .param_count = 2,
@@ -2406,7 +2356,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_NEEDCOLL,
         .call = position_func,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "PRINTF",
         .param_count = -1,
@@ -2415,7 +2364,6 @@ static struct {
         .is_deterministic = true,
         .flags = 0,
         .call = printfFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "QUOTE",
         .param_count = 1,
@@ -2424,7 +2372,6 @@ static struct {
         .is_deterministic = true,
         .flags = 0,
         .call = quoteFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "RANDOM",
         .param_count = 0,
@@ -2433,7 +2380,6 @@ static struct {
         .is_deterministic = false,
         .flags = 0,
         .call = randomFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "RANDOMBLOB",
         .param_count = 1,
@@ -2442,7 +2388,6 @@ static struct {
         .is_deterministic = false,
         .flags = 0,
         .call = randomBlob,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "REPLACE",
         .param_count = 3,
@@ -2451,7 +2396,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_DERIVEDCOLL,
         .call = replaceFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "ROUND",
         .param_count = -1,
@@ -2460,7 +2404,6 @@ static struct {
         .is_deterministic = true,
         .flags = 0,
         .call = roundFunc,
-        .check_param_count = sql_builtin_round_check_param_count,
         .finalize = NULL},
        {.name = "ROW_COUNT",
         .param_count = 0,
@@ -2469,7 +2412,6 @@ static struct {
         .is_deterministic = true,
         .flags = 0,
         .call = sql_row_count,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "SOUNDEX",
         .param_count = 1,
@@ -2478,7 +2420,6 @@ static struct {
         .is_deterministic = true,
         .flags = 0,
         .call = soundexFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "SUBSTR",
         .param_count = -1,
@@ -2487,7 +2428,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_DERIVEDCOLL,
         .call = substrFunc,
-        .check_param_count = sql_builtin_substr_check_param_count,
         .finalize = NULL},
        {.name = "SUM",
         .param_count = 1,
@@ -2496,7 +2436,6 @@ static struct {
         .is_deterministic = false,
         .flags = 0,
         .call = sum_step,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = sumFinalize},
        {.name = "TOTAL",
         .param_count = 1,
@@ -2505,7 +2444,6 @@ static struct {
         .is_deterministic = false,
         .flags = 0,
         .call = sum_step,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = totalFinalize},
        {.name = "TRIM",
         .param_count = -1,
@@ -2514,7 +2452,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_DERIVEDCOLL,
         .call = trim_func,
-        .check_param_count = sql_builtin_trim_check_param_count,
         .finalize = NULL},
        {.name = "TYPEOF",
         .param_count = 1,
@@ -2523,7 +2460,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_TYPEOF,
         .call = typeofFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "UNICODE",
         .param_count = 1,
@@ -2532,7 +2468,6 @@ static struct {
         .is_deterministic = true,
         .flags = 0,
         .call = unicodeFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "UNLIKELY",
         .param_count = 1,
@@ -2541,7 +2476,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_UNLIKELY,
         .call = sql_builtin_stub,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "UPPER",
         .param_count = 1,
@@ -2550,7 +2484,6 @@ static struct {
         .is_deterministic = true,
         .flags = SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
         .call = UpperICUFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "VERSION",
         .param_count = 0,
@@ -2559,7 +2492,6 @@ static struct {
         .is_deterministic = true,
         .flags = 0,
         .call = sql_func_version,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
        {.name = "ZEROBLOB",
         .param_count = 1,
@@ -2568,7 +2500,6 @@ static struct {
         .is_deterministic = true,
         .flags = 0,
         .call = zeroblobFunc,
-        .check_param_count = sql_builtin_default_check_param_count,
         .finalize = NULL},
 };
 
@@ -2607,8 +2538,8 @@ func_sql_builtin_new(struct func_def *def)
                goto end;
        func->flags = sql_builtins[idx].flags;
        func->call = sql_builtins[idx].call;
-       func->check_param_count = sql_builtins[idx].check_param_count;
        func->finalize = sql_builtins[idx].finalize;
+
        def->param_count = sql_builtins[idx].param_count;
        def->is_deterministic = sql_builtins[idx].is_deterministic;
        def->returns = sql_builtins[idx].returns;
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 2c037a5a4..cd4a303dd 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -38,8 +38,6 @@
 #include "sqlInt.h"
 #include <stdlib.h>
 #include <string.h>
-#include "box/func.h"
-#include "box/func_def.h"
 #include "box/schema.h"
 
 /*
@@ -678,18 +676,10 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
                                pParse->is_aborted = true;
                                pNC->nErr++;
                        } else if (wrong_num_args) {
-                               const char *err;
-                               if (func->def->param_count >= 0) {
-                                       err = "invalid number of arguments is "
-                                             "passed to %.*s(): expected %d, "
-                                             "got %d";
-                               } else {
-                                       err = "invalid number of arguments is "
-                                             "passed to %.*s()";
-                               }
-                               diag_set(ClientError, ER_SQL_PARSER_GENERIC,
-                                        tt_sprintf(err, nId, zId,
-                                                   func->def->param_count, n));
+
+                               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+                                        tt_sprintf("%d", func->def->param_count),
+                                        nId);
                                pParse->is_aborted = true;
                                pNC->nErr++;
                        }
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 91a640816..c27eb1022 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -4335,8 +4335,6 @@ struct func_sql_builtin {
        struct func base;
        /** A bitmask of SQL flags. */
        uint16_t flags;
-       /** User data to pass in call method. */
-       void *user_data;
        /**
         * A VDBE-memory-compatible call method for a function.
         * SQL Builting functions doesn't use base class call
@@ -4347,11 +4345,6 @@ struct func_sql_builtin {
         * privileges as SQL.
         */
        void (*call)(sql_context *ctx, int argc, sql_value **argv);
-       /**
-        * Check whether do the function support a specified
-        * number of input arguments.
-        */
-       int (*check_param_count)(struct func_sql_builtin *func, int argc);
        /**
         * A VDBE-memory-compatible finalize method
         * (is valid only for aggregate function).

> /*
>  * Routines to implement min() and max() aggregate functions.
>  */
> @@ -1713,6 +1751,8 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
> 	Mem *pBest;
> 	UNUSED_PARAMETER(NotUsed);
> 
> +	struct func_sql_builtin *func =
> +		(struct func_sql_builtin *)context->func;
> 	pBest = (Mem *) sql_aggregate_context(context, sizeof(*pBest));
> 	if (!pBest)
> 		return;
> @@ -1721,20 +1761,17 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
> 		if (pBest->flags)
> 			sqlSkipAccumulatorLoad(context);
> 	} else if (pBest->flags) {
> -		int max;
> 		int cmp;
> 		struct coll *pColl = sqlGetFuncCollSeq(context);
> -		/* This step function is used for both the min() and max() aggregates,
> -		 * the only difference between the two being that the sense of the
> -		 * comparison is inverted. For the max() aggregate, the
> -		 * sql_user_data() function returns (void *)-1. For min() it
> -		 * returns (void *)db, where db is the sql* database pointer.
> -		 * Therefore the next statement sets variable 'max' to 1 for the max()
> -		 * aggregate, or 0 for min().
> +		/*
> +		 * This step function is used for both the min()
> +		 * and max() aggregates, the only difference
> +		 * between the two being that the sense of the
> +		 * comparison is inverted.
> 		 */
> -		max = sql_user_data(context) != 0;
> +		bool is_max = (func->flags & SQL_FUNC_MAX) != 0;

I’d rather move introduction of SQL_FUNC_MAX flag to a separate patch.

> 		cmp = sqlMemCompare(pBest, pArg, pColl);
> -		if ((max && cmp < 0) || (!max && cmp > 0)) {
> +		if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
> 			sqlVdbeMemCopy(pBest, pArg);
> 		} else {
> 			sqlSkipAccumulatorLoad(context);
> @@ -1816,128 +1853,503 @@ groupConcatFinalize(sql_context * context)
> 	}
> }
> 
> +static int
> +sql_builtin_group_concat_check_param_count(struct func_sql_builtin *func,
> +					   int argc)
> +{
> +	(void) func;
> +	return argc != 1 && argc != 2 ? -1 : 0;
> +}
> 
> -/*
> - * All of the FuncDef structures in the aBuiltinFunc[] array above
> - * to the global function hash table.  This occurs at start-time (as
> - * a consequence of calling sql_initialize()).
> - *
> - * After this routine runs
> +struct func *
> +sql_func_by_signature(const char *name, int argc)
> +{
> +	struct func *base = func_by_name(name, strlen(name));
> +	if (base == NULL || !base->def->exports.sql)
> +		return NULL;
> +	if (base->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
> +		struct func_sql_builtin *func =
> +			(struct func_sql_builtin *) base;
> +		if (func->check_param_count(func, argc) != 0)
> +			return NULL;
> +	} else {
> +		if (base->def->param_count != -1 &&
> +		    base->def->param_count != argc)
> +			return NULL;
> +	}
> +	return base;
> +}
> +
> +

Nit: one extra empty line.

> +static int
> +sql_builtin_call_stub(struct func *func, struct port *args, struct port *ret)

I’d rename it to func_sql_builtin_call_stub()

> +{
> +	(void) func; (void) args; (void) ret;
> +	diag_set(ClientError, ER_UNSUPPORTED,
> +		 "sql builtin function", "Lua frontend");
> +	return -1;
> +}
> +
> +static void
> +sql_builtin_stub(sql_context *ctx, int argc, sql_value **argv)
> +{
> +	(void) argc; (void) argv;
> +	diag_set(ClientError, ER_SQL_EXECUTE,
> +		 tt_sprintf("function '%s' is not implemented",
> +			    ctx->func->def->name));
> +	ctx->is_aborted = true;
> +}
> +
> +static int
> +sql_builtin_default_check_param_count(struct func_sql_builtin *func, int argc)
> +{
> +	return (func->base.def->param_count != -1 &&
> +		argc != func->base.def->param_count) ? -1 : 0;
> +}
> +
> +static int
> +sql_builtin_coalesce_check_param_count(struct func_sql_builtin *func, int argc)
> +{
> +	(void) func;
> +	return argc == 0 || argc == 1 ? -1 : 0;
> +}
> +
> +/**
> + * A sequence of SQL builtins definitions in
> + * lexicographic order.
>  */
> -void
> -sqlRegisterBuiltinFunctions(void)
> +static struct {
> +	const char *name;
> +	int param_count;
> +	enum field_type returns;
> +	enum func_aggregate aggregate;
> +	bool is_deterministic;
> +	uint16_t flags;
> +	void (*call)(sql_context *ctx, int argc, sql_value **argv);
> +	int (*check_param_count)(struct func_sql_builtin *func, int argc);
> +	void (*finalize)(sql_context *ctx);

Let’s reorganise a bit this struct:

@@ -2209,17 +2209,24 @@ sql_builtin_coalesce_check_param_count(struct func_sql_builtin *func, int argc)
  * lexicographic order.
  */
 static struct {
+       /**
+        * Name is used to find corresponding entry in array
+        * sql_builtins applying binary search.
+        */
        const char *name;
-       int param_count;
-       enum field_type returns;
-       enum func_aggregate aggregate;
-       bool is_deterministic;
+       /** Members below are related to struct func_sql_builtin. */
        uint16_t flags;
        void (*call)(sql_context *ctx, int argc, sql_value **argv);
        int (*check_param_count)(struct func_sql_builtin *func, int argc);
        void (*finalize)(sql_context *ctx);
+       /** Members below are related to struct func_def. */
+       bool is_deterministic;
+       int param_count;
+       enum field_type returns;
+       enum func_aggregate aggregate;

Now you can use memcpy() to copy corresponding parts
to struct func_def and struct func_sql_builtin (it is up to you).

> +} sql_builtins[] = {
> +	{.name = "ABS",
> +	 .param_count = 1,
> +	 .returns = FIELD_TYPE_NUMBER,
> +	 .aggregate = FUNC_AGGREGATE_NONE,
> +	 .is_deterministic = true,
> +	 .flags = 0,
> +	 .call = absFunc,
> +	 .check_param_count = sql_builtin_default_check_param_count,
> +	 .finalize = NULL},
> +	{.name = "AVG”,

Please, leave one empty line between structs, so that
one can visually separate them:

-       {.name = "ABS",
+       {
+        .name = "ABS",
         .param_count = 1,
         .returns = FIELD_TYPE_NUMBER,
         .aggregate = FUNC_AGGREGATE_NONE,
@@ -2227,8 +2234,9 @@ static struct {
         .flags = 0,
         .call = absFunc,
         .check_param_count = sql_builtin_default_check_param_count,
-        .finalize = NULL},
-       {.name = "AVG",
+        .finalize = NULL
+       }, {
+        .name = "AVG",
         .param_count = 1,
         .returns = FIELD_TYPE_NUMBER,
         .is_deterministic = false,
@@ -2236,8 +2244,9 @@ static struct {
         .flags = 0,
         .call = sum_step,
         .check_param_count = sql_builtin_default_check_param_count,
-        .finalize = avgFinalize},
-       {.name = "CHAR",
+        .finalize = avgFinalize
+       }, {
+        .name = "CHAR",

etc

> diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
> index 207f57ba6..682bb9c8e 100644
> --- a/src/box/sql/resolve.c
> +++ b/src/box/sql/resolve.c
> @@ -38,6 +38,9 @@
> #include "sqlInt.h"
> #include <stdlib.h>
> #include <string.h>
> +#include "box/func.h"
> +#include "box/func_def.h”

func.h and func_def.h seem to be redundant -
I’ve removed them and project has compiled.

> +#include "box/schema.h"
> 
> /*
>  * Walk the expression tree pExpr and increase the aggregate function
> @@ -596,27 +599,28 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
> 			int is_agg = 0;	/* True if is an aggregate function */
> 			int nId;	/* Number of characters in function name */
> 			const char *zId;	/* The function name. */
> -			FuncDef *pDef;	/* Information about the function */
> 
> 			assert(!ExprHasProperty(pExpr, EP_xIsSelect));
> 			zId = pExpr->u.zToken;
> 			nId = sqlStrlen30(zId);
> -			pDef = sqlFindFunction(pParse->db, zId, n, 0);
> -			if (pDef == 0) {
> -				pDef =
> -				    sqlFindFunction(pParse->db, zId, -2,0);
> -				if (pDef == 0) {
> +			struct func *func = sql_func_by_signature(zId, n);
> +			if (func == NULL) {
> +				func = func_by_name(zId, nId);

This makes you include box/schema.h

> +				if (func == NULL || !func->def->exports.sql)
> 					no_such_func = 1;

"No such function” and “not available in SQL” are different errors.
Please, handle both cases with proper error messages and add
test cases.

> -				} else {
> +				else
> 					wrong_num_args = 1;
> -				}
> 			} else {
> -				is_agg = pDef->xFinalize != 0;
> -				pExpr->type = pDef->ret_type;
> +				is_agg = func->def->language ==
> +					 FUNC_LANGUAGE_SQL_BUILTIN &&
> +					 func->def->aggregate ==
> +					 FUNC_AGGREGATE_GROUP;

== FUNC_AGGREGATE — check on language is redundant.
Instead, add an assertion.

> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index ddb5de87e..b2ce868d6 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -4381,7 +4381,9 @@ is_simple_count(struct Select *select, struct AggInfo *agg_info)
> 		return NULL;
> 	if (NEVER(agg_info->nFunc == 0))
> 		return NULL;
> -	if ((agg_info->aFunc->pFunc->funcFlags & SQL_FUNC_COUNT) == 0 ||
> +	assert(agg_info->aFunc->func->def->language ==
> +	       FUNC_LANGUAGE_SQL_BUILTIN);
> +	if (!sql_func_flag_is_set(agg_info->aFunc->func, SQL_FUNC_COUNT) == 0 ||

Nit: sql_func_flag_is_set() returns boolean, no need to check
it on equality to zero. 

> diff --git a/src/box/alter.cc b/src/box/alter.cc
> index 4f2e34bf0..22d3040ef 100644
> --- a/src/box/alter.cc
> +++ b/src/box/alter.cc
> @@ -2926,6 +2926,12 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
> 				  (unsigned) old_func->def->uid,
> 				  "function has references");
> 		}
> +		/* Can't' drop a builtin function. */
> +		if (old_func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
> +			tnt_raise(ClientError, ER_DROP_FUNCTION,
> +				  (unsigned) old_func->def->uid,
> +				  "function is SQL builtin”);

Nit: …SQL built-in.

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

* [tarantool-patches] Re: [PATCH v3 7/9] sql: get rid of FuncDef function hash
  2019-08-20 19:04       ` n.pettik
@ 2019-08-20 19:12         ` Konstantin Osipov
  0 siblings, 0 replies; 19+ messages in thread
From: Konstantin Osipov @ 2019-08-20 19:12 UTC (permalink / raw)
  To: n.pettik; +Cc: tarantool-patches, Kirill Shcherbatov

* n.pettik <korablev@tarantool.org> [19/08/20 22:05]:

Turns out check_count is not such a brilliant idea, eh?-)

I don't care much either way, but obviously I don't see a good
reason to move something that can be checked at prepare stage to
execution stage.

With a function in func there is at least a chance to do the check
at prepare stage.

> 
> > diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> > index 5a3e8f1c1..91a640816 100644
> > --- a/src/box/sql/sqlInt.h
> > +++ b/src/box/sql/sqlInt.h
> > 
> > @@ -4487,9 +4330,64 @@ Expr *sqlExprForVectorField(Parse *, Expr *, int);
> >  */
> > extern int sqlSubProgramsRemaining;
> > 
> > -/** Register built-in functions to work with ANALYZE data. */
> > -void
> > -sql_register_analyze_builtins(void);
> > +struct func_sql_builtin {
> > +	/** Function object base class. */
> > +	struct func base;
> > +	/** A bitmask of SQL flags. */
> > +	uint16_t flags;
> > +	/** User data to pass in call method. */
> > +	void *user_data;
> 
> ‘user_data' is not used anymore, please remove.
> 
> > +	/**
> > +	 * A VDBE-memory-compatible call method for a function.
> > +	 * SQL Builting functions doesn't use base class call
> > +	 * method to gain the best possible performance for SQL
> > +	 * requests. As builtin functions are predefined and
> > +	 * non of them modifie schema, access checks are
> > +	 * redundant, functions have the same execution
> > +	 * privileges as SQL.
> > +	 */
> > +	void (*call)(sql_context *ctx, int argc, sql_value **argv);
> > +	/**
> > +	 * Check whether do the function support a specified
> > +	 * number of input arguments.
> 
> -> Check whether function supports given number of arguments.
> For instance, trim() function can accept one, two or three arguments.
> 
> > +	 */
> > +	int (*check_param_count)(struct func_sql_builtin *func, int argc);
> > 
> > 
> > diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
> > index f77c019fb..d0c15d1fe 100644
> > --- a/src/box/sql/vdbeInt.h
> > +++ b/src/box/sql/vdbeInt.h
> > @@ -46,6 +46,8 @@
> >  */
> > typedef struct VdbeOp Op;
> > 
> > +struct func;
> > +
> > /*
> >  * Boolean values
> >  */
> > 
> > @@ -516,7 +523,17 @@ int sqlVdbeMemNumerify(Mem *);
> > int sqlVdbeMemCast(Mem *, enum field_type type);
> > int sqlVdbeMemFromBtree(BtCursor *, u32, u32, Mem *);
> > void sqlVdbeMemRelease(Mem * p);
> > -int sqlVdbeMemFinalize(Mem *, FuncDef *);
> > +
> > +/**
> > + * Memory cell pMem contains the context of an aggregate function.
> 
> Again: pMem -> mem.
> 
> > diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> > index 07c019db9..b583b60c9 100644
> > --- a/src/box/sql/func.c
> > +++ b/src/box/sql/func.c
> > 
> > @@ -498,6 +501,13 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
> > 	}
> > }
> > 
> > +static int
> > +sql_builtin_substr_check_param_count(struct func_sql_builtin *func, int argc)
> > +{
> > +	(void) func;
> > +	return argc != 2 && argc != 3 ? -1 : 0;
> > +}
> > +
> > /*
> >  * Implementation of the round() function
> >  */
> > @@ -538,6 +548,13 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
> > 	sql_result_double(context, r);
> > }
> > 
> > +static int
> > +sql_builtin_round_check_param_count(struct func_sql_builtin *func, int argc)
> > +{
> > +	(void) func;
> > +	return argc != 1 && argc != 2 ? -1 : 0;
> > +}
> > +
> > /*
> >  * Allocate nByte bytes of space using sqlMalloc(). If the
> >  * allocation fails, return NULL. If nByte is larger than the
> > @@ -968,6 +985,13 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
> > 	sql_result_bool(context, res == MATCH);
> > }
> > 
> > +static int
> > +sql_builtin_like_check_param_count(struct func_sql_builtin *func, int argc)
> > +{
> > +	(void) func;
> > +	return argc != 2 && argc != 3 ? -1 : 0;
> > +}
> > +
> > /*
> >  * Implementation of the NULLIF(x,y) function.  The result is the first
> >  * argument if the arguments are different.  The result is NULL if the
> > @@ -1514,6 +1538,13 @@ trim_func(struct sql_context *context, int argc, sql_value **argv)
> > 	}
> > }
> > 
> > +static int
> > +sql_builtin_trim_check_param_count(struct func_sql_builtin *func, int argc)
> > +{
> > +	(void) func;
> > +	return argc != 1 && argc != 2 && argc != 3 ? -1 : 0;
> > +}
> > +
> > /*
> >  * Compute the soundex encoding of a word.
> >  *
> > @@ -1703,6 +1734,13 @@ countFinalize(sql_context * context)
> > 	sql_result_uint(context, p ? p->n : 0);
> > }
> > 
> > +static int
> > +sql_builtin_count_check_param_count(struct func_sql_builtin *func, int argc)
> > +{
> > +	(void) func;
> > +	return argc != 0 && argc != 1 ? -1 : 0;
> > +}
> > +
> 
> To be honest, all these stubs and idea with count_check
> function at all now seems to be not as good as it used to be.
> I’ve removed this check and instead added several runtime
> ones and IMHO it looks way much better:
> 
> np/sql-builtins
> 
> diff --git a/src/box/errcode.h b/src/box/errcode.h
> index 817275b97..d4aca2caa 100644
> --- a/src/box/errcode.h
> +++ b/src/box/errcode.h
> @@ -253,6 +253,7 @@ struct errcode_record {
>         /*198 */_(ER_FUNC_INDEX_FUNC,           "Failed to build a key for functional index '%s' of space '%s': %s") \
>         /*199 */_(ER_FUNC_INDEX_FORMAT,         "Key format doesn't match one defined in functional index '%s' of space '%s': %s") \
>         /*200 */_(ER_FUNC_INDEX_PARTS,          "Wrong functional index definition: %s") \
> +       /*200 */_(ER_FUNC_WRONG_ARG_COUNT,      "Wrong number of arguments is passed to %s: expected %s, got %d")
>  
>  /*
>   * !IMPORTANT! Please follow instructions at start of the file
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 74f434346..9f5dffd73 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -691,7 +691,12 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
>         i64 p1, p2;
>         int negP2 = 0;
>  
> -       assert(argc == 3 || argc == 2);
> +       if (argc != 2 && argc != 3) {
> +               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "SUBSTR",
> +                        "1 or 2", argc);
> +               context->is_aborted = true;
> +               return;
> +       }
>         if (sql_value_is_null(argv[1])
>             || (argc == 3 && sql_value_is_null(argv[2]))
>             ) {
> @@ -779,13 +784,6 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
>         }
>  }
>  
> -static int
> -sql_builtin_substr_check_param_count(struct func_sql_builtin *func, int argc)
> -{
> -       (void) func;
> -       return argc != 2 && argc != 3 ? -1 : 0;
> -}
> -
>  /*
>   * Implementation of the round() function
>   */
> @@ -794,7 +792,12 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
>  {
>         int n = 0;
>         double r;
> -       assert(argc == 1 || argc == 2);
> +       if (argc != 1 && argc != 2) {
> +               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ROUND",
> +                        "1 or 2", argc);
> +               context->is_aborted = true;
> +               return;
> +       }
>         if (argc == 2) {
>                 if (sql_value_is_null(argv[1]))
>                         return;
> @@ -826,13 +829,6 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
>         sql_result_double(context, r);
>  }
>  
> -static int
> -sql_builtin_round_check_param_count(struct func_sql_builtin *func, int argc)
> -{
> -       (void) func;
> -       return argc != 1 && argc != 2 ? -1 : 0;
> -}
> -
>  /*
>   * Allocate nByte bytes of space using sqlMalloc(). If the
>   * allocation fails, return NULL. If nByte is larger than the
> @@ -1193,6 +1189,12 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
>  {
>         u32 escape = SQL_END_OF_STRING;
>         int nPat;
> +       if (argc != 2 && argc != 3) {
> +               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
> +                        "LIKE", "2 or 3", argc);
> +               context->is_aborted = true;
> +               return;
> +       }
>         sql *db = sql_context_db_handle(context);
>         int rhs_type = sql_value_type(argv[0]);
>         int lhs_type = sql_value_type(argv[1]);
> @@ -1263,13 +1265,6 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
>         sql_result_bool(context, res == MATCH);
>  }
>  
> -static int
> -sql_builtin_like_check_param_count(struct func_sql_builtin *func, int argc)
> -{
> -       (void) func;
> -       return argc != 2 && argc != 3 ? -1 : 0;
> -}
> -
>  /*
>   * Implementation of the NULLIF(x,y) function.  The result is the first
>   * argument if the arguments are different.  The result is NULL if the
> @@ -1812,17 +1807,12 @@ trim_func(struct sql_context *context, int argc, sql_value **argv)
>                 trim_func_three_args(context, argv[0], argv[1], argv[2]);
>                 break;
>         default:
> -               unreachable();
> +               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "TRIM",
> +                        "1 or 2 or 3", argc);
> +               context->is_aborted = true;
>         }
>  }
>  
> -static int
> -sql_builtin_trim_check_param_count(struct func_sql_builtin *func, int argc)
> -{
> -       (void) func;
> -       return argc != 1 && argc != 2 && argc != 3 ? -1 : 0;
> -}
> -
>  /*
>   * Compute the soundex encoding of a word.
>   *
> @@ -1998,6 +1988,12 @@ static void
>  countStep(sql_context * context, int argc, sql_value ** argv)
>  {
>         CountCtx *p;
> +       if (argc != 0 && argc != 1) {
> +               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
> +                        "COUNT", "0 or 1", argc);
> +               context->is_aborted = true;
> +               return;
> +       }
>         p = sql_aggregate_context(context, sizeof(*p));
>         if ((argc == 0 || ! sql_value_is_null(argv[0])) && p) {
>                 p->n++;
> @@ -2012,13 +2008,6 @@ countFinalize(sql_context * context)
>         sql_result_uint(context, p ? p->n : 0);
>  }
>  
> -static int
> -sql_builtin_count_check_param_count(struct func_sql_builtin *func, int argc)
> -{
> -       (void) func;
> -       return argc != 0 && argc != 1 ? -1 : 0;
> -}
> -
>  /*
>   * Routines to implement min() and max() aggregate functions.
>   */
> @@ -2083,7 +2072,12 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
>         StrAccum *pAccum;
>         const char *zSep;
>         int nVal, nSep;
> -       assert(argc == 1 || argc == 2);
> +       if (argc != 1 && argc != 2) {
> +               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
> +                        "GROUP_CONCAT", "1 or 2", argc);
> +               context->is_aborted = true;
> +               return;
> +       }
>         if (sql_value_is_null(argv[0]))
>                 return;
>         pAccum =
> @@ -2131,14 +2125,6 @@ groupConcatFinalize(sql_context * context)
>         }
>  }
>  
> -static int
> -sql_builtin_group_concat_check_param_count(struct func_sql_builtin *func,
> -                                          int argc)
> -{
> -       (void) func;
> -       return argc != 1 && argc != 2 ? -1 : 0;
> -}
> -
>  int
>  sql_is_like_func(struct Expr *expr)
>  {
> @@ -2158,16 +2144,9 @@ sql_func_by_signature(const char *name, int argc)
>         struct func *base = func_by_name(name, strlen(name));
>         if (base == NULL || !base->def->exports.sql)
>                 return NULL;
> -       if (base->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
> -               struct func_sql_builtin *func =
> -                       (struct func_sql_builtin *) base;
> -               if (func->check_param_count(func, argc) != 0)
> -                       return NULL;
> -       } else {
> -               if (base->def->param_count != -1 &&
> -                   base->def->param_count != argc)
> -                       return NULL;
> -       }
> +
> +       if (base->def->param_count != -1 && base->def->param_count != argc)
> +               return NULL;
>         return base;
>  }
>  
> @@ -2190,34 +2169,25 @@ sql_builtin_stub(sql_context *ctx, int argc, sql_value **argv)
>         ctx->is_aborted = true;
>  }
>  
> -static int
> -sql_builtin_default_check_param_count(struct func_sql_builtin *func, int argc)
> -{
> -       return (func->base.def->param_count != -1 &&
> -               argc != func->base.def->param_count) ? -1 : 0;
> -}
> -
> -static int
> -sql_builtin_coalesce_check_param_count(struct func_sql_builtin *func, int argc)
> -{
> -       (void) func;
> -       return argc == 0 || argc == 1 ? -1 : 0;
> -}
> -
>  /**
>   * A sequence of SQL builtins definitions in
>   * lexicographic order.
>   */
>  static struct {
> +       /**
> +        * Name is used to find corresponding entry in array
> +        * sql_builtins applying binary search.
> +        */
>         const char *name;
> -       int param_count;
> -       enum field_type returns;
> -       enum func_aggregate aggregate;
> -       bool is_deterministic;
> +       /** Members below are related to struct func_sql_builtin. */
>         uint16_t flags;
>         void (*call)(sql_context *ctx, int argc, sql_value **argv);
> -       int (*check_param_count)(struct func_sql_builtin *func, int argc);
>         void (*finalize)(sql_context *ctx);
> +       /** Members below are related to struct func_def. */
> +       bool is_deterministic;
> +       int param_count;
> +       enum field_type returns;
> +       enum func_aggregate aggregate;
>  } sql_builtins[] = {
>         {.name = "ABS",
>          .param_count = 1,
> @@ -2226,7 +2196,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = 0,
>          .call = absFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "AVG",
>          .param_count = 1,
> @@ -2235,7 +2204,6 @@ static struct {
>          .aggregate = FUNC_AGGREGATE_GROUP,
>          .flags = 0,
>          .call = sum_step,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = avgFinalize},
>         {.name = "CHAR",
>          .param_count = -1,
> @@ -2244,7 +2212,6 @@ static struct {
>          .aggregate = FUNC_AGGREGATE_NONE,
>          .flags = 0,
>          .call = charFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "CHARACTER_LENGTH",
>          .param_count = 1,
> @@ -2253,7 +2220,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = 0,
>          .call = lengthFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "CHAR_LENGTH",
>          .param_count = 1,
> @@ -2262,7 +2228,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = 0,
>          .call = lengthFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "COALESCE",
>          .param_count = -1,
> @@ -2271,7 +2236,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_COALESCE,
>          .call = sql_builtin_stub,
> -        .check_param_count = sql_builtin_coalesce_check_param_count,
>          .finalize = NULL},
>         {.name = "COUNT",
>          .param_count = -1,
> @@ -2280,7 +2244,6 @@ static struct {
>          .is_deterministic = false,
>          .flags = 0,
>          .call = countStep,
> -        .check_param_count = sql_builtin_count_check_param_count,
>          .finalize = countFinalize},
>         {.name = "GREATEST",
>          .param_count = -1,
> @@ -2289,7 +2252,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MAX,
>          .call = minmaxFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "GROUP_CONCAT",
>          .param_count = -1,
> @@ -2298,7 +2260,6 @@ static struct {
>          .is_deterministic = false,
>          .flags = 0,
>          .call = groupConcatStep,
> -        .check_param_count = sql_builtin_group_concat_check_param_count,
>          .finalize = groupConcatFinalize},
>         {.name = "HEX",
>          .param_count = 1,
> @@ -2307,7 +2268,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = 0,
>          .call = hexFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "IFNULL",
>          .param_count = 2,
> @@ -2316,7 +2276,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_COALESCE,
>          .call = sql_builtin_stub,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "LEAST",
>          .param_count = -1,
> @@ -2325,7 +2284,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MIN,
>          .call = minmaxFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "LENGTH",
>          .param_count = 1,
> @@ -2334,7 +2292,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_LENGTH,
>          .call = lengthFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "LIKE",
>          .param_count = -1,
> @@ -2343,7 +2300,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_LIKE,
>          .call = likeFunc,
> -        .check_param_count = sql_builtin_like_check_param_count,
>          .finalize = NULL},
>         {.name = "LIKELIHOOD",
>          .param_count = 2,
> @@ -2352,7 +2308,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_UNLIKELY,
>          .call = sql_builtin_stub,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "LIKELY",
>          .param_count = 1,
> @@ -2361,7 +2316,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_UNLIKELY,
>          .call = sql_builtin_stub,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "LOWER",
>          .param_count = 1,
> @@ -2370,7 +2324,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
>          .call = LowerICUFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "MAX",
>          .param_count = 1,
> @@ -2379,7 +2332,6 @@ static struct {
>          .is_deterministic = false,
>          .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MAX,
>          .call = minmaxStep,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = minMaxFinalize},
>         {.name = "MIN",
>          .param_count = 1,
> @@ -2388,7 +2340,6 @@ static struct {
>          .is_deterministic = false,
>          .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MIN,
>          .call = minmaxStep,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = minMaxFinalize},
>         {.name = "NULLIF",
>          .param_count = 2,
> @@ -2397,7 +2348,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_NEEDCOLL,
>          .call = nullifFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "POSITION",
>          .param_count = 2,
> @@ -2406,7 +2356,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_NEEDCOLL,
>          .call = position_func,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "PRINTF",
>          .param_count = -1,
> @@ -2415,7 +2364,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = 0,
>          .call = printfFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "QUOTE",
>          .param_count = 1,
> @@ -2424,7 +2372,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = 0,
>          .call = quoteFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "RANDOM",
>          .param_count = 0,
> @@ -2433,7 +2380,6 @@ static struct {
>          .is_deterministic = false,
>          .flags = 0,
>          .call = randomFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "RANDOMBLOB",
>          .param_count = 1,
> @@ -2442,7 +2388,6 @@ static struct {
>          .is_deterministic = false,
>          .flags = 0,
>          .call = randomBlob,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "REPLACE",
>          .param_count = 3,
> @@ -2451,7 +2396,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_DERIVEDCOLL,
>          .call = replaceFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "ROUND",
>          .param_count = -1,
> @@ -2460,7 +2404,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = 0,
>          .call = roundFunc,
> -        .check_param_count = sql_builtin_round_check_param_count,
>          .finalize = NULL},
>         {.name = "ROW_COUNT",
>          .param_count = 0,
> @@ -2469,7 +2412,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = 0,
>          .call = sql_row_count,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "SOUNDEX",
>          .param_count = 1,
> @@ -2478,7 +2420,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = 0,
>          .call = soundexFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "SUBSTR",
>          .param_count = -1,
> @@ -2487,7 +2428,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_DERIVEDCOLL,
>          .call = substrFunc,
> -        .check_param_count = sql_builtin_substr_check_param_count,
>          .finalize = NULL},
>         {.name = "SUM",
>          .param_count = 1,
> @@ -2496,7 +2436,6 @@ static struct {
>          .is_deterministic = false,
>          .flags = 0,
>          .call = sum_step,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = sumFinalize},
>         {.name = "TOTAL",
>          .param_count = 1,
> @@ -2505,7 +2444,6 @@ static struct {
>          .is_deterministic = false,
>          .flags = 0,
>          .call = sum_step,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = totalFinalize},
>         {.name = "TRIM",
>          .param_count = -1,
> @@ -2514,7 +2452,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_DERIVEDCOLL,
>          .call = trim_func,
> -        .check_param_count = sql_builtin_trim_check_param_count,
>          .finalize = NULL},
>         {.name = "TYPEOF",
>          .param_count = 1,
> @@ -2523,7 +2460,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_TYPEOF,
>          .call = typeofFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "UNICODE",
>          .param_count = 1,
> @@ -2532,7 +2468,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = 0,
>          .call = unicodeFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "UNLIKELY",
>          .param_count = 1,
> @@ -2541,7 +2476,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_UNLIKELY,
>          .call = sql_builtin_stub,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "UPPER",
>          .param_count = 1,
> @@ -2550,7 +2484,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
>          .call = UpperICUFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "VERSION",
>          .param_count = 0,
> @@ -2559,7 +2492,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = 0,
>          .call = sql_func_version,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>         {.name = "ZEROBLOB",
>          .param_count = 1,
> @@ -2568,7 +2500,6 @@ static struct {
>          .is_deterministic = true,
>          .flags = 0,
>          .call = zeroblobFunc,
> -        .check_param_count = sql_builtin_default_check_param_count,
>          .finalize = NULL},
>  };
>  
> @@ -2607,8 +2538,8 @@ func_sql_builtin_new(struct func_def *def)
>                 goto end;
>         func->flags = sql_builtins[idx].flags;
>         func->call = sql_builtins[idx].call;
> -       func->check_param_count = sql_builtins[idx].check_param_count;
>         func->finalize = sql_builtins[idx].finalize;
> +
>         def->param_count = sql_builtins[idx].param_count;
>         def->is_deterministic = sql_builtins[idx].is_deterministic;
>         def->returns = sql_builtins[idx].returns;
> diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
> index 2c037a5a4..cd4a303dd 100644
> --- a/src/box/sql/resolve.c
> +++ b/src/box/sql/resolve.c
> @@ -38,8 +38,6 @@
>  #include "sqlInt.h"
>  #include <stdlib.h>
>  #include <string.h>
> -#include "box/func.h"
> -#include "box/func_def.h"
>  #include "box/schema.h"
>  
>  /*
> @@ -678,18 +676,10 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
>                                 pParse->is_aborted = true;
>                                 pNC->nErr++;
>                         } else if (wrong_num_args) {
> -                               const char *err;
> -                               if (func->def->param_count >= 0) {
> -                                       err = "invalid number of arguments is "
> -                                             "passed to %.*s(): expected %d, "
> -                                             "got %d";
> -                               } else {
> -                                       err = "invalid number of arguments is "
> -                                             "passed to %.*s()";
> -                               }
> -                               diag_set(ClientError, ER_SQL_PARSER_GENERIC,
> -                                        tt_sprintf(err, nId, zId,
> -                                                   func->def->param_count, n));
> +
> +                               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
> +                                        tt_sprintf("%d", func->def->param_count),
> +                                        nId);
>                                 pParse->is_aborted = true;
>                                 pNC->nErr++;
>                         }
> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index 91a640816..c27eb1022 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -4335,8 +4335,6 @@ struct func_sql_builtin {
>         struct func base;
>         /** A bitmask of SQL flags. */
>         uint16_t flags;
> -       /** User data to pass in call method. */
> -       void *user_data;
>         /**
>          * A VDBE-memory-compatible call method for a function.
>          * SQL Builting functions doesn't use base class call
> @@ -4347,11 +4345,6 @@ struct func_sql_builtin {
>          * privileges as SQL.
>          */
>         void (*call)(sql_context *ctx, int argc, sql_value **argv);
> -       /**
> -        * Check whether do the function support a specified
> -        * number of input arguments.
> -        */
> -       int (*check_param_count)(struct func_sql_builtin *func, int argc);
>         /**
>          * A VDBE-memory-compatible finalize method
>          * (is valid only for aggregate function).
> 
> > /*
> >  * Routines to implement min() and max() aggregate functions.
> >  */
> > @@ -1713,6 +1751,8 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
> > 	Mem *pBest;
> > 	UNUSED_PARAMETER(NotUsed);
> > 
> > +	struct func_sql_builtin *func =
> > +		(struct func_sql_builtin *)context->func;
> > 	pBest = (Mem *) sql_aggregate_context(context, sizeof(*pBest));
> > 	if (!pBest)
> > 		return;
> > @@ -1721,20 +1761,17 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
> > 		if (pBest->flags)
> > 			sqlSkipAccumulatorLoad(context);
> > 	} else if (pBest->flags) {
> > -		int max;
> > 		int cmp;
> > 		struct coll *pColl = sqlGetFuncCollSeq(context);
> > -		/* This step function is used for both the min() and max() aggregates,
> > -		 * the only difference between the two being that the sense of the
> > -		 * comparison is inverted. For the max() aggregate, the
> > -		 * sql_user_data() function returns (void *)-1. For min() it
> > -		 * returns (void *)db, where db is the sql* database pointer.
> > -		 * Therefore the next statement sets variable 'max' to 1 for the max()
> > -		 * aggregate, or 0 for min().
> > +		/*
> > +		 * This step function is used for both the min()
> > +		 * and max() aggregates, the only difference
> > +		 * between the two being that the sense of the
> > +		 * comparison is inverted.
> > 		 */
> > -		max = sql_user_data(context) != 0;
> > +		bool is_max = (func->flags & SQL_FUNC_MAX) != 0;
> 
> I’d rather move introduction of SQL_FUNC_MAX flag to a separate patch.
> 
> > 		cmp = sqlMemCompare(pBest, pArg, pColl);
> > -		if ((max && cmp < 0) || (!max && cmp > 0)) {
> > +		if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
> > 			sqlVdbeMemCopy(pBest, pArg);
> > 		} else {
> > 			sqlSkipAccumulatorLoad(context);
> > @@ -1816,128 +1853,503 @@ groupConcatFinalize(sql_context * context)
> > 	}
> > }
> > 
> > +static int
> > +sql_builtin_group_concat_check_param_count(struct func_sql_builtin *func,
> > +					   int argc)
> > +{
> > +	(void) func;
> > +	return argc != 1 && argc != 2 ? -1 : 0;
> > +}
> > 
> > -/*
> > - * All of the FuncDef structures in the aBuiltinFunc[] array above
> > - * to the global function hash table.  This occurs at start-time (as
> > - * a consequence of calling sql_initialize()).
> > - *
> > - * After this routine runs
> > +struct func *
> > +sql_func_by_signature(const char *name, int argc)
> > +{
> > +	struct func *base = func_by_name(name, strlen(name));
> > +	if (base == NULL || !base->def->exports.sql)
> > +		return NULL;
> > +	if (base->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
> > +		struct func_sql_builtin *func =
> > +			(struct func_sql_builtin *) base;
> > +		if (func->check_param_count(func, argc) != 0)
> > +			return NULL;
> > +	} else {
> > +		if (base->def->param_count != -1 &&
> > +		    base->def->param_count != argc)
> > +			return NULL;
> > +	}
> > +	return base;
> > +}
> > +
> > +
> 
> Nit: one extra empty line.
> 
> > +static int
> > +sql_builtin_call_stub(struct func *func, struct port *args, struct port *ret)
> 
> I’d rename it to func_sql_builtin_call_stub()
> 
> > +{
> > +	(void) func; (void) args; (void) ret;
> > +	diag_set(ClientError, ER_UNSUPPORTED,
> > +		 "sql builtin function", "Lua frontend");
> > +	return -1;
> > +}
> > +
> > +static void
> > +sql_builtin_stub(sql_context *ctx, int argc, sql_value **argv)
> > +{
> > +	(void) argc; (void) argv;
> > +	diag_set(ClientError, ER_SQL_EXECUTE,
> > +		 tt_sprintf("function '%s' is not implemented",
> > +			    ctx->func->def->name));
> > +	ctx->is_aborted = true;
> > +}
> > +
> > +static int
> > +sql_builtin_default_check_param_count(struct func_sql_builtin *func, int argc)
> > +{
> > +	return (func->base.def->param_count != -1 &&
> > +		argc != func->base.def->param_count) ? -1 : 0;
> > +}
> > +
> > +static int
> > +sql_builtin_coalesce_check_param_count(struct func_sql_builtin *func, int argc)
> > +{
> > +	(void) func;
> > +	return argc == 0 || argc == 1 ? -1 : 0;
> > +}
> > +
> > +/**
> > + * A sequence of SQL builtins definitions in
> > + * lexicographic order.
> >  */
> > -void
> > -sqlRegisterBuiltinFunctions(void)
> > +static struct {
> > +	const char *name;
> > +	int param_count;
> > +	enum field_type returns;
> > +	enum func_aggregate aggregate;
> > +	bool is_deterministic;
> > +	uint16_t flags;
> > +	void (*call)(sql_context *ctx, int argc, sql_value **argv);
> > +	int (*check_param_count)(struct func_sql_builtin *func, int argc);
> > +	void (*finalize)(sql_context *ctx);
> 
> Let’s reorganise a bit this struct:
> 
> @@ -2209,17 +2209,24 @@ sql_builtin_coalesce_check_param_count(struct func_sql_builtin *func, int argc)
>   * lexicographic order.
>   */
>  static struct {
> +       /**
> +        * Name is used to find corresponding entry in array
> +        * sql_builtins applying binary search.
> +        */
>         const char *name;
> -       int param_count;
> -       enum field_type returns;
> -       enum func_aggregate aggregate;
> -       bool is_deterministic;
> +       /** Members below are related to struct func_sql_builtin. */
>         uint16_t flags;
>         void (*call)(sql_context *ctx, int argc, sql_value **argv);
>         int (*check_param_count)(struct func_sql_builtin *func, int argc);
>         void (*finalize)(sql_context *ctx);
> +       /** Members below are related to struct func_def. */
> +       bool is_deterministic;
> +       int param_count;
> +       enum field_type returns;
> +       enum func_aggregate aggregate;
> 
> Now you can use memcpy() to copy corresponding parts
> to struct func_def and struct func_sql_builtin (it is up to you).
> 
> > +} sql_builtins[] = {
> > +	{.name = "ABS",
> > +	 .param_count = 1,
> > +	 .returns = FIELD_TYPE_NUMBER,
> > +	 .aggregate = FUNC_AGGREGATE_NONE,
> > +	 .is_deterministic = true,
> > +	 .flags = 0,
> > +	 .call = absFunc,
> > +	 .check_param_count = sql_builtin_default_check_param_count,
> > +	 .finalize = NULL},
> > +	{.name = "AVG”,
> 
> Please, leave one empty line between structs, so that
> one can visually separate them:
> 
> -       {.name = "ABS",
> +       {
> +        .name = "ABS",
>          .param_count = 1,
>          .returns = FIELD_TYPE_NUMBER,
>          .aggregate = FUNC_AGGREGATE_NONE,
> @@ -2227,8 +2234,9 @@ static struct {
>          .flags = 0,
>          .call = absFunc,
>          .check_param_count = sql_builtin_default_check_param_count,
> -        .finalize = NULL},
> -       {.name = "AVG",
> +        .finalize = NULL
> +       }, {
> +        .name = "AVG",
>          .param_count = 1,
>          .returns = FIELD_TYPE_NUMBER,
>          .is_deterministic = false,
> @@ -2236,8 +2244,9 @@ static struct {
>          .flags = 0,
>          .call = sum_step,
>          .check_param_count = sql_builtin_default_check_param_count,
> -        .finalize = avgFinalize},
> -       {.name = "CHAR",
> +        .finalize = avgFinalize
> +       }, {
> +        .name = "CHAR",
> 
> etc
> 
> > diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
> > index 207f57ba6..682bb9c8e 100644
> > --- a/src/box/sql/resolve.c
> > +++ b/src/box/sql/resolve.c
> > @@ -38,6 +38,9 @@
> > #include "sqlInt.h"
> > #include <stdlib.h>
> > #include <string.h>
> > +#include "box/func.h"
> > +#include "box/func_def.h”
> 
> func.h and func_def.h seem to be redundant -
> I’ve removed them and project has compiled.
> 
> > +#include "box/schema.h"
> > 
> > /*
> >  * Walk the expression tree pExpr and increase the aggregate function
> > @@ -596,27 +599,28 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
> > 			int is_agg = 0;	/* True if is an aggregate function */
> > 			int nId;	/* Number of characters in function name */
> > 			const char *zId;	/* The function name. */
> > -			FuncDef *pDef;	/* Information about the function */
> > 
> > 			assert(!ExprHasProperty(pExpr, EP_xIsSelect));
> > 			zId = pExpr->u.zToken;
> > 			nId = sqlStrlen30(zId);
> > -			pDef = sqlFindFunction(pParse->db, zId, n, 0);
> > -			if (pDef == 0) {
> > -				pDef =
> > -				    sqlFindFunction(pParse->db, zId, -2,0);
> > -				if (pDef == 0) {
> > +			struct func *func = sql_func_by_signature(zId, n);
> > +			if (func == NULL) {
> > +				func = func_by_name(zId, nId);
> 
> This makes you include box/schema.h
> 
> > +				if (func == NULL || !func->def->exports.sql)
> > 					no_such_func = 1;
> 
> "No such function” and “not available in SQL” are different errors.
> Please, handle both cases with proper error messages and add
> test cases.
> 
> > -				} else {
> > +				else
> > 					wrong_num_args = 1;
> > -				}
> > 			} else {
> > -				is_agg = pDef->xFinalize != 0;
> > -				pExpr->type = pDef->ret_type;
> > +				is_agg = func->def->language ==
> > +					 FUNC_LANGUAGE_SQL_BUILTIN &&
> > +					 func->def->aggregate ==
> > +					 FUNC_AGGREGATE_GROUP;
> 
> == FUNC_AGGREGATE — check on language is redundant.
> Instead, add an assertion.
> 
> > diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> > index ddb5de87e..b2ce868d6 100644
> > --- a/src/box/sql/select.c
> > +++ b/src/box/sql/select.c
> > @@ -4381,7 +4381,9 @@ is_simple_count(struct Select *select, struct AggInfo *agg_info)
> > 		return NULL;
> > 	if (NEVER(agg_info->nFunc == 0))
> > 		return NULL;
> > -	if ((agg_info->aFunc->pFunc->funcFlags & SQL_FUNC_COUNT) == 0 ||
> > +	assert(agg_info->aFunc->func->def->language ==
> > +	       FUNC_LANGUAGE_SQL_BUILTIN);
> > +	if (!sql_func_flag_is_set(agg_info->aFunc->func, SQL_FUNC_COUNT) == 0 ||
> 
> Nit: sql_func_flag_is_set() returns boolean, no need to check
> it on equality to zero. 
> 
> > diff --git a/src/box/alter.cc b/src/box/alter.cc
> > index 4f2e34bf0..22d3040ef 100644
> > --- a/src/box/alter.cc
> > +++ b/src/box/alter.cc
> > @@ -2926,6 +2926,12 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
> > 				  (unsigned) old_func->def->uid,
> > 				  "function has references");
> > 		}
> > +		/* Can't' drop a builtin function. */
> > +		if (old_func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
> > +			tnt_raise(ClientError, ER_DROP_FUNCTION,
> > +				  (unsigned) old_func->def->uid,
> > +				  "function is SQL builtin”);
> 
> Nit: …SQL built-in.
> 
> 

-- 
Konstantin Osipov, Moscow, Russia

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

end of thread, other threads:[~2019-08-20 19:12 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-08-16 13:26 [tarantool-patches] [PATCH v3 0/9] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 1/9] sql: remove SQL_PreferBuiltin flag Kirill Shcherbatov
2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 2/9] sql: GREATEST, LEAST instead of MIN/MAX overload Kirill Shcherbatov
2019-08-16 18:57   ` [tarantool-patches] " n.pettik
2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 3/9] sql: wrap all trim functions in dispatcher Kirill Shcherbatov
2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 4/9] sql: rework SQL_FUNC_COUNT flag semantics Kirill Shcherbatov
2019-08-16 18:55   ` [tarantool-patches] " n.pettik
2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 5/9] sql: rename OP_Function to OP_BuiltinFunction Kirill Shcherbatov
2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 6/9] sql: remove SQL_FUNC_SLOCHNG flag Kirill Shcherbatov
2019-08-16 18:54   ` [tarantool-patches] " n.pettik
2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 7/9] sql: get rid of FuncDef function hash Kirill Shcherbatov
2019-08-16 14:09   ` [tarantool-patches] " Konstantin Osipov
2019-08-16 18:59     ` n.pettik
2019-08-19 15:51     ` Kirill Shcherbatov
2019-08-20 13:47       ` Konstantin Osipov
2019-08-20 19:04       ` n.pettik
2019-08-20 19:12         ` Konstantin Osipov
2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 8/9] sql: get rid of box.internal.sql_function_create Kirill Shcherbatov
2019-08-16 13:26 ` [tarantool-patches] [PATCH v3 9/9] sql: better error messages on invalid arguments Kirill Shcherbatov

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