Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH v2 0/8] sql: uniform SQL and Lua functions subsystem
@ 2019-08-08 14:50 Kirill Shcherbatov
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 1/8] sql: remove SQL_PreferBuiltin flag Kirill Shcherbatov
                   ` (7 more replies)
  0 siblings, 8 replies; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-08 14:50 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.

Changes in version 2:
  - introduced GREATER, LESSER entries
  - reworked multiple signatures for functions: only SQL builtins
    support name overload and it is trivial and processed by
    single function implementation
  - introduced OP_BuiltinFunction to use func_sql_builtin's
    function implementation directly without port proxy object

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

Kirill Shcherbatov (8):
  sql: remove SQL_PreferBuiltin flag
  sql: GREATEST, LEAST instead of MIN/MAX overload
  sql: wrap all trim functions in dispatcher
  sql: get rid of SQL_FUNC_COUNT flag
  sql: introduce a signature_mask for functions
  sql: rename OP_Function to OP_BuiltinFunction
  sql: get rid of FuncDef function hash
  box: get rid of box.internal.sql_function_create

 src/box/func_def.h              |   2 +-
 src/box/lua/lua_sql.h           |  39 --
 src/box/port.h                  |  17 +
 src/box/sql.h                   |   5 +
 src/box/sql/sqlInt.h            | 240 ++++--------
 src/box/sql/vdbe.h              |   9 +-
 src/box/sql/vdbeInt.h           |  26 +-
 src/lib/core/port.h             |  19 +
 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.c                   |  24 ++
 src/box/sql/analyze.c           |  41 +--
 src/box/sql/callback.c          | 212 +----------
 src/box/sql/date.c              |  28 --
 src/box/sql/expr.c              |  64 ++--
 src/box/sql/func.c              | 627 ++++++++++++++++++++++++++------
 src/box/sql/global.c            |   7 -
 src/box/sql/main.c              | 137 -------
 src/box/sql/resolve.c           |  50 +--
 src/box/sql/select.c            |   9 +-
 src/box/sql/vdbe.c              |  94 +++--
 src/box/sql/vdbeapi.c           |  11 +-
 src/box/sql/vdbeaux.c           |  31 +-
 src/box/sql/vdbemem.c           |  50 ++-
 src/box/sql/whereexpr.c         |   2 +-
 src/box/CMakeLists.txt          |   1 -
 src/box/bootstrap.snap          | Bin 5907 -> 5944 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/function1.result       | 179 ++++++++-
 test/box/function1.test.lua     |  62 +++-
 test/sql-tap/alias.test.lua     |  11 +-
 test/sql-tap/check.test.lua     |  11 +-
 test/sql-tap/coalesce.test.lua  |   2 +-
 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/lua_sql.test.lua   | 121 +++---
 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/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/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 +-
 68 files changed, 1458 insertions(+), 1392 deletions(-)
 delete mode 100644 src/box/lua/lua_sql.h
 delete mode 100644 src/box/lua/lua_sql.c

-- 
2.22.0

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

* [tarantool-patches] [PATCH v2 1/8] sql: remove SQL_PreferBuiltin flag
  2019-08-08 14:50 [tarantool-patches] [PATCH v2 0/8] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
@ 2019-08-08 14:50 ` Kirill Shcherbatov
  2019-08-09 16:07   ` [tarantool-patches] " n.pettik
  2019-08-12 21:58   ` Konstantin Osipov
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 2/8] sql: GREATEST, LEAST instead of MIN/MAX overload Kirill Shcherbatov
                   ` (6 subsequent siblings)
  7 siblings, 2 replies; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-08 14:50 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.0

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

* [tarantool-patches] [PATCH v2 2/8] sql: GREATEST, LEAST instead of MIN/MAX overload
  2019-08-08 14:50 [tarantool-patches] [PATCH v2 0/8] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 1/8] sql: remove SQL_PreferBuiltin flag Kirill Shcherbatov
@ 2019-08-08 14:50 ` Kirill Shcherbatov
  2019-08-09 17:37   ` [tarantool-patches] " n.pettik
  2019-08-12 21:59   ` Konstantin Osipov
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 3/8] sql: wrap all trim functions in dispatcher Kirill Shcherbatov
                   ` (5 subsequent siblings)
  7 siblings, 2 replies; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-08 14:50 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

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. This must be fixed.

Moreover it is an important stem 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
---
 src/box/sql/func.c             |   6 +-
 src/box/bootstrap.snap         | Bin 5907 -> 5944 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/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 +-
 28 files changed, 172 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..73b39332e 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.2.2
+--------------------------------------------------------------------------------
+
+local function upgrade_to_2_2_2()
+    log.info("Create GREATEST and LEAST SQL Builtins")
+    local new_builtins = {"GREATEST", "LEAST"}
+    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")
+    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, 2, 2), func = upgrade_to_2_2_2, auto = true},
     }
 
     for _, handler in ipairs(handlers) do
diff --git a/test-run b/test-run
index 947c65cda..f8311bc09 160000
--- a/test-run
+++ b/test-run
@@ -1 +1 @@
-Subproject commit 947c65cda7f26ceb5f601bbf154e70f9c3f755e9
+Subproject commit f8311bc092b99d0a1c05740673b8a46c3f83296f
diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
index 7fc0ca97d..25f374bab 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, 2, 2]
 ...
 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..8ae77abc3 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/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 82ad2eddb..69df26238 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.0

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

* [tarantool-patches] [PATCH v2 3/8] sql: wrap all trim functions in dispatcher
  2019-08-08 14:50 [tarantool-patches] [PATCH v2 0/8] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 1/8] sql: remove SQL_PreferBuiltin flag Kirill Shcherbatov
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 2/8] sql: GREATEST, LEAST instead of MIN/MAX overload Kirill Shcherbatov
@ 2019-08-08 14:50 ` Kirill Shcherbatov
  2019-08-09 18:05   ` [tarantool-patches] " n.pettik
  2019-08-12 22:00   ` Konstantin Osipov
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 4/8] sql: get rid of SQL_FUNC_COUNT flag Kirill Shcherbatov
                   ` (4 subsequent siblings)
  7 siblings, 2 replies; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-08 14:50 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 stem 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 | 32 +++++++++++++++++++++++++++++---
 1 file changed, 29 insertions(+), 3 deletions(-)

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index e00764c3f..f9c0a819e 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1496,6 +1496,32 @@ trim_func_three_args(struct sql_context *context, int argc, sql_value **argv)
 		       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 call 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, argc, argv);
+		break;
+	case 2:
+		trim_func_two_args(context, argc, argv);
+		break;
+	case 3:
+		trim_func_three_args(context, argc, argv);
+		break;
+	default:
+		unreachable();
+	}
+}
+
 /*
  * Compute the soundex encoding of a word.
  *
@@ -1840,9 +1866,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.0

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

* [tarantool-patches] [PATCH v2 4/8] sql: get rid of SQL_FUNC_COUNT flag
  2019-08-08 14:50 [tarantool-patches] [PATCH v2 0/8] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
                   ` (2 preceding siblings ...)
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 3/8] sql: wrap all trim functions in dispatcher Kirill Shcherbatov
@ 2019-08-08 14:50 ` Kirill Shcherbatov
  2019-08-12 22:01   ` [tarantool-patches] " Konstantin Osipov
  2019-08-13 20:35   ` n.pettik
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 5/8] sql: introduce a signature_mask for functions Kirill Shcherbatov
                   ` (3 subsequent siblings)
  7 siblings, 2 replies; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-08 14:50 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. Using SQL_FUNC_COUNT flag to mark a
dummy (non-functional) function entry with 0 arguments is a bad
practice and should be reworked in relation with introducing
a uniform functions cache in further patches.

Therefore SQL_FUNC_COUNT flag test in is_simple_count helper
was replaced with more straightforward test with manually looking
for count of function arguments passed.

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

diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 4adb30c5c..941c87420 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -1312,7 +1312,6 @@ 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 */
 #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 f9c0a819e..e011fd958 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1910,8 +1910,8 @@ sqlRegisterBuiltinFunctions(void)
 			  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),
+		AGGREGATE(count, 0, 0, 0, countStep, countFinalize,
+			  FIELD_TYPE_INTEGER),
 		AGGREGATE(count, 1, 0, 0, countStep, countFinalize,
 			  FIELD_TYPE_INTEGER),
 		AGGREGATE(group_concat, 1, 0, 0, groupConcatStep,
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index c31076694..921a52150 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4381,7 +4381,8 @@ 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->pExpr->x.pList != NULL &&
+	    agg_info->aFunc->pExpr->x.pList->nExpr > 0)
 		return NULL;
 	if (expr->flags & EP_Distinct)
 		return NULL;
-- 
2.22.0

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

* [tarantool-patches] [PATCH v2 5/8] sql: introduce a signature_mask for functions
  2019-08-08 14:50 [tarantool-patches] [PATCH v2 0/8] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
                   ` (3 preceding siblings ...)
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 4/8] sql: get rid of SQL_FUNC_COUNT flag Kirill Shcherbatov
@ 2019-08-08 14:50 ` Kirill Shcherbatov
  2019-08-12 22:04   ` [tarantool-patches] " Konstantin Osipov
  2019-08-13 20:38   ` n.pettik
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 6/8] sql: rename OP_Function to OP_BuiltinFunction Kirill Shcherbatov
                   ` (2 subsequent siblings)
  7 siblings, 2 replies; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-08 14:50 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

This patch replaces nArgs field with signature_mask bitmask that
allows to use an only hash table entry for all builtin functions
overloads.

The code refactoring is not a goal of this patch: the most of
affected code would be removed in following patches. The role of
this patch itself is to introduce such mechanism (signature_mask)
in Tarantool's SQL.

Needed for #2200, #4113, #2233
---
 src/box/sql/sqlInt.h   | 70 ++++++++++++++++++++------------
 src/box/sql/callback.c | 22 +++-------
 src/box/sql/func.c     | 91 ++++++++++++++++++------------------------
 src/box/sql/main.c     |  4 +-
 src/box/sql/vdbeaux.c  |  6 ++-
 5 files changed, 94 insertions(+), 99 deletions(-)

diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 941c87420..114ac0e4b 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -1254,7 +1254,20 @@ struct type_def {
  * field is used by per-connection app-def functions.
  */
 struct FuncDef {
-	i8 nArg;		/* Number of arguments.  -1 means unlimited */
+	/**
+	 * A bitmask representing all supported function
+	 * overloads. The function supports argc == n iff this
+	 * bitmask has bit n set 1. In particular case, a bitmask
+	 * (~0) means this function works with any possible
+	 * argument.
+	 *
+	 * The count of arguments for function is limited with
+	 * (CHAR_BITS*sizeof(uint64_t) - 1). When the highest bit
+	 * of the mask is set, this means that greater values
+	 * are supported. E.g. greatest function works correctly
+	 * with any number of input arguments.
+	 */
+	uint64_t signature_mask;
 	u16 funcFlags;		/* Some combination of sql_FUNC_* */
 	void *pUserData;	/* User data parameter */
 	FuncDef *pNext;		/* Next function with same name */
@@ -1342,9 +1355,9 @@ enum trim_side_mask {
  * The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are
  * used to create the initializers for the FuncDef structures.
  *
- *   FUNCTION(zName, nArg, iArg, bNC, xFunc)
+ *   FUNCTION(zName, mask, iArg, bNC, xFunc)
  *     Used to create a scalar function definition of a function zName
- *     implemented by C function xFunc that accepts nArg arguments. The
+ *     implemented by C function xFunc that accepts mask 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.
@@ -1354,56 +1367,61 @@ enum trim_side_mask {
  *     STRING which collation should be derived from first
  *     argument (trim, substr etc).
  *
- *   VFUNCTION(zName, nArg, iArg, bNC, xFunc)
+ *   VFUNCTION(zName, mask, iArg, bNC, xFunc)
  *     Like FUNCTION except it omits the sql_FUNC_CONSTANT flag.
  *
- *   DFUNCTION(zName, nArg, iArg, bNC, xFunc)
+ *   DFUNCTION(zName, mask, 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)
+ *   AGGREGATE(zName, mask, 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)
+ *   LIKEFUNC(zName, mask, 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
+ *     that accepts mask 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), \
+#define FUNCTION(zName, mask, iArg, bNC, xFunc, type) \
+  {mask, 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), \
+#define FUNCTION_COLL(zName, mask, iArg, bNC, xFunc) \
+  {mask, 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), \
+#define VFUNCTION(zName, mask, iArg, bNC, xFunc, type) \
+  {mask, (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), \
+#define DFUNCTION(zName, mask, iArg, bNC, xFunc, type) \
+  {mask, 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,\
+#define FUNCTION2(zName, mask, iArg, bNC, xFunc, extraFlags, type) \
+  {mask,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), \
+#define STR_FUNCTION(zName, mask, pArg, bNC, xFunc) \
+  {mask, 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, \
+#define LIKEFUNC(zName, mask, arg, flags, type) \
+  {mask, 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), \
+#define AGGREGATE(zName, mask, arg, nc, xStep, xFinal, type) \
+  {mask, (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, \
+#define AGGREGATE2(zName, mask, arg, nc, xStep, xFinal, extraFlags, type) \
+  {mask, (nc*SQL_FUNC_NEEDCOLL)|extraFlags, \
    SQL_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}, type}
 
+#define ARGC_MASK_FULL (~0ULL)
+#define ARGC_MASK(a) (a < 0 ? ARGC_MASK_FULL : (1ULL << a))
+#define ARGC_MASK2(a, b) (ARGC_MASK(a) | ARGC_MASK(b))
+#define ARGC_MASK3(a, b, c) (ARGC_MASK2(a, b) | ARGC_MASK(c))
+
 /*
  * All current savepoints are stored in a linked list starting at
  * sql.pSavepoint. The first element in the list is the most recently
diff --git a/src/box/sql/callback.c b/src/box/sql/callback.c
index 6c272de71..737c24d98 100644
--- a/src/box/sql/callback.c
+++ b/src/box/sql/callback.c
@@ -36,6 +36,7 @@
  */
 
 #include "box/coll_id_cache.h"
+#include "box/column_mask.h"
 #include "sqlInt.h"
 #include "box/session.h"
 
@@ -91,26 +92,13 @@ 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;
-
+		return FUNC_PERFECT_MATCH;
 	/* Wrong number of arguments means "no match" */
-	if (p->nArg != nArg && p->nArg >= 0)
+	if (!column_mask_fieldno_is_set(p->signature_mask, (uint32_t)nArg))
 		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;
+	return p->signature_mask == ARGC_MASK(nArg) ? FUNC_PERFECT_MATCH : 1;
 }
 
 /*
@@ -240,7 +228,7 @@ sqlFindFunction(sql * db,	/* An open database */
 	     sqlDbMallocZero(db, sizeof(*pBest) + nName + 1)) != 0) {
 		FuncDef *pOther;
 		pBest->zName = (const char *)&pBest[1];
-		pBest->nArg = (u16) nArg;
+		pBest->signature_mask = ARGC_MASK(nArg);
 		pBest->funcFlags = 0;
 		memcpy((char *)&pBest[1], zName, nName + 1);
 		pOther =
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index e011fd958..8e07ce892 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1859,73 +1859,60 @@ sqlRegisterBuiltinFunctions(void)
 	 * 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,
+		FUNCTION(soundex, ARGC_MASK(1), 0, 0, soundexFunc, FIELD_TYPE_STRING),
+		FUNCTION2(unlikely, ARGC_MASK(1), 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
 			  FIELD_TYPE_BOOLEAN),
-		FUNCTION2(likelihood, 2, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
+		FUNCTION2(likelihood, ARGC_MASK(2), 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
 			  FIELD_TYPE_BOOLEAN),
-		FUNCTION2(likely, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
+		FUNCTION2(likely, ARGC_MASK(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,
+		FUNCTION_COLL(trim, ARGC_MASK3(1, 2, 3), 3, 0, trim_func),
+		FUNCTION(least, ARGC_MASK_FULL, 0, 1, minmaxFunc, FIELD_TYPE_SCALAR),
+		AGGREGATE2(min, ARGC_MASK(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,
+		FUNCTION(greatest, ARGC_MASK_FULL, 1, 1, minmaxFunc, FIELD_TYPE_SCALAR),
+		AGGREGATE2(max, ARGC_MASK(1), 1, 1, minmaxStep, minMaxFinalize,
 			   SQL_FUNC_MINMAX, FIELD_TYPE_SCALAR),
-		FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQL_FUNC_TYPEOF,
+		FUNCTION2(typeof, ARGC_MASK(1), 0, 0, typeofFunc, SQL_FUNC_TYPEOF,
 			  FIELD_TYPE_STRING),
-		FUNCTION2(length, 1, 0, 0, lengthFunc, SQL_FUNC_LENGTH,
+		FUNCTION2(length, ARGC_MASK(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,
+		FUNCTION(char_length, ARGC_MASK(1), 0, 0, lengthFunc, FIELD_TYPE_INTEGER),
+		FUNCTION(character_length, ARGC_MASK(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,
+		FUNCTION(position, ARGC_MASK(2), 0, 1, position_func, FIELD_TYPE_INTEGER),
+		FUNCTION(printf, ARGC_MASK_FULL, 0, 0, printfFunc, FIELD_TYPE_STRING),
+		FUNCTION(unicode, ARGC_MASK(1), 0, 0, unicodeFunc, FIELD_TYPE_STRING),
+		FUNCTION(char, ARGC_MASK_FULL, 0, 0, charFunc, FIELD_TYPE_STRING),
+		FUNCTION(abs, ARGC_MASK(1), 0, 0, absFunc, FIELD_TYPE_NUMBER),
+		FUNCTION(round, ARGC_MASK2(1, 2), 0, 0, roundFunc, FIELD_TYPE_INTEGER),
+		FUNCTION_COLL(upper, ARGC_MASK(1), 0, 1, UpperICUFunc),
+		FUNCTION_COLL(lower, ARGC_MASK(1), 0, 1, LowerICUFunc),
+		FUNCTION(hex, ARGC_MASK(1), 0, 0, hexFunc, FIELD_TYPE_STRING),
+		FUNCTION2(ifnull, ARGC_MASK(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,
+		VFUNCTION(random, ARGC_MASK(0), 0, 0, randomFunc, FIELD_TYPE_INTEGER),
+		VFUNCTION(randomblob, ARGC_MASK(1), 0, 0, randomBlob, FIELD_TYPE_VARBINARY),
+		FUNCTION(nullif, ARGC_MASK(2), 0, 1, nullifFunc, FIELD_TYPE_SCALAR),
+		FUNCTION(version, ARGC_MASK(0), 0, 0, sql_func_version, FIELD_TYPE_STRING),
+		FUNCTION(quote, ARGC_MASK(1), 0, 0, quoteFunc, FIELD_TYPE_STRING),
+		VFUNCTION(row_count, ARGC_MASK(0), 0, 0, sql_row_count, FIELD_TYPE_INTEGER),
+		FUNCTION_COLL(replace, ARGC_MASK(3), 0, 0, replaceFunc),
+		FUNCTION(zeroblob, ARGC_MASK(1), 0, 0, zeroblobFunc, FIELD_TYPE_VARBINARY),
+		FUNCTION_COLL(substr, ARGC_MASK2(2, 3), 0, 0, substrFunc),
+		AGGREGATE(sum, ARGC_MASK(1), 0, 0, sum_step, sumFinalize,
 			  FIELD_TYPE_NUMBER),
-		AGGREGATE(total, 1, 0, 0, sum_step, totalFinalize,
+		AGGREGATE(total, ARGC_MASK(1), 0, 0, sum_step, totalFinalize,
 			  FIELD_TYPE_NUMBER),
-		AGGREGATE(avg, 1, 0, 0, sum_step, avgFinalize,
+		AGGREGATE(avg, ARGC_MASK(1), 0, 0, sum_step, avgFinalize,
 			  FIELD_TYPE_NUMBER),
-		AGGREGATE(count, 0, 0, 0, countStep, countFinalize,
+		AGGREGATE(count, ARGC_MASK2(0, 1), 0, 0, countStep, countFinalize,
 			  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,
+		AGGREGATE(group_concat, ARGC_MASK2(1, 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,
+		LIKEFUNC(like, ARGC_MASK2(2, 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,
+		FUNCTION2(coalesce, ARGC_MASK_FULL & ~ARGC_MASK2(0, 1), 0, 0, noopFunc, SQL_FUNC_COALESCE,
 			  FIELD_TYPE_SCALAR),
 	};
 	sql_register_analyze_builtins();
diff --git a/src/box/sql/main.c b/src/box/sql/main.c
index 748dc1b8e..eb6e4a7db 100644
--- a/src/box/sql/main.c
+++ b/src/box/sql/main.c
@@ -321,7 +321,7 @@ sqlCreateFunc(sql * db,
 	 * operation to continue but invalidate all precompiled statements.
 	 */
 	p = sqlFindFunction(db, zFunctionName, nArg, 0);
-	if (p && p->nArg == nArg) {
+	if (p != NULL && (p->signature_mask & nArg) != 0) {
 		if (db->nVdbeActive) {
 			diag_set(ClientError, ER_CREATE_FUNCTION, zFunctionName,
 				 "unable to create function due to active "\
@@ -351,7 +351,7 @@ sqlCreateFunc(sql * db,
 	p->xSFunc = xSFunc ? xSFunc : xStep;
 	p->xFinalize = xFinal;
 	p->pUserData = pUserData;
-	p->nArg = (u16) nArg;
+	p->signature_mask = ARGC_MASK(nArg);
 	p->ret_type = type;
 	return 0;
 }
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index e7accc745..d32404580 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -1151,13 +1151,15 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
 		}
 	case P4_FUNCDEF:{
 			FuncDef *pDef = pOp->p4.pFunc;
-			sqlXPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg);
+			sqlXPrintf(&x, "%s(%d)", pDef->zName,
+				   pDef->signature_mask);
 			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);
+			sqlXPrintf(&x, "%s(%d)", pDef->zName,
+				   pDef->signature_mask);
 			break;
 		}
 #endif
-- 
2.22.0

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

* [tarantool-patches] [PATCH v2 6/8] sql: rename OP_Function to OP_BuiltinFunction
  2019-08-08 14:50 [tarantool-patches] [PATCH v2 0/8] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
                   ` (4 preceding siblings ...)
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 5/8] sql: introduce a signature_mask for functions Kirill Shcherbatov
@ 2019-08-08 14:50 ` Kirill Shcherbatov
  2019-08-12 22:04   ` [tarantool-patches] " Konstantin Osipov
  2019-08-13 20:36   ` n.pettik
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 7/8] sql: get rid of FuncDef function hash Kirill Shcherbatov
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 8/8] box: get rid of box.internal.sql_function_create Kirill Shcherbatov
  7 siblings, 2 replies; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-08 14:50 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.0

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

* [tarantool-patches] [PATCH v2 7/8] sql: get rid of FuncDef function hash
  2019-08-08 14:50 [tarantool-patches] [PATCH v2 0/8] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
                   ` (5 preceding siblings ...)
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 6/8] sql: rename OP_Function to OP_BuiltinFunction Kirill Shcherbatov
@ 2019-08-08 14:50 ` Kirill Shcherbatov
  2019-08-12 22:11   ` [tarantool-patches] " Konstantin Osipov
  2019-08-13 20:40   ` n.pettik
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 8/8] box: get rid of box.internal.sql_function_create Kirill Shcherbatov
  7 siblings, 2 replies; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-08 14:50 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 typica while
port API call is not supported and protected with stub.

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 would be fixed in the following 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         | 248 +++++++---------------------
 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.c                |  23 +++
 src/box/sql/analyze.c        |  35 ++--
 src/box/sql/callback.c       | 191 ----------------------
 src/box/sql/date.c           |  28 ----
 src/box/sql/expr.c           |  64 ++++----
 src/box/sql/func.c           | 309 ++++++++++++++++++++++++-----------
 src/box/sql/global.c         |   7 -
 src/box/sql/main.c           | 137 ----------------
 src/box/sql/resolve.c        |  50 +++---
 src/box/sql/select.c         |   6 +-
 src/box/sql/vdbe.c           |  20 ++-
 src/box/sql/vdbeapi.c        |  11 +-
 src/box/sql/vdbeaux.c        |  33 +---
 src/box/sql/vdbemem.c        |  50 +++---
 src/box/sql/whereexpr.c      |   2 +-
 src/box/CMakeLists.txt       |   1 -
 test/sql-tap/where2.test.lua |   4 +-
 25 files changed, 456 insertions(+), 1080 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 114ac0e4b..6ef1a1d4a 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"
@@ -566,26 +568,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 +1021,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 +1102,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,78 +1211,12 @@ 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 {
-	/**
-	 * A bitmask representing all supported function
-	 * overloads. The function supports argc == n iff this
-	 * bitmask has bit n set 1. In particular case, a bitmask
-	 * (~0) means this function works with any possible
-	 * argument.
-	 *
-	 * The count of arguments for function is limited with
-	 * (CHAR_BITS*sizeof(uint64_t) - 1). When the highest bit
-	 * of the mask is set, this means that greater values
-	 * are supported. E.g. greatest function works correctly
-	 * with any number of input arguments.
-	 */
-	uint64_t signature_mask;
-	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
@@ -1327,7 +1228,6 @@ struct FuncDestructor {
 #define SQL_FUNC_TYPEOF   0x0080	/* Built-in typeof() function */
 #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 */
 #define SQL_FUNC_SLOCHNG  0x2000	/* "Slow Change". Value constant during a
 					 * single query - might change over time
@@ -1351,72 +1251,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, mask, iArg, bNC, xFunc)
- *     Used to create a scalar function definition of a function zName
- *     implemented by C function xFunc that accepts mask 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, mask, iArg, bNC, xFunc)
- *     Like FUNCTION except it omits the sql_FUNC_CONSTANT flag.
- *
- *   DFUNCTION(zName, mask, 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, mask, 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, mask, pArg, flags)
- *     Used to create a scalar function definition of a function zName
- *     that accepts mask 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, mask, iArg, bNC, xFunc, type) \
-  {mask, SQL_FUNC_CONSTANT|(bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define FUNCTION_COLL(zName, mask, iArg, bNC, xFunc) \
-  {mask, 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, mask, iArg, bNC, xFunc, type) \
-  {mask, (bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define DFUNCTION(zName, mask, iArg, bNC, xFunc, type) \
-  {mask, SQL_FUNC_SLOCHNG|(bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define FUNCTION2(zName, mask, iArg, bNC, xFunc, extraFlags, type) \
-  {mask,SQL_FUNC_CONSTANT|(bNC*SQL_FUNC_NEEDCOLL)|extraFlags,\
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define STR_FUNCTION(zName, mask, pArg, bNC, xFunc) \
-  {mask, SQL_FUNC_SLOCHNG|(bNC*SQL_FUNC_NEEDCOLL), \
-   pArg, 0, xFunc, 0, #zName, {SQL_AFF_STRING, {0}}}
-#define LIKEFUNC(zName, mask, arg, flags, type) \
-  {mask, SQL_FUNC_NEEDCOLL|SQL_FUNC_CONSTANT|flags, \
-   (void *)(SQL_INT_TO_PTR(arg)), 0, likeFunc, 0, #zName, {0}, type}
-#define AGGREGATE(zName, mask, arg, nc, xStep, xFinal, type) \
-  {mask, (nc*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}, type}
-#define AGGREGATE2(zName, mask, arg, nc, xStep, xFinal, extraFlags, type) \
-  {mask, (nc*SQL_FUNC_NEEDCOLL)|extraFlags, \
-   SQL_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}, type}
-
 #define ARGC_MASK_FULL (~0ULL)
 #define ARGC_MASK(a) (a < 0 ? ARGC_MASK_FULL : (1ULL << a))
 #define ARGC_MASK2(a, b) (ARGC_MASK(a) | ARGC_MASK(b))
@@ -1614,7 +1448,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 */
 		/**
@@ -3562,10 +3397,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.
@@ -4138,7 +3969,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;
 
 /**
@@ -4274,21 +4104,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
@@ -4325,7 +4147,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,
@@ -4516,9 +4337,62 @@ 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 representing all supported function
+	 * overloads. The function supports argc == n iff this
+	 * bitmask has bit n set 1. In particular case, a bitmask
+	 * (~0) means this function works with any possible
+	 * argument.
+	 *
+	 * The count of arguments for function is limited with
+	 * (CHAR_BITS*sizeof(uint64_t) - 1). When the highest bit
+	 * of the mask is set, this means that greater values
+	 * are supported. E.g. greatest function works correctly
+	 * with any number of input arguments.
+	 */
+	uint64_t signature_mask;
+	/** A bitmask of SQL flags. */
+	uint16_t flags;
+	/** User data to pass in call method. */
+	void *user_data;
+	/** A call method for a function. */
+	void (*call)(sql_context *ctx, int argc, sql_value **argv);
+	/** Finalize method (only for aggregate function). */
+	void (*finalize)(sql_context *ctx);
+};
+
+/**
+ * A SQL method to find a function in a hash by it's 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 was found and NULL otherwise.
+ */
+struct func *
+sql_func_by_signature(const char *name, uint32_t argc);
+
+/**
+ * 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_test(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;
+}
 
 /**
  * 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..f085477c1 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 pMem.
+ *
+ * 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.c b/src/box/sql.c
index 0ab3a506f..a731332c7 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1262,3 +1262,26 @@ vdbe_field_ref_prepare_tuple(struct vdbe_field_ref *field_ref,
 	vdbe_field_ref_create(field_ref, tuple, tuple_data(tuple),
 			      tuple->bsize);
 }
+
+struct func *
+sql_func_by_signature(const char *name, uint32_t 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) {
+		return NULL;
+	} else {
+		/*
+		 * The param_count field is not valid for sql
+		 * builtin functions because they define an
+		 * signature_mask with all supported overloads.
+		 */
+		struct func_sql_builtin *builtin =
+			(struct func_sql_builtin *)func;
+		if (!column_mask_fieldno_is_set(builtin->signature_mask,
+						argc))
+			return NULL;
+	}
+	return func;
+}
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index bd52d12df..b68d86dfe 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,11 @@ 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 +855,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 +956,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 +1746,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 737c24d98..b489f0c81 100644
--- a/src/box/sql/callback.c
+++ b/src/box/sql/callback.c
@@ -57,194 +57,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 */
-    )
-{
-	/* nArg of -2 is a special case */
-	if (nArg == (-2))
-		return FUNC_PERFECT_MATCH;
-	/* Wrong number of arguments means "no match" */
-	if (!column_mask_fieldno_is_set(p->signature_mask, (uint32_t)nArg))
-		return 0;
-	return p->signature_mask == ARGC_MASK(nArg) ? FUNC_PERFECT_MATCH : 1;
-}
-
-/*
- * 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->signature_mask = ARGC_MASK(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..64b3bc835 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_test(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,9 @@ 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 ||
+			    func->def->aggregate == FUNC_AGGREGATE_GROUP) {
 				diag_set(ClientError, ER_NO_SUCH_FUNCTION,
 					 zId);
 				pParse->is_aborted = true;
@@ -4002,7 +4000,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_test(func, SQL_FUNC_COALESCE)) {
 				int endCoalesce = sqlVdbeMakeLabel(v);
 				assert(nFarg >= 2);
 				sqlExprCode(pParse, pFarg->a[0].pExpr,
@@ -4026,7 +4024,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_test(func, SQL_FUNC_UNLIKELY)) {
 				assert(nFarg >= 1);
 				return sqlExprCodeTarget(pParse,
 							     pFarg->a[0].pExpr,
@@ -4049,7 +4047,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_test(func, SQL_FUNC_NEEDCOLL)) {
 				struct coll *unused = NULL;
 				uint32_t curr_id = COLL_NONE;
 				bool is_curr_forced = false;
@@ -4096,9 +4094,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_test(func, SQL_FUNC_LENGTH |
+							     SQL_FUNC_TYPEOF)) {
 					u8 exprOp;
 					assert(nFarg == 1);
 					assert(pFarg->a[0].pExpr != 0);
@@ -4109,14 +4106,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 +4118,15 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			} else {
 				r1 = 0;
 			}
-			if (pDef->funcFlags & SQL_FUNC_NEEDCOLL) {
+			if (sql_func_flag_test(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 +5434,19 @@ 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);
+						uint32_t argc =
+							pExpr->x.pList != NULL ?
+							pExpr->x.pList->nExpr : 0;
+						pItem->func =
+							sql_func_by_signature(
+								pExpr->u.zToken,
+								argc);
+						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 8e07ce892..f07c52b95 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -1825,114 +1825,231 @@ 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);
+	struct func *func = sql_func_by_signature(expr->u.zToken, 2);
 	assert(func != NULL);
-	if ((func->funcFlags & SQL_FUNC_LIKE) == 0)
+	if (!sql_func_flag_test(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;
+}
+
+#define REG_FUNC(name, signature_mask, returns, flags,     \
+		 call, user_data, is_deterministic)        \
+	{name, signature_mask, returns, flags, call, NULL, \
+	 user_data, FUNC_AGGREGATE_NONE, is_deterministic}
+
+#define AGG_FUNC(name, signature_mask, returns, flags,     \
+		 call, finalize, user_data)                \
+	{name, signature_mask, returns, flags, call,       \
+	 finalize, user_data, FUNC_AGGREGATE_GROUP, false}
+
+#define STUB_FUNC(name)                                    \
+	{name, 0, FIELD_TYPE_ANY, 0, sql_builtin_stub,     \
+	 NULL, NULL, FUNC_AGGREGATE_NONE, false}
+
+/**
+ * A sequence of SQL builtins definitions in
+ * lexicographic order.
  */
-void
-sqlRegisterBuiltinFunctions(void)
+static struct {
+	const char *name;
+	uint64_t signature_mask;
+	enum field_type returns;
+	uint16_t flags;
+	void (*call)(sql_context *ctx, int argc, sql_value **argv);
+	void (*finalize)(sql_context *ctx);
+	void *user_data;
+	enum func_aggregate aggregate;
+	bool is_deterministic;
+} sql_builtins[] = {
+	REG_FUNC("ABS", ARGC_MASK(1), FIELD_TYPE_NUMBER, 0,
+		 absFunc, NULL, true),
+	AGG_FUNC("AVG", ARGC_MASK(1), FIELD_TYPE_NUMBER, 0,
+		 sum_step, avgFinalize, NULL),
+	STUB_FUNC("CEIL"), STUB_FUNC("CEILING"),
+	REG_FUNC("CHAR", ARGC_MASK_FULL, FIELD_TYPE_STRING, 0,
+		 charFunc, NULL, true),
+	REG_FUNC("CHARACTER_LENGTH", ARGC_MASK(1), FIELD_TYPE_INTEGER, 0,
+		 lengthFunc, NULL, true),
+	REG_FUNC("CHAR_LENGTH", ARGC_MASK(1), FIELD_TYPE_INTEGER, 0,
+		 lengthFunc, NULL, true),
+	REG_FUNC("COALESCE", ARGC_MASK_FULL & ~ARGC_MASK2(0, 1),
+		 FIELD_TYPE_SCALAR, SQL_FUNC_COALESCE,
+		 sql_builtin_stub, NULL, true),
+	AGG_FUNC("COUNT", ARGC_MASK2(0, 1), FIELD_TYPE_INTEGER, 0,
+		 countStep, countFinalize, NULL),
+	STUB_FUNC("CURRENT_DATE"), STUB_FUNC("CURRENT_TIME"),
+	STUB_FUNC("CURRENT_TIMESTAMP"), STUB_FUNC("DATE"),
+	STUB_FUNC("DATETIME"), STUB_FUNC("EVERY"),
+	STUB_FUNC("EXISTS"), STUB_FUNC("EXP"), STUB_FUNC("EXTRACT"),
+	STUB_FUNC("FLOOR"), STUB_FUNC("GREATER"),
+	REG_FUNC("GREATEST", ARGC_MASK_FULL, FIELD_TYPE_SCALAR,
+		 SQL_FUNC_NEEDCOLL, minmaxFunc, SQL_INT_TO_PTR(1), true),
+	AGG_FUNC("GROUP_CONCAT", ARGC_MASK2(1, 2), FIELD_TYPE_STRING, 0,
+		 groupConcatStep, groupConcatFinalize, NULL),
+	REG_FUNC("HEX", ARGC_MASK(1), FIELD_TYPE_STRING, 0,
+		 hexFunc, NULL, true),
+	REG_FUNC("IFNULL", ARGC_MASK(2), FIELD_TYPE_INTEGER, SQL_FUNC_COALESCE,
+		 sql_builtin_stub, NULL, true),
+	STUB_FUNC("JULIANDAY"),
+	REG_FUNC("LEAST", ARGC_MASK_FULL, FIELD_TYPE_SCALAR, SQL_FUNC_NEEDCOLL,
+		 minmaxFunc, SQL_INT_TO_PTR(0), true),
+	REG_FUNC("LENGTH", ARGC_MASK(1), FIELD_TYPE_INTEGER, SQL_FUNC_LENGTH,
+		 lengthFunc, NULL, true),
+	STUB_FUNC("LESSER"),
+	REG_FUNC("LIKE", ARGC_MASK2(2, 3), FIELD_TYPE_INTEGER,
+		 SQL_FUNC_NEEDCOLL | SQL_FUNC_LIKE,
+		 likeFunc, SQL_INT_TO_PTR(1), true),
+	REG_FUNC("LIKELIHOOD", ARGC_MASK(2), FIELD_TYPE_BOOLEAN,
+		 SQL_FUNC_UNLIKELY, sql_builtin_stub, NULL, true),
+	REG_FUNC("LIKELY", ARGC_MASK(1), FIELD_TYPE_BOOLEAN, SQL_FUNC_UNLIKELY,
+		 sql_builtin_stub, NULL, true),
+	STUB_FUNC("LN"),
+	REG_FUNC("LOWER", ARGC_MASK(1), FIELD_TYPE_STRING,
+		 SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
+		 LowerICUFunc, NULL, true),
+	AGG_FUNC("MAX", ARGC_MASK(1), FIELD_TYPE_SCALAR,
+		 SQL_FUNC_NEEDCOLL | SQL_FUNC_MINMAX,
+		 minmaxStep, minMaxFinalize, SQL_INT_TO_PTR(1)),
+	AGG_FUNC("MIN", ARGC_MASK(1), FIELD_TYPE_SCALAR,
+		 SQL_FUNC_NEEDCOLL | SQL_FUNC_MINMAX,
+		 minmaxStep, minMaxFinalize, SQL_INT_TO_PTR(0)),
+	STUB_FUNC("MOD"),
+	REG_FUNC("NULLIF", ARGC_MASK(2), FIELD_TYPE_SCALAR, SQL_FUNC_NEEDCOLL,
+		 nullifFunc, NULL, true),
+	STUB_FUNC("OCTET_LENGTH"),
+	REG_FUNC("POSITION", ARGC_MASK(2), FIELD_TYPE_INTEGER,
+		 SQL_FUNC_NEEDCOLL, position_func, NULL, true),
+	STUB_FUNC("POWER"),
+	REG_FUNC("PRINTF", ARGC_MASK_FULL, FIELD_TYPE_STRING, 0,
+		 printfFunc, NULL, true),
+	REG_FUNC("QUOTE", ARGC_MASK(1), FIELD_TYPE_STRING, 0,
+		 quoteFunc, NULL, true),
+	REG_FUNC("RANDOM", ARGC_MASK(0), FIELD_TYPE_INTEGER, 0,
+		 randomFunc, NULL, false),
+	REG_FUNC("RANDOMBLOB", ARGC_MASK(1), FIELD_TYPE_VARBINARY, 0,
+		 randomBlob, NULL, false),
+	REG_FUNC("REPLACE", ARGC_MASK(3), FIELD_TYPE_STRING,
+		 SQL_FUNC_DERIVEDCOLL, replaceFunc, NULL, true),
+	REG_FUNC("ROUND", ARGC_MASK2(1, 2), FIELD_TYPE_INTEGER, 0,
+		 roundFunc, NULL, true),
+	REG_FUNC("ROW_COUNT", ARGC_MASK(0), FIELD_TYPE_INTEGER, 0,
+		 sql_row_count, NULL, true),
+	STUB_FUNC("SOME"),
+	REG_FUNC("SOUNDEX", ARGC_MASK(1), FIELD_TYPE_STRING, 0,
+		 soundexFunc, NULL, true),
+	STUB_FUNC("SQRT"), STUB_FUNC("STRFTIME"),
+	REG_FUNC("SUBSTR", ARGC_MASK2(2, 3), FIELD_TYPE_STRING,
+		 SQL_FUNC_DERIVEDCOLL, substrFunc, NULL, true),
+	AGG_FUNC("SUM", ARGC_MASK(1), FIELD_TYPE_NUMBER, 0, sum_step,
+		 sumFinalize, NULL),
+	STUB_FUNC("TIME"),
+	AGG_FUNC("TOTAL", ARGC_MASK(1), FIELD_TYPE_NUMBER, 0, sum_step,
+		 totalFinalize, NULL),
+	REG_FUNC("TRIM", ARGC_MASK3(1, 2, 3), FIELD_TYPE_STRING,
+		 SQL_FUNC_DERIVEDCOLL, trim_func, NULL, true),
+	REG_FUNC("TYPEOF", ARGC_MASK(1), FIELD_TYPE_STRING,
+		 SQL_FUNC_TYPEOF, typeofFunc, NULL, true),
+	REG_FUNC("UNICODE", ARGC_MASK(1), FIELD_TYPE_STRING, 0,
+		 unicodeFunc, NULL, true),
+	REG_FUNC("UNLIKELY", ARGC_MASK(1), FIELD_TYPE_BOOLEAN,
+		 SQL_FUNC_UNLIKELY, sql_builtin_stub, NULL, true),
+	REG_FUNC("UPPER", ARGC_MASK(1), FIELD_TYPE_STRING,
+		 SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
+		 UpperICUFunc, NULL, true),
+	REG_FUNC("VERSION", ARGC_MASK(0), FIELD_TYPE_STRING, 0,
+		 sql_func_version, NULL, true),
+	REG_FUNC("ZEROBLOB", ARGC_MASK(1), FIELD_TYPE_VARBINARY, 0,
+		 zeroblobFunc, NULL, true),
+	STUB_FUNC("_sql_stat_get"), STUB_FUNC("_sql_stat_init"),
+	STUB_FUNC("_sql_stat_push"),
+};
+
+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, ARGC_MASK(1), 0, 0, soundexFunc, FIELD_TYPE_STRING),
-		FUNCTION2(unlikely, ARGC_MASK(1), 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION2(likelihood, ARGC_MASK(2), 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION2(likely, ARGC_MASK(1), 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION_COLL(trim, ARGC_MASK3(1, 2, 3), 3, 0, trim_func),
-		FUNCTION(least, ARGC_MASK_FULL, 0, 1, minmaxFunc, FIELD_TYPE_SCALAR),
-		AGGREGATE2(min, ARGC_MASK(1), 0, 1, minmaxStep, minMaxFinalize,
-			   SQL_FUNC_MINMAX, FIELD_TYPE_SCALAR),
-		FUNCTION(greatest, ARGC_MASK_FULL, 1, 1, minmaxFunc, FIELD_TYPE_SCALAR),
-		AGGREGATE2(max, ARGC_MASK(1), 1, 1, minmaxStep, minMaxFinalize,
-			   SQL_FUNC_MINMAX, FIELD_TYPE_SCALAR),
-		FUNCTION2(typeof, ARGC_MASK(1), 0, 0, typeofFunc, SQL_FUNC_TYPEOF,
-			  FIELD_TYPE_STRING),
-		FUNCTION2(length, ARGC_MASK(1), 0, 0, lengthFunc, SQL_FUNC_LENGTH,
-			  FIELD_TYPE_INTEGER),
-		FUNCTION(char_length, ARGC_MASK(1), 0, 0, lengthFunc, FIELD_TYPE_INTEGER),
-		FUNCTION(character_length, ARGC_MASK(1), 0, 0, lengthFunc,
-			 FIELD_TYPE_INTEGER),
-		FUNCTION(position, ARGC_MASK(2), 0, 1, position_func, FIELD_TYPE_INTEGER),
-		FUNCTION(printf, ARGC_MASK_FULL, 0, 0, printfFunc, FIELD_TYPE_STRING),
-		FUNCTION(unicode, ARGC_MASK(1), 0, 0, unicodeFunc, FIELD_TYPE_STRING),
-		FUNCTION(char, ARGC_MASK_FULL, 0, 0, charFunc, FIELD_TYPE_STRING),
-		FUNCTION(abs, ARGC_MASK(1), 0, 0, absFunc, FIELD_TYPE_NUMBER),
-		FUNCTION(round, ARGC_MASK2(1, 2), 0, 0, roundFunc, FIELD_TYPE_INTEGER),
-		FUNCTION_COLL(upper, ARGC_MASK(1), 0, 1, UpperICUFunc),
-		FUNCTION_COLL(lower, ARGC_MASK(1), 0, 1, LowerICUFunc),
-		FUNCTION(hex, ARGC_MASK(1), 0, 0, hexFunc, FIELD_TYPE_STRING),
-		FUNCTION2(ifnull, ARGC_MASK(2), 0, 0, noopFunc, SQL_FUNC_COALESCE,
-			  FIELD_TYPE_INTEGER),
-		VFUNCTION(random, ARGC_MASK(0), 0, 0, randomFunc, FIELD_TYPE_INTEGER),
-		VFUNCTION(randomblob, ARGC_MASK(1), 0, 0, randomBlob, FIELD_TYPE_VARBINARY),
-		FUNCTION(nullif, ARGC_MASK(2), 0, 1, nullifFunc, FIELD_TYPE_SCALAR),
-		FUNCTION(version, ARGC_MASK(0), 0, 0, sql_func_version, FIELD_TYPE_STRING),
-		FUNCTION(quote, ARGC_MASK(1), 0, 0, quoteFunc, FIELD_TYPE_STRING),
-		VFUNCTION(row_count, ARGC_MASK(0), 0, 0, sql_row_count, FIELD_TYPE_INTEGER),
-		FUNCTION_COLL(replace, ARGC_MASK(3), 0, 0, replaceFunc),
-		FUNCTION(zeroblob, ARGC_MASK(1), 0, 0, zeroblobFunc, FIELD_TYPE_VARBINARY),
-		FUNCTION_COLL(substr, ARGC_MASK2(2, 3), 0, 0, substrFunc),
-		AGGREGATE(sum, ARGC_MASK(1), 0, 0, sum_step, sumFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE(total, ARGC_MASK(1), 0, 0, sum_step, totalFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE(avg, ARGC_MASK(1), 0, 0, sum_step, avgFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE(count, ARGC_MASK2(0, 1), 0, 0, countStep, countFinalize,
-			  FIELD_TYPE_INTEGER),
-		AGGREGATE(group_concat, ARGC_MASK2(1, 2), 0, 0, groupConcatStep,
-			  groupConcatFinalize, FIELD_TYPE_STRING),
-		LIKEFUNC(like, ARGC_MASK2(2, 3), 1, SQL_FUNC_LIKE,
-			 FIELD_TYPE_INTEGER),
-		FUNCTION2(coalesce, ARGC_MASK_FULL & ~ARGC_MASK2(0, 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);
+	if (def->body != NULL || def->is_sandboxed) {
+		diag_set(ClientError, ER_CREATE_FUNCTION, def->name,
+			 "body and is_sandboxed options are not compatible "
+			 "with SQL language");
+		return NULL;
+	}
+	/** 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
+	if (idx == -1) {
+		diag_set(ClientError, ER_CREATE_FUNCTION, def->name,
+			 "unknown sql builtin name");
+		return NULL;
+	}
+	struct func_sql_builtin *func =
+		(struct func_sql_builtin *) malloc(sizeof(*func));
+	if (func == NULL) {
+		diag_set(OutOfMemory, sizeof(*func), "malloc", "func");
+		return NULL;
+	}
+	func->flags = sql_builtins[idx].flags;
+	func->user_data = sql_builtins[idx].user_data;
+	func->call = sql_builtins[idx].call;
+	func->finalize = sql_builtins[idx].finalize;
+	func->signature_mask = sql_builtins[idx].signature_mask;
+	func->base.vtab = &func_sql_builtin_vtab;
+	func->base.def = def;
+
+	def->is_deterministic = sql_builtins[idx].is_deterministic;
+	def->returns = sql_builtins[idx].returns;
+	def->aggregate = sql_builtins[idx].aggregate;
+	def->exports.sql = true;
+	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 eb6e4a7db..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 != NULL && (p->signature_mask & nArg) != 0) {
-		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->signature_mask = ARGC_MASK(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 0b90edd06..0c54afef6 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,30 @@ 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, strlen(zId));
+				if (func == NULL || !func->def->exports.sql) {
+					func = NULL;
 					no_such_func = 1;
 				} 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_test(func,
+						       SQL_FUNC_UNLIKELY)) {
 					ExprSetProperty(pExpr,
 							EP_Unlikely | EP_Skip);
 					if (n == 2) {
@@ -643,21 +649,19 @@ 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 |
-						 SQL_FUNC_SLOCHNG)) {
+				if (func->def->is_deterministic ||
+				    sql_func_flag_test(func, SQL_FUNC_SLOCHNG)) {
 					/* 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) {
+				if (!func->def->is_deterministic) {
 					/* Date/time functions that use 'now', and other functions
 					 * that might change over time cannot be used
 					 * in an index.
@@ -700,18 +704,14 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 					pExpr->op2++;
 					pNC2 = pNC2->pNext;
 				}
-				assert(pDef != 0);
+				assert(func != NULL);
 				if (pNC2) {
+					pNC2->ncFlags |= NC_HasAgg;
 					assert(SQL_FUNC_MINMAX ==
 					       NC_MinMaxAgg);
-					testcase((pDef->
-						  funcFlags &
-						  SQL_FUNC_MINMAX) != 0);
-					pNC2->ncFlags |=
-					    NC_HasAgg | (pDef->
-							 funcFlags &
-							 SQL_FUNC_MINMAX);
-
+					if (sql_func_flag_test(func,
+							       SQL_FUNC_MINMAX))
+						pNC2->ncFlags |= NC_MinMaxAgg;
 				}
 				pNC->ncFlags |= NC_AllowAgg;
 			}
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 921a52150..bce5cef3b 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -5277,7 +5277,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);
 	}
 }
 
@@ -5318,7 +5318,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_test(pF->func, SQL_FUNC_NEEDCOLL)) {
 			struct coll *coll = NULL;
 			struct ExprList_item *pItem;
 			int j;
@@ -5337,7 +5337,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..a66becc89 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 *)pCtx->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 *)pCtx->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 1aee3cf85..4ff8db621 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -484,8 +484,9 @@ sql_step(sql_stmt * pStmt)
 void *
 sql_user_data(sql_context * p)
 {
-	assert(p && p->pFunc);
-	return p->pFunc->pUserData;
+	assert(p != NULL && p->func != NULL);
+	assert(p->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	return ((struct func_sql_builtin *)p->func)->user_data;
 }
 
 /*
@@ -547,7 +548,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 +564,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 &&
+	       p->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN &&
+	       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 d32404580..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,17 +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->signature_mask);
+	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->signature_mask);
+			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 b8c31ecec..8b2e816f2 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -312,33 +312,28 @@ 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)) {
+	if (ALWAYS(func != NULL &&
+		   func->def->language == FUNC_LANGUAGE_SQL_BUILTIN &&
+		   func->def->aggregate == FUNC_AGGREGATE_GROUP)) {
 		sql_context ctx;
 		Mem t;
-		assert((pMem->flags & MEM_Null) != 0 || pFunc == pMem->u.pDef);
+		assert((mem->flags & MEM_Null) != 0 || func == mem->u.func);
 		memset(&ctx, 0, sizeof(ctx));
 		memset(&t, 0, sizeof(t));
 		t.flags = MEM_Null;
-		t.db = pMem->db;
+		t.db = mem->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));
+		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));
 		if (ctx.is_aborted)
 			return -1;
 	}
@@ -359,7 +354,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 +1284,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,13 +1294,13 @@ 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 | SQL_FUNC_SLOCHNG)) ==
-	    0 || (pFunc->funcFlags & SQL_FUNC_NEEDCOLL)
-	    ) {
+	struct func *func = sql_func_by_signature(p->u.zToken, nVal);
+	assert(func != NULL);
+	if (func->def->language != FUNC_LANGUAGE_SQL_BUILTIN ||
+	    (!func->def->is_deterministic &&
+	     !sql_func_flag_test(func, SQL_FUNC_SLOCHNG)) ||
+	    sql_func_flag_test(func, SQL_FUNC_NEEDCOLL))
 		return 0;
-	}
 
 	if (pList) {
 		apVal =
@@ -1334,8 +1328,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/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.0

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

* [tarantool-patches] [PATCH v2 8/8] box: get rid of box.internal.sql_function_create
  2019-08-08 14:50 [tarantool-patches] [PATCH v2 0/8] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
                   ` (6 preceding siblings ...)
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 7/8] sql: get rid of FuncDef function hash Kirill Shcherbatov
@ 2019-08-08 14:50 ` Kirill Shcherbatov
  2019-08-13 20:43   ` [tarantool-patches] " n.pettik
  7 siblings, 1 reply; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-08 14:50 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/func_def.h              |   2 +-
 src/box/port.h                  |  17 ++
 src/lib/core/port.h             |  19 +++
 src/box/call.c                  |   1 +
 src/box/execute.c               |   1 +
 src/box/lua/call.c              |   4 +
 src/box/port.c                  |   4 +
 src/box/sql.c                   |   3 +-
 src/box/sql/expr.c              |   6 +-
 src/box/sql/func.c              | 277 ++++++++++++++++++++++++++++++++
 src/box/sql/vdbe.c              |  45 ++++++
 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, 783 insertions(+), 132 deletions(-)

diff --git a/src/box/func_def.h b/src/box/func_def.h
index d99d89190..988d080f0 100644
--- a/src/box/func_def.h
+++ b/src/box/func_def.h
@@ -110,7 +110,7 @@ struct func_def {
 	 */
 	bool is_sandboxed;
 	/** The count of function's input arguments. */
-	int param_count;
+	uint32_t param_count;
 	/** The type of the value returned by function. */
 	enum field_type returns;
 	/** Function aggregate option. */
diff --git a/src/box/port.h b/src/box/port.h
index a7f5d81bd..26f85b145 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;
+struct sql_context;
+
+/** Port implementation used with vdbe memory variables. */
+struct port_vdbemem {
+    const struct port_vtab *vtab;
+    struct sql_value *mem;
+    uint32_t size;
+};
+
+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 size);
+
 void
 port_init(void);
 
diff --git a/src/lib/core/port.h b/src/lib/core/port.h
index 09a026df5..17096d0d0 100644
--- a/src/lib/core/port.h
+++ b/src/lib/core/port.h
@@ -98,6 +98,19 @@ 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
+	 * variables. The SQL VDBE uses this representation for
+	 * process data. This API is usefull to pass VDBE
+	 * variables to sql builtin functions.
+	 * The lifecycle of the returned value is
+	 * implementation-specific: it may either be returned
+	 * directly from the port, in which case the data will
+	 * stay alive as long as the port is alive, or it may be
+	 * allocated on the fiber()->gc, in which case the caller
+	 * is responsible for cleaning up.
+	 */
+	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 +163,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.c b/src/box/sql.c
index a731332c7..bbc957736 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1270,7 +1270,8 @@ sql_func_by_signature(const char *name, uint32_t argc)
 	if (func == NULL || !func->def->exports.sql)
 		return NULL;
 	if (func->def->language != FUNC_LANGUAGE_SQL_BUILTIN) {
-		return NULL;
+		if (func->def->param_count != argc)
+			return NULL;
 	} else {
 		/*
 		 * The param_count field is not valid for sql
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 64b3bc835..1aaab3b64 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -4122,9 +4122,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 f07c52b95..ea94d6151 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -45,6 +45,10 @@
 #include <unicode/uchar.h>
 #include <unicode/ucol.h>
 #include "box/coll_id_cache.h"
+#include "box/port.h"
+#include "box/tuple.h"
+#include "lua/utils.h"
+#include "mpstream.h"
 
 /*
  * Return the collating function associated with a function.
@@ -70,6 +74,279 @@ 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->size; 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->size);
+	for (uint32_t i = 0; i < port->size && !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: {
+			mpstream_encode_str(&stream,
+					(const char *) sql_value_text(param));
+			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 size)
+{
+	struct port_vdbemem *port = (struct port_vdbemem *) base;
+	port->vtab = &port_vdbemem_vtab;
+	port->mem = mem;
+	port->size = size;
+}
+
+static struct sql_value *
+port_vdbemem_get_vdbemem(struct port *base, uint32_t *size)
+{
+	struct port_vdbemem *port = (struct port_vdbemem *) base;
+	assert(port->vtab == &port_vdbemem_vtab);
+	*size = port->size;
+	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 a66becc89..23b6995d7 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,50 @@ 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.
+ *
+ * P1 is a 32-bit bitmask indicating whether or not each argument
+ * to the function was determined to be constant at compile time.
+ * If the first argument was constant then bit 0 of P1 is set.
+ */
+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 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]
  *
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 5b091f72b..48c9ad6cf 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 f894472f8..5daa2ee1d 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.0

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

* [tarantool-patches] Re: [PATCH v2 1/8] sql: remove SQL_PreferBuiltin flag
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 1/8] sql: remove SQL_PreferBuiltin flag Kirill Shcherbatov
@ 2019-08-09 16:07   ` n.pettik
  2019-08-12 21:58   ` Konstantin Osipov
  1 sibling, 0 replies; 38+ messages in thread
From: n.pettik @ 2019-08-09 16:07 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov

LGTM (can be pushed separately from other patches
from patch-set).

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

* [tarantool-patches] Re: [PATCH v2 2/8] sql: GREATEST, LEAST instead of MIN/MAX overload
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 2/8] sql: GREATEST, LEAST instead of MIN/MAX overload Kirill Shcherbatov
@ 2019-08-09 17:37   ` n.pettik
  2019-08-13  8:26     ` Kirill Shcherbatov
  2019-08-12 21:59   ` Konstantin Osipov
  1 sibling, 1 reply; 38+ messages in thread
From: n.pettik @ 2019-08-09 17:37 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov



> On 8 Aug 2019, at 17:50, Kirill Shcherbatov <kshcherbatov@tarantool.org> wrote:
> 
> 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. This must be fixed.

So, basically this patch does two things: renames existing
scalar min/max funcs and reserves names for them in NoSQL
cache. Please, explicitly outline these facts in commit message.
Also, I guess name change should be documented.

> Moreover it is an important stem to get rid of function's name

Nit: stem -> step.

> overloading required for replace FuncDef cache with Tarantool's
> function cache.
> diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
> index 046eb3ee4..73b39332e 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.2.2
> +--------------------------------------------------------------------------------
> +
> +local function upgrade_to_2_2_2()

Does this patch come in scope of 2.2.2?
I thought that it will be part of 2.3.1 (at least
issues to be closed are related to 2.3).

The rest is OK.

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

* [tarantool-patches] Re: [PATCH v2 3/8] sql: wrap all trim functions in dispatcher
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 3/8] sql: wrap all trim functions in dispatcher Kirill Shcherbatov
@ 2019-08-09 18:05   ` n.pettik
  2019-08-13  8:28     ` Kirill Shcherbatov
  2019-08-12 22:00   ` Konstantin Osipov
  1 sibling, 1 reply; 38+ messages in thread
From: n.pettik @ 2019-08-09 18:05 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov



> On 8 Aug 2019, at 17:50, Kirill Shcherbatov <kshcherbatov@tarantool.org> wrote:
> 
> 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 stem to get rid of function's name

Nit: stem -> step

> overloading required for replace FuncDef cache with Tarantool's
> function cache.
> 
> Needed for #2200, #4113, #2233
> ---
> src/box/sql/func.c | 32 +++++++++++++++++++++++++++++---
> 1 file changed, 29 insertions(+), 3 deletions(-)
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index e00764c3f..f9c0a819e 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -1496,6 +1496,32 @@ trim_func_three_args(struct sql_context *context, int argc, sql_value **argv)
> 		       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 call corresponding

Nit: call -> calls

> + * 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, argc, argv);

Why not unpack arguments right here?
IMHO it would look much better.

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

* [tarantool-patches] Re: [PATCH v2 1/8] sql: remove SQL_PreferBuiltin flag
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 1/8] sql: remove SQL_PreferBuiltin flag Kirill Shcherbatov
  2019-08-09 16:07   ` [tarantool-patches] " n.pettik
@ 2019-08-12 21:58   ` Konstantin Osipov
  1 sibling, 0 replies; 38+ messages in thread
From: Konstantin Osipov @ 2019-08-12 21:58 UTC (permalink / raw)
  To: tarantool-patches; +Cc: korablev, Kirill Shcherbatov

* Kirill Shcherbatov <kshcherbatov@tarantool.org> [19/08/08 17:53]:
> The SQL_PreferBuiltin flag is redundant (because builtin names
> are forbidden for UDFs) so we may to remove it.
> 
> Needed for #4113, #2200, #2233

lgtm

 

-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v2 2/8] sql: GREATEST, LEAST instead of MIN/MAX overload
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 2/8] sql: GREATEST, LEAST instead of MIN/MAX overload Kirill Shcherbatov
  2019-08-09 17:37   ` [tarantool-patches] " n.pettik
@ 2019-08-12 21:59   ` Konstantin Osipov
  1 sibling, 0 replies; 38+ messages in thread
From: Konstantin Osipov @ 2019-08-12 21:59 UTC (permalink / raw)
  To: tarantool-patches; +Cc: korablev, Kirill Shcherbatov

* Kirill Shcherbatov <kshcherbatov@tarantool.org> [19/08/08 17:53]:
> 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. This must be fixed.
> 
> Moreover it is an important stem to get rid of function's name
> overloading required for replace FuncDef cache with Tarantool's
> function cache.

lgtm (agree with nikita's nits, but otherwise please push, push
push).


-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v2 3/8] sql: wrap all trim functions in dispatcher
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 3/8] sql: wrap all trim functions in dispatcher Kirill Shcherbatov
  2019-08-09 18:05   ` [tarantool-patches] " n.pettik
@ 2019-08-12 22:00   ` Konstantin Osipov
  1 sibling, 0 replies; 38+ messages in thread
From: Konstantin Osipov @ 2019-08-12 22:00 UTC (permalink / raw)
  To: tarantool-patches; +Cc: korablev, Kirill Shcherbatov

* Kirill Shcherbatov <kshcherbatov@tarantool.org> [19/08/08 17:53]:
> 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 stem to get rid of function's name
> overloading required for replace FuncDef cache with Tarantool's
> function cache.
> 
> Needed for #2200, #4113, #2233

Agree with Nikita's nit, otherwise lgtm.


-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v2 4/8] sql: get rid of SQL_FUNC_COUNT flag
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 4/8] sql: get rid of SQL_FUNC_COUNT flag Kirill Shcherbatov
@ 2019-08-12 22:01   ` Konstantin Osipov
  2019-08-13 20:35   ` n.pettik
  1 sibling, 0 replies; 38+ messages in thread
From: Konstantin Osipov @ 2019-08-12 22:01 UTC (permalink / raw)
  To: tarantool-patches; +Cc: korablev, Kirill Shcherbatov

* Kirill Shcherbatov <kshcherbatov@tarantool.org> [19/08/08 17:53]:

lgtm

 

-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v2 5/8] sql: introduce a signature_mask for functions
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 5/8] sql: introduce a signature_mask for functions Kirill Shcherbatov
@ 2019-08-12 22:04   ` Konstantin Osipov
  2019-08-13  8:32     ` Kirill Shcherbatov
  2019-08-13 20:38   ` n.pettik
  1 sibling, 1 reply; 38+ messages in thread
From: Konstantin Osipov @ 2019-08-12 22:04 UTC (permalink / raw)
  To: tarantool-patches; +Cc: korablev, Kirill Shcherbatov

* Kirill Shcherbatov <kshcherbatov@tarantool.org> [19/08/08 17:53]:
> This patch replaces nArgs field with signature_mask bitmask that
> allows to use an only hash table entry for all builtin functions
> overloads.
> 
> The code refactoring is not a goal of this patch: the most of
> affected code would be removed in following patches. The role of
> this patch itself is to introduce such mechanism (signature_mask)
> in Tarantool's SQL.
> 
> Needed for #2200, #4113, #2233
> ---
>  src/box/sql/sqlInt.h   | 70 ++++++++++++++++++++------------
>  src/box/sql/callback.c | 22 +++-------
>  src/box/sql/func.c     | 91 ++++++++++++++++++------------------------
>  src/box/sql/main.c     |  4 +-
>  src/box/sql/vdbeaux.c  |  6 ++-
>  5 files changed, 94 insertions(+), 99 deletions(-)
> 
> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index 941c87420..114ac0e4b 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -1254,7 +1254,20 @@ struct type_def {
>   * field is used by per-connection app-def functions.
>   */
>  struct FuncDef {
> -	i8 nArg;		/* Number of arguments.  -1 means unlimited */
> +	/**
> +	 * A bitmask representing all supported function
> +	 * overloads. The function supports argc == n iff this
> +	 * bitmask has bit n set 1. In particular case, a bitmask
> +	 * (~0) means this function works with any possible
> +	 * argument.
> +	 *
> +	 * The count of arguments for function is limited with
> +	 * (CHAR_BITS*sizeof(uint64_t) - 1). When the highest bit
> +	 * of the mask is set, this means that greater values
> +	 * are supported. E.g. greatest function works correctly
> +	 * with any number of input arguments.
> +	 */
> +	uint64_t signature_mask;

Good idea, but why make the mask sooo big?-)))
I don't think we have more than 3 arguments in any overloaded
function?

> +	if (!column_mask_fieldno_is_set(p->signature_mask, (uint32_t)nArg))
>  		return 0;

Are you sure you want to use column mask api just to test a bit?


-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v2 6/8] sql: rename OP_Function to OP_BuiltinFunction
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 6/8] sql: rename OP_Function to OP_BuiltinFunction Kirill Shcherbatov
@ 2019-08-12 22:04   ` Konstantin Osipov
  2019-08-13 20:36   ` n.pettik
  1 sibling, 0 replies; 38+ messages in thread
From: Konstantin Osipov @ 2019-08-12 22:04 UTC (permalink / raw)
  To: tarantool-patches; +Cc: korablev, Kirill Shcherbatov

* Kirill Shcherbatov <kshcherbatov@tarantool.org> [19/08/08 17:53]:
> 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.
> 

lgtm

-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v2 7/8] sql: get rid of FuncDef function hash
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 7/8] sql: get rid of FuncDef function hash Kirill Shcherbatov
@ 2019-08-12 22:11   ` Konstantin Osipov
  2019-08-13  7:29     ` Kirill Shcherbatov
  2019-08-13 20:40   ` n.pettik
  1 sibling, 1 reply; 38+ messages in thread
From: Konstantin Osipov @ 2019-08-12 22:11 UTC (permalink / raw)
  To: tarantool-patches; +Cc: korablev, Kirill Shcherbatov

* Kirill Shcherbatov <kshcherbatov@tarantool.org> [19/08/08 17:53]:

I like the patch, there is only one nit here:


> -void
> -sqlRegisterBuiltinFunctions(void)
> +static struct {
> +	const char *name;
> +	uint64_t signature_mask;
> +	enum field_type returns;
> +	uint16_t flags;
> +	void (*call)(sql_context *ctx, int argc, sql_value **argv);
> +	void (*finalize)(sql_context *ctx);
> +	void *user_data;
> +	enum func_aggregate aggregate;
> +	bool is_deterministic;
> +} sql_builtins[] = {


> +	REG_FUNC("ABS", ARGC_MASK(1), FIELD_TYPE_NUMBER, 0,
> +		 absFunc, NULL, true),


This looks like huge overengineering.

A bunch of macros, a binary search, can you just open-code
inserting all of these functions into the hash? It would be
perhaps a bit more copy-paste, but it will be easy to maintain.

Am I missing something?


-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v2 7/8] sql: get rid of FuncDef function hash
  2019-08-12 22:11   ` [tarantool-patches] " Konstantin Osipov
@ 2019-08-13  7:29     ` Kirill Shcherbatov
  2019-08-13  8:42       ` Konstantin Osipov
  0 siblings, 1 reply; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-13  7:29 UTC (permalink / raw)
  To: Konstantin Osipov, Tarantool MailList, Nikita Pettik

>> +	REG_FUNC("ABS", ARGC_MASK(1), FIELD_TYPE_NUMBER, 0,
>> +		 absFunc, NULL, true),
> 
> 
> This looks like huge overengineering.
> 
> A bunch of macros, a binary search, can you just open-code
> inserting all of these functions into the hash? It would be
> perhaps a bit more copy-paste, but it will be easy to maintain.
> 
> Am I missing something?

This constructor is called automatically during function recovery
by prototype defined in snapshot. Thus all functions are
initialized in a one place: in on_replace_dd_func trigger.

I like this concept because it is uniform and all implementation-dependent
details (like the completion of the function definition initialization) are hidden
in the corresponding module (sql/func.c).


Technically we may skip a function cache entry creation for SQL_BUILTIN 
tuple type in on_replace_dd_func trigger, to perform the whole preparements
during Tarantool initialization. This require to distinguish SQL_BUILTIN entries
and other types in trigger and this is not cool, I believe.

Finally, a current approach enforces keeping snapshot will all built in definitions
in actual state. 

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

* [tarantool-patches] Re: [PATCH v2 2/8] sql: GREATEST, LEAST instead of MIN/MAX overload
  2019-08-09 17:37   ` [tarantool-patches] " n.pettik
@ 2019-08-13  8:26     ` Kirill Shcherbatov
  0 siblings, 0 replies; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-13  8:26 UTC (permalink / raw)
  To: n.pettik, Tarantool MailList

> So, basically this patch does two things: renames existing
> scalar min/max funcs and reserves names for them in NoSQL
> cache. Please, explicitly outline these facts in commit message.
> Also, I guess name change should be documented.
Fixed. 

> 
>> Moreover it is an important stem to get rid of function's name
> 
> Nit: stem -> step.
FIxed.

> Does this patch come in scope of 2.2.2?
> I thought that it will be part of 2.3.1 (at least
> issues to be closed are related to 2.3).
> 
> The rest is OK.
Fixed.

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

* [tarantool-patches] Re: [PATCH v2 3/8] sql: wrap all trim functions in dispatcher
  2019-08-09 18:05   ` [tarantool-patches] " n.pettik
@ 2019-08-13  8:28     ` Kirill Shcherbatov
  2019-08-13 22:19       ` n.pettik
  0 siblings, 1 reply; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-13  8:28 UTC (permalink / raw)
  To: n.pettik, Tarantool MailList

>> 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 stem to get rid of function's name
> 
> Nit: stem -> step

>> +
>> +/**
>> + * Normalize args from @a argv input array when it has one,
>> + * two or three args.
>> + *
>> + * This is a dispatcher function that call corresponding
> 
> Nit: call -> calls
Fixed.

>> +	switch (argc) {
>> +	case 1:
>> +		trim_func_one_arg(context, argc, argv);
> 
> Why not unpack arguments right here?
> IMHO it would look much better.
Can't understand, what do you mean? Those functions are static and would be
inlined. They handle own corner case. Moreover, if I would unpack values externally,
I need to pass three values (type, size, value) for each argument.

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

* [tarantool-patches] Re: [PATCH v2 5/8] sql: introduce a signature_mask for functions
  2019-08-12 22:04   ` [tarantool-patches] " Konstantin Osipov
@ 2019-08-13  8:32     ` Kirill Shcherbatov
  2019-08-13  8:44       ` Konstantin Osipov
  0 siblings, 1 reply; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-13  8:32 UTC (permalink / raw)
  To: Konstantin Osipov, Tarantool MailList; +Cc: Nikita Pettik

> +	 * The count of arguments for function is limited with
>> +	 * (CHAR_BITS*sizeof(uint64_t) - 1). When the highest bit
>> +	 * of the mask is set, this means that greater values
>> +	 * are supported. E.g. greatest function works correctly
>> +	 * with any number of input arguments.
>> +	 */
>> +	uint64_t signature_mask;
> 
> Good idea, but why make the mask sooo big?-)))
> I don't think we have more than 3 arguments in any overloaded
> function?
63 arguments is not too much for UDF (this would use for UDF too), I guess.
Moreover, column_mask API is worked with 64bit bitmasks.

> 
>> +	if (!column_mask_fieldno_is_set(p->signature_mask, (uint32_t)nArg))
>>  		return 0;
> 
> Are you sure you want to use column mask api just to test a bit?
It is convenient, because column_mask is "smart" and it also return true for
fieldno > 64: this is ok for uniform scalar functions like
GREATEST(1,.......)

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

* [tarantool-patches] Re: [PATCH v2 7/8] sql: get rid of FuncDef function hash
  2019-08-13  7:29     ` Kirill Shcherbatov
@ 2019-08-13  8:42       ` Konstantin Osipov
  2019-08-13  9:45         ` Kirill Shcherbatov
  0 siblings, 1 reply; 38+ messages in thread
From: Konstantin Osipov @ 2019-08-13  8:42 UTC (permalink / raw)
  To: Kirill Shcherbatov; +Cc: Tarantool MailList, Nikita Pettik

* Kirill Shcherbatov <kshcherbatov@tarantool.org> [19/08/13 10:30]:
> >> +	REG_FUNC("ABS", ARGC_MASK(1), FIELD_TYPE_NUMBER, 0,
> >> +		 absFunc, NULL, true),
> > 
> > 
> > This looks like huge overengineering.
> > 
> > A bunch of macros, a binary search, can you just open-code
> > inserting all of these functions into the hash? It would be
> > perhaps a bit more copy-paste, but it will be easy to maintain.
> > 
> > Am I missing something?
> 
> This constructor is called automatically during function recovery
> by prototype defined in snapshot. Thus all functions are
> initialized in a one place: in on_replace_dd_func trigger.
> 
> I like this concept because it is uniform and all implementation-dependent
> details (like the completion of the function definition initialization) are hidden
> in the corresponding module (sql/func.c).

What does this code do? Why do you need an anonymous structure,
instead of initializing struct func_sql_builtn object right away?
What do you need the binary search for?
Pre-ordering the array for the binary search is very fragile.

> Technically we may skip a function cache entry creation for SQL_BUILTIN 
> tuple type in on_replace_dd_func trigger, to perform the whole preparements
> during Tarantool initialization. This require to distinguish SQL_BUILTIN entries
> and other types in trigger and this is not cool, I believe.
> 
> Finally, a current approach enforces keeping snapshot will all built in definitions
> in actual state. 

Can this be coded in Lua, in upgrade.lua?

-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v2 5/8] sql: introduce a signature_mask for functions
  2019-08-13  8:32     ` Kirill Shcherbatov
@ 2019-08-13  8:44       ` Konstantin Osipov
  0 siblings, 0 replies; 38+ messages in thread
From: Konstantin Osipov @ 2019-08-13  8:44 UTC (permalink / raw)
  To: Kirill Shcherbatov; +Cc: Tarantool MailList, Nikita Pettik

* Kirill Shcherbatov <kshcherbatov@tarantool.org> [19/08/13 11:32]:
> > Good idea, but why make the mask sooo big?-)))
> > I don't think we have more than 3 arguments in any overloaded
> > function?
> 63 arguments is not too much for UDF (this would use for UDF too), I guess.
> Moreover, column_mask API is worked with 64bit bitmasks.

When do you plan to use it for UDFs? I'm not aware of any work in
the pipeline. If it is in 3-6 months or more, then this is clearly
an overkill, as it may never happen
> 
> > 
> >> +	if (!column_mask_fieldno_is_set(p->signature_mask, (uint32_t)nArg))
> >>  		return 0;
> > 
> > Are you sure you want to use column mask api just to test a bit?
> It is convenient, because column_mask is "smart" and it also return true for
> fieldno > 64: this is ok for uniform scalar functions like
> GREATEST(1,.......)

In absence of UDFs you can static-assert that the number of used
bits never goes above 8, and you will never need the smart
features of the column mask.

-- 
Konstantin Osipov, Moscow, Russia

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

* [tarantool-patches] Re: [PATCH v2 7/8] sql: get rid of FuncDef function hash
  2019-08-13  8:42       ` Konstantin Osipov
@ 2019-08-13  9:45         ` Kirill Shcherbatov
  0 siblings, 0 replies; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-13  9:45 UTC (permalink / raw)
  To: Konstantin Osipov, Tarantool MailList; +Cc: Nikita Pettik

>> I like this concept because it is uniform and all implementation-dependent
>> details (like the completion of the function definition initialization) are hidden
>> in the corresponding module (sql/func.c).
> 
> What does this code do? Why do you need an anonymous structure,
> instead of initializing struct func_sql_builtn object right away?
> What do you need the binary search for?
> Pre-ordering the array for the binary search is very fragile.

This new anonymous structure contain information both to complete
func_sql_builtn construction and func_def initialization.
...
        func->flags = sql_builtins[idx].flags;                                                             
	func->user_data = sql_builtins[idx].user_data;                                                     
	func->call = sql_builtins[idx].call;     
...
        def->is_deterministic = sql_builtins[idx].is_deterministic;                                        
	def->returns = sql_builtins[idx].returns;                                                          
	def->aggregate = sql_builtins[idx].aggregate;                                                      
	def->exports.sql = true;       
...

(def is an func_sql_builtin_new constructor argument)

The binary search is required to find corresponding metadata entry in
func_sql_builtin_new constructor. 

> Can this be coded in Lua, in upgrade.lua?
I mean in current implementation you need to add a snapshoot entry to
introduce a new function AND define a metadata entry in block with
binary search.

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

* [tarantool-patches] Re: [PATCH v2 4/8] sql: get rid of SQL_FUNC_COUNT flag
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 4/8] sql: get rid of SQL_FUNC_COUNT flag Kirill Shcherbatov
  2019-08-12 22:01   ` [tarantool-patches] " Konstantin Osipov
@ 2019-08-13 20:35   ` n.pettik
  2019-08-14  7:25     ` Kirill Shcherbatov
  1 sibling, 1 reply; 38+ messages in thread
From: n.pettik @ 2019-08-13 20:35 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov



> On 8 Aug 2019, at 17:50, Kirill Shcherbatov <kshcherbatov@tarantool.org> wrote:
> 
> 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. Using SQL_FUNC_COUNT flag to mark a
> dummy (non-functional)

Why do you consider it to be non-functional?

> function entry with 0 arguments is a bad practice

Why? Please, support this with arguments.

As I understand, the real reason is that we are striving
to remove function overloading, isn’t it?

> and should be reworked in relation with introducing
> a uniform functions cache in further patches.
> 
> Therefore SQL_FUNC_COUNT flag test in is_simple_count helper
> was replaced with more straightforward test with manually looking
> for count of function arguments passed.
> 
> Needed for #2200, #4113, #2233
> ---
> 
> -	if ((agg_info->aFunc[0].pFunc->funcFlags & SQL_FUNC_COUNT) == 0)
> +	if (agg_info->aFunc->pExpr->x.pList != NULL &&
> +	    agg_info->aFunc->pExpr->x.pList->nExpr > 0)
> 		return NULL;

Hm, this code works just by accident :) There’s no check
that function is really COUNT, but we are lucky enough
and there’s no other aggregate function which can take
empty list of arguments...

> 	if (expr->flags & EP_Distinct)
> 		return NULL;
> -- 
> 2.22.0

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

* [tarantool-patches] Re: [PATCH v2 6/8] sql: rename OP_Function to OP_BuiltinFunction
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 6/8] sql: rename OP_Function to OP_BuiltinFunction Kirill Shcherbatov
  2019-08-12 22:04   ` [tarantool-patches] " Konstantin Osipov
@ 2019-08-13 20:36   ` n.pettik
  1 sibling, 0 replies; 38+ messages in thread
From: n.pettik @ 2019-08-13 20:36 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov

This is OK.

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

* [tarantool-patches] Re: [PATCH v2 5/8] sql: introduce a signature_mask for functions
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 5/8] sql: introduce a signature_mask for functions Kirill Shcherbatov
  2019-08-12 22:04   ` [tarantool-patches] " Konstantin Osipov
@ 2019-08-13 20:38   ` n.pettik
  2019-08-14  7:21     ` Kirill Shcherbatov
  1 sibling, 1 reply; 38+ messages in thread
From: n.pettik @ 2019-08-13 20:38 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov



> On 8 Aug 2019, at 17:50, Kirill Shcherbatov <kshcherbatov@tarantool.org> wrote:
> 
> This patch replaces nArgs field with signature_mask bitmask that
> allows to use an only hash table entry for all builtin functions
> overloads.
> 
> The code refactoring is not a goal of this patch: the most of
> affected code would be removed in following patches. The role of
> this patch itself is to introduce such mechanism (signature_mask)
> in Tarantool's SQL.

TBO I do not understand why should you bother about
function overloading depending on count of arguments.
Why can’t we simply incapsulate this by dispatching
arguments count like we do for trim function? Or, in case
function is able to take var args, let just add another one
flag indicating this fact. This mask make things be complicated:
you have to add custom sql_func_by_signature() etc.

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

* [tarantool-patches] Re: [PATCH v2 7/8] sql: get rid of FuncDef function hash
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 7/8] sql: get rid of FuncDef function hash Kirill Shcherbatov
  2019-08-12 22:11   ` [tarantool-patches] " Konstantin Osipov
@ 2019-08-13 20:40   ` n.pettik
  2019-08-16 12:57     ` Kirill Shcherbatov
  1 sibling, 1 reply; 38+ messages in thread
From: n.pettik @ 2019-08-13 20:40 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov



> On 8 Aug 2019, at 17:50, Kirill Shcherbatov <kshcherbatov@tarantool.org> wrote:
> 
> 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 typica while

Nit: typic? Please, consider using spell-checker.

> port API call is not supported and protected with stub.
> 
> 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 would be fixed in the following commit:

Nit: 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
> ---
> 
> diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
> index 114ac0e4b..6ef1a1d4a 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -1244,78 +1211,12 @@ struct type_def {
> };
> 
> @@ -4516,9 +4337,62 @@ 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 representing all supported function
> +	 * overloads. The function supports argc == n iff this
> +	 * bitmask has bit n set 1. In particular case, a bitmask
> +	 * (~0) means this function works with any possible
> +	 * argument.
> +	 *
> +	 * The count of arguments for function is limited with
> +	 * (CHAR_BITS*sizeof(uint64_t) - 1). When the highest bit
> +	 * of the mask is set, this means that greater values
> +	 * are supported. E.g. greatest function works correctly
> +	 * with any number of input arguments.
> +	 */
> +	uint64_t signature_mask;
> +	/** A bitmask of SQL flags. */
> +	uint16_t flags;
> +	/** User data to pass in call method. */
> +	void *user_data;
> +	/** A call method for a function. */

Add explanation why we don’t use basic (i.e. call
method of base class) “call” method. Furthermore,
built-ins are called bypassing func_access_check()
function. This should be either documented or fixed.

> +	void (*call)(sql_context *ctx, int argc, sql_value **argv);
> +	/** Finalize method (only for aggregate function). */
> +	void (*finalize)(sql_context *ctx);

We hold aggregate attribute in func_def; I guess
lua/C aggregate are not going to be implemented
soon (or someday at all); so, considering these
points, why not to move this method to the base vtab?

> +};
> +
> +/**
> + * A SQL method to find a function in a hash by it's name and

Nit: it’s (it is) -> its.

> + * 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 was found and NULL otherwise.

Nit: was found -> is found.

> + */
> +struct func *
> +sql_func_by_signature(const char *name, uint32_t argc);
> +
> +/**
> + * 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_test(struct func *func, uint16_t flag)

At least I would rename it to sql_func_test_flag() or
sql_builtin_flag_is_set() or smth like that.

> +{
> +	if (func->def->language != FUNC_LANGUAGE_SQL_BUILTIN)
> +		return false;
> +	return (((struct func_sql_builtin *)func)->flags & flag) != 0;
> +}
> 
> diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h
> index f77c019fb..f085477c1 100644
> --- a/src/box/sql/vdbeInt.h
> +++ b/src/box/sql/vdbeInt.h
> 
> @@ -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.

Nit: pMem -> mem.

> + * This routine calls the finalize method for that function. The
> + * result of the aggregate is stored back into pMem.
> + *
> + * 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/sql/analyze.c b/src/box/sql/analyze.c
> index bd52d12df..b68d86dfe 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

Is this change really related to the patch? Same for
all other added MAYBE_UNUSED modifiers.

> 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,11 @@ 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);

Instead of asserting func existence let’s raise an error: now
user can remove built-in function from hash - there’s no
protection to avoid such cases. Note that such opportunity
(removing built-ins from cache) has been introduced by moving
built-ins to the global func cache. In this case, assertion will fail
in debug mode and lead to unpredictable consequences in
release mode. Another option is to introduce mentioned protection,
i.e. disallow user to delete functions which are declared with
FUNC_LANGUAGE_SQL_BUILTIN flag.

> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index 1f9d91705..64b3bc835 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> 
> @@ -3991,8 +3988,9 @@ 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 ||
> +			    func->def->aggregate == FUNC_AGGREGATE_GROUP) {

Why do we need check on aggregation type?

> @@ -4128,12 +4118,15 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
> 			} else {
> 				r1 = 0;
> 			}
> -			if (pDef->funcFlags & SQL_FUNC_NEEDCOLL) {
> +			if (sql_func_flag_test(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);
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index 8e07ce892..f07c52b95 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -1825,114 +1825,231 @@ groupConcatFinalize(sql_context * context)
> }
> +
> +#define REG_FUNC(name, signature_mask, returns, flags,     \
> +		 call, user_data, is_deterministic)        \
> +	{name, signature_mask, returns, flags, call, NULL, \
> +	 user_data, FUNC_AGGREGATE_NONE, is_deterministic}
> +
> +#define AGG_FUNC(name, signature_mask, returns, flags,     \
> +		 call, finalize, user_data)                \
> +	{name, signature_mask, returns, flags, call,       \
> +	 finalize, user_data, FUNC_AGGREGATE_GROUP, false}
> +static struct func_vtab func_sql_builtin_vtab;
> +
> +struct func *
> +func_sql_builtin_new(struct func_def *def)
> {
> +	assert(def->language == FUNC_LANGUAGE_SQL_BUILTIN);
> +	if (def->body != NULL || def->is_sandboxed) {
> +		diag_set(ClientError, ER_CREATE_FUNCTION, def->name,
> +			 "body and is_sandboxed options are not compatible "
> +			 "with SQL language”);

Why not move this check to func_def_new_from_tuple() ?

> +		return NULL;
> +	}
> +	func->flags = sql_builtins[idx].flags;
> +	func->user_data = sql_builtins[idx].user_data;
> +	func->call = sql_builtins[idx].call;
> +	func->finalize = sql_builtins[idx].finalize;
> +	func->signature_mask = sql_builtins[idx].signature_mask;
> +	func->base.vtab = &func_sql_builtin_vtab;
> +	func->base.def = def;
> +

Why not memcpy?

> +	def->is_deterministic = sql_builtins[idx].is_deterministic;
> +	def->returns = sql_builtins[idx].returns;
> +	def->aggregate = sql_builtins[idx].aggregate;
> +	def->exports.sql = true;
> +	return &func->base;
> +}
> +
> +static struct func_vtab func_sql_builtin_vtab = {
> +	.call = sql_builtin_call_stub,
> +	.destroy = func_sql_builtin_destroy,
> +};
> 
> diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
> index 0b90edd06..0c54afef6 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,30 @@ 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, strlen(zId));
> +				if (func == NULL || !func->def->exports.sql) {
> +					func = NULL;
> 					no_such_func = 1;
> 				} 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;;

Nit: double semicolon at the end of string.

Can aggregate function be non-builtin?

> +				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_test(func,
> +						       SQL_FUNC_UNLIKELY)) {
> 					ExprSetProperty(pExpr,
> 							EP_Unlikely | EP_Skip);
> 					if (n == 2) {
> @@ -643,21 +649,19 @@ 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 |
> -						 SQL_FUNC_SLOCHNG)) {
> +				if (func->def->is_deterministic ||
> +				    sql_func_flag_test(func, SQL_FUNC_SLOCHNG)) {

SQL_FUNC_SLOCHNG seems to be unused.
So, you can simply remove it.

> @@ -700,18 +704,14 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
> 					pExpr->op2++;
> 					pNC2 = pNC2->pNext;
> 				}
> -				assert(pDef != 0);
> +				assert(func != NULL);
> 				if (pNC2) {
> +					pNC2->ncFlags |= NC_HasAgg;
> 					assert(SQL_FUNC_MINMAX ==
> 					       NC_MinMaxAgg);
> -					testcase((pDef->
> -						  funcFlags &
> -						  SQL_FUNC_MINMAX) != 0);
> -					pNC2->ncFlags |=
> -					    NC_HasAgg | (pDef->
> -							 funcFlags &
> -							 SQL_FUNC_MINMAX);
> -
> +					if (sql_func_flag_test(func,
> +							       SQL_FUNC_MINMAX))
> +						pNC2->ncFlags |= NC_MinMaxAgg;
> 				}
> 				pNC->ncFlags |= NC_AllowAgg;
> 			}
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index 28552f64a..a66becc89 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"
> 
> @@ -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 *)pCtx->func)->call)(pCtx, pCtx->argc,
> +							 pCtx->argv);
> 
> 	/* If the function returned an error, throw an exception */
> 	if (pCtx->is_aborted)
> 
> @@ -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 *)pCtx->func)->call)(pCtx, pCtx->argc,
> +							 pCtx->argv);

Nit: please, split it into two steps: type cast and function call.

struct func_sql_builtin *func = …;
func->call();

> 	if (pCtx->is_aborted) {
> 		sqlVdbeMemRelease(&t);
> 		goto abort_due_to_error;
> 
> diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
> index 1aee3cf85..4ff8db621 100644
> --- a/src/box/sql/vdbeapi.c
> +++ b/src/box/sql/vdbeapi.c
> @@ -484,8 +484,9 @@ sql_step(sql_stmt * pStmt)
> void *
> sql_user_data(sql_context * p)
> {
> -	assert(p && p->pFunc);
> -	return p->pFunc->pUserData;
> +	assert(p != NULL && p->func != NULL);
> +	assert(p->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
> +	return ((struct func_sql_builtin *)p->func)->user_data;

user_data is used only for min/max functions. Let’s consider removing it.

> @@ -563,7 +564,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 &&
> +	       p->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN &&
> +	       p->func->def->aggregate == FUNC_AGGREGATE_GROUP);

Please, split this assert into three ones. IMHO it would
improve code readability.

> diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
> index b8c31ecec..8b2e816f2 100644
> --- a/src/box/sql/vdbemem.c
> +++ b/src/box/sql/vdbemem.c
> @@ -312,33 +312,28 @@ 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)) {
> +	if (ALWAYS(func != NULL &&

Please, move assert inside if condition to a separate stmt(s):

diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 8b2e816f2..958d67916 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -315,12 +315,12 @@ sqlVdbeMemStringify(Mem * pMem)
 int
 sql_vdbemem_finalize(struct Mem *mem, struct func *func)
 {
-       if (ALWAYS(func != NULL &&
-                  func->def->language == FUNC_LANGUAGE_SQL_BUILTIN &&
-                  func->def->aggregate == FUNC_AGGREGATE_GROUP)) {
-               sql_context ctx;
-               Mem t;
-               assert((mem->flags & MEM_Null) != 0 || func == mem->u.func);
+       assert(func != NULL);
+       assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+       assert(func->def->language == FUNC_AGGREGATE_GROUP);
+       sql_context ctx;
+       Mem t;
+       assert((mem->flags & MEM_Null) != 0 || func == mem->u.func);

...


> +		   func->def->language == FUNC_LANGUAGE_SQL_BUILTIN &&
> +		   func->def->aggregate == FUNC_AGGREGATE_GROUP)) {
> 		sql_context ctx;
> 		Mem t;
> -		assert((pMem->flags & MEM_Null) != 0 || pFunc == pMem->u.pDef);
> +		assert((mem->flags & MEM_Null) != 0 || func == mem->u.func);
> 		memset(&ctx, 0, sizeof(ctx));
> 		memset(&t, 0, sizeof(t));
> 		t.flags = MEM_Null;
> -		t.db = pMem->db;
> +		t.db = mem->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));
> +		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));
> 		if (ctx.is_aborted)
> 			return -1;
> 	}

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

* [tarantool-patches] Re: [PATCH v2 8/8] box: get rid of box.internal.sql_function_create
  2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 8/8] box: get rid of box.internal.sql_function_create Kirill Shcherbatov
@ 2019-08-13 20:43   ` n.pettik
  2019-08-16 12:57     ` Kirill Shcherbatov
  0 siblings, 1 reply; 38+ messages in thread
From: n.pettik @ 2019-08-13 20:43 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov


> diff --git a/src/box/func_def.h b/src/box/func_def.h
> index d99d89190..988d080f0 100644
> --- a/src/box/func_def.h
> +++ b/src/box/func_def.h
> @@ -110,7 +110,7 @@ struct func_def {
> 	 */
> 	bool is_sandboxed;
> 	/** The count of function's input arguments. */
> -	int param_count;
> +	uint32_t param_count;
> 	/** The type of the value returned by function. */
> 	enum field_type returns;
> 	/** Function aggregate option. */
> diff --git a/src/box/port.h b/src/box/port.h
> index a7f5d81bd..26f85b145 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;
> +struct sql_context;

Nit: forward declaration of sql_context is redundant.

> +
> +/** Port implementation used with vdbe memory variables. */
> +struct port_vdbemem {
> +    const struct port_vtab *vtab;
> +    struct sql_value *mem;
> +    uint32_t size;

Nit: I’d better call it mem_count (or at least add brief explanation comment).

> diff --git a/src/lib/core/port.h b/src/lib/core/port.h
> index 09a026df5..17096d0d0 100644
> --- a/src/lib/core/port.h
> +++ b/src/lib/core/port.h
> @@ -98,6 +98,19 @@ 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
> +	 * variables. The SQL VDBE uses this representation for
> +	 * process data. This API is usefull to pass VDBE
> +	 * variables to sql builtin functions.
> +	 * The lifecycle of the returned value is
> +	 * implementation-specific: it may either be returned
> +	 * directly from the port, in which case the data will
> +	 * stay alive as long as the port is alive, or it may be
> +	 * allocated on the fiber()->gc, in which case the caller
> +	 * is responsible for cleaning up.
> +	 */

Consider comment fixes:

diff --git a/src/lib/core/port.h b/src/lib/core/port.h
index 17096d0d0..c6ca115a1 100644
--- a/src/lib/core/port.h
+++ b/src/lib/core/port.h
@@ -99,16 +99,12 @@ struct port_vtab {
         **/
        const char *(*get_msgpack)(struct port *port, uint32_t *size);
        /**
-        * Get the content of a port as a sequence of vdbe memory
-        * variables. The SQL VDBE uses this representation for
-        * process data. This API is usefull to pass VDBE
-        * variables to sql builtin functions.
-        * The lifecycle of the returned value is
-        * implementation-specific: it may either be returned
-        * directly from the port, in which case the data will
-        * stay alive as long as the port is alive, or it may be
-        * allocated on the fiber()->gc, in which case the caller
-        * is responsible for cleaning up.
+        * 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. */

> +	struct sql_value *(*get_vdbemem)(struct port *port, uint32_t *size);
> 	/** Destroy a port and release associated resources. */
> 	void (*destroy)(struct port *port);
> };
> 
> 
> diff --git a/src/box/sql/func.c b/src/box/sql/func.c
> index f07c52b95..ea94d6151 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -45,6 +45,10 @@
> #include <unicode/uchar.h>
> #include <unicode/ucol.h>
> #include "box/coll_id_cache.h"
> +#include "box/port.h"
> +#include "box/tuple.h"
> +#include "lua/utils.h"
> +#include "mpstream.h"
> 
> /*
>  * Return the collating function associated with a function.
> @@ -70,6 +74,279 @@ 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)

Nit: we already have sql_vdbe_mem_alloc_region()
which allocates memory for string using region.
Could you rename it (the original one I mean) to avoid
any confusions?

> +{
> +	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 const char *
> +port_vdbemem_get_msgpack(struct port *base, uint32_t *size)

In fact, this function is unused and is not tested at all.

> +{
> +	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->size);
> +	for (uint32_t i = 0; i < port->size && !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: {
> +			mpstream_encode_str(&stream,
> +					(const char *) sql_value_text(param));

Nit: a bit broken indentation. You can use auxiliary tmp var.

> +			break;
> +		}
> +		case MP_BIN: {
> +			mpstream_encode_binl(&stream, sql_value_bytes(param));
> +			mpstream_memcpy(&stream, sql_value_blob(param),
> +					sql_value_bytes(param));
> +			break;

> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index a66becc89..23b6995d7 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> 
> @@ -1767,6 +1768,50 @@ 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.

Nit: you don’t check this fact.

> + *
> + * P1 is a 32-bit bitmask indicating whether or not each argument
> + * to the function was determined to be constant at compile time.
> + * If the first argument was constant then bit 0 of P1 is set.

This comment is not related to the code: P1 is not involved at all.

> + */
> +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 into register P3 */
> +	if (pOut->flags & (MEM_Str|MEM_Blob))

Nit:

diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 23b6995d7..69cbab45c 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1803,8 +1803,11 @@ case OP_Function: {
        if (mem == NULL)
                goto abort_due_to_error;
 
-       /* Copy the result of the function into register P3 */
-       if (pOut->flags & (MEM_Str|MEM_Blob))
+       /*
+        * Copy the result of the function invocation into
+        * register P3.
+        */
+       if ((pOut->flags & (MEM_Str | MEM_Blob)) != 0)
                if (sqlVdbeMemTooBig(pOut)) goto too_big;
 
        REGISTER_TRACE(p, pOp->p3, pOut);

> +		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]
>  *
> 
> diff --git a/test/box/function1.result b/test/box/function1.result
> index 5b091f72b..48c9ad6cf 100644
> --- a/test/box/function1.result
> +++ b/test/box/function1.result
> @@ -366,6 +366,177 @@ 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()

Oh, could you please fix this awful error message?
At least, it lacks a verb. Personally I would prefer smth like:
“invalid number of arguments is passed to %s: expected %d, got %d"

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

* [tarantool-patches] Re: [PATCH v2 3/8] sql: wrap all trim functions in dispatcher
  2019-08-13  8:28     ` Kirill Shcherbatov
@ 2019-08-13 22:19       ` n.pettik
  0 siblings, 0 replies; 38+ messages in thread
From: n.pettik @ 2019-08-13 22:19 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov



> On 13 Aug 2019, at 11:28, Kirill Shcherbatov <kshcherbatov@tarantool.org> wrote:
> 
>>> 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 stem to get rid of function's name
>> 
>> Nit: stem -> step
> 
>>> +
>>> +/**
>>> + * Normalize args from @a argv input array when it has one,
>>> + * two or three args.
>>> + *
>>> + * This is a dispatcher function that call corresponding
>> 
>> Nit: call -> calls
> Fixed.
> 
>>> +	switch (argc) {
>>> +	case 1:
>>> +		trim_func_one_arg(context, argc, argv);
>> 
>> Why not unpack arguments right here?
>> IMHO it would look much better.
> Can't understand, what do you mean? Those functions are static and would be
> inlined. They handle own corner case. Moreover, if I would unpack values externally,
> I need to pass three values (type, size, value) for each argument.

Already replied in PM, but anyway copy answer here:

I want you to get rid of argc and explicitly pass
one, two or three args of struct Mem/strcut sql_value
type to corresponding functions.

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

* [tarantool-patches] Re: [PATCH v2 5/8] sql: introduce a signature_mask for functions
  2019-08-13 20:38   ` n.pettik
@ 2019-08-14  7:21     ` Kirill Shcherbatov
  0 siblings, 0 replies; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-14  7:21 UTC (permalink / raw)
  To: n.pettik, Tarantool MailList; +Cc: Konstantin Osipov

> TBO I do not understand why should you bother about
> function overloading depending on count of arguments.
> Why can’t we simply incapsulate this by dispatching
> arguments count like we do for trim function? 
Because the current SQL engine raises Invalid arguments count
error during parsing request. I'd like to keep this logic.

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

* [tarantool-patches] Re: [PATCH v2 4/8] sql: get rid of SQL_FUNC_COUNT flag
  2019-08-13 20:35   ` n.pettik
@ 2019-08-14  7:25     ` Kirill Shcherbatov
  0 siblings, 0 replies; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-14  7:25 UTC (permalink / raw)
  To: n.pettik, Tarantool MailList

>> A helper routine is_simple_count decides whether such
>> optimisation is correct. Using SQL_FUNC_COUNT flag to mark a
>> dummy (non-functional)
> 
> Why do you consider it to be non-functional?
> 
>> function entry with 0 arguments is a bad practice
> 
> Why? Please, support this with arguments.
Because the hastable entry define a function that **NEVER** called.
It relies on arguments overloading and it is strange.

> Hm, this code works just by accident :) There’s no check
> that function is really COUNT, but we are lucky enough
> and there’s no other aggregate function which can take
> empty list of arguments...
Maybe because the helper function's name is "is_simple_count"? =)

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

* [tarantool-patches] Re: [PATCH v2 7/8] sql: get rid of FuncDef function hash
  2019-08-13 20:40   ` n.pettik
@ 2019-08-16 12:57     ` Kirill Shcherbatov
  2019-08-20 16:06       ` n.pettik
  0 siblings, 1 reply; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-16 12:57 UTC (permalink / raw)
  To: tarantool-patches, n.pettik

>> It has a sql-specific method :call and :finalize typica while
> 
> Nit: typic? Please, consider using spell-checker.
>> Following tests using sql_create_function are broken now.
>> They would be fixed in the following commit:
> 
> Nit: are going to be fixed in the next commit.
Fixed.

>> +	/** A call method for a function. */
> 
> Add explanation why we don’t use basic (i.e. call
> method of base class) “call” method. Furthermore,
> built-ins are called bypassing func_access_check()
> function. This should be either documented or fixed.
	/**
	 * 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);

>> +	/** Finalize method (only for aggregate function). */
>> +	void (*finalize)(sql_context *ctx);
> 
> We hold aggregate attribute in func_def; I guess
> lua/C aggregate are not going to be implemented
> soon (or someday at all); so, considering these
> points, why not to move this method to the base vtab?
Verbally discussed. It is vdbe-memory-oriented API, so
it shouldn't be in abstract function's vtab.

>> +/**
>> + * A SQL method to find a function in a hash by it's name and
> 
> Nit: it’s (it is) -> its.
>> + * Returns not NULL function pointer when a valid and exported
>> + * to SQL engine function was found and NULL otherwise.
> 
> Nit: was found -> is found.
Fixed.

>> +static inline bool
>> +sql_func_flag_test(struct func *func, uint16_t flag)
> 
> At least I would rename it to sql_func_test_flag() or
> sql_builtin_flag_is_set() or smth like that.
Verbally discussed, I believe that it is the most representative name for
this helper.

>> +/**
>> + * Memory cell pMem contains the context of an aggregate function.
> 
> Nit: pMem -> mem.
Fixed.

>> -static void
>> +MAYBE_UNUSED static void
> 
> Is this change really related to the patch? Same for
> all other added MAYBE_UNUSED modifiers.
I don't initialze analyze functions because the are currently
unused, but I don't like to remove their implementation.

>> 	assert(func != NULL);
> 
> Instead of asserting func existence let’s raise an error: now
> user can remove built-in function from hash - there’s no
> protection to avoid such cases. Note that such opportunity
> (removing built-ins from cache) has been introduced by moving
> built-ins to the global func cache. In this case, assertion will fail
> in debug mode and lead to unpredictable consequences in
> release mode. Another option is to introduce mentioned protection,
> i.e. disallow user to delete functions which are declared with
> FUNC_LANGUAGE_SQL_BUILTIN flag.
I've introduce remove protection in alter.cc

>> +			    func->def->aggregate == FUNC_AGGREGATE_GROUP) {
> 
> Why do we need check on aggregation type?
Works also without it, thanks.

>> +		diag_set(ClientError, ER_CREATE_FUNCTION, def->name,
>> +			 "body and is_sandboxed options are not compatible "
>> +			 "with SQL language”);
> 
> Why not move this check to func_def_new_from_tuple() ?
Already there.

>> +	func->flags = sql_builtins[idx].flags;
>> +	func->user_data = sql_builtins[idx].user_data;
>> +	func->call = sql_builtins[idx].call;
>> +	func->finalize = sql_builtins[idx].finalize;
>> +	func->signature_mask = sql_builtins[idx].signature_mask;
>> +	func->base.vtab = &func_sql_builtin_vtab;
>> +	func->base.def = def;
>> +
> Why not memcpy?
Because it is partial func def initialization. Can't just mecpy it.

> Nit: double semicolon at the end of string.
Fixed.
> 
> Can aggregate function be non-builtin?
Not for now, but I don't like to rely on it so I make all
corresponding checks.

> SQL_FUNC_SLOCHNG seems to be unused.
> So, you can simply remove it.
Removed in separate commit.

>> +	(*((struct func_sql_builtin *)pCtx->func)->call)(pCtx, pCtx->argc,
>> +							 pCtx->argv);
> 
> Nit: please, split it into two steps: type cast and function call.
Done.

> user_data is used only for min/max functions. Let’s consider removing it.
Removed user_data at all.

>> +	assert(p != NULL && p->func != NULL &&
>> +	       p->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN &&
>> +	       p->func->def->aggregate == FUNC_AGGREGATE_GROUP);
> 
> Please, split this assert into three ones. IMHO it would
> improve code readability.
Done.

>> -	if (ALWAYS(pFunc && pFunc->xFinalize)) {
>> +	if (ALWAYS(func != NULL &&
> 
> Please, move assert inside if condition to a separate stmt(s):
Done.

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

* [tarantool-patches] Re: [PATCH v2 8/8] box: get rid of box.internal.sql_function_create
  2019-08-13 20:43   ` [tarantool-patches] " n.pettik
@ 2019-08-16 12:57     ` Kirill Shcherbatov
  2019-08-20 19:36       ` n.pettik
  0 siblings, 1 reply; 38+ messages in thread
From: Kirill Shcherbatov @ 2019-08-16 12:57 UTC (permalink / raw)
  To: tarantool-patches, n.pettik

>> +struct sql_context;
> 
> Nit: forward declaration of sql_context is redundant.
>> +    uint32_t size;
> 
> Nit: I’d better call it mem_count (or at least add brief explanation comment).
Fixed.
> +	 * allocated on the fiber()->gc, in which case the caller
>> +	 * is responsible for cleaning up.
>> +	 */
> 
> Consider comment fixes:
Applied.

>> +static struct Mem *
>> +vdbemem_alloc_on_region(uint32_t count)
> 
> Nit: we already have sql_vdbe_mem_alloc_region()
> which allocates memory for string using region.
> Could you rename it (the original one I mean) to avoid
> any confusions?
Renamed to sql_vdbe_mem_alloc_blob_region

>> +static const char *
>> +port_vdbemem_get_msgpack(struct port *base, uint32_t *size)
> 
> In fact, this function is unused and is not tested at all.
It is not so. Take a look to box/function1.test.lua

>> +			mpstream_encode_str(&stream,
>> +					(const char *) sql_value_text(param));
> 
> Nit: a bit broken indentation. You can use auxiliary tmp var.
Fixed.

>> + * Register P3 must not be one of the function inputs.
> 
> Nit: you don’t check this fact.
It is just recommendation for a user. Plz Forget about it.
(moreover it *never* tested in other places)
> Oh, could you please fix this awful error message?
> At least, it lacks a verb. Personally I would prefer smth like:
> “invalid number of arguments is passed to %s: expected %d, got %d"
Fixed as a separate patch.

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

* [tarantool-patches] Re: [PATCH v2 7/8] sql: get rid of FuncDef function hash
  2019-08-16 12:57     ` Kirill Shcherbatov
@ 2019-08-20 16:06       ` n.pettik
  0 siblings, 0 replies; 38+ messages in thread
From: n.pettik @ 2019-08-20 16:06 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov


>>> +	/** A call method for a function. */
>> 
>> Add explanation why we don’t use basic (i.e. call
>> method of base class) “call” method. Furthermore,
>> built-ins are called bypassing func_access_check()
>> function. This should be either documented or fixed.
> 	/**
> 	 * A VDBE-memory-compatible call method for a function.
> 	 * SQL Builting functions doesn't use base class call

Builting -> built-in; doesn’t -> don't

> 	 * method to gain the best possible performance for SQL
> 	 * requests.

Can’t parse this sentence at all.

> As builtin functions are predefined and
> 	 * non of them modifie schema, access checks are

God please, use spell checker.

> 	 * redundant, functions have the same execution
> 	 * privileges as SQL.
> 	 */
> 	void (*call)(sql_context *ctx, int argc, sql_value **argv);
> 
>>> +/**
>>> + * Memory cell pMem contains the context of an aggregate function.
>> 
>> Nit: pMem -> mem.
> Fixed.

It’s not.

>>> 	assert(func != NULL);
>> 
>> Instead of asserting func existence let’s raise an error: now
>> user can remove built-in function from hash - there’s no
>> protection to avoid such cases. Note that such opportunity
>> (removing built-ins from cache) has been introduced by moving
>> built-ins to the global func cache. In this case, assertion will fail
>> in debug mode and lead to unpredictable consequences in
>> release mode. Another option is to introduce mentioned protection,
>> i.e. disallow user to delete functions which are declared with
>> FUNC_LANGUAGE_SQL_BUILTIN flag.
> I've introduce remove protection in alter.cc

Well, now we have another problem: user can create
built-in function, but can’t drop it:

tarantool> box.schema.func.create('WAITFOR', {language = 'SQL_BUILTIN',   param_list = {'integer'}, returns = 'integer',exports = {'SQL'}})
---
…

tarantool> box.schema.func.drop('WAITFOR')
---
- error: 'Can''t drop function 1: function is SQL builtin'
...



>> Can aggregate function be non-builtin?
> Not for now, but I don't like to rely on it so I make all
> corresponding checks.

Please, don’t cut the context related to comment.
Add assertion than, instead of absolutely redundant if-check.
If someday ability of declaring non-builtins as aggregates
would be introduced, one can easily find and fix this place.

>> user_data is used only for min/max functions. Let’s consider removing it.
> Removed user_data at all.

I’d better move it in a separate commit.

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

* [tarantool-patches] Re: [PATCH v2 8/8] box: get rid of box.internal.sql_function_create
  2019-08-16 12:57     ` Kirill Shcherbatov
@ 2019-08-20 19:36       ` n.pettik
  0 siblings, 0 replies; 38+ messages in thread
From: n.pettik @ 2019-08-20 19:36 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov


>>> +static struct Mem *
>>> +vdbemem_alloc_on_region(uint32_t count)
>> 
>> Nit: we already have sql_vdbe_mem_alloc_region()
>> which allocates memory for string using region.
>> Could you rename it (the original one I mean) to avoid
>> any confusions?
> Renamed to sql_vdbe_mem_alloc_blob_region

Please, move this fix to a separate patch.

>>> +static const char *
>>> +port_vdbemem_get_msgpack(struct port *base, uint32_t *size)
>> 
>> In fact, this function is unused and is not tested at all.
> It is not so. Take a look to box/function1.test.lua

Oh, ok. Sorry, I’m so used to running only sql suite
that forget to look at other tests.

>>> + * Register P3 must not be one of the function inputs.
>> 
>> Nit: you don’t check this fact.
> It is just recommendation for a user.

As a rule, users don’t even open source code.
This is solely developer oriented comment, so either place
assertion verifying that P3 doesn’t get into mentioned range
(if it is really vital) or remove this comment.

> Plz Forget about it.
> (moreover it *never* tested in other places)

Why should it be?

Also you skipped one nit:

-       if (pOut->flags & (MEM_Str|MEM_Blob))

->

+       if ((pOut->flags & (MEM_Str | MEM_Blob)) != 0)
               if (sqlVdbeMemTooBig(pOut)) goto too_big;

Please, apply.

>> Oh, could you please fix this awful error message?
>> At least, it lacks a verb. Personally I would prefer smth like:
>> “invalid number of arguments is passed to %s: expected %d, got %d"
> Fixed as a separate patch.

Look at fixes at np/sql-builtins
I’ve introduced separate error code for invalid argument count.

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

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

Thread overview: 38+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-08-08 14:50 [tarantool-patches] [PATCH v2 0/8] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 1/8] sql: remove SQL_PreferBuiltin flag Kirill Shcherbatov
2019-08-09 16:07   ` [tarantool-patches] " n.pettik
2019-08-12 21:58   ` Konstantin Osipov
2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 2/8] sql: GREATEST, LEAST instead of MIN/MAX overload Kirill Shcherbatov
2019-08-09 17:37   ` [tarantool-patches] " n.pettik
2019-08-13  8:26     ` Kirill Shcherbatov
2019-08-12 21:59   ` Konstantin Osipov
2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 3/8] sql: wrap all trim functions in dispatcher Kirill Shcherbatov
2019-08-09 18:05   ` [tarantool-patches] " n.pettik
2019-08-13  8:28     ` Kirill Shcherbatov
2019-08-13 22:19       ` n.pettik
2019-08-12 22:00   ` Konstantin Osipov
2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 4/8] sql: get rid of SQL_FUNC_COUNT flag Kirill Shcherbatov
2019-08-12 22:01   ` [tarantool-patches] " Konstantin Osipov
2019-08-13 20:35   ` n.pettik
2019-08-14  7:25     ` Kirill Shcherbatov
2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 5/8] sql: introduce a signature_mask for functions Kirill Shcherbatov
2019-08-12 22:04   ` [tarantool-patches] " Konstantin Osipov
2019-08-13  8:32     ` Kirill Shcherbatov
2019-08-13  8:44       ` Konstantin Osipov
2019-08-13 20:38   ` n.pettik
2019-08-14  7:21     ` Kirill Shcherbatov
2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 6/8] sql: rename OP_Function to OP_BuiltinFunction Kirill Shcherbatov
2019-08-12 22:04   ` [tarantool-patches] " Konstantin Osipov
2019-08-13 20:36   ` n.pettik
2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 7/8] sql: get rid of FuncDef function hash Kirill Shcherbatov
2019-08-12 22:11   ` [tarantool-patches] " Konstantin Osipov
2019-08-13  7:29     ` Kirill Shcherbatov
2019-08-13  8:42       ` Konstantin Osipov
2019-08-13  9:45         ` Kirill Shcherbatov
2019-08-13 20:40   ` n.pettik
2019-08-16 12:57     ` Kirill Shcherbatov
2019-08-20 16:06       ` n.pettik
2019-08-08 14:50 ` [tarantool-patches] [PATCH v2 8/8] box: get rid of box.internal.sql_function_create Kirill Shcherbatov
2019-08-13 20:43   ` [tarantool-patches] " n.pettik
2019-08-16 12:57     ` Kirill Shcherbatov
2019-08-20 19:36       ` n.pettik

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