Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH v4 0/4] sql: uniform SQL and Lua functions subsystem
@ 2019-08-21 15:28 Kirill Shcherbatov
  2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 1/4] sql: rename sql_vdbe_mem_alloc_region helper Kirill Shcherbatov
                   ` (5 more replies)
  0 siblings, 6 replies; 17+ messages in thread
From: Kirill Shcherbatov @ 2019-08-21 15:28 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

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

Kirill Shcherbatov (4):
  sql: rename sql_vdbe_mem_alloc_region helper
  sql: replace flag MINMAX with flags MIN and MAX
  sql: get rid of FuncDef function hash
  sql: support user-defined functions in SQL

Changes in v4:
  - removed check_param_count method; use dynamic checks instead
  - user can't create a builtin anymore
  - changes are split for 3 patches
  - many minor fixes

I'll cite @Korablev's review comments below:

> ‘user_data' is not used anymore, please remove.
done

> +	 */
> +	int (*check_param_count)(struct func_sql_builtin *func, int argc);
> Check whether function supports given number of arguments.
> For instance, trim() function can accept one, two or three arguments.
Applied.

> Again: pMem -> mem.
done

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

I've adopted a code on your branch.

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

> Nit: one extra empty line.
fixed

> I’d rename it to func_sql_builtin_call_stub()
done

> Let’s reorganise a bit this struct:
done

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

I don't like memcpy in this case

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

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

> This makes you include box/schema.h
discussed in Telegram; let's keep it.

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

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

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

> Nit: …SQL built-in.
Fixed

> > Renamed to sql_vdbe_mem_alloc_blob_region
> Please, move this fix to a separate patch.
Done.

> 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.
I've drop this comment.

> 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.
Done.

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

> > 	 * method to gain the best possible performance for SQL
> > 	 * requests.
> Can’t parse this sentence at all.
Updated the whole comment:
/**
  * A VDBE-memory-compatible call method.
  * SQL built-ins don't use func base class "call"
  * method to provide a best performance for SQL requests.
  * Access checks are redundant, because all SQL built-ins
  * are predefined and are executed on SQL privilege level.
  */
void (*call)(sql_context *ctx, int argc, sql_value **argv);


> 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'}})
Solved. Included in test coverage.


> >> 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.
Verbally discussed, why it is not ok.

 src/box/errcode.h               |    1 +
 src/box/lua/lua_sql.h           |   39 --
 src/box/port.h                  |   17 +
 src/box/sql.h                   |    1 +
 src/box/sql/sqlInt.h            |  234 ++-----
 src/box/sql/vdbe.h              |   11 +-
 src/box/sql/vdbeInt.h           |   23 +-
 src/lib/core/port.h             |   15 +
 src/box/call.c                  |    1 +
 src/box/execute.c               |    1 +
 src/box/func.c                  |   33 +-
 src/box/lua/call.c              |    6 +-
 src/box/lua/lua_sql.c           |  205 ------
 src/box/port.c                  |    4 +
 src/box/sql/analyze.c           |   34 +-
 src/box/sql/callback.c          |  204 ------
 src/box/sql/date.c              |   28 -
 src/box/sql/expr.c              |   83 ++-
 src/box/sql/func.c              | 1051 +++++++++++++++++++++++++++----
 src/box/sql/global.c            |    7 -
 src/box/sql/main.c              |  137 ----
 src/box/sql/resolve.c           |   79 ++-
 src/box/sql/select.c            |   10 +-
 src/box/sql/vdbe.c              |   65 +-
 src/box/sql/vdbeapi.c           |   17 +-
 src/box/sql/vdbeaux.c           |   33 +-
 src/box/sql/vdbemem.c           |   65 +-
 src/box/sql/whereexpr.c         |    2 +-
 src/box/CMakeLists.txt          |    1 -
 src/box/alter.cc                |    6 +
 src/box/lua/schema.lua          |    3 +-
 test/box/function1.result       |  196 ++++++
 test/box/function1.test.lua     |   70 ++
 test/box/misc.result            |    1 +
 test/sql-tap/alias.test.lua     |   11 +-
 test/sql-tap/check.test.lua     |   11 +-
 test/sql-tap/func.test.lua      |   22 +-
 test/sql-tap/func2.test.lua     |   18 +-
 test/sql-tap/func5.test.lua     |   27 +-
 test/sql-tap/limit.test.lua     |    4 +-
 test/sql-tap/lua_sql.test.lua   |  121 ++--
 test/sql-tap/select1.test.lua   |   12 +-
 test/sql-tap/subquery.test.lua  |   21 +-
 test/sql-tap/trigger9.test.lua  |    8 +-
 test/sql-tap/where2.test.lua    |    4 +-
 test/sql/errinj.result          |   26 -
 test/sql/errinj.test.lua        |   10 -
 test/sql/func-recreate.result   |   41 +-
 test/sql/func-recreate.test.lua |   26 +-
 test/sql/icu-upper-lower.result |    4 +-
 50 files changed, 1751 insertions(+), 1298 deletions(-)
 delete mode 100644 src/box/lua/lua_sql.h
 delete mode 100644 src/box/lua/lua_sql.c

-- 
2.22.1

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

* [tarantool-patches] [PATCH v4 1/4] sql: rename sql_vdbe_mem_alloc_region helper
  2019-08-21 15:28 [tarantool-patches] [PATCH v4 0/4] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
@ 2019-08-21 15:28 ` Kirill Shcherbatov
  2019-08-22 13:04   ` [tarantool-patches] " n.pettik
  2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 2/4] sql: replace flag MINMAX with flags MIN and MAX Kirill Shcherbatov
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 17+ messages in thread
From: Kirill Shcherbatov @ 2019-08-21 15:28 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

Changed sql_vdbe_mem_alloc_region routine name to the
sql_vdbe_mem_alloc_blob_region because we are going to introduce
a new function with a really similar (but more appropriate) name
vdbemem_alloc_on_region in following patch.

Needed for #2200, #4113, #2233
---
 src/box/sql/vdbe.h    | 2 +-
 src/box/sql/vdbe.c    | 2 +-
 src/box/sql/vdbeaux.c | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index 8f16202ba..e5a85fe0e 100644
--- a/src/box/sql/vdbe.h
+++ b/src/box/sql/vdbe.h
@@ -278,7 +278,7 @@ int sqlVdbeRecordCompare(struct sql *db, int key_count,
 			     const void *key1, UnpackedRecord *key2);
 UnpackedRecord *sqlVdbeAllocUnpackedRecord(struct sql *,
 					       struct key_def *);
-int sql_vdbe_mem_alloc_region(Mem *, uint32_t);
+int sql_vdbe_mem_alloc_blob_region(Mem *, uint32_t);
 
 void sqlVdbeLinkSubProgram(Vdbe *, SubProgram *);
 
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index 16e005844..a2f78607a 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -3919,7 +3919,7 @@ case OP_RowData: {
 	}
 	testcase( n==0);
 
-	if (sql_vdbe_mem_alloc_region(pOut, n) != 0)
+	if (sql_vdbe_mem_alloc_blob_region(pOut, n) != 0)
 		goto abort_due_to_error;
 	sqlCursorPayload(pCrsr, 0, n, pOut->z);
 	UPDATE_MAX_BLOBSIZE(pOut);
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index e7accc745..5bcf2ccd8 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -2805,7 +2805,7 @@ sqlVdbeAllocUnpackedRecord(struct sql *db, struct key_def *key_def)
 
 /* Allocate memory for internal VDBE structure on region. */
 int
-sql_vdbe_mem_alloc_region(Mem *vdbe_mem, uint32_t size)
+sql_vdbe_mem_alloc_blob_region(Mem *vdbe_mem, uint32_t size)
 {
 	vdbe_mem->n = size;
 	vdbe_mem->z = region_alloc(&fiber()->gc, size);
-- 
2.22.1

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

* [tarantool-patches] [PATCH v4 2/4] sql: replace flag MINMAX with flags MIN and MAX
  2019-08-21 15:28 [tarantool-patches] [PATCH v4 0/4] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
  2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 1/4] sql: rename sql_vdbe_mem_alloc_region helper Kirill Shcherbatov
@ 2019-08-21 15:28 ` Kirill Shcherbatov
  2019-08-22 13:30   ` [tarantool-patches] " n.pettik
  2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 3/4] sql: get rid of FuncDef function hash Kirill Shcherbatov
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 17+ messages in thread
From: Kirill Shcherbatov @ 2019-08-21 15:28 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

Replaced sql function flag SQL_FUNC_MINMAX with couple new flags
SQL_FUNC_MIN and SQL_FUNC_MAX. This allows to distinguish MIN and
MAX function by flag instead of using user_data context.

This allows to delete user_data field in FundDef object in
further refactoring.

Needed for #2200, #4113, #2233
---
 src/box/sql/sqlInt.h  | 12 +++++++-----
 src/box/sql/func.c    | 31 +++++++++++++++----------------
 src/box/sql/resolve.c | 16 ++++++----------
 3 files changed, 28 insertions(+), 31 deletions(-)

diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 5a3e8f1c1..d71fd06d7 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -1296,7 +1296,7 @@ struct FuncDestructor {
  * are assert() statements in the code to verify this.
  *
  * Value constraints (enforced via assert()):
- *     SQL_FUNC_MINMAX    ==  NC_MinMaxAgg      == SF_MinMaxAgg
+ *     NC_MinMaxAgg      == SF_MinMaxAgg
  *     SQL_FUNC_LENGTH    ==  OPFLAG_LENGTHARG
  *     SQL_FUNC_TYPEOF    ==  OPFLAG_TYPEOFARG
  *     SQL_FUNC_CONSTANT  ==  sql_DETERMINISTIC from the API
@@ -1317,8 +1317,10 @@ struct FuncDestructor {
 #define SQL_FUNC_COALESCE 0x0200	/* Built-in coalesce() or ifnull() */
 #define SQL_FUNC_UNLIKELY 0x0400	/* Built-in unlikely() function */
 #define SQL_FUNC_CONSTANT 0x0800	/* Constant inputs give a constant output */
-#define SQL_FUNC_MINMAX   0x1000	/* True for min() and max() aggregates */
-
+/** Built-in min() or least() function. */
+#define SQL_FUNC_MIN      0x1000
+/** Built-in max() or greatest() function. */
+#define SQL_FUNC_MAX      0x2000
 /**
  * If function returns string, it may require collation to be
  * applied on its result. For instance, result of substr()
@@ -2014,7 +2016,7 @@ struct NameContext {
  *
  * Value constraints (all checked via assert()):
  *    NC_HasAgg    == SF_HasAgg
- *    NC_MinMaxAgg == SF_MinMaxAgg == sql_FUNC_MINMAX
+ *    NC_MinMaxAgg == SF_MinMaxAgg
  *
  */
 #define NC_AllowAgg  0x0001	/* Aggregate functions are allowed here */
@@ -2072,7 +2074,7 @@ struct Select {
  *
  * Value constraints (all checked via assert())
  *     SF_HasAgg     == NC_HasAgg
- *     SF_MinMaxAgg  == NC_MinMaxAgg     == sql_FUNC_MINMAX
+ *     SF_MinMaxAgg  == NC_MinMaxAgg
  *     SF_FixedLimit == WHERE_USE_LIMIT
  */
 #define SF_Distinct       0x00001	/* Output should be DISTINCT */
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 07c019db9..a46df60ed 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -77,12 +77,12 @@ static void
 minmaxFunc(sql_context * context, int argc, sql_value ** argv)
 {
 	int i;
-	int mask;		/* 0 for min() or 0xffffffff for max() */
 	int iBest;
 	struct coll *pColl;
+	struct FuncDef *func = context->pFunc;
 
 	assert(argc > 1);
-	mask = sql_user_data(context) == 0 ? 0 : -1;
+	int mask = (func->funcFlags & SQL_FUNC_MAX) != 0 ? -1 : 0;
 	pColl = sqlGetFuncCollSeq(context);
 	assert(mask == -1 || mask == 0);
 	iBest = 0;
@@ -1721,20 +1721,17 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 		if (pBest->flags)
 			sqlSkipAccumulatorLoad(context);
 	} else if (pBest->flags) {
-		int max;
 		int cmp;
 		struct coll *pColl = sqlGetFuncCollSeq(context);
-		/* This step function is used for both the min() and max() aggregates,
-		 * the only difference between the two being that the sense of the
-		 * comparison is inverted. For the max() aggregate, the
-		 * sql_user_data() function returns (void *)-1. For min() it
-		 * returns (void *)db, where db is the sql* database pointer.
-		 * Therefore the next statement sets variable 'max' to 1 for the max()
-		 * aggregate, or 0 for min().
+		/*
+		 * This step function is used for both the min()
+		 * and max() aggregates, the only difference
+		 * between the two being that the sense of the
+		 * comparison is inverted.
 		 */
-		max = sql_user_data(context) != 0;
+		bool is_max = (context->pFunc->funcFlags & SQL_FUNC_MAX) != 0;
 		cmp = sqlMemCompare(pBest, pArg, pColl);
-		if ((max && cmp < 0) || (!max && cmp > 0)) {
+		if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
 			sqlVdbeMemCopy(pBest, pArg);
 		} else {
 			sqlSkipAccumulatorLoad(context);
@@ -1861,12 +1858,14 @@ sqlRegisterBuiltinFunctions(void)
 		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),
+		FUNCTION2(least, -1, 0, 1, minmaxFunc, SQL_FUNC_MIN,
+			  FIELD_TYPE_SCALAR),
 		AGGREGATE2(min, 1, 0, 1, minmaxStep, minMaxFinalize,
-			   SQL_FUNC_MINMAX, FIELD_TYPE_SCALAR),
-		FUNCTION(greatest, -1, 1, 1, minmaxFunc, FIELD_TYPE_SCALAR),
+			   SQL_FUNC_MIN, FIELD_TYPE_SCALAR),
+		FUNCTION2(greatest, -1, 1, 1, minmaxFunc, SQL_FUNC_MAX,
+			  FIELD_TYPE_SCALAR),
 		AGGREGATE2(max, 1, 1, 1, minmaxStep, minMaxFinalize,
-			   SQL_FUNC_MINMAX, FIELD_TYPE_SCALAR),
+			   SQL_FUNC_MAX, FIELD_TYPE_SCALAR),
 		FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQL_FUNC_TYPEOF,
 			  FIELD_TYPE_STRING),
 		FUNCTION2(length, 1, 0, 0, lengthFunc, SQL_FUNC_LENGTH,
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 207f57ba6..83dff47b6 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -700,16 +700,12 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 				}
 				assert(pDef != 0);
 				if (pNC2) {
-					assert(SQL_FUNC_MINMAX ==
-					       NC_MinMaxAgg);
-					testcase((pDef->
-						  funcFlags &
-						  SQL_FUNC_MINMAX) != 0);
-					pNC2->ncFlags |=
-					    NC_HasAgg | (pDef->
-							 funcFlags &
-							 SQL_FUNC_MINMAX);
-
+					pNC2->ncFlags |= NC_HasAgg;
+					if ((pDef->funcFlags &
+					    SQL_FUNC_MIN) != 0 ||
+					    (pDef->funcFlags &
+					    SQL_FUNC_MAX) != 0)
+						pNC2->ncFlags |= NC_MinMaxAgg;
 				}
 				pNC->ncFlags |= NC_AllowAgg;
 			}
-- 
2.22.1

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

* [tarantool-patches] [PATCH v4 3/4] sql: get rid of FuncDef function hash
  2019-08-21 15:28 [tarantool-patches] [PATCH v4 0/4] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
  2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 1/4] sql: rename sql_vdbe_mem_alloc_region helper Kirill Shcherbatov
  2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 2/4] sql: replace flag MINMAX with flags MIN and MAX Kirill Shcherbatov
@ 2019-08-21 15:28 ` Kirill Shcherbatov
  2019-08-22 14:37   ` [tarantool-patches] " n.pettik
  2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 4/4] sql: support user-defined functions in SQL Kirill Shcherbatov
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 17+ messages in thread
From: Kirill Shcherbatov @ 2019-08-21 15:28 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

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

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

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

Part of #2200, #4113, #2233
---
 src/box/errcode.h               |   1 +
 src/box/lua/lua_sql.h           |  39 --
 src/box/sql.h                   |   1 +
 src/box/sql/sqlInt.h            | 222 +++-------
 src/box/sql/vdbe.h              |   9 +-
 src/box/sql/vdbeInt.h           |  23 +-
 src/box/func.c                  |  33 +-
 src/box/lua/call.c              |   2 -
 src/box/lua/lua_sql.c           | 205 ---------
 src/box/sql/analyze.c           |  34 +-
 src/box/sql/callback.c          | 204 ---------
 src/box/sql/date.c              |  28 --
 src/box/sql/expr.c              |  83 ++--
 src/box/sql/func.c              | 760 +++++++++++++++++++++++++++-----
 src/box/sql/global.c            |   7 -
 src/box/sql/main.c              | 137 ------
 src/box/sql/resolve.c           |  71 +--
 src/box/sql/select.c            |  10 +-
 src/box/sql/vdbe.c              |  20 +-
 src/box/sql/vdbeapi.c           |  17 +-
 src/box/sql/vdbeaux.c           |  31 +-
 src/box/sql/vdbemem.c           |  65 ++-
 src/box/sql/whereexpr.c         |   2 +-
 src/box/CMakeLists.txt          |   1 -
 src/box/alter.cc                |   6 +
 test/box/function1.result       |  20 +
 test/box/function1.test.lua     |   7 +
 test/box/misc.result            |   1 +
 test/sql-tap/func.test.lua      |  22 +-
 test/sql-tap/func2.test.lua     |  18 +-
 test/sql-tap/limit.test.lua     |   4 +-
 test/sql-tap/select1.test.lua   |  12 +-
 test/sql-tap/where2.test.lua    |   4 +-
 test/sql/icu-upper-lower.result |   4 +-
 34 files changed, 952 insertions(+), 1151 deletions(-)
 delete mode 100644 src/box/lua/lua_sql.h
 delete mode 100644 src/box/lua/lua_sql.c

diff --git a/src/box/errcode.h b/src/box/errcode.h
index 817275b97..2c19046fc 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -253,6 +253,7 @@ struct errcode_record {
 	/*198 */_(ER_FUNC_INDEX_FUNC,		"Failed to build a key for functional index '%s' of space '%s': %s") \
 	/*199 */_(ER_FUNC_INDEX_FORMAT,		"Key format doesn't match one defined in functional index '%s' of space '%s': %s") \
 	/*200 */_(ER_FUNC_INDEX_PARTS,		"Wrong functional index definition: %s") \
+	/*200 */_(ER_FUNC_WRONG_ARG_COUNT,	"Wrong number of arguments is passed to %s(): expected %s, got %d")
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/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 7644051b4..b29f5aab5 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;
 
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index d71fd06d7..d08321408 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -70,6 +70,8 @@
 #include "box/column_mask.h"
 #include "parse_def.h"
 #include "box/field_def.h"
+#include "box/func.h"
+#include "box/func_def.h"
 #include "box/sql.h"
 #include "box/txn.h"
 #include "trivia/util.h"
@@ -541,9 +543,6 @@ void
 sql_row_count(struct sql_context *context, MAYBE_UNUSED int unused1,
 	      MAYBE_UNUSED sql_value **unused2);
 
-void *
-sql_user_data(sql_context *);
-
 void *
 sql_aggregate_context(sql_context *,
 			  int nBytes);
@@ -566,26 +565,6 @@ sql_initialize(void);
 #define SQL_TRACE_ROW        0x04
 #define SQL_TRACE_CLOSE      0x08
 
-#define SQL_DETERMINISTIC    0x800
-
-int
-sql_create_function_v2(sql * db,
-			   const char *zFunctionName,
-			   enum field_type type,
-			   int nArg,
-			   int flags,
-			   void *pApp,
-			   void (*xFunc) (sql_context *,
-					  int,
-					  sql_value **),
-			   void (*xStep) (sql_context *,
-					  int,
-					  sql_value **),
-			   void (*xFinal)
-			   (sql_context *),
-			   void (*xDestroy) (void *)
-	);
-
 #define SQL_OPEN_READONLY         0x00000001	/* Ok for sql_open_v2() */
 #define SQL_OPEN_READWRITE        0x00000002	/* Ok for sql_open_v2() */
 #define SQL_OPEN_CREATE           0x00000004	/* Ok for sql_open_v2() */
@@ -1039,9 +1018,6 @@ typedef struct Column Column;
 typedef struct Expr Expr;
 typedef struct ExprList ExprList;
 typedef struct ExprSpan ExprSpan;
-typedef struct FuncDestructor FuncDestructor;
-typedef struct FuncDef FuncDef;
-typedef struct FuncDefHash FuncDefHash;
 typedef struct IdList IdList;
 typedef struct KeyClass KeyClass;
 typedef struct Lookaside Lookaside;
@@ -1123,18 +1099,6 @@ struct LookasideSlot {
 	LookasideSlot *pNext;	/* Next buffer in the list of free buffers */
 };
 
-/*
- * A hash table for built-in function definitions.  (Application-defined
- * functions use a regular table table from hash.h.)
- *
- * Hash each FuncDef structure into one of the FuncDefHash.a[] slots.
- * Collisions are on the FuncDef.u.pHash chain.
- */
-#define SQL_FUNC_HASH_SZ 23
-struct FuncDefHash {
-	FuncDef *a[SQL_FUNC_HASH_SZ];	/* Hash table for functions */
-};
-
 /*
  * Each database connection is an instance of the following structure.
  */
@@ -1244,65 +1208,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 {
-	i8 nArg;		/* Number of arguments.  -1 means unlimited */
-	u16 funcFlags;		/* Some combination of sql_FUNC_* */
-	void *pUserData;	/* User data parameter */
-	FuncDef *pNext;		/* Next function with same name */
-	void (*xSFunc) (sql_context *, int, sql_value **);	/* func or agg-step */
-	void (*xFinalize) (sql_context *);	/* Agg finalizer */
-	const char *zName;	/* SQL name of the function. */
-	union {
-		FuncDef *pHash;	/* Next with a different name but the same hash */
-		FuncDestructor *pDestructor;	/* Reference counted destructor function */
-	} u;
-	/* Return type. */
-	enum field_type ret_type;
-};
-
-/*
- * This structure encapsulates a user-function destructor callback (as
- * configured using create_function_v2()) and a reference counter. When
- * create_function_v2() is called to create a function with a destructor,
- * a single object of this type is allocated. FuncDestructor.nRef is set to
- * the number of FuncDef objects created (either 1 or 3, depending on whether
- * or not the specified encoding is sql_ANY). The FuncDef.pDestructor
- * member of each of the new FuncDef objects is set to point to the allocated
- * FuncDestructor.
- *
- * Thereafter, when one of the FuncDef objects is deleted, the reference
- * count on this object is decremented. When it reaches 0, the destructor
- * is invoked and the FuncDestructor structure freed.
- */
-struct FuncDestructor {
-	int nRef;
-	void (*xDestroy) (void *);
-	void *pUserData;
-};
-
-/*
- * Possible values for FuncDef.flags.  Note that the _LENGTH and _TYPEOF
- * values must correspond to OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG.  And
- * sql_FUNC_CONSTANT must be the same as sql_DETERMINISTIC.  There
- * are assert() statements in the code to verify this.
- *
  * Value constraints (enforced via assert()):
  *     NC_MinMaxAgg      == SF_MinMaxAgg
  *     SQL_FUNC_LENGTH    ==  OPFLAG_LENGTHARG
  *     SQL_FUNC_TYPEOF    ==  OPFLAG_TYPEOFARG
- *     SQL_FUNC_CONSTANT  ==  sql_DETERMINISTIC from the API
  */
 #define SQL_FUNC_LIKE     0x0004	/* Candidate for the LIKE optimization */
-#define SQL_FUNC_EPHEM    0x0010	/* Ephemeral.  Delete with VDBE */
 #define SQL_FUNC_NEEDCOLL 0x0020	/* sqlGetFuncCollSeq() might be called.
 					 * The flag is set when the collation
 					 * of function arguments should be
@@ -1316,11 +1227,10 @@ struct FuncDestructor {
 #define SQL_FUNC_COUNT    0x0100
 #define SQL_FUNC_COALESCE 0x0200	/* Built-in coalesce() or ifnull() */
 #define SQL_FUNC_UNLIKELY 0x0400	/* Built-in unlikely() function */
-#define SQL_FUNC_CONSTANT 0x0800	/* Constant inputs give a constant output */
 /** Built-in min() or least() function. */
 #define SQL_FUNC_MIN      0x1000
 /** Built-in max() or greatest() function. */
-#define SQL_FUNC_MAX      0x2000
+#define SQL_FUNC_MAX   0x2000
 /**
  * If function returns string, it may require collation to be
  * applied on its result. For instance, result of substr()
@@ -1340,61 +1250,6 @@ enum trim_side_mask {
 	TRIM_BOTH = TRIM_LEADING | TRIM_TRAILING
 };
 
-/*
- * The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are
- * used to create the initializers for the FuncDef structures.
- *
- *   FUNCTION(zName, nArg, iArg, bNC, xFunc)
- *     Used to create a scalar function definition of a function zName
- *     implemented by C function xFunc that accepts nArg arguments. The
- *     value passed as iArg is cast to a (void*) and made available
- *     as the user-data (sql_user_data()) for the function. If
- *     argument bNC is true, then the sql_FUNC_NEEDCOLL flag is set.
- *
- *   FUNCTION_COLL
- *     Like FUNCTION except it assumes that function returns
- *     STRING which collation should be derived from first
- *     argument (trim, substr etc).
- *
- *   VFUNCTION(zName, nArg, iArg, bNC, xFunc)
- *     Like FUNCTION except it omits the sql_FUNC_CONSTANT flag.
- *
- *   AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal)
- *     Used to create an aggregate function definition implemented by
- *     the C functions xStep and xFinal. The first four parameters
- *     are interpreted in the same way as the first 4 parameters to
- *     FUNCTION().
- *
- *   LIKEFUNC(zName, nArg, pArg, flags)
- *     Used to create a scalar function definition of a function zName
- *     that accepts nArg arguments and is implemented by a call to C
- *     function likeFunc. Argument pArg is cast to a (void *) and made
- *     available as the function user-data (sql_user_data()). The
- *     FuncDef.flags variable is set to the value passed as the flags
- *     parameter.
- */
-#define FUNCTION(zName, nArg, iArg, bNC, xFunc, type) \
-  {nArg, SQL_FUNC_CONSTANT|(bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define FUNCTION_COLL(zName, nArg, iArg, bNC, xFunc) \
-  {nArg, SQL_FUNC_CONSTANT|SQL_FUNC_DERIVEDCOLL|(bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, FIELD_TYPE_STRING}
-#define VFUNCTION(zName, nArg, iArg, bNC, xFunc, type) \
-  {nArg, (bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags, type) \
-  {nArg,SQL_FUNC_CONSTANT|(bNC*SQL_FUNC_NEEDCOLL)|extraFlags,\
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define LIKEFUNC(zName, nArg, arg, flags, type) \
-  {nArg, SQL_FUNC_NEEDCOLL|SQL_FUNC_CONSTANT|flags, \
-   (void *)(SQL_INT_TO_PTR(arg)), 0, likeFunc, 0, #zName, {0}, type}
-#define AGGREGATE(zName, nArg, arg, nc, xStep, xFinal, type) \
-  {nArg, (nc*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}, type}
-#define AGGREGATE2(zName, nArg, arg, nc, xStep, xFinal, extraFlags, type) \
-  {nArg, (nc*SQL_FUNC_NEEDCOLL)|extraFlags, \
-   SQL_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}, type}
-
 /*
  * All current savepoints are stored in a linked list starting at
  * sql.pSavepoint. The first element in the list is the most recently
@@ -1587,7 +1442,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 */
 		/**
@@ -3535,10 +3391,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.
@@ -4111,7 +3963,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;
 
 /**
@@ -4247,21 +4098,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
@@ -4298,7 +4141,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,
@@ -4489,9 +4331,55 @@ Expr *sqlExprForVectorField(Parse *, Expr *, int);
  */
 extern int sqlSubProgramsRemaining;
 
-/** Register built-in functions to work with ANALYZE data. */
-void
-sql_register_analyze_builtins(void);
+struct func_sql_builtin {
+	/** Function object base class. */
+	struct func base;
+	/** A bitmask of SQL flags. */
+	uint16_t flags;
+	/**
+	 * A VDBE-memory-compatible call method.
+	 * SQL built-ins don't use func base class "call"
+	 * method to provide a best performance for SQL requests.
+	 * Access checks are redundant, because all SQL built-ins
+	 * are predefined and are executed on SQL privilege level.
+	 */
+	void (*call)(sql_context *ctx, int argc, sql_value **argv);
+	/**
+	 * A VDBE-memory-compatible finalize method
+	 * (is valid only for aggregate function).
+	 */
+	void (*finalize)(sql_context *ctx);
+};
+
+/**
+ * Test whether SQL-specific flag is set for given function.
+ * Currently only SQL Builtin Functions have such hint flags,
+ * so function returns false for other functions. Such approach
+ * decreases code complexity and allows do not distinguish
+ * functions by implementation details where it is unnecessary.
+ *
+ * Returns true when given flag is set for a given function and
+ * false otherwise.
+ */
+static inline bool
+sql_func_flag_is_set(struct func *func, uint16_t flag)
+{
+	if (func->def->language != FUNC_LANGUAGE_SQL_BUILTIN)
+		return false;
+	return (((struct func_sql_builtin *)func)->flags & flag) != 0;
+}
+
+/**
+ * A SQL method to find a function in a hash by its name and
+ * count of arguments. Only functions that have 'SQL' engine
+ * export field set true and have exactly the same signature
+ * are returned.
+ *
+ * Returns not NULL function pointer when a valid and exported
+ * to SQL engine function is found and NULL otherwise.
+ */
+struct func *
+sql_func_by_signature(const char *name, int argc);
 
 /**
  * Generate VDBE code to halt execution with correct error if
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index e5a85fe0e..f8dbd0fa3 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..af55f4cdc 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 mem contains the context of an aggregate function.
+ * This routine calls the finalize method for that function. The
+ * result of the aggregate is stored back into mem.
+ *
+ * Returns -1 if the finalizer reports an error. 0 otherwise.
+ */
+int
+sql_vdbemem_finalize(struct Mem *mem, struct func *func);
+
 const char *sqlOpcodeName(int);
 int sqlVdbeMemGrow(Mem * pMem, int n, int preserve);
 int sqlVdbeMemClearAndResize(Mem * pMem, int n);
diff --git a/src/box/func.c b/src/box/func.c
index b35d05dca..e8cc99081 100644
--- a/src/box/func.c
+++ b/src/box/func.c
@@ -381,38 +381,9 @@ 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,
-};
+extern struct func *
+func_sql_builtin_new(struct func_def *def);
 
 struct func *
 func_new(struct func_def *def)
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index 0ac2eb7a6..001578b5a 100644
--- a/src/box/lua/call.c
+++ b/src/box/lua/call.c
@@ -44,7 +44,6 @@
 #include "box/port.h"
 #include "box/lua/tuple.h"
 #include "small/obuf.h"
-#include "lua_sql.h"
 #include "trivia/util.h"
 #include "mpstream.h"
 
@@ -968,7 +967,6 @@ static struct trigger on_alter_func_in_lua = {
 
 static const struct luaL_Reg boxlib_internal[] = {
 	{"call_loadproc",  lbox_call_loadproc},
-	{"sql_create_function",  lbox_sql_create_function},
 	{"module_reload", lbox_module_reload},
 	{"func_call", lbox_func_call},
 	{NULL, NULL}
diff --git a/src/box/lua/lua_sql.c b/src/box/lua/lua_sql.c
deleted file mode 100644
index 67a51a82c..000000000
--- a/src/box/lua/lua_sql.c
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * 1. Redistributions of source code must retain the above
- *    copyright notice, this list of conditions and the
- *    following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above
- *    copyright notice, this list of conditions and the following
- *    disclaimer in the documentation and/or other materials
- *    provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
- * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include "lua.h"
-#include "lua/utils.h"
-
-#include "box/lua/call.h"
-#include "box/sql/sqlInt.h"
-#include "box/sql/vdbeInt.h"
-
-struct lua_sql_func_info {
-	int func_ref;
-};
-
-/**
- * This function is callback which is called by sql engine.
- *
- * Purpose of this function is to call lua func from sql.
- * Lua func should be previously registered in sql
- * (see lbox_sql_create_function).
- */
-static void
-lua_sql_call(sql_context *pCtx, int nVal, sql_value **apVal) {
-	lua_State *L = lua_newthread(tarantool_L);
-	int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX);
-	struct lua_sql_func_info *func_info = sql_user_data(pCtx);
-
-	lua_rawgeti(L, LUA_REGISTRYINDEX, func_info->func_ref);
-	for (int i = 0; i < nVal; i++) {
-		sql_value *param = apVal[i];
-		switch (sql_value_type(param)) {
-		case MP_INT:
-			luaL_pushint64(L, sql_value_int64(param));
-			break;
-		case MP_UINT:
-			luaL_pushuint64(L, sql_value_uint64(param));
-			break;
-		case MP_DOUBLE:
-			lua_pushnumber(L, sql_value_double(param));
-			break;
-		case MP_STR:
-			lua_pushstring(L, (const char *) sql_value_text(param));
-			break;
-		case MP_BIN:
-			lua_pushlstring(L, sql_value_blob(param),
-					(size_t) sql_value_bytes(param));
-			break;
-		case MP_NIL:
-			lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_nil_ref);
-			break;
-		case MP_BOOL:
-			lua_pushboolean(L, sql_value_boolean(param));
-			break;
-		default:
-			diag_set(ClientError, ER_SQL_EXECUTE, "Unsupported "\
-				 "type passed to Lua");
-			pCtx->is_aborted = true;
-			goto error;
-		}
-	}
-	if (lua_pcall(L, lua_gettop(L) - 1, 1, 0) != 0){
-		diag_set(ClientError, ER_SQL_EXECUTE, lua_tostring(L, -1));
-		pCtx->is_aborted = true;
-		goto error;
-	}
-	switch(lua_type(L, -1)) {
-	case LUA_TBOOLEAN:
-		sql_result_bool(pCtx, lua_toboolean(L, -1));
-		break;
-	case LUA_TNUMBER:
-		sql_result_double(pCtx, lua_tonumber(L, -1));
-		break;
-	case LUA_TSTRING:
-		sql_result_text(pCtx, lua_tostring(L, -1), -1,
-				    SQL_TRANSIENT);
-		break;
-	case LUA_TNIL:
-		sql_result_null(pCtx);
-		break;
-	default:
-		diag_set(ClientError, ER_SQL_EXECUTE, "Unsupported type "\
-			 "passed from Lua");
-		pCtx->is_aborted = true;
-		goto error;
-	}
-error:
-	luaL_unref(tarantool_L, LUA_REGISTRYINDEX, coro_ref);
-	return;
-}
-
-static void
-lua_sql_destroy(void *p)
-{
-	struct lua_sql_func_info *func_info = p;
-	luaL_unref(tarantool_L, LUA_REGISTRYINDEX, func_info->func_ref);
-	free(func_info);
-	return;
-}
-
-/**
- * A helper to register lua function in SQL during runtime.
- * It makes available queries like this: "SELECT lua_func(arg);"
- *
- * sql_create_function *p argument is used to store func ref
- * to lua function (it identifies actual lua func to call if there
- * are many of them). SQL function must have name and type of
- * returning value. Additionally, it can feature number of
- * arguments and deterministic flag.
- */
-int
-lbox_sql_create_function(struct lua_State *L)
-{
-	struct sql *db = sql_get();
-	if (db == NULL)
-		return luaL_error(L, "Please call box.cfg{} first");
-	int argc = lua_gettop(L);
-	/*
-	 * Three function prototypes are possible:
-	 * 1. sql_create_function("func_name", "type", func);
-	 * 2. sql_create_function("func_name", "type", func,
-	 *                        func_arg_num);
-	 * 3. sql_create_function("func_name", "type", func,
-	 *                        func_arg_num, is_deterministic);
-	 */
-	if (!(argc == 3 && lua_isstring(L, 1) && lua_isstring(L, 2) &&
-	    lua_isfunction(L, 3)) &&
-	    !(argc == 4 && lua_isstring(L, 1) && lua_isstring(L, 2) &&
-	      lua_isfunction(L, 3) && lua_isnumber(L, 4)) &&
-	    !(argc == 5 && lua_isstring(L, 1) && lua_isstring(L, 2) &&
-	      lua_isfunction(L, 3) && lua_isnumber(L, 4) &&
-	      lua_isboolean(L, 5)))
-		return luaL_error(L, "Invalid arguments");
-	enum field_type type;
-	const char *type_arg = lua_tostring(L, 2);
-	if (strcmp(type_arg, "INT") == 0 || strcmp(type_arg, "INTEGER") == 0)
-		type = FIELD_TYPE_INTEGER;
-	else if (strcmp(type_arg, "TEXT") == 0)
-		type = FIELD_TYPE_STRING;
-	else if (strcmp(type_arg, "NUMBER") == 0)
-		type = FIELD_TYPE_NUMBER;
-	else if (strcmp(type_arg, "VARBINARY") == 0)
-		type = FIELD_TYPE_SCALAR;
-	else if (strcmp(type_arg, "BOOL") == 0 ||
-		 strcmp(type_arg, "BOOLEAN") == 0)
-		type = FIELD_TYPE_BOOLEAN;
-	else
-		return luaL_error(L, "Unknown type");
-	/* -1 indicates any number of arguments. */
-	int func_arg_num = -1;
-	bool is_deterministic = false;
-	if (argc == 4) {
-		func_arg_num = lua_tointeger(L, 4);
-		lua_pop(L, 1);
-	} else if (argc == 5) {
-		is_deterministic = lua_toboolean(L, 5);
-		func_arg_num = lua_tointeger(L, 4);
-		lua_pop(L, 2);
-	}
-	size_t name_len;
-	const char *name = lua_tolstring(L, 1, &name_len);
-	char *normalized_name =
-		sql_normalized_name_region_new(&fiber()->gc, name, name_len);
-	if (normalized_name == NULL)
-		return luaT_error(L);
-	struct lua_sql_func_info *func_info =
-		(struct lua_sql_func_info *) malloc(sizeof(*func_info));
-	if (func_info == NULL)
-		return luaL_error(L, "out of memory");
-	func_info->func_ref = luaL_ref(L, LUA_REGISTRYINDEX);
-	int rc = sql_create_function_v2(db, normalized_name, type, func_arg_num,
-					   is_deterministic ? SQL_DETERMINISTIC : 0,
-					   func_info, lua_sql_call, NULL, NULL,
-					   lua_sql_destroy);
-	if (rc != 0)
-		return luaT_error(L);
-	return 0;
-}
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index bd52d12df..b9858c8d6 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -263,7 +263,7 @@ stat4Destructor(void *pOld)
  * return value is BLOB, but it is really just a pointer to the Stat4Accum
  * object.
  */
-static void
+MAYBE_UNUSED static void
 statInit(sql_context * context, int argc, sql_value ** argv)
 {
 	Stat4Accum *p;
@@ -535,7 +535,7 @@ samplePushPrevious(Stat4Accum * p, int iChng)
  *
  * The R parameter is only used for STAT4
  */
-static void
+MAYBE_UNUSED static void
 statPush(sql_context * context, int argc, sql_value ** argv)
 {
 	int i;
@@ -608,7 +608,7 @@ statPush(sql_context * context, int argc, sql_value ** argv)
  * The content to returned is determined by the parameter J
  * which is one of the STAT_GET_xxxx values defined above.
  */
-static void
+MAYBE_UNUSED static void
 statGet(sql_context * context, int argc, sql_value ** argv)
 {
 	Stat4Accum *p = (Stat4Accum *) sql_value_blob(argv[0]);
@@ -715,11 +715,10 @@ callStatGet(Vdbe * v, int regStat4, int iParam, int regOut)
 {
 	assert(regOut != regStat4 && regOut != regStat4 + 1);
 	sqlVdbeAddOp2(v, OP_Integer, iParam, regStat4 + 1);
-	struct FuncDef *func =
-		sqlFindFunction(sql_get(), "_sql_stat_get", 2, 0);
+	struct func *func = sql_func_by_signature("_sql_stat_get", 2);
 	assert(func != NULL);
 	sqlVdbeAddOp4(v, OP_BuiltinFunction0, 0, regStat4, regOut,
-		      (char *)func, P4_FUNCDEF);
+		      (char *)func, P4_FUNC);
 	sqlVdbeChangeP5(v, 2);
 }
 
@@ -855,11 +854,11 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
 		sqlVdbeAddOp2(v, OP_Count, idx_cursor, stat4_reg + 3);
 		sqlVdbeAddOp2(v, OP_Integer, part_count, stat4_reg + 1);
 		sqlVdbeAddOp2(v, OP_Integer, part_count, stat4_reg + 2);
-		struct FuncDef *init_func =
-			sqlFindFunction(sql_get(), "_sql_stat_init", 3, 0);
+		struct func *init_func =
+			sql_func_by_signature("_sql_stat_init", 3);
 		assert(init_func != NULL);
 		sqlVdbeAddOp4(v, OP_BuiltinFunction0, 0, stat4_reg + 1,
-			      stat4_reg, (char *)init_func, P4_FUNCDEF);
+			      stat4_reg, (char *)init_func, P4_FUNC);
 		sqlVdbeChangeP5(v, 3);
 		/*
 		 * Implementation of the following:
@@ -956,11 +955,11 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
 		sqlVdbeAddOp3(v, OP_MakeRecord, stat_key_reg,
 				  pk_part_count, key_reg);
 		assert(chng_reg == (stat4_reg + 1));
-		struct FuncDef *push_func =
-			sqlFindFunction(sql_get(), "_sql_stat_push", 3, 0);
+		struct func *push_func =
+			sql_func_by_signature("_sql_stat_push", 3);
 		assert(push_func != NULL);
 		sqlVdbeAddOp4(v, OP_BuiltinFunction0, 1, stat4_reg, tmp_reg,
-			      (char *)push_func, P4_FUNCDEF);
+			      (char *)push_func, P4_FUNC);
 		sqlVdbeChangeP5(v, 3);
 		sqlVdbeAddOp2(v, OP_Next, idx_cursor, next_row_addr);
 		/* Add the entry to the stat1 table. */
@@ -1746,14 +1745,3 @@ fail:
 	box_txn_rollback();
 	return -1;
 }
-
-void
-sql_register_analyze_builtins(void)
-{
-	static FuncDef funcs[] = {
-		FUNCTION(_sql_stat_get, 2, 0, 0, statGet, FIELD_TYPE_ANY),
-		FUNCTION(_sql_stat_push, 3, 0, 0, statPush, FIELD_TYPE_ANY),
-		FUNCTION(_sql_stat_init, 3, 0, 0, statInit, FIELD_TYPE_ANY),
-	};
-	sqlInsertBuiltinFuncs(funcs, nelem(funcs));
-}
diff --git a/src/box/sql/callback.c b/src/box/sql/callback.c
index 6c272de71..290363db6 100644
--- a/src/box/sql/callback.c
+++ b/src/box/sql/callback.c
@@ -56,207 +56,3 @@ sql_get_coll_seq(Parse *parser, const char *name, uint32_t *coll_id)
 		return p->coll;
 	}
 }
-
-/* During the search for the best function definition, this procedure
- * is called to test how well the function passed as the first argument
- * matches the request for a function with nArg arguments in a system
- * that uses encoding enc. The value returned indicates how well the
- * request is matched. A higher value indicates a better match.
- *
- * If nArg is -1 that means to only return a match (non-zero) if p->nArg
- * is also -1.  In other words, we are searching for a function that
- * takes a variable number of arguments.
- *
- * If nArg is -2 that means that we are searching for any function
- * regardless of the number of arguments it uses, so return a positive
- * match score for any
- *
- * The returned value is always between 0 and 6, as follows:
- *
- * 0: Not a match.
- * 1: UTF8/16 conversion required and function takes any number of arguments.
- * 2: UTF16 byte order change required and function takes any number of args.
- * 3: encoding matches and function takes any number of arguments
- * 4: UTF8/16 conversion required - argument count matches exactly
- * 5: UTF16 byte order conversion required - argument count matches exactly
- * 6: Perfect match:  encoding and argument count match exactly.
- *
- * If nArg==(-2) then any function with a non-null xSFunc is
- * a perfect match and any function with xSFunc NULL is
- * a non-match.
- */
-#define FUNC_PERFECT_MATCH 4	/* The score for a perfect match */
-static int
-matchQuality(FuncDef * p,	/* The function we are evaluating for match quality */
-	     int nArg		/* Desired number of arguments.  (-1)==any */
-    )
-{
-	int match;
-
-	/* nArg of -2 is a special case */
-	if (nArg == (-2))
-		return (p->xSFunc == 0) ? 0 : FUNC_PERFECT_MATCH;
-
-	/* Wrong number of arguments means "no match" */
-	if (p->nArg != nArg && p->nArg >= 0)
-		return 0;
-
-	/* Give a better score to a function with a specific number of arguments
-	 * than to function that accepts any number of arguments.
-	 */
-	if (p->nArg == nArg) {
-		match = 4;
-	} else {
-		match = 1;
-	}
-
-	return match;
-}
-
-/*
- * Search a FuncDefHash for a function with the given name.  Return
- * a pointer to the matching FuncDef if found, or 0 if there is no match.
- */
-static FuncDef *
-functionSearch(int h,		/* Hash of the name */
-	       const char *zFunc	/* Name of function */
-    )
-{
-	FuncDef *p;
-	for (p = sqlBuiltinFunctions.a[h]; p; p = p->u.pHash) {
-		if (sqlStrICmp(p->zName, zFunc) == 0) {
-			return p;
-		}
-	}
-	return 0;
-}
-
-/*
- * Insert a new FuncDef into a FuncDefHash hash table.
- */
-void
-sqlInsertBuiltinFuncs(FuncDef * aDef,	/* List of global functions to be inserted */
-			  int nDef	/* Length of the apDef[] list */
-    )
-{
-	int i;
-	for (i = 0; i < nDef; i++) {
-		FuncDef *pOther;
-		const char *zName = aDef[i].zName;
-		int nName = sqlStrlen30(zName);
-		int h =
-		    (sqlUpperToLower[(u8) zName[0]] +
-		     nName) % SQL_FUNC_HASH_SZ;
-		pOther = functionSearch(h, zName);
-		if (pOther) {
-			assert(pOther != &aDef[i] && pOther->pNext != &aDef[i]);
-			aDef[i].pNext = pOther->pNext;
-			pOther->pNext = &aDef[i];
-		} else {
-			aDef[i].pNext = 0;
-			aDef[i].u.pHash = sqlBuiltinFunctions.a[h];
-			sqlBuiltinFunctions.a[h] = &aDef[i];
-		}
-	}
-}
-
-/*
- * Locate a user function given a name, a number of arguments and a flag
- * indicating whether the function prefers UTF-16 over UTF-8.  Return a
- * pointer to the FuncDef structure that defines that function, or return
- * NULL if the function does not exist.
- *
- * If the createFlag argument is true, then a new (blank) FuncDef
- * structure is created and liked into the "db" structure if a
- * no matching function previously existed.
- *
- * If nArg is -2, then the first valid function found is returned.  A
- * function is valid if xSFunc is non-zero.  The nArg==(-2)
- * case is used to see if zName is a valid function name for some number
- * of arguments.  If nArg is -2, then createFlag must be 0.
- *
- * If createFlag is false, then a function with the required name and
- * number of arguments may be returned even if the eTextRep flag does not
- * match that requested.
- */
-FuncDef *
-sqlFindFunction(sql * db,	/* An open database */
-		    const char *zName,	/* Name of the function.  zero-terminated */
-		    int nArg,	/* Number of arguments.  -1 means any number */
-		    u8 createFlag	/* Create new entry if true and does not otherwise exist */
-    )
-{
-	FuncDef *p;		/* Iterator variable */
-	FuncDef *pBest = 0;	/* Best match found so far */
-	int bestScore = 0;	/* Score of best match */
-	int h;			/* Hash value */
-	int nName;		/* Length of the name */
-
-	assert(nArg >= (-2));
-	assert(nArg >= (-1) || createFlag == 0);
-	nName = sqlStrlen30(zName);
-
-	/* First search for a match amongst the application-defined functions.
-	 */
-	p = (FuncDef *) sqlHashFind(&db->aFunc, zName);
-	while (p) {
-		int score = matchQuality(p, nArg);
-		if (score > bestScore) {
-			pBest = p;
-			bestScore = score;
-		}
-		p = p->pNext;
-	}
-
-	/* If no match is found, search the built-in functions.
-	 *
-	 * Except, if createFlag is true, that means that we are trying to
-	 * install a new function.  Whatever FuncDef structure is returned it will
-	 * have fields overwritten with new information appropriate for the
-	 * new function.  But the FuncDefs for built-in functions are read-only.
-	 * So we must not search for built-ins when creating a new function.
-	 */
-	if (!createFlag && (pBest == NULL)) {
-		bestScore = 0;
-		h = (sqlUpperToLower[(u8) zName[0]] +
-		     nName) % SQL_FUNC_HASH_SZ;
-		p = functionSearch(h, zName);
-		while (p) {
-			int score = matchQuality(p, nArg);
-			if (score > bestScore) {
-				pBest = p;
-				bestScore = score;
-			}
-			p = p->pNext;
-		}
-	}
-
-	/* If the createFlag parameter is true and the search did not reveal an
-	 * exact match for the name, number of arguments and encoding, then add a
-	 * new entry to the hash table and return it.
-	 */
-	if (createFlag && bestScore < FUNC_PERFECT_MATCH &&
-	    (pBest =
-	     sqlDbMallocZero(db, sizeof(*pBest) + nName + 1)) != 0) {
-		FuncDef *pOther;
-		pBest->zName = (const char *)&pBest[1];
-		pBest->nArg = (u16) nArg;
-		pBest->funcFlags = 0;
-		memcpy((char *)&pBest[1], zName, nName + 1);
-		pOther =
-		    (FuncDef *) sqlHashInsert(&db->aFunc, pBest->zName,
-						  pBest);
-		if (pOther == pBest) {
-			sqlDbFree(db, pBest);
-			sqlOomFault(db);
-			return 0;
-		} else {
-			pBest->pNext = pOther;
-		}
-	}
-
-	if (pBest && (pBest->xSFunc || createFlag)) {
-		return pBest;
-	}
-	return 0;
-}
diff --git a/src/box/sql/date.c b/src/box/sql/date.c
index 2e2a71ad2..dffc23616 100644
--- a/src/box/sql/date.c
+++ b/src/box/sql/date.c
@@ -1290,31 +1290,3 @@ currentTimeFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 }
 #endif
-
-/*
- * This function registered all of the above C functions as SQL
- * functions.  This should be the only routine in this file with
- * external linkage.
- */
-void
-sqlRegisterDateTimeFunctions(void)
-{
-	static FuncDef aDateTimeFuncs[] = {
-#if 0
-		DFUNCTION(julianday, -1, 0, 0, juliandayFunc, FIELD_TYPE_NUMBER),
-		DFUNCTION(date, -1, 0, 0, dateFunc, FIELD_TYPE_STRING),
-		DFUNCTION(time, -1, 0, 0, timeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(datetime, -1, 0, 0, datetimeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(strftime, -1, 0, 0, strftimeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(current_time, 0, 0, 0, ctimeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc,
-			  FIELD_TYPE_STRING),
-		DFUNCTION(current_date, 0, 0, 0, cdateFunc, FIELD_TYPE_STRING),
-		STR_FUNCTION(current_time, 0, "%H:%M:%S", 0, currentTimeFunc),
-		STR_FUNCTION(current_date, 0, "%Y-%m-%d", 0, currentTimeFunc),
-		STR_FUNCTION(current_timestamp, 0, "%Y-%m-%d %H:%M:%S", 0,
-			     currentTimeFunc),
-#endif
-	};
-	sqlInsertBuiltinFuncs(aDateTimeFuncs, ArraySize(aDateTimeFuncs));
-}
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 1f9d91705..9055a0770 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -328,12 +328,11 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
 		if (op == TK_FUNCTION) {
 			uint32_t arg_count = p->x.pList == NULL ? 0 :
 					     p->x.pList->nExpr;
-			struct FuncDef *func = sqlFindFunction(parse->db,
-							       p->u.zToken,
-							       arg_count, 0);
+			struct func *func =
+				sql_func_by_signature(p->u.zToken, arg_count);
 			if (func == NULL)
 				break;
-			if ((func->funcFlags & SQL_FUNC_DERIVEDCOLL) != 0) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL)) {
 				/*
 				 * Now we use quite straightforward
 				 * approach assuming that resulting
@@ -342,7 +341,7 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
 				 * built-in functions: trim, upper,
 				 * lower, replace, substr.
 				 */
-				assert(func->ret_type == FIELD_TYPE_STRING);
+				assert(func->def->returns == FIELD_TYPE_STRING);
 				p = p->x.pList->a->pExpr;
 				continue;
 			}
@@ -3975,11 +3974,9 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 	case TK_FUNCTION:{
 			ExprList *pFarg;	/* List of function arguments */
 			int nFarg;	/* Number of function arguments */
-			FuncDef *pDef;	/* The function definition object */
 			const char *zId;	/* The function name */
 			u32 constMask = 0;	/* Mask of function arguments that are constant */
 			int i;	/* Loop counter */
-			sql *db = pParse->db;	/* The database connection */
 			struct coll *coll = NULL;
 
 			assert(!ExprHasProperty(pExpr, EP_xIsSelect));
@@ -3991,8 +3988,8 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			nFarg = pFarg ? pFarg->nExpr : 0;
 			assert(!ExprHasProperty(pExpr, EP_IntValue));
 			zId = pExpr->u.zToken;
-			pDef = sqlFindFunction(db, zId, nFarg, 0);
-			if (pDef == 0 || pDef->xFinalize != 0) {
+			struct func *func = sql_func_by_signature(zId, nFarg);
+			if (func == NULL) {
 				diag_set(ClientError, ER_NO_SUCH_FUNCTION,
 					 zId);
 				pParse->is_aborted = true;
@@ -4002,9 +3999,16 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			 * IFNULL() functions.  This avoids unnecessary evaluation of
 			 * arguments past the first non-NULL argument.
 			 */
-			if (pDef->funcFlags & SQL_FUNC_COALESCE) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_COALESCE)) {
 				int endCoalesce = sqlVdbeMakeLabel(v);
-				assert(nFarg >= 2);
+				if (nFarg < 2) {
+					diag_set(ClientError,
+						 ER_FUNC_WRONG_ARG_COUNT,
+						 func->def->name,
+						 ">= 2", nFarg);
+					pParse->is_aborted = true;
+					break;
+				}
 				sqlExprCode(pParse, pFarg->a[0].pExpr,
 						target);
 				for (i = 1; i < nFarg; i++) {
@@ -4026,8 +4030,15 @@ 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) {
-				assert(nFarg >= 1);
+			if (sql_func_flag_is_set(func, SQL_FUNC_UNLIKELY)) {
+				if (nFarg < 1) {
+					diag_set(ClientError,
+						 ER_FUNC_WRONG_ARG_COUNT,
+						 func->def->name,
+						 ">= 1", nFarg);
+					pParse->is_aborted = true;
+					break;
+				}
 				return sqlExprCodeTarget(pParse,
 							     pFarg->a[0].pExpr,
 							     target);
@@ -4049,7 +4060,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			 * is done using ANSI rules from
 			 * collations_check_compatibility().
 			 */
-			if ((pDef->funcFlags & SQL_FUNC_NEEDCOLL) != 0) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) {
 				struct coll *unused = NULL;
 				uint32_t curr_id = COLL_NONE;
 				bool is_curr_forced = false;
@@ -4096,9 +4107,8 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				 * or OPFLAG_TYPEOFARG respectively, to avoid unnecessary data
 				 * loading.
 				 */
-				if ((pDef->
-				     funcFlags & (SQL_FUNC_LENGTH |
-						  SQL_FUNC_TYPEOF)) != 0) {
+				if (sql_func_flag_is_set(func, SQL_FUNC_LENGTH |
+							       SQL_FUNC_TYPEOF)) {
 					u8 exprOp;
 					assert(nFarg == 1);
 					assert(pFarg->a[0].pExpr != 0);
@@ -4109,14 +4119,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 +4131,15 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			} else {
 				r1 = 0;
 			}
-			if (pDef->funcFlags & SQL_FUNC_NEEDCOLL) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) {
 				sqlVdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
 						  (char *)coll, P4_COLLSEQ);
 			}
-			sqlVdbeAddOp4(v, OP_BuiltinFunction0, constMask, r1,
-					  target, (char *)pDef, P4_FUNCDEF);
+			assert(func->def->language ==
+			       FUNC_LANGUAGE_SQL_BUILTIN);
+			int op = OP_BuiltinFunction0;
+			sqlVdbeAddOp4(v, op, constMask, r1, target,
+				      (char *)func, P4_FUNC);
 			sqlVdbeChangeP5(v, (u8) nFarg);
 			if (nFarg && constMask == 0) {
 				sqlReleaseTempRange(pParse, r1, nFarg);
@@ -5441,12 +5447,21 @@ analyzeAggregate(Walker * pWalker, Expr * pExpr)
 						pItem->iMem = ++pParse->nMem;
 						assert(!ExprHasProperty
 						       (pExpr, EP_IntValue));
-						pItem->pFunc = sqlFindFunction(
-							pParse->db,
-							pExpr->u.zToken,
-							pExpr->x.pList ?
-							pExpr->x.pList->nExpr : 0,
-							0);
+						const char *name =
+							pExpr->u.zToken;
+						uint32_t argc =
+							pExpr->x.pList != NULL ?
+							pExpr->x.pList->nExpr : 0;
+						pItem->func =
+							sql_func_by_signature(
+								name, argc);
+						assert(pItem->func != NULL);
+						assert(pItem->func->def->
+						       language ==
+						       FUNC_LANGUAGE_SQL_BUILTIN &&
+						       pItem->func->def->
+						       aggregate ==
+						       FUNC_AGGREGATE_GROUP);
 						if (pExpr->flags & EP_Distinct) {
 							pItem->iDistinct =
 								pParse->nTab++;
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index a46df60ed..041183b8a 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -45,6 +45,8 @@
 #include <unicode/uchar.h>
 #include <unicode/ucol.h>
 #include "box/coll_id_cache.h"
+#include "box/schema.h"
+#include "box/func.h"
 
 /*
  * Return the collating function associated with a function.
@@ -79,10 +81,11 @@ minmaxFunc(sql_context * context, int argc, sql_value ** argv)
 	int i;
 	int iBest;
 	struct coll *pColl;
-	struct FuncDef *func = context->pFunc;
+	struct func_sql_builtin *func =
+		(struct func_sql_builtin *)context->func;
 
 	assert(argc > 1);
-	int mask = (func->funcFlags & SQL_FUNC_MAX) != 0 ? -1 : 0;
+	int mask = (func->flags & SQL_FUNC_MAX) != 0 ? -1 : 0;
 	pColl = sqlGetFuncCollSeq(context);
 	assert(mask == -1 || mask == 0);
 	iBest = 0;
@@ -410,7 +413,12 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 	i64 p1, p2;
 	int negP2 = 0;
 
-	assert(argc == 3 || argc == 2);
+	if (argc != 2 && argc != 3) {
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "SUBSTR",
+			 "1 or 2", argc);
+		context->is_aborted = true;
+		return;
+	}
 	if (sql_value_is_null(argv[1])
 	    || (argc == 3 && sql_value_is_null(argv[2]))
 	    ) {
@@ -506,7 +514,12 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 {
 	int n = 0;
 	double r;
-	assert(argc == 1 || argc == 2);
+	if (argc != 1 && argc != 2) {
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ROUND",
+			 "1 or 2", argc);
+		context->is_aborted = true;
+		return;
+	}
 	if (argc == 2) {
 		if (sql_value_is_null(argv[1]))
 			return;
@@ -898,6 +911,12 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 {
 	u32 escape = SQL_END_OF_STRING;
 	int nPat;
+	if (argc != 2 && argc != 3) {
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+			 "LIKE", "2 or 3", argc);
+		context->is_aborted = true;
+		return;
+	}
 	sql *db = sql_context_db_handle(context);
 	int rhs_type = sql_value_type(argv[0]);
 	int lhs_type = sql_value_type(argv[1]);
@@ -1510,7 +1529,9 @@ trim_func(struct sql_context *context, int argc, sql_value **argv)
 		trim_func_three_args(context, argv[0], argv[1], argv[2]);
 		break;
 	default:
-		unreachable();
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "TRIM",
+			 "1 or 2 or 3", argc);
+		context->is_aborted = true;
 	}
 }
 
@@ -1689,6 +1710,12 @@ static void
 countStep(sql_context * context, int argc, sql_value ** argv)
 {
 	CountCtx *p;
+	if (argc != 0 && argc != 1) {
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+			 "COUNT", "0 or 1", argc);
+		context->is_aborted = true;
+		return;
+	}
 	p = sql_aggregate_context(context, sizeof(*p));
 	if ((argc == 0 || ! sql_value_is_null(argv[0])) && p) {
 		p->n++;
@@ -1713,6 +1740,8 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 	Mem *pBest;
 	UNUSED_PARAMETER(NotUsed);
 
+	struct func_sql_builtin *func =
+		(struct func_sql_builtin *)context->func;
 	pBest = (Mem *) sql_aggregate_context(context, sizeof(*pBest));
 	if (!pBest)
 		return;
@@ -1729,7 +1758,7 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 		 * between the two being that the sense of the
 		 * comparison is inverted.
 		 */
-		bool is_max = (context->pFunc->funcFlags & SQL_FUNC_MAX) != 0;
+		bool is_max = (func->flags & SQL_FUNC_MAX) != 0;
 		cmp = sqlMemCompare(pBest, pArg, pColl);
 		if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
 			sqlVdbeMemCopy(pBest, pArg);
@@ -1765,7 +1794,12 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
 	StrAccum *pAccum;
 	const char *zSep;
 	int nVal, nSep;
-	assert(argc == 1 || argc == 2);
+	if (argc != 1 && argc != 2) {
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+			 "GROUP_CONCAT", "1 or 2", argc);
+		context->is_aborted = true;
+		return;
+	}
 	if (sql_value_is_null(argv[0]))
 		return;
 	pAccum =
@@ -1814,129 +1848,621 @@ groupConcatFinalize(sql_context * context)
 }
 
 int
-sql_is_like_func(struct sql *db, struct Expr *expr)
+sql_is_like_func(struct Expr *expr)
 {
 	if (expr->op != TK_FUNCTION || !expr->x.pList ||
 	    expr->x.pList->nExpr != 2)
 		return 0;
 	assert(!ExprHasProperty(expr, EP_xIsSelect));
-	struct FuncDef *func = sqlFindFunction(db, expr->u.zToken, 2, 0);
-	assert(func != NULL);
-	if ((func->funcFlags & SQL_FUNC_LIKE) == 0)
+	struct func *func = sql_func_by_signature(expr->u.zToken, 2);
+	if (func == NULL || !sql_func_flag_is_set(func, SQL_FUNC_LIKE))
 		return 0;
 	return 1;
 }
 
-/*
- * All of the FuncDef structures in the aBuiltinFunc[] array above
- * to the global function hash table.  This occurs at start-time (as
- * a consequence of calling sql_initialize()).
- *
- * After this routine runs
+struct func *
+sql_func_by_signature(const char *name, int argc)
+{
+	struct func *base = func_by_name(name, strlen(name));
+	if (base == NULL || !base->def->exports.sql)
+		return NULL;
+
+	if (base->def->param_count != -1 && base->def->param_count != argc)
+		return NULL;
+	return base;
+}
+
+static int
+func_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;
+}
+
+/**
+ * A sequence of SQL builtins definitions in
+ * lexicographic order.
  */
-void
-sqlRegisterBuiltinFunctions(void)
-{
-	/*
-	 * 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 struct {
+	/**
+	 * Name is used to find corresponding entry in array
+	 * sql_builtins applying binary search.
 	 */
-	static FuncDef aBuiltinFunc[] = {
-		FUNCTION(soundex, 1, 0, 0, soundexFunc, FIELD_TYPE_STRING),
-		FUNCTION2(unlikely, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION2(likelihood, 2, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION2(likely, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION_COLL(trim, 1, 3, 0, trim_func),
-		FUNCTION_COLL(trim, 2, 3, 0, trim_func),
-		FUNCTION_COLL(trim, 3, 3, 0, trim_func),
-		FUNCTION2(least, -1, 0, 1, minmaxFunc, SQL_FUNC_MIN,
-			  FIELD_TYPE_SCALAR),
-		AGGREGATE2(min, 1, 0, 1, minmaxStep, minMaxFinalize,
-			   SQL_FUNC_MIN, FIELD_TYPE_SCALAR),
-		FUNCTION2(greatest, -1, 1, 1, minmaxFunc, SQL_FUNC_MAX,
-			  FIELD_TYPE_SCALAR),
-		AGGREGATE2(max, 1, 1, 1, minmaxStep, minMaxFinalize,
-			   SQL_FUNC_MAX, FIELD_TYPE_SCALAR),
-		FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQL_FUNC_TYPEOF,
-			  FIELD_TYPE_STRING),
-		FUNCTION2(length, 1, 0, 0, lengthFunc, SQL_FUNC_LENGTH,
-			  FIELD_TYPE_INTEGER),
-		FUNCTION(char_length, 1, 0, 0, lengthFunc, FIELD_TYPE_INTEGER),
-		FUNCTION(character_length, 1, 0, 0, lengthFunc,
-			 FIELD_TYPE_INTEGER),
-		FUNCTION(position, 2, 0, 1, position_func, FIELD_TYPE_INTEGER),
-		FUNCTION(printf, -1, 0, 0, printfFunc, FIELD_TYPE_STRING),
-		FUNCTION(unicode, 1, 0, 0, unicodeFunc, FIELD_TYPE_STRING),
-		FUNCTION(char, -1, 0, 0, charFunc, FIELD_TYPE_STRING),
-		FUNCTION(abs, 1, 0, 0, absFunc, FIELD_TYPE_NUMBER),
-		FUNCTION(round, 1, 0, 0, roundFunc, FIELD_TYPE_INTEGER),
-		FUNCTION(round, 2, 0, 0, roundFunc, FIELD_TYPE_INTEGER),
-		FUNCTION_COLL(upper, 1, 0, 1, UpperICUFunc),
-		FUNCTION_COLL(lower, 1, 0, 1, LowerICUFunc),
-		FUNCTION(hex, 1, 0, 0, hexFunc, FIELD_TYPE_STRING),
-		FUNCTION2(ifnull, 2, 0, 0, noopFunc, SQL_FUNC_COALESCE,
-			  FIELD_TYPE_INTEGER),
-		VFUNCTION(random, 0, 0, 0, randomFunc, FIELD_TYPE_INTEGER),
-		VFUNCTION(randomblob, 1, 0, 0, randomBlob, FIELD_TYPE_VARBINARY),
-		FUNCTION(nullif, 2, 0, 1, nullifFunc, FIELD_TYPE_SCALAR),
-		FUNCTION(version, 0, 0, 0, sql_func_version, FIELD_TYPE_STRING),
-		FUNCTION(quote, 1, 0, 0, quoteFunc, FIELD_TYPE_STRING),
-		VFUNCTION(row_count, 0, 0, 0, sql_row_count, FIELD_TYPE_INTEGER),
-		FUNCTION_COLL(replace, 3, 0, 0, replaceFunc),
-		FUNCTION(zeroblob, 1, 0, 0, zeroblobFunc, FIELD_TYPE_VARBINARY),
-		FUNCTION_COLL(substr, 2, 0, 0, substrFunc),
-		FUNCTION_COLL(substr, 3, 0, 0, substrFunc),
-		AGGREGATE(sum, 1, 0, 0, sum_step, sumFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE(total, 1, 0, 0, sum_step, totalFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE(avg, 1, 0, 0, sum_step, avgFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE2(count, 0, 0, 0, countStep, countFinalize,
-			  SQL_FUNC_COUNT, FIELD_TYPE_INTEGER),
-		AGGREGATE2(count, 1, 0, 0, countStep, countFinalize,
-			   SQL_FUNC_COUNT, FIELD_TYPE_INTEGER),
-		AGGREGATE(group_concat, 1, 0, 0, groupConcatStep,
-			  groupConcatFinalize, FIELD_TYPE_STRING),
-		AGGREGATE(group_concat, 2, 0, 0, groupConcatStep,
-			  groupConcatFinalize, FIELD_TYPE_STRING),
+	const char *name;
+	/** Members below are related to struct func_sql_builtin. */
+	uint16_t flags;
+	void (*call)(sql_context *ctx, int argc, sql_value **argv);
+	void (*finalize)(sql_context *ctx);
+	/** Members below are related to struct func_def. */
+	bool is_deterministic;
+	int param_count;
+	enum field_type returns;
+	enum func_aggregate aggregate;
+	bool export_to_sql;
+} sql_builtins[] = {
+	{.name = "ABS",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = absFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "AVG",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .is_deterministic = false,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .flags = 0,
+	 .call = sum_step,
+	 .finalize = avgFinalize,
+	 .export_to_sql = true,
+	}, {
+	 .name = "CEIL",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "CEILING",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "CHAR",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .is_deterministic = true,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .flags = 0,
+	 .call = charFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	 }, {
+	 .name = "CHARACTER_LENGTH",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = lengthFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "CHAR_LENGTH",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = lengthFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "COALESCE",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_COALESCE,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "COUNT",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = countStep,
+	 .finalize = countFinalize,
+	 .export_to_sql = true,
+	}, {
+	 .name = "CURRENT_DATE",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "CURRENT_TIME",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "CURRENT_TIMESTAMP",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "DATE",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "DATETIME",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "EVERY",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "EXISTS",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "EXP",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "EXTRACT",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "FLOOR",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "GREATER",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "GREATEST",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MAX,
+	 .call = minmaxFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "GROUP_CONCAT",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = groupConcatStep,
+	 .finalize = groupConcatFinalize,
+	 .export_to_sql = true,
+	}, {
+	 .name = "HEX",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = hexFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "IFNULL",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_COALESCE,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "JULIANDAY",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "LEAST",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MIN,
+	 .call = minmaxFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "LENGTH",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_LENGTH,
+	 .call = lengthFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "LESSER",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "LIKE",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_LIKE,
+	 .call = likeFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "LIKELIHOOD",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_BOOLEAN,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_UNLIKELY,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "LIKELY",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_BOOLEAN,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_UNLIKELY,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "LN",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "LOWER",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
+	 .call = LowerICUFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "MAX",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MAX,
+	 .call = minmaxStep,
+	 .finalize = minMaxFinalize,
+	 .export_to_sql = true,
+	}, {
+	 .name = "MIN",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MIN,
+	 .call = minmaxStep,
+	 .finalize = minMaxFinalize,
+	 .export_to_sql = true,
+	}, {
+	 .name = "MOD",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "NULLIF",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL,
+	 .call = nullifFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "OCTET_LENGTH",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "POSITION",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL,
+	 .call = position_func,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "POWER",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "PRINTF",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = printfFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "QUOTE",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = quoteFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "RANDOM",
+	 .param_count = 0,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = randomFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "RANDOMBLOB",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_VARBINARY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = randomBlob,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "REPLACE",
+	 .param_count = 3,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL,
+	 .call = replaceFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "ROUND",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = roundFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "ROW_COUNT",
+	 .param_count = 0,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = sql_row_count,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "SOME",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "SOUNDEX",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = soundexFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "SQRT",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "STRFTIME",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "SUBSTR",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL,
+	 .call = substrFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "SUM",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = sum_step,
+	 .finalize = sumFinalize,
+	 .export_to_sql = true,
+	}, {
+	 .name = "TIME",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "TOTAL",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = sum_step,
+	 .finalize = totalFinalize,
+	 .export_to_sql = true,
+	}, {
+	 .name = "TRIM",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL,
+	 .call = trim_func,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "TYPEOF",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_TYPEOF,
+	 .call = typeofFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "UNICODE",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = unicodeFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "UNLIKELY",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_BOOLEAN,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_UNLIKELY,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "UPPER",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
+	 .call = UpperICUFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "VERSION",
+	 .param_count = 0,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = sql_func_version,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "ZEROBLOB",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_VARBINARY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = zeroblobFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "_sql_stat_get",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "_sql_stat_init",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	}, {
+	 .name = "_sql_stat_push",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	},
+};
 
-		LIKEFUNC(like, 2, 1, SQL_FUNC_LIKE,
-			 FIELD_TYPE_INTEGER),
-		LIKEFUNC(like, 3, 1, SQL_FUNC_LIKE,
-			 FIELD_TYPE_INTEGER),
-		FUNCTION(coalesce, 1, 0, 0, 0, FIELD_TYPE_SCALAR),
-		FUNCTION(coalesce, 0, 0, 0, 0, FIELD_TYPE_SCALAR),
-		FUNCTION2(coalesce, -1, 0, 0, noopFunc, SQL_FUNC_COALESCE,
-			  FIELD_TYPE_SCALAR),
-	};
-	sql_register_analyze_builtins();
-	sqlRegisterDateTimeFunctions();
-	sqlInsertBuiltinFuncs(aBuiltinFunc, ArraySize(aBuiltinFunc));
+static struct func_vtab func_sql_builtin_vtab;
 
-#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");
+struct func *
+func_sql_builtin_new(struct func_def *def)
+{
+	assert(def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	/** Binary search for corresponding builtin entry. */
+	int idx = -1, left = 0, right = nelem(sql_builtins) - 1;
+	while (left <= right) {
+		uint32_t mid = (left + right) / 2;
+		int rc = strcmp(def->name, sql_builtins[mid].name);
+		if (rc == 0) {
+			idx = mid;
+			break;
 		}
+		if (rc < 0)
+			right = mid - 1;
+		else
+			left = mid + 1;
 	}
-#endif
+	/* Some builtins are not implemented yet. */
+	if (idx == -1) {
+		diag_set(ClientError, ER_CREATE_FUNCTION, def->name,
+			 "given built-in is not predefined");
+		return NULL;
+	}
+	struct func_sql_builtin *func =
+		(struct func_sql_builtin *) calloc(1, sizeof(*func));
+	if (func == NULL) {
+		diag_set(OutOfMemory, sizeof(*func), "malloc", "func");
+		return NULL;
+	}
+	func->base.def = def;
+	func->base.vtab = &func_sql_builtin_vtab;
+	func->flags = sql_builtins[idx].flags;
+	func->call = sql_builtins[idx].call;
+	func->finalize = sql_builtins[idx].finalize;
+	def->param_count = sql_builtins[idx].param_count;
+	def->is_deterministic = sql_builtins[idx].is_deterministic;
+	def->returns = sql_builtins[idx].returns;
+	def->aggregate = sql_builtins[idx].aggregate;
+	def->exports.sql = sql_builtins[idx].export_to_sql;
+	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 = func_sql_builtin_call_stub,
+	.destroy = func_sql_builtin_destroy,
+};
diff --git a/src/box/sql/global.c b/src/box/sql/global.c
index 6cadef809..c25b83de1 100644
--- a/src/box/sql/global.c
+++ b/src/box/sql/global.c
@@ -162,13 +162,6 @@ SQL_WSD struct sqlConfig sqlConfig = {
 	0x7ffffffe		/* iOnceResetThreshold */
 };
 
-/*
- * Hash table for global functions - functions common to all
- * database connections.  After initialization, this table is
- * read-only.
- */
-FuncDefHash sqlBuiltinFunctions;
-
 /*
  * The value of the "pending" byte must be 0x40000000 (1 byte past the
  * 1-gibabyte boundary) in a compatible database.  sql never uses
diff --git a/src/box/sql/main.c b/src/box/sql/main.c
index 748dc1b8e..0b20f2132 100644
--- a/src/box/sql/main.c
+++ b/src/box/sql/main.c
@@ -131,9 +131,6 @@ sql_initialize(void)
 	if (sqlGlobalConfig.isInit == 0
 	    && sqlGlobalConfig.inProgress == 0) {
 		sqlGlobalConfig.inProgress = 1;
-		memset(&sqlBuiltinFunctions, 0,
-		       sizeof(sqlBuiltinFunctions));
-		sqlRegisterBuiltinFunctions();
 		sql_os_init();
 		sqlGlobalConfig.isInit = 1;
 		sqlGlobalConfig.inProgress = 0;
@@ -242,25 +239,6 @@ sqlCloseSavepoints(Vdbe * pVdbe)
 	pVdbe->anonymous_savepoint = NULL;
 }
 
-/*
- * Invoke the destructor function associated with FuncDef p, if any. Except,
- * if this is not the last copy of the function, do not invoke it. Multiple
- * copies of a single function are created when create_function() is called
- * with SQL_ANY as the encoding.
- */
-static void
-functionDestroy(sql * db, FuncDef * p)
-{
-	FuncDestructor *pDestructor = p->u.pDestructor;
-	if (pDestructor) {
-		pDestructor->nRef--;
-		if (pDestructor->nRef == 0) {
-			pDestructor->xDestroy(pDestructor->pUserData);
-			sqlDbFree(db, pDestructor);
-		}
-	}
-}
-
 /*
  * Rollback all database files.  If tripCode is not 0, then
  * any write cursors are invalidated ("tripped" - as in "tripping a circuit
@@ -279,121 +257,6 @@ sqlRollbackAll(Vdbe * pVdbe)
 	}
 }
 
-/*
- * This function is exactly the same as sql_create_function(), except
- * that it is designed to be called by internal code. The difference is
- * that if a malloc() fails in sql_create_function(), an error code
- * is returned and the mallocFailed flag cleared.
- */
-int
-sqlCreateFunc(sql * db,
-		  const char *zFunctionName,
-		  enum field_type type,
-		  int nArg,
-		  int flags,
-		  void *pUserData,
-		  void (*xSFunc) (sql_context *, int, sql_value **),
-		  void (*xStep) (sql_context *, int, sql_value **),
-		  void (*xFinal) (sql_context *),
-		  FuncDestructor * pDestructor)
-{
-	FuncDef *p;
-	int extraFlags;
-
-	if (zFunctionName == 0 ||
-	    (xSFunc && (xFinal || xStep)) ||
-	    (!xSFunc && (xFinal && !xStep)) ||
-	    (!xSFunc && (!xFinal && xStep)) ||
-	    (nArg < -1 || nArg > SQL_MAX_FUNCTION_ARG) ||
-	    (255 < (sqlStrlen30(zFunctionName)))) {
-		diag_set(ClientError, ER_CREATE_FUNCTION, zFunctionName,
-			 "wrong function definition");
-		return -1;
-	}
-
-	assert(SQL_FUNC_CONSTANT == SQL_DETERMINISTIC);
-	extraFlags = flags & SQL_DETERMINISTIC;
-
-
-	/* Check if an existing function is being overridden or deleted. If so,
-	 * and there are active VMs, then return an error. If a function
-	 * is being overridden/deleted but there are no active VMs, allow the
-	 * operation to continue but invalidate all precompiled statements.
-	 */
-	p = sqlFindFunction(db, zFunctionName, nArg, 0);
-	if (p && p->nArg == nArg) {
-		if (db->nVdbeActive) {
-			diag_set(ClientError, ER_CREATE_FUNCTION, zFunctionName,
-				 "unable to create function due to active "\
-				 "statements");
-			return -1;
-		} else {
-			sqlExpirePreparedStatements(db);
-		}
-	}
-
-	p = sqlFindFunction(db, zFunctionName, nArg, 1);
-	assert(p || db->mallocFailed);
-	if (p == NULL)
-		return -1;
-
-	/* If an older version of the function with a configured destructor is
-	 * being replaced invoke the destructor function here.
-	 */
-	functionDestroy(db, p);
-
-	if (pDestructor) {
-		pDestructor->nRef++;
-	}
-	p->u.pDestructor = pDestructor;
-	p->funcFlags = extraFlags;
-	testcase(p->funcFlags & SQL_DETERMINISTIC);
-	p->xSFunc = xSFunc ? xSFunc : xStep;
-	p->xFinalize = xFinal;
-	p->pUserData = pUserData;
-	p->nArg = (u16) nArg;
-	p->ret_type = type;
-	return 0;
-}
-
-int
-sql_create_function_v2(sql * db,
-			   const char *zFunc,
-			   enum field_type type,
-			   int nArg,
-			   int flags,
-			   void *p,
-			   void (*xSFunc) (sql_context *, int,
-					   sql_value **),
-			   void (*xStep) (sql_context *, int,
-					  sql_value **),
-			   void (*xFinal) (sql_context *),
-			   void (*xDestroy) (void *))
-{
-	FuncDestructor *pArg = 0;
-
-	if (xDestroy) {
-		pArg =
-		    (FuncDestructor *) sqlDbMallocZero(db,
-							   sizeof
-							   (FuncDestructor));
-		if (!pArg) {
-			xDestroy(p);
-			return -1;
-		}
-		pArg->xDestroy = xDestroy;
-		pArg->pUserData = p;
-	}
-	int rc = sqlCreateFunc(db, zFunc, type, nArg, flags, p, xSFunc, xStep,
-			       xFinal, pArg);
-	if (pArg && pArg->nRef == 0) {
-		assert(rc != 0);
-		xDestroy(p);
-		sqlDbFree(db, pArg);
-	}
-	return rc;
-}
-
 /*
  * This array defines hard upper bounds on limit values.  The
  * initializer must be kept in sync with the SQL_LIMIT_*
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 83dff47b6..231c670f5 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -38,6 +38,7 @@
 #include "sqlInt.h"
 #include <stdlib.h>
 #include <string.h>
+#include "box/schema.h"
 
 /*
  * Walk the expression tree pExpr and increase the aggregate function
@@ -591,32 +592,46 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 	case TK_FUNCTION:{
 			ExprList *pList = pExpr->x.pList;	/* The argument list */
 			int n = pList ? pList->nExpr : 0;	/* Number of arguments */
-			int no_such_func = 0;	/* True if no such function exists */
-			int wrong_num_args = 0;	/* True if wrong number of arguments */
 			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) {
-					no_such_func = 1;
+			struct func *func = sql_func_by_signature(zId, n);
+			if (func == NULL) {
+				func = func_by_name(zId, nId);
+				if (func == NULL) {
+					diag_set(ClientError,
+						 ER_NO_SUCH_FUNCTION, zId);
+				} else if (!func->def->exports.sql) {
+					diag_set(ClientError,
+						 ER_SQL_PARSER_GENERIC,
+						 tt_sprintf("function %.*s() "
+							    "is not available "
+							    "in SQL", nId, zId));
 				} else {
-					wrong_num_args = 1;
+					uint32_t argc = func->def->param_count;
+					const char *err = tt_sprintf("%d", argc);
+					diag_set(ClientError,
+						 ER_FUNC_WRONG_ARG_COUNT,
+						 func->def->name, err, n);
 				}
+				pParse->is_aborted = true;
+				pNC->nErr++;
 			} else {
-				is_agg = pDef->xFinalize != 0;
-				pExpr->type = pDef->ret_type;
+				is_agg = func->def->aggregate ==
+					 FUNC_AGGREGATE_GROUP;
+				assert(!is_agg ||
+				       func->def->language ==
+				       FUNC_LANGUAGE_SQL_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_is_set(func,
+							 SQL_FUNC_UNLIKELY)) {
 					ExprSetProperty(pExpr,
 							EP_Unlikely | EP_Skip);
 					if (n == 2) {
@@ -643,19 +658,17 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 						 */
 						/* TUNING: unlikely() probability is 0.0625.  likely() is 0.9375 */
 						pExpr->iTable =
-						    pDef->zName[0] ==
-						    'u' ? 8388608 : 125829120;
+						    func->def->name[0] == 'u' ?
+						    8388608 : 125829120;
 					}
 				}
-				if ((pDef->funcFlags & SQL_FUNC_CONSTANT) != 0) {
+				if (func->def->is_deterministic) {
 					/* For the purposes of the EP_ConstFunc flag, date and time
 					 * functions and other functions that change slowly are considered
 					 * constant because they are constant for the duration of one query
 					 */
 					ExprSetProperty(pExpr, EP_ConstFunc);
-				}
-				if ((pDef->funcFlags & SQL_FUNC_CONSTANT) ==
-				    0) {
+				} else {
 					/* Date/time functions that use 'now', and other functions
 					 * that might change over time cannot be used
 					 * in an index.
@@ -671,17 +684,6 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 				pParse->is_aborted = true;
 				pNC->nErr++;
 				is_agg = 0;
-			} else if (no_such_func && pParse->db->init.busy == 0) {
-				diag_set(ClientError, ER_NO_SUCH_FUNCTION, zId);
-				pParse->is_aborted = true;
-				pNC->nErr++;
-			} else if (wrong_num_args) {
-				const char *err = "wrong number of arguments "\
-						  "to function %.*s()";
-				diag_set(ClientError, ER_SQL_PARSER_GENERIC,
-					 tt_sprintf(err, nId, zId));
-				pParse->is_aborted = true;
-				pNC->nErr++;
 			}
 			if (is_agg)
 				pNC->ncFlags &= ~NC_AllowAgg;
@@ -698,13 +700,12 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 					pExpr->op2++;
 					pNC2 = pNC2->pNext;
 				}
-				assert(pDef != 0);
+				assert(func != NULL);
 				if (pNC2) {
 					pNC2->ncFlags |= NC_HasAgg;
-					if ((pDef->funcFlags &
-					    SQL_FUNC_MIN) != 0 ||
-					    (pDef->funcFlags &
-					    SQL_FUNC_MAX) != 0)
+					if (sql_func_flag_is_set(func,
+							         SQL_FUNC_MIN |
+								 SQL_FUNC_MAX))
 						pNC2->ncFlags |= NC_MinMaxAgg;
 				}
 				pNC->ncFlags |= NC_AllowAgg;
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index ddb5de87e..cdcdbaf2b 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4381,7 +4381,9 @@ is_simple_count(struct Select *select, struct AggInfo *agg_info)
 		return NULL;
 	if (NEVER(agg_info->nFunc == 0))
 		return NULL;
-	if ((agg_info->aFunc->pFunc->funcFlags & SQL_FUNC_COUNT) == 0 ||
+	assert(agg_info->aFunc->func->def->language ==
+	       FUNC_LANGUAGE_SQL_BUILTIN);
+	if (sql_func_flag_is_set(agg_info->aFunc->func, SQL_FUNC_COUNT) ||
 	    (agg_info->aFunc->pExpr->x.pList != NULL &&
 	     agg_info->aFunc->pExpr->x.pList->nExpr > 0))
 		return NULL;
@@ -5278,7 +5280,7 @@ finalizeAggFunctions(Parse * pParse, AggInfo * pAggInfo)
 		assert(!ExprHasProperty(pF->pExpr, EP_xIsSelect));
 		sqlVdbeAddOp2(v, OP_AggFinal, pF->iMem,
 				  pList ? pList->nExpr : 0);
-		sqlVdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
+		sqlVdbeAppendP4(v, pF->func, P4_FUNC);
 	}
 }
 
@@ -5319,7 +5321,7 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 			vdbe_insert_distinct(pParse, pF->iDistinct, pF->reg_eph,
 					     addrNext, 1, regAgg);
 		}
-		if (pF->pFunc->funcFlags & SQL_FUNC_NEEDCOLL) {
+		if (sql_func_flag_is_set(pF->func, SQL_FUNC_NEEDCOLL)) {
 			struct coll *coll = NULL;
 			struct ExprList_item *pItem;
 			int j;
@@ -5338,7 +5340,7 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 					  (char *)coll, P4_COLLSEQ);
 		}
 		sqlVdbeAddOp3(v, OP_AggStep0, 0, regAgg, pF->iMem);
-		sqlVdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
+		sqlVdbeAppendP4(v, pF->func, P4_FUNC);
 		sqlVdbeChangeP5(v, (u8) nArg);
 		sql_expr_type_cache_change(pParse, regAgg, nArg);
 		sqlReleaseTempRange(pParse, regAgg, nArg);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index a2f78607a..c5d91ceee 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"
@@ -1720,7 +1722,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));
@@ -1728,7 +1730,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;
@@ -1763,7 +1765,9 @@ case OP_BuiltinFunction: {
 	}
 #endif
 	pCtx->is_aborted = false;
-	(*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */
+	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
+	func->call(pCtx, pCtx->argc, pCtx->argv);
 
 	/* If the function returned an error, throw an exception */
 	if (pCtx->is_aborted)
@@ -5021,7 +5025,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));
@@ -5029,7 +5033,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;
@@ -5071,7 +5075,9 @@ case OP_AggStep: {
 	pCtx->pOut = &t;
 	pCtx->is_aborted = false;
 	pCtx->skipFlag = 0;
-	(pCtx->pFunc->xSFunc)(pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */
+	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
+	func->call(pCtx, pCtx->argc, pCtx->argv);
 	if (pCtx->is_aborted) {
 		sqlVdbeMemRelease(&t);
 		goto abort_due_to_error;
@@ -5103,7 +5109,7 @@ case OP_AggFinal: {
 	assert(pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor));
 	pMem = &aMem[pOp->p1];
 	assert((pMem->flags & ~(MEM_Null|MEM_Agg))==0);
-	if (sqlVdbeMemFinalize(pMem, pOp->p4.pFunc) != 0)
+	if (sql_vdbemem_finalize(pMem, pOp->p4.func) != 0)
 		goto abort_due_to_error;
 	UPDATE_MAX_BLOBSIZE(pMem);
 	if (sqlVdbeMemTooBig(pMem)) {
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index dfaff9365..bf40b44fa 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -477,17 +477,6 @@ sql_step(sql_stmt * pStmt)
 	return sqlStep(v);
 }
 
-/*
- * Extract the user data from a sql_context structure and return a
- * pointer to it.
- */
-void *
-sql_user_data(sql_context * p)
-{
-	assert(p && p->pFunc);
-	return p->pFunc->pUserData;
-}
-
 /*
  * Extract the user data from a sql_context structure and return a
  * pointer to it.
@@ -547,7 +536,7 @@ createAggContext(sql_context * p, int nByte)
 	} else {
 		sqlVdbeMemClearAndResize(pMem, nByte);
 		pMem->flags = MEM_Agg;
-		pMem->u.pDef = p->pFunc;
+		pMem->u.func = p->func;
 		if (pMem->z) {
 			memset(pMem->z, 0, nByte);
 		}
@@ -563,7 +552,9 @@ createAggContext(sql_context * p, int nByte)
 void *
 sql_aggregate_context(sql_context * p, int nByte)
 {
-	assert(p && p->pFunc && p->pFunc->xFinalize);
+	assert(p != NULL && p->func != NULL);
+	assert(p->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	assert(p->func->def->aggregate == FUNC_AGGREGATE_GROUP);
 	testcase(nByte < 0);
 	if ((p->pMem->flags & MEM_Agg) == 0) {
 		return createAggContext(p, nByte);
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 5bcf2ccd8..3bd4ff433 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -648,24 +648,11 @@ sqlVdbeJumpHere(Vdbe * p, int addr)
 	sqlVdbeChangeP2(p, addr, p->nOp);
 }
 
-/*
- * If the input FuncDef structure is ephemeral, then free it.  If
- * the FuncDef is not ephermal, then do nothing.
- */
-static void
-freeEphemeralFunction(sql * db, FuncDef * pDef)
-{
-	if ((pDef->funcFlags & SQL_FUNC_EPHEM) != 0) {
-		sqlDbFree(db, pDef);
-	}
-}
-
 static void vdbeFreeOpArray(sql *, Op *, int);
 
 static SQL_NOINLINE void
 freeP4FuncCtx(sql * db, sql_context * p)
 {
-	freeEphemeralFunction(db, p->pFunc);
 	sqlDbFree(db, p);
 }
 
@@ -689,13 +676,11 @@ freeP4(sql * db, int p4type, void *p4)
 	case P4_KEYINFO:
 		sql_key_info_unref(p4);
 		break;
-	case P4_FUNCDEF:{
-			freeEphemeralFunction(db, (FuncDef *) p4);
-			break;
-		}
 	case P4_MEM:
 		sqlValueFree((sql_value *) p4);
 		break;
+	default:
+		break;
 	}
 }
 
@@ -1149,15 +1134,17 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
 				sqlXPrintf(&x, "(binary)");
 			break;
 		}
-	case P4_FUNCDEF:{
-			FuncDef *pDef = pOp->p4.pFunc;
-			sqlXPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg);
+	case P4_FUNC:{
+			struct func *func = pOp->p4.func;
+			sqlXPrintf(&x, "%s(%d)", func->def->name,
+				   func->def->param_count);
 			break;
 		}
 #if defined(SQL_DEBUG) || defined(VDBE_PROFILE)
 	case P4_FUNCCTX:{
-			FuncDef *pDef = pOp->p4.pCtx->pFunc;
-			sqlXPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg);
+			struct func *func = pOp->p4.func;
+			sqlXPrintf(&x, "%s(%d)", func->def->name,
+				   func->def->param_count);
 			break;
 		}
 #endif
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 5516d7fb1..ac0dfa333 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -312,37 +312,29 @@ sqlVdbeMemStringify(Mem * pMem)
 	return 0;
 }
 
-/*
- * Memory cell pMem contains the context of an aggregate function.
- * This routine calls the finalize method for that function.  The
- * result of the aggregate is stored back into pMem.
- *
- * Return -1 if the finalizer reports an error. 0 otherwise.
- */
 int
-sqlVdbeMemFinalize(Mem * pMem, FuncDef * pFunc)
+sql_vdbemem_finalize(struct Mem *mem, struct func *func)
 {
-	if (ALWAYS(pFunc && pFunc->xFinalize)) {
-		sql_context ctx;
-		Mem t;
-		assert((pMem->flags & MEM_Null) != 0 || pFunc == pMem->u.pDef);
-		memset(&ctx, 0, sizeof(ctx));
-		memset(&t, 0, sizeof(t));
-		t.flags = MEM_Null;
-		t.db = pMem->db;
-		t.field_type = field_type_MAX;
-		ctx.pOut = &t;
-		ctx.pMem = pMem;
-		ctx.pFunc = pFunc;
-		pFunc->xFinalize(&ctx);	/* IMP: R-24505-23230 */
-		assert((pMem->flags & MEM_Dyn) == 0);
-		if (pMem->szMalloc > 0)
-			sqlDbFree(pMem->db, pMem->zMalloc);
-		memcpy(pMem, &t, sizeof(t));
-		if (ctx.is_aborted)
-			return -1;
-	}
-	return 0;
+	assert(func != NULL);
+	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	assert(func->def->aggregate == FUNC_AGGREGATE_GROUP);
+	assert((mem->flags & MEM_Null) != 0 || func == mem->u.func);
+	sql_context ctx;
+	memset(&ctx, 0, sizeof(ctx));
+	Mem t;
+	memset(&t, 0, sizeof(t));
+	t.flags = MEM_Null;
+	t.db = mem->db;
+	t.field_type = field_type_MAX;
+	ctx.pOut = &t;
+	ctx.pMem = mem;
+	ctx.func = func;
+	((struct func_sql_builtin *)func)->finalize(&ctx);
+	assert((mem->flags & MEM_Dyn) == 0);
+	if (mem->szMalloc > 0)
+		sqlDbFree(mem->db, mem->zMalloc);
+	memcpy(mem, &t, sizeof(t));
+	return ctx.is_aborted ? -1 : 0;
 }
 
 /*
@@ -359,7 +351,7 @@ vdbeMemClearExternAndSetNull(Mem * p)
 {
 	assert(VdbeMemDynamic(p));
 	if (p->flags & MEM_Agg) {
-		sqlVdbeMemFinalize(p, p->u.pDef);
+		sql_vdbemem_finalize(p, p->u.func);
 		assert((p->flags & MEM_Agg) == 0);
 		testcase(p->flags & MEM_Dyn);
 	}
@@ -1289,7 +1281,6 @@ valueFromFunction(sql * db,	/* The database connection */
 	sql_context ctx;	/* Context object for function invocation */
 	sql_value **apVal = 0;	/* Function arguments */
 	int nVal = 0;		/* Size of apVal[] array */
-	FuncDef *pFunc = 0;	/* Function definition */
 	sql_value *pVal = 0;	/* New value */
 	int rc = 0;	/* Return code */
 	ExprList *pList = 0;	/* Function arguments */
@@ -1300,10 +1291,10 @@ valueFromFunction(sql * db,	/* The database connection */
 	pList = p->x.pList;
 	if (pList)
 		nVal = pList->nExpr;
-	pFunc = sqlFindFunction(db, p->u.zToken, nVal, 0);
-	assert(pFunc);
-	if ((pFunc->funcFlags & SQL_FUNC_CONSTANT) == 0 ||
-	    (pFunc->funcFlags & SQL_FUNC_NEEDCOLL))
+	struct func *func = sql_func_by_signature(p->u.zToken, nVal);
+	if (func == NULL || func->def->language != FUNC_LANGUAGE_SQL_BUILTIN ||
+	    !func->def->is_deterministic ||
+	    sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL))
 		return 0;
 
 	if (pList) {
@@ -1332,8 +1323,8 @@ valueFromFunction(sql * db,	/* The database connection */
 	assert(!pCtx->pParse->is_aborted);
 	memset(&ctx, 0, sizeof(ctx));
 	ctx.pOut = pVal;
-	ctx.pFunc = pFunc;
-	pFunc->xSFunc(&ctx, nVal, apVal);
+	ctx.func = func;
+	((struct func_sql_builtin *)func)->call(&ctx, nVal, apVal);
 	assert(!ctx.is_aborted);
 	sql_value_apply_type(pVal, type);
 	assert(rc == 0);
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index 8adf6a5f1..98615b118 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -273,7 +273,7 @@ like_optimization_is_valid(Parse *pParse, Expr *pExpr, Expr **ppPrefix,
 	/* Result code to return. */
 	int rc;
 
-	if (!sql_is_like_func(db, pExpr)) {
+	if (!sql_is_like_func(pExpr)) {
 		return 0;
 	}
 	pList = pExpr->x.pList;
diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 9bba37bcb..9d2fcea4b 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -130,7 +130,6 @@ add_library(box STATIC
     ${lua_sources}
     lua/init.c
     lua/call.c
-    lua/lua_sql.c
     lua/cfg.cc
     lua/console.c
     lua/tuple.c
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 4f2e34bf0..c0780d6da 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -2926,6 +2926,12 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 				  (unsigned) old_func->def->uid,
 				  "function has references");
 		}
+		/* Can't' drop a builtin function. */
+		if (old_func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
+			tnt_raise(ClientError, ER_DROP_FUNCTION,
+				  (unsigned) old_func->def->uid,
+				  "function is SQL built-in");
+		}
 		struct trigger *on_commit =
 			txn_alter_trigger_new(on_drop_func_commit, old_func);
 		struct trigger *on_rollback =
diff --git a/test/box/function1.result b/test/box/function1.result
index 5b091f72b..0ece5448a 100644
--- a/test/box/function1.result
+++ b/test/box/function1.result
@@ -825,6 +825,26 @@ box.func.LUA:call({"return 1 + 1"})
 ---
 - 2
 ...
+box.schema.user.grant('guest', 'execute', 'function', 'SUM')
+---
+...
+c = net.connect(box.cfg.listen)
+---
+...
+c:call("SUM")
+---
+- error: sql builtin function does not support Lua frontend
+...
+c:close()
+---
+...
+box.schema.user.revoke('guest', 'execute', 'function', 'SUM')
+---
+...
+box.schema.func.drop("SUM")
+---
+- error: 'Can''t drop function 1: function is SQL built-in'
+...
 -- Introduce function options
 box.schema.func.create('test', {body = "function(tuple) return tuple end", is_deterministic = true, opts = {is_multikey = true}})
 ---
diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua
index f894472f8..665972eda 100644
--- a/test/box/function1.test.lua
+++ b/test/box/function1.test.lua
@@ -294,6 +294,13 @@ ok == true
 
 box.func.LUA:call({"return 1 + 1"})
 
+box.schema.user.grant('guest', 'execute', 'function', 'SUM')
+c = net.connect(box.cfg.listen)
+c:call("SUM")
+c:close()
+box.schema.user.revoke('guest', 'execute', 'function', 'SUM')
+box.schema.func.drop("SUM")
+
 -- Introduce function options
 box.schema.func.create('test', {body = "function(tuple) return tuple end", is_deterministic = true, opts = {is_multikey = true}})
 box.func['test'].is_multikey == true
diff --git a/test/box/misc.result b/test/box/misc.result
index 7a15dabf0..feb72f69c 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -529,6 +529,7 @@ t;
   198: box.error.FUNC_INDEX_FUNC
   199: box.error.FUNC_INDEX_FORMAT
   200: box.error.FUNC_INDEX_PARTS
+  201: box.error.FUNC_WRONG_ARG_COUNT
 ...
 test_run:cmd("setopt delimiter ''");
 ---
diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
index ec06c903d..b70fac373 100755
--- a/test/sql-tap/func.test.lua
+++ b/test/sql-tap/func.test.lua
@@ -69,7 +69,7 @@ test:do_catchsql_test(
         SELECT length(*) FROM tbl1 ORDER BY t1
     ]], {
         -- <func-1.1>
-        1, "wrong number of arguments to function LENGTH()"
+        1, "Wrong number of arguments is passed to LENGTH(): expected 1, got 0"
         -- </func-1.1>
     })
 
@@ -79,7 +79,7 @@ test:do_catchsql_test(
         SELECT length(t1,5) FROM tbl1 ORDER BY t1
     ]], {
         -- <func-1.2>
-        1, "wrong number of arguments to function LENGTH()"
+        1, "Wrong number of arguments is passed to LENGTH(): expected 1, got 2"
         -- </func-1.2>
     })
 
@@ -365,7 +365,7 @@ test:do_test(
         return test:catchsql("SELECT abs(a,b) FROM t1")
     end, {
         -- <func-4.1>
-        1, "wrong number of arguments to function ABS()"
+        1, "Wrong number of arguments is passed to ABS(): expected 1, got 2"
         -- </func-4.1>
     })
 
@@ -377,7 +377,7 @@ test:do_catchsql_test(
         SELECT abs() FROM t1
     ]], {
         -- <func-4.2>
-        1, "wrong number of arguments to function ABS()"
+        1, "Wrong number of arguments is passed to ABS(): expected 1, got 0"
         -- </func-4.2>
     })
 
@@ -428,7 +428,7 @@ test:do_catchsql_test(
         SELECT round(a,b,c) FROM t1
     ]], {
         -- <func-4.5>
-        1, "wrong number of arguments to function ROUND()"
+        1, "Wrong number of arguments is passed to ROUND(): expected 1 or 2, got 3"
         -- </func-4.5>
     })
 
@@ -488,7 +488,7 @@ test:do_catchsql_test(
         SELECT round() FROM t1 ORDER BY a
     ]], {
         -- <func-4.11>
-        1, "wrong number of arguments to function ROUND()"
+        1, "Wrong number of arguments is passed to ROUND(): expected 1 or 2, got 0"
         -- </func-4.11>
     })
 
@@ -778,7 +778,7 @@ test:do_catchsql_test(
         SELECT upper(*) FROM t2
     ]], {
         -- <func-5.5>
-        1, "wrong number of arguments to function UPPER()"
+        1, "Wrong number of arguments is passed to UPPER(): expected 1, got 0"
         -- </func-5.5>
     })
 
@@ -1782,7 +1782,7 @@ test:do_catchsql_test(
         SELECT replace(1,2);
     ]], {
         -- <func-21.1>
-        1, "wrong number of arguments to function REPLACE()"
+        1, "Wrong number of arguments is passed to REPLACE(): expected 3, got 2"
         -- </func-21.1>
     })
 
@@ -1792,7 +1792,7 @@ test:do_catchsql_test(
         SELECT replace(1,2,3,4);
     ]], {
         -- <func-21.2>
-        1, "wrong number of arguments to function REPLACE()"
+        1, "Wrong number of arguments is passed to REPLACE(): expected 3, got 4"
         -- </func-21.2>
     })
 
@@ -2540,7 +2540,7 @@ test:do_catchsql_test(
         SELECT coalesce()
     ]], {
         -- <func-27.1>
-        1, "wrong number of arguments to function COALESCE()"
+        1, "Wrong number of arguments is passed to COALESCE(): expected >= 2, got 0"
         -- </func-27.1>
     })
 
@@ -2550,7 +2550,7 @@ test:do_catchsql_test(
         SELECT coalesce(1)
     ]], {
         -- <func-27.2>
-        1, "wrong number of arguments to function COALESCE()"
+        1, "Wrong number of arguments is passed to COALESCE(): expected >= 2, got 1"
         -- </func-27.2>
     })
 
diff --git a/test/sql-tap/func2.test.lua b/test/sql-tap/func2.test.lua
index b70197d09..c9bd3665f 100755
--- a/test/sql-tap/func2.test.lua
+++ b/test/sql-tap/func2.test.lua
@@ -50,7 +50,7 @@ test:do_catchsql_test(
         SELECT SUBSTR()
     ]], {
         -- <func2-1.2.1>
-        1, "wrong number of arguments to function SUBSTR()"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0"
         -- </func2-1.2.1>
     })
 
@@ -60,7 +60,7 @@ test:do_catchsql_test(
         SELECT SUBSTR('Supercalifragilisticexpialidocious')
     ]], {
         -- <func2-1.2.2>
-        1, "wrong number of arguments to function SUBSTR()"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1"
         -- </func2-1.2.2>
     })
 
@@ -70,7 +70,7 @@ test:do_catchsql_test(
         SELECT SUBSTR('Supercalifragilisticexpialidocious', 1,1,1)
     ]], {
         -- <func2-1.2.3>
-        1, "wrong number of arguments to function SUBSTR()"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4"
         -- </func2-1.2.3>
     })
 
@@ -673,7 +673,7 @@ if ("ሴ" ~= "u1234")
             SELECT SUBSTR()
         ]], {
             -- <func2-2.1.2>
-            1, "wrong number of arguments to function SUBSTR()"
+            1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0"
             -- </func2-2.1.2>
         })
 
@@ -683,7 +683,7 @@ if ("ሴ" ~= "u1234")
             SELECT SUBSTR('hiሴho')
         ]], {
             -- <func2-2.1.3>
-            1, "wrong number of arguments to function SUBSTR()"
+            1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1"
             -- </func2-2.1.3>
         })
 
@@ -693,7 +693,7 @@ if ("ሴ" ~= "u1234")
             SELECT SUBSTR('hiሴho', 1,1,1)
         ]], {
             -- <func2-2.1.4>
-            1, "wrong number of arguments to function SUBSTR()"
+            1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4"
             -- </func2-2.1.4>
         })
 
@@ -1038,7 +1038,7 @@ test:do_catchsql_test(
         SELECT SUBSTR()
     ]], {
         -- <func2-3.1.2>
-        1, "wrong number of arguments to function SUBSTR()"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0"
         -- </func2-3.1.2>
     })
 
@@ -1048,7 +1048,7 @@ test:do_catchsql_test(
         SELECT SUBSTR(x'1234')
     ]], {
         -- <func2-3.1.3>
-        1, "wrong number of arguments to function SUBSTR()"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1"
         -- </func2-3.1.3>
     })
 
@@ -1058,7 +1058,7 @@ test:do_catchsql_test(
         SELECT SUBSTR(x'1234', 1,1,1)
     ]], {
         -- <func2-3.1.4>
-        1, "wrong number of arguments to function SUBSTR()"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4"
         -- </func2-3.1.4>
     })
 
diff --git a/test/sql-tap/limit.test.lua b/test/sql-tap/limit.test.lua
index 8445ab18e..870233942 100755
--- a/test/sql-tap/limit.test.lua
+++ b/test/sql-tap/limit.test.lua
@@ -771,7 +771,7 @@ test:do_catchsql_test(
         SELECT * FROM t1 LIMIT replace(1)
     ]], {
         -- <limit-12.1>
-        1, "wrong number of arguments to function REPLACE()"
+        1, "Wrong number of arguments is passed to REPLACE(): expected 3, got 1"
         -- </limit-12.1>
     })
 
@@ -781,7 +781,7 @@ test:do_catchsql_test(
         SELECT * FROM t1 LIMIT 5 OFFSET replace(1)
     ]], {
         -- <limit-12.2>
-        1, 'wrong number of arguments to function REPLACE()'
+        1, 'Wrong number of arguments is passed to REPLACE(): expected 3, got 1'
         -- </limit-12.2>
     })
 
diff --git a/test/sql-tap/select1.test.lua b/test/sql-tap/select1.test.lua
index 924c0ccb1..4bbfbd67b 100755
--- a/test/sql-tap/select1.test.lua
+++ b/test/sql-tap/select1.test.lua
@@ -244,7 +244,7 @@ test:do_catchsql_test(
         SELECT count(f1,f2) FROM test1
     ]], {
         -- <select1-2.1>
-        1, "wrong number of arguments to function COUNT()"
+        1, "Wrong number of arguments is passed to COUNT(): expected 0 or 1, got 2"
         -- </select1-2.1>
     })
 
@@ -324,7 +324,7 @@ test:do_catchsql_test(
         SELECT min(*) FROM test1
     ]], {
         -- <select1-2.6>
-        1, "wrong number of arguments to function MIN()"
+        1, "Wrong number of arguments is passed to MIN(): expected 1, got 0"
         -- </select1-2.6>
     })
 
@@ -389,7 +389,7 @@ test:do_catchsql_test(
         SELECT MAX(*) FROM test1
     ]], {
         -- <select1-2.9>
-        1, "wrong number of arguments to function MAX()"
+        1, "Wrong number of arguments is passed to MAX(): expected 1, got 0"
         -- </select1-2.9>
     })
 
@@ -469,7 +469,7 @@ test:do_catchsql_test(
         SELECT SUM(*) FROM test1
     ]], {
         -- <select1-2.14>
-        1, "wrong number of arguments to function SUM()"
+        1, "Wrong number of arguments is passed to SUM(): expected 1, got 0"
         -- </select1-2.14>
     })
 
@@ -489,7 +489,7 @@ test:do_catchsql_test(
         SELECT sum(f1,f2) FROM test1
     ]], {
         -- <select1-2.16>
-        1, "wrong number of arguments to function SUM()"
+        1, "Wrong number of arguments is passed to SUM(): expected 1, got 2"
         -- </select1-2.16>
     })
 
@@ -691,7 +691,7 @@ test:do_catchsql_test(
         SELECT f1 FROM test1 WHERE count(f1,f2)!=11
     ]], {
         -- <select1-3.9>
-        1, "wrong number of arguments to function COUNT()"
+        1, "misuse of aggregate function COUNT()"
         -- </select1-3.9>
     })
 
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>
     })
 
diff --git a/test/sql/icu-upper-lower.result b/test/sql/icu-upper-lower.result
index 88266c8c5..8ff7528f3 100644
--- a/test/sql/icu-upper-lower.result
+++ b/test/sql/icu-upper-lower.result
@@ -277,7 +277,7 @@ test_run:cmd("setopt delimiter ''");
 box.execute("select upper('1', 2)")
 ---
 - null
-- wrong number of arguments to function UPPER()
+- 'Wrong number of arguments is passed to UPPER(): expected 1, got 2'
 ...
 box.execute("select upper(\"1\")")
 ---
@@ -287,5 +287,5 @@ box.execute("select upper(\"1\")")
 box.execute("select upper()")
 ---
 - null
-- wrong number of arguments to function UPPER()
+- 'Wrong number of arguments is passed to UPPER(): expected 1, got 0'
 ...
-- 
2.22.1

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

* [tarantool-patches] [PATCH v4 4/4] sql: support user-defined functions in SQL
  2019-08-21 15:28 [tarantool-patches] [PATCH v4 0/4] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
                   ` (2 preceding siblings ...)
  2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 3/4] sql: get rid of FuncDef function hash Kirill Shcherbatov
@ 2019-08-21 15:28 ` Kirill Shcherbatov
  2019-08-22 15:23   ` [tarantool-patches] " n.pettik
  2019-08-29 15:09 ` [tarantool-patches] Re: [PATCH v4 0/4] sql: uniform SQL and Lua functions subsystem Nikita Pettik
  2019-08-29 17:12 ` Kirill Yukhin
  5 siblings, 1 reply; 17+ messages in thread
From: Kirill Shcherbatov @ 2019-08-21 15:28 UTC (permalink / raw)
  To: tarantool-patches, korablev; +Cc: Kirill Shcherbatov

Closes #2200
Closes #4113
Closes #2233

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

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

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

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

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

Example:
box.execute('SELECT lua(\'return 1 + 1\')')
- metadata:
  - name: lua('return 1 + 1')
    type: any
  rows:
  - [2]
box.execute('SELECT lua(\'return box.cfg.memtx_memory\')')
- metadata:
  - name: lua('return box.cfg.memtx_memory')
    type: any
  rows:
  - [268435456]
---
 src/box/port.h                  |  17 ++
 src/lib/core/port.h             |  15 ++
 src/box/call.c                  |   1 +
 src/box/execute.c               |   1 +
 src/box/lua/call.c              |   4 +
 src/box/port.c                  |   4 +
 src/box/sql/expr.c              |   6 +-
 src/box/sql/func.c              | 278 ++++++++++++++++++++++++++++++++
 src/box/sql/vdbe.c              |  43 +++++
 src/box/lua/schema.lua          |   3 +-
 test/box/function1.result       | 176 ++++++++++++++++++++
 test/box/function1.test.lua     |  63 ++++++++
 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 ++-
 22 files changed, 783 insertions(+), 130 deletions(-)

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

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

* [tarantool-patches] Re: [PATCH v4 1/4] sql: rename sql_vdbe_mem_alloc_region helper
  2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 1/4] sql: rename sql_vdbe_mem_alloc_region helper Kirill Shcherbatov
@ 2019-08-22 13:04   ` n.pettik
  2019-08-23 15:02     ` Kirill Shcherbatov
  0 siblings, 1 reply; 17+ messages in thread
From: n.pettik @ 2019-08-22 13:04 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov



> On 21 Aug 2019, at 18:28, Kirill Shcherbatov <kshcherbatov@tarantool.org> wrote:
> 
> Changed sql_vdbe_mem_alloc_region routine name to the
> sql_vdbe_mem_alloc_blob_region because we are going to introduce
> a new function with a really similar (but more appropriate) name
> vdbemem_alloc_on_region in following patch.
> 
> Needed for #2200, #4113, #2233
> ---
> src/box/sql/vdbe.h    | 2 +-
> src/box/sql/vdbe.c    | 2 +-
> src/box/sql/vdbeaux.c | 2 +-
> 3 files changed, 3 insertions(+), 3 deletions(-)
> 
> diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
> index 8f16202ba..e5a85fe0e 100644
> --- a/src/box/sql/vdbe.h
> +++ b/src/box/sql/vdbe.h
> @@ -278,7 +278,7 @@ int sqlVdbeRecordCompare(struct sql *db, int key_count,
> 			     const void *key1, UnpackedRecord *key2);
> UnpackedRecord *sqlVdbeAllocUnpackedRecord(struct sql *,
> 					       struct key_def *);
> -int sql_vdbe_mem_alloc_region(Mem *, uint32_t);
> +int sql_vdbe_mem_alloc_blob_region(Mem *, uint32_t);

You can also remove ’sql’ prefix (vdbe_ prefix implies that
namespace is related to sql) and make it static.

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

* [tarantool-patches] Re: [PATCH v4 2/4] sql: replace flag MINMAX with flags MIN and MAX
  2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 2/4] sql: replace flag MINMAX with flags MIN and MAX Kirill Shcherbatov
@ 2019-08-22 13:30   ` n.pettik
  0 siblings, 0 replies; 17+ messages in thread
From: n.pettik @ 2019-08-22 13:30 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov

lgtm

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

* [tarantool-patches] Re: [PATCH v4 3/4] sql: get rid of FuncDef function hash
  2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 3/4] sql: get rid of FuncDef function hash Kirill Shcherbatov
@ 2019-08-22 14:37   ` n.pettik
  2019-08-23 15:02     ` [tarantool-patches] [PATCH v4 4/5] " Kirill Shcherbatov
                       ` (2 more replies)
  0 siblings, 3 replies; 17+ messages in thread
From: n.pettik @ 2019-08-22 14:37 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov


> @@ -1316,11 +1227,10 @@ struct FuncDestructor {
> #define SQL_FUNC_COUNT    0x0100
> #define SQL_FUNC_COALESCE 0x0200	/* Built-in coalesce() or ifnull() */
> #define SQL_FUNC_UNLIKELY 0x0400	/* Built-in unlikely() function */
> -#define SQL_FUNC_CONSTANT 0x0800	/* Constant inputs give a constant output */
> /** Built-in min() or least() function. */
> #define SQL_FUNC_MIN      0x1000
> /** Built-in max() or greatest() function. */
> -#define SQL_FUNC_MAX      0x2000
> +#define SQL_FUNC_MAX   0x2000

Nit: extra diff.

> @@ -4489,9 +4331,55 @@ Expr *sqlExprForVectorField(Parse *, Expr *, int);
>  */
> extern int sqlSubProgramsRemaining;
> 
> -/** Register built-in functions to work with ANALYZE data. */
> -void
> -sql_register_analyze_builtins(void);
> +struct func_sql_builtin {
> +	/** Function object base class. */
> +	struct func base;
> +	/** A bitmask of SQL flags. */
> +	uint16_t flags;
> +	/**
> +	 * A VDBE-memory-compatible call method.
> +	 * SQL built-ins don't use func base class "call"
> +	 * method to provide a best performance for SQL requests.
> +	 * Access checks are redundant, because all SQL built-ins
> +	 * are predefined and are executed on SQL privilege level.

Which doesn’t exist yet… I asked you to document or fix it.
Comment in source code is OK, but it should be present in
documentation as well.

> diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
> index 1f9d91705..9055a0770 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -4002,9 +3999,16 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
> 			 * IFNULL() functions.  This avoids unnecessary evaluation of
> 			 * arguments past the first non-NULL argument.
> 			 */
> -			if (pDef->funcFlags & SQL_FUNC_COALESCE) {
> +			if (sql_func_flag_is_set(func, SQL_FUNC_COALESCE)) {
> 				int endCoalesce = sqlVdbeMakeLabel(v);
> -				assert(nFarg >= 2);
> +				if (nFarg < 2) {
> +					diag_set(ClientError,
> +						 ER_FUNC_WRONG_ARG_COUNT,
> +						 func->def->name,
> +						 ">= 2", nFarg);

-> “more than one”/“at least two”

What is more, you can move introduction of ER_FUNC_WRONG_…
to a separate auxiliary patch.

> @@ -4026,8 +4030,15 @@ 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) {
> -				assert(nFarg >= 1);
> +			if (sql_func_flag_is_set(func, SQL_FUNC_UNLIKELY)) {
> +				if (nFarg < 1) {
> +					diag_set(ClientError,
> +						 ER_FUNC_WRONG_ARG_COUNT,
> +						 func->def->name,
> +						 ">= 1", nFarg);

Nit: “at least one”

> +	const char *name;
> +	/** Members below are related to struct func_sql_builtin. */
> +	uint16_t flags;
> +	void (*call)(sql_context *ctx, int argc, sql_value **argv);
> +	void (*finalize)(sql_context *ctx);
> +	/** Members below are related to struct func_def. */
> +	bool is_deterministic;
> +	int param_count;
> +	enum field_type returns;
> +	enum func_aggregate aggregate;
> +	bool export_to_sql;
> +} sql_builtins[] = {
> +	{.name = "ABS",
> +	 .param_count = 1,
> +	 .returns = FIELD_TYPE_NUMBER,
> +	 .aggregate = FUNC_AGGREGATE_NONE,
> +	 .is_deterministic = true,
> +	 .flags = 0,
> +	 .call = absFunc,
> +	 .finalize = NULL,
> +	 .export_to_sql = true,
> +	}, {
> +	 .name = "AVG",
> +	 .param_count = 1,
> +	 .returns = FIELD_TYPE_NUMBER,
> +	 .is_deterministic = false,
> +	 .aggregate = FUNC_AGGREGATE_GROUP,
> +	 .flags = 0,
> +	 .call = sum_step,
> +	 .finalize = avgFinalize,
> +	 .export_to_sql = true,
> +	}, {
> +	 .name = "CEIL",
> +	 .call = sql_builtin_stub,
> +	 .export_to_sql = false,
> 

Nit: personally I’d not skip members and fill in them all.

> +struct func *
> +func_sql_builtin_new(struct func_def *def)
> +{
> +	assert(def->language == FUNC_LANGUAGE_SQL_BUILTIN);
> +	/** Binary search for corresponding builtin entry. */
> +	int idx = -1, left = 0, right = nelem(sql_builtins) - 1;
> +	while (left <= right) {
> +		uint32_t mid = (left + right) / 2;
> +		int rc = strcmp(def->name, sql_builtins[mid].name);
> +		if (rc == 0) {
> +			idx = mid;
> +			break;
> 		}
> +		if (rc < 0)
> +			right = mid - 1;
> +		else
> +			left = mid + 1;
> 	}
> -#endif
> +	/* Some builtins are not implemented yet. */

Please, left comment describing why we really do need this check.
I mean the fact that it disallows user to create random built-in functions.

> diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
> index 83dff47b6..231c670f5 100644
> --- a/src/box/sql/resolve.c
> +++ b/src/box/sql/resolve.c
> @@ -38,6 +38,7 @@
> #include "sqlInt.h"
> #include <stdlib.h>
> #include <string.h>
> +#include "box/schema.h"
> 
> /*
>  * Walk the expression tree pExpr and increase the aggregate function
> @@ -591,32 +592,46 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
> 	case TK_FUNCTION:{
> 			ExprList *pList = pExpr->x.pList;	/* The argument list */
> 			int n = pList ? pList->nExpr : 0;	/* Number of arguments */
> -			int no_such_func = 0;	/* True if no such function exists */
> -			int wrong_num_args = 0;	/* True if wrong number of arguments */
> 			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) {
> -					no_such_func = 1;
> +			struct func *func = sql_func_by_signature(zId, n);
> +			if (func == NULL) {
> +				func = func_by_name(zId, nId);
> +				if (func == NULL) {
> +					diag_set(ClientError,
> +						 ER_NO_SUCH_FUNCTION, zId);
> +				} else if (!func->def->exports.sql) {
> +					diag_set(ClientError,
> +						 ER_SQL_PARSER_GENERIC,
> +						 tt_sprintf("function %.*s() "
> +							    "is not available "
> +							    "in SQL", nId, zId));
> 				} else {

Let’s avoid call of sql_func_by_signature(). Consider refactoring:

diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 231c670f5..e7cf22cb6 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -599,30 +599,32 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
                        assert(!ExprHasProperty(pExpr, EP_xIsSelect));
                        zId = pExpr->u.zToken;
                        nId = sqlStrlen30(zId);
-                       struct func *func = sql_func_by_signature(zId, n);
+                       struct func *func = func_by_name(zId, nId);
                        if (func == NULL) {
-                               func = func_by_name(zId, nId);
-                               if (func == NULL) {
-                                       diag_set(ClientError,
-                                                ER_NO_SUCH_FUNCTION, zId);
-                               } else if (!func->def->exports.sql) {
-                                       diag_set(ClientError,
-                                                ER_SQL_PARSER_GENERIC,
-                                                tt_sprintf("function %.*s() "
-                                                           "is not available "
-                                                           "in SQL", nId, zId));
-                               } else {
-                                       uint32_t argc = func->def->param_count;
-                                       const char *err = tt_sprintf("%d", argc);
-                                       diag_set(ClientError,
-                                                ER_FUNC_WRONG_ARG_COUNT,
-                                                func->def->name, err, n);
-                               }
+                               diag_set(ClientError, ER_NO_SUCH_FUNCTION, zId);
                                pParse->is_aborted = true;
                                pNC->nErr++;
-                       } else {
-                               is_agg = func->def->aggregate ==
-                                        FUNC_AGGREGATE_GROUP;
+                               return WRC_Abort;
+                       }
+                       if (!func->def->exports.sql) {
+                               diag_set(ClientError, ER_SQL_PARSER_GENERIC,
+                                        tt_sprintf("function %.*s() is not "
+                                                   "available in SQL", nId,
+                                                   zId));
+                               pParse->is_aborted = true;
+                               pNC->nErr++;
+                               return WRC_Abort;
+                       }
+                       if (func->def->param_count != n) {
+                               uint32_t argc = func->def->param_count;
+                               const char *err = tt_sprintf("%d", argc);
+                               diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+                                        func->def->name, err, n);
+                               pParse->is_aborted = true;
+                               pNC->nErr++;
+                               return WRC_Abort;
+                       }

etc

> diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua
> index f894472f8..665972eda 100644
> --- a/test/box/function1.test.lua
> +++ b/test/box/function1.test.lua
> @@ -294,6 +294,13 @@ ok == true
> 
> box.func.LUA:call({"return 1 + 1"})
> 
> +box.schema.user.grant('guest', 'execute', 'function', 'SUM')
> +c = net.connect(box.cfg.listen)
> +c:call("SUM")
> +c:close()
> +box.schema.user.revoke('guest', 'execute', 'function', 'SUM')
> +box.schema.func.drop("SUM”)

Didn’t forget check that user can’t create manually built-ins?

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

* [tarantool-patches] Re: [PATCH v4 4/4] sql: support user-defined functions in SQL
  2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 4/4] sql: support user-defined functions in SQL Kirill Shcherbatov
@ 2019-08-22 15:23   ` n.pettik
  2019-08-23 15:02     ` Kirill Shcherbatov
  0 siblings, 1 reply; 17+ messages in thread
From: n.pettik @ 2019-08-22 15:23 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Kirill Shcherbatov


> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index c5d91ceee..9fb217822 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"
> @@ -1783,6 +1784,48 @@ case OP_BuiltinFunction: {
> 	break;
> }
> 
> +/* Opcode: Function P1 P2 P3 P4 P5

Nit: P1 is unused. As a rule we spot unused arguments with stars (*).
And I’d swap P1 and P5, i.e. pass range of registers in P1-P2,
save result to P3

The rest now seems to be OK.

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

* [tarantool-patches] [PATCH v4 4/5] sql: get rid of FuncDef function hash
  2019-08-22 14:37   ` [tarantool-patches] " n.pettik
@ 2019-08-23 15:02     ` Kirill Shcherbatov
  2019-08-23 15:02     ` [tarantool-patches] [PATCH v4 3/5] sql: remove name overloading for SQL builtins Kirill Shcherbatov
  2019-08-23 15:02     ` [tarantool-patches] Re: [PATCH v4 3/4] sql: get rid of FuncDef function hash Kirill Shcherbatov
  2 siblings, 0 replies; 17+ messages in thread
From: Kirill Shcherbatov @ 2019-08-23 15:02 UTC (permalink / raw)
  To: tarantool-patches, n.pettik

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

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

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

Part of #2200, #4113, #2233

@TarantoolBot document
Title: SQL builtins priveleges

All SQL built-ins are executed on SQL privilege level that is
undefined yet.
---
 src/box/lua/lua_sql.h       |  39 --
 src/box/sql/sqlInt.h        | 220 +++------
 src/box/sql/vdbe.h          |   9 +-
 src/box/sql/vdbeInt.h       |  23 +-
 src/box/func.c              |  33 +-
 src/box/lua/call.c          |   2 -
 src/box/lua/lua_sql.c       | 205 ---------
 src/box/sql/analyze.c       |  34 +-
 src/box/sql/callback.c      | 204 ---------
 src/box/sql/date.c          |  28 --
 src/box/sql/expr.c          |  75 ++--
 src/box/sql/func.c          | 874 +++++++++++++++++++++++++++++++-----
 src/box/sql/global.c        |   7 -
 src/box/sql/main.c          | 137 ------
 src/box/sql/resolve.c       | 132 +++---
 src/box/sql/select.c        |  10 +-
 src/box/sql/vdbe.c          |  18 +-
 src/box/sql/vdbeapi.c       |  17 +-
 src/box/sql/vdbeaux.c       |  31 +-
 src/box/sql/vdbemem.c       |  65 ++-
 src/box/sql/whereexpr.c     |   2 +-
 src/box/CMakeLists.txt      |   1 -
 src/box/alter.cc            |   6 +
 test/box/function1.result   |  20 +
 test/box/function1.test.lua |   7 +
 25 files changed, 1057 insertions(+), 1142 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/sqlInt.h b/src/box/sql/sqlInt.h
index a3a255a47..e617edd79 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -70,6 +70,8 @@
 #include "box/column_mask.h"
 #include "parse_def.h"
 #include "box/field_def.h"
+#include "box/func.h"
+#include "box/func_def.h"
 #include "box/sql.h"
 #include "box/txn.h"
 #include "trivia/util.h"
@@ -541,9 +543,6 @@ void
 sql_row_count(struct sql_context *context, MAYBE_UNUSED int unused1,
 	      MAYBE_UNUSED sql_value **unused2);
 
-void *
-sql_user_data(sql_context *);
-
 void *
 sql_aggregate_context(sql_context *,
 			  int nBytes);
@@ -566,26 +565,6 @@ sql_initialize(void);
 #define SQL_TRACE_ROW        0x04
 #define SQL_TRACE_CLOSE      0x08
 
-#define SQL_DETERMINISTIC    0x800
-
-int
-sql_create_function_v2(sql * db,
-			   const char *zFunctionName,
-			   enum field_type type,
-			   int nArg,
-			   int flags,
-			   void *pApp,
-			   void (*xFunc) (sql_context *,
-					  int,
-					  sql_value **),
-			   void (*xStep) (sql_context *,
-					  int,
-					  sql_value **),
-			   void (*xFinal)
-			   (sql_context *),
-			   void (*xDestroy) (void *)
-	);
-
 #define SQL_OPEN_READONLY         0x00000001	/* Ok for sql_open_v2() */
 #define SQL_OPEN_READWRITE        0x00000002	/* Ok for sql_open_v2() */
 #define SQL_OPEN_CREATE           0x00000004	/* Ok for sql_open_v2() */
@@ -1039,9 +1018,6 @@ typedef struct Column Column;
 typedef struct Expr Expr;
 typedef struct ExprList ExprList;
 typedef struct ExprSpan ExprSpan;
-typedef struct FuncDestructor FuncDestructor;
-typedef struct FuncDef FuncDef;
-typedef struct FuncDefHash FuncDefHash;
 typedef struct IdList IdList;
 typedef struct KeyClass KeyClass;
 typedef struct Lookaside Lookaside;
@@ -1122,18 +1098,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.
  */
@@ -1243,65 +1207,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 {
-	i8 nArg;		/* Number of arguments.  -1 means unlimited */
-	u16 funcFlags;		/* Some combination of sql_FUNC_* */
-	void *pUserData;	/* User data parameter */
-	FuncDef *pNext;		/* Next function with same name */
-	void (*xSFunc) (sql_context *, int, sql_value **);	/* func or agg-step */
-	void (*xFinalize) (sql_context *);	/* Agg finalizer */
-	const char *zName;	/* SQL name of the function. */
-	union {
-		FuncDef *pHash;	/* Next with a different name but the same hash */
-		FuncDestructor *pDestructor;	/* Reference counted destructor function */
-	} u;
-	/* Return type. */
-	enum field_type ret_type;
-};
-
-/*
- * This structure encapsulates a user-function destructor callback (as
- * configured using create_function_v2()) and a reference counter. When
- * create_function_v2() is called to create a function with a destructor,
- * a single object of this type is allocated. FuncDestructor.nRef is set to
- * the number of FuncDef objects created (either 1 or 3, depending on whether
- * or not the specified encoding is sql_ANY). The FuncDef.pDestructor
- * member of each of the new FuncDef objects is set to point to the allocated
- * FuncDestructor.
- *
- * Thereafter, when one of the FuncDef objects is deleted, the reference
- * count on this object is decremented. When it reaches 0, the destructor
- * is invoked and the FuncDestructor structure freed.
- */
-struct FuncDestructor {
-	int nRef;
-	void (*xDestroy) (void *);
-	void *pUserData;
-};
-
-/*
- * Possible values for FuncDef.flags.  Note that the _LENGTH and _TYPEOF
- * values must correspond to OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG.  And
- * sql_FUNC_CONSTANT must be the same as sql_DETERMINISTIC.  There
- * are assert() statements in the code to verify this.
- *
  * Value constraints (enforced via assert()):
  *     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
@@ -1315,7 +1226,6 @@ struct FuncDestructor {
 #define SQL_FUNC_COUNT    0x0100
 #define SQL_FUNC_COALESCE 0x0200	/* Built-in coalesce() or ifnull() */
 #define SQL_FUNC_UNLIKELY 0x0400	/* Built-in unlikely() function */
-#define SQL_FUNC_CONSTANT 0x0800	/* Constant inputs give a constant output */
 /** Built-in min() or least() function. */
 #define SQL_FUNC_MIN      0x1000
 /** Built-in max() or greatest() function. */
@@ -1339,61 +1249,6 @@ enum trim_side_mask {
 	TRIM_BOTH = TRIM_LEADING | TRIM_TRAILING
 };
 
-/*
- * The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are
- * used to create the initializers for the FuncDef structures.
- *
- *   FUNCTION(zName, nArg, iArg, bNC, xFunc)
- *     Used to create a scalar function definition of a function zName
- *     implemented by C function xFunc that accepts nArg arguments. The
- *     value passed as iArg is cast to a (void*) and made available
- *     as the user-data (sql_user_data()) for the function. If
- *     argument bNC is true, then the sql_FUNC_NEEDCOLL flag is set.
- *
- *   FUNCTION_COLL
- *     Like FUNCTION except it assumes that function returns
- *     STRING which collation should be derived from first
- *     argument (trim, substr etc).
- *
- *   VFUNCTION(zName, nArg, iArg, bNC, xFunc)
- *     Like FUNCTION except it omits the sql_FUNC_CONSTANT flag.
- *
- *   AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal)
- *     Used to create an aggregate function definition implemented by
- *     the C functions xStep and xFinal. The first four parameters
- *     are interpreted in the same way as the first 4 parameters to
- *     FUNCTION().
- *
- *   LIKEFUNC(zName, nArg, pArg, flags)
- *     Used to create a scalar function definition of a function zName
- *     that accepts nArg arguments and is implemented by a call to C
- *     function likeFunc. Argument pArg is cast to a (void *) and made
- *     available as the function user-data (sql_user_data()). The
- *     FuncDef.flags variable is set to the value passed as the flags
- *     parameter.
- */
-#define FUNCTION(zName, nArg, iArg, bNC, xFunc, type) \
-  {nArg, SQL_FUNC_CONSTANT|(bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define FUNCTION_COLL(zName, nArg, iArg, bNC, xFunc) \
-  {nArg, SQL_FUNC_CONSTANT|SQL_FUNC_DERIVEDCOLL|(bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, FIELD_TYPE_STRING}
-#define VFUNCTION(zName, nArg, iArg, bNC, xFunc, type) \
-  {nArg, (bNC*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags, type) \
-  {nArg,SQL_FUNC_CONSTANT|(bNC*SQL_FUNC_NEEDCOLL)|extraFlags,\
-   SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
-#define LIKEFUNC(zName, nArg, arg, flags, type) \
-  {nArg, SQL_FUNC_NEEDCOLL|SQL_FUNC_CONSTANT|flags, \
-   (void *)(SQL_INT_TO_PTR(arg)), 0, likeFunc, 0, #zName, {0}, type}
-#define AGGREGATE(zName, nArg, arg, nc, xStep, xFinal, type) \
-  {nArg, (nc*SQL_FUNC_NEEDCOLL), \
-   SQL_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}, type}
-#define AGGREGATE2(zName, nArg, arg, nc, xStep, xFinal, extraFlags, type) \
-  {nArg, (nc*SQL_FUNC_NEEDCOLL)|extraFlags, \
-   SQL_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}, type}
-
 /*
  * The following are used as the second parameter to sqlSavepoint(),
  * and as the P1 argument to the OP_Savepoint instruction.
@@ -1574,7 +1429,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 */
 		/**
@@ -3522,10 +3378,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.
@@ -4098,7 +3950,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;
 
 /**
@@ -4234,21 +4085,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
@@ -4285,7 +4128,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,
@@ -4476,9 +4318,55 @@ Expr *sqlExprForVectorField(Parse *, Expr *, int);
  */
 extern int sqlSubProgramsRemaining;
 
-/** Register built-in functions to work with ANALYZE data. */
-void
-sql_register_analyze_builtins(void);
+struct func_sql_builtin {
+	/** Function object base class. */
+	struct func base;
+	/** A bitmask of SQL flags. */
+	uint16_t flags;
+	/**
+	 * A VDBE-memory-compatible call method.
+	 * SQL built-ins don't use func base class "call"
+	 * method to provide a best performance for SQL requests.
+	 * Access checks are redundant, because all SQL built-ins
+	 * are predefined and are executed on SQL privilege level.
+	 */
+	void (*call)(sql_context *ctx, int argc, sql_value **argv);
+	/**
+	 * A VDBE-memory-compatible finalize method
+	 * (is valid only for aggregate function).
+	 */
+	void (*finalize)(sql_context *ctx);
+};
+
+/**
+ * Test whether SQL-specific flag is set for given function.
+ * Currently only SQL Builtin Functions have such hint flags,
+ * so function returns false for other functions. Such approach
+ * decreases code complexity and allows do not distinguish
+ * functions by implementation details where it is unnecessary.
+ *
+ * Returns true when given flag is set for a given function and
+ * false otherwise.
+ */
+static inline bool
+sql_func_flag_is_set(struct func *func, uint16_t flag)
+{
+	if (func->def->language != FUNC_LANGUAGE_SQL_BUILTIN)
+		return false;
+	return (((struct func_sql_builtin *)func)->flags & flag) != 0;
+}
+
+/**
+ * A SQL method to find a function in a hash by its name and
+ * count of arguments. Only functions that have 'SQL' engine
+ * export field set true and have exactly the same signature
+ * are returned.
+ *
+ * Returns not NULL function pointer when a valid and exported
+ * to SQL engine function is found and NULL otherwise.
+ */
+struct func *
+sql_func_by_signature(const char *name, int argc);
 
 /**
  * Generate VDBE code to halt execution with correct error if
diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
index b97aa6ada..29ff99867 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 3a416aea5..104a05613 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. */
@@ -512,7 +519,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 mem contains the context of an aggregate function.
+ * This routine calls the finalize method for that function. The
+ * result of the aggregate is stored back into mem.
+ *
+ * Returns -1 if the finalizer reports an error. 0 otherwise.
+ */
+int
+sql_vdbemem_finalize(struct Mem *mem, struct func *func);
+
 const char *sqlOpcodeName(int);
 int sqlVdbeMemGrow(Mem * pMem, int n, int preserve);
 int sqlVdbeMemClearAndResize(Mem * pMem, int n);
diff --git a/src/box/func.c b/src/box/func.c
index b35d05dca..e8cc99081 100644
--- a/src/box/func.c
+++ b/src/box/func.c
@@ -381,38 +381,9 @@ 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,
-};
+extern struct func *
+func_sql_builtin_new(struct func_def *def);
 
 struct func *
 func_new(struct func_def *def)
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index 0ac2eb7a6..001578b5a 100644
--- a/src/box/lua/call.c
+++ b/src/box/lua/call.c
@@ -44,7 +44,6 @@
 #include "box/port.h"
 #include "box/lua/tuple.h"
 #include "small/obuf.h"
-#include "lua_sql.h"
 #include "trivia/util.h"
 #include "mpstream.h"
 
@@ -968,7 +967,6 @@ static struct trigger on_alter_func_in_lua = {
 
 static const struct luaL_Reg boxlib_internal[] = {
 	{"call_loadproc",  lbox_call_loadproc},
-	{"sql_create_function",  lbox_sql_create_function},
 	{"module_reload", lbox_module_reload},
 	{"func_call", lbox_func_call},
 	{NULL, NULL}
diff --git a/src/box/lua/lua_sql.c b/src/box/lua/lua_sql.c
deleted file mode 100644
index 67a51a82c..000000000
--- a/src/box/lua/lua_sql.c
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * 1. Redistributions of source code must retain the above
- *    copyright notice, this list of conditions and the
- *    following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above
- *    copyright notice, this list of conditions and the following
- *    disclaimer in the documentation and/or other materials
- *    provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
- * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include "lua.h"
-#include "lua/utils.h"
-
-#include "box/lua/call.h"
-#include "box/sql/sqlInt.h"
-#include "box/sql/vdbeInt.h"
-
-struct lua_sql_func_info {
-	int func_ref;
-};
-
-/**
- * This function is callback which is called by sql engine.
- *
- * Purpose of this function is to call lua func from sql.
- * Lua func should be previously registered in sql
- * (see lbox_sql_create_function).
- */
-static void
-lua_sql_call(sql_context *pCtx, int nVal, sql_value **apVal) {
-	lua_State *L = lua_newthread(tarantool_L);
-	int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX);
-	struct lua_sql_func_info *func_info = sql_user_data(pCtx);
-
-	lua_rawgeti(L, LUA_REGISTRYINDEX, func_info->func_ref);
-	for (int i = 0; i < nVal; i++) {
-		sql_value *param = apVal[i];
-		switch (sql_value_type(param)) {
-		case MP_INT:
-			luaL_pushint64(L, sql_value_int64(param));
-			break;
-		case MP_UINT:
-			luaL_pushuint64(L, sql_value_uint64(param));
-			break;
-		case MP_DOUBLE:
-			lua_pushnumber(L, sql_value_double(param));
-			break;
-		case MP_STR:
-			lua_pushstring(L, (const char *) sql_value_text(param));
-			break;
-		case MP_BIN:
-			lua_pushlstring(L, sql_value_blob(param),
-					(size_t) sql_value_bytes(param));
-			break;
-		case MP_NIL:
-			lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_nil_ref);
-			break;
-		case MP_BOOL:
-			lua_pushboolean(L, sql_value_boolean(param));
-			break;
-		default:
-			diag_set(ClientError, ER_SQL_EXECUTE, "Unsupported "\
-				 "type passed to Lua");
-			pCtx->is_aborted = true;
-			goto error;
-		}
-	}
-	if (lua_pcall(L, lua_gettop(L) - 1, 1, 0) != 0){
-		diag_set(ClientError, ER_SQL_EXECUTE, lua_tostring(L, -1));
-		pCtx->is_aborted = true;
-		goto error;
-	}
-	switch(lua_type(L, -1)) {
-	case LUA_TBOOLEAN:
-		sql_result_bool(pCtx, lua_toboolean(L, -1));
-		break;
-	case LUA_TNUMBER:
-		sql_result_double(pCtx, lua_tonumber(L, -1));
-		break;
-	case LUA_TSTRING:
-		sql_result_text(pCtx, lua_tostring(L, -1), -1,
-				    SQL_TRANSIENT);
-		break;
-	case LUA_TNIL:
-		sql_result_null(pCtx);
-		break;
-	default:
-		diag_set(ClientError, ER_SQL_EXECUTE, "Unsupported type "\
-			 "passed from Lua");
-		pCtx->is_aborted = true;
-		goto error;
-	}
-error:
-	luaL_unref(tarantool_L, LUA_REGISTRYINDEX, coro_ref);
-	return;
-}
-
-static void
-lua_sql_destroy(void *p)
-{
-	struct lua_sql_func_info *func_info = p;
-	luaL_unref(tarantool_L, LUA_REGISTRYINDEX, func_info->func_ref);
-	free(func_info);
-	return;
-}
-
-/**
- * A helper to register lua function in SQL during runtime.
- * It makes available queries like this: "SELECT lua_func(arg);"
- *
- * sql_create_function *p argument is used to store func ref
- * to lua function (it identifies actual lua func to call if there
- * are many of them). SQL function must have name and type of
- * returning value. Additionally, it can feature number of
- * arguments and deterministic flag.
- */
-int
-lbox_sql_create_function(struct lua_State *L)
-{
-	struct sql *db = sql_get();
-	if (db == NULL)
-		return luaL_error(L, "Please call box.cfg{} first");
-	int argc = lua_gettop(L);
-	/*
-	 * Three function prototypes are possible:
-	 * 1. sql_create_function("func_name", "type", func);
-	 * 2. sql_create_function("func_name", "type", func,
-	 *                        func_arg_num);
-	 * 3. sql_create_function("func_name", "type", func,
-	 *                        func_arg_num, is_deterministic);
-	 */
-	if (!(argc == 3 && lua_isstring(L, 1) && lua_isstring(L, 2) &&
-	    lua_isfunction(L, 3)) &&
-	    !(argc == 4 && lua_isstring(L, 1) && lua_isstring(L, 2) &&
-	      lua_isfunction(L, 3) && lua_isnumber(L, 4)) &&
-	    !(argc == 5 && lua_isstring(L, 1) && lua_isstring(L, 2) &&
-	      lua_isfunction(L, 3) && lua_isnumber(L, 4) &&
-	      lua_isboolean(L, 5)))
-		return luaL_error(L, "Invalid arguments");
-	enum field_type type;
-	const char *type_arg = lua_tostring(L, 2);
-	if (strcmp(type_arg, "INT") == 0 || strcmp(type_arg, "INTEGER") == 0)
-		type = FIELD_TYPE_INTEGER;
-	else if (strcmp(type_arg, "TEXT") == 0)
-		type = FIELD_TYPE_STRING;
-	else if (strcmp(type_arg, "NUMBER") == 0)
-		type = FIELD_TYPE_NUMBER;
-	else if (strcmp(type_arg, "VARBINARY") == 0)
-		type = FIELD_TYPE_SCALAR;
-	else if (strcmp(type_arg, "BOOL") == 0 ||
-		 strcmp(type_arg, "BOOLEAN") == 0)
-		type = FIELD_TYPE_BOOLEAN;
-	else
-		return luaL_error(L, "Unknown type");
-	/* -1 indicates any number of arguments. */
-	int func_arg_num = -1;
-	bool is_deterministic = false;
-	if (argc == 4) {
-		func_arg_num = lua_tointeger(L, 4);
-		lua_pop(L, 1);
-	} else if (argc == 5) {
-		is_deterministic = lua_toboolean(L, 5);
-		func_arg_num = lua_tointeger(L, 4);
-		lua_pop(L, 2);
-	}
-	size_t name_len;
-	const char *name = lua_tolstring(L, 1, &name_len);
-	char *normalized_name =
-		sql_normalized_name_region_new(&fiber()->gc, name, name_len);
-	if (normalized_name == NULL)
-		return luaT_error(L);
-	struct lua_sql_func_info *func_info =
-		(struct lua_sql_func_info *) malloc(sizeof(*func_info));
-	if (func_info == NULL)
-		return luaL_error(L, "out of memory");
-	func_info->func_ref = luaL_ref(L, LUA_REGISTRYINDEX);
-	int rc = sql_create_function_v2(db, normalized_name, type, func_arg_num,
-					   is_deterministic ? SQL_DETERMINISTIC : 0,
-					   func_info, lua_sql_call, NULL, NULL,
-					   lua_sql_destroy);
-	if (rc != 0)
-		return luaT_error(L);
-	return 0;
-}
diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
index bd52d12df..b9858c8d6 100644
--- a/src/box/sql/analyze.c
+++ b/src/box/sql/analyze.c
@@ -263,7 +263,7 @@ stat4Destructor(void *pOld)
  * return value is BLOB, but it is really just a pointer to the Stat4Accum
  * object.
  */
-static void
+MAYBE_UNUSED static void
 statInit(sql_context * context, int argc, sql_value ** argv)
 {
 	Stat4Accum *p;
@@ -535,7 +535,7 @@ samplePushPrevious(Stat4Accum * p, int iChng)
  *
  * The R parameter is only used for STAT4
  */
-static void
+MAYBE_UNUSED static void
 statPush(sql_context * context, int argc, sql_value ** argv)
 {
 	int i;
@@ -608,7 +608,7 @@ statPush(sql_context * context, int argc, sql_value ** argv)
  * The content to returned is determined by the parameter J
  * which is one of the STAT_GET_xxxx values defined above.
  */
-static void
+MAYBE_UNUSED static void
 statGet(sql_context * context, int argc, sql_value ** argv)
 {
 	Stat4Accum *p = (Stat4Accum *) sql_value_blob(argv[0]);
@@ -715,11 +715,10 @@ callStatGet(Vdbe * v, int regStat4, int iParam, int regOut)
 {
 	assert(regOut != regStat4 && regOut != regStat4 + 1);
 	sqlVdbeAddOp2(v, OP_Integer, iParam, regStat4 + 1);
-	struct FuncDef *func =
-		sqlFindFunction(sql_get(), "_sql_stat_get", 2, 0);
+	struct func *func = sql_func_by_signature("_sql_stat_get", 2);
 	assert(func != NULL);
 	sqlVdbeAddOp4(v, OP_BuiltinFunction0, 0, regStat4, regOut,
-		      (char *)func, P4_FUNCDEF);
+		      (char *)func, P4_FUNC);
 	sqlVdbeChangeP5(v, 2);
 }
 
@@ -855,11 +854,11 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
 		sqlVdbeAddOp2(v, OP_Count, idx_cursor, stat4_reg + 3);
 		sqlVdbeAddOp2(v, OP_Integer, part_count, stat4_reg + 1);
 		sqlVdbeAddOp2(v, OP_Integer, part_count, stat4_reg + 2);
-		struct FuncDef *init_func =
-			sqlFindFunction(sql_get(), "_sql_stat_init", 3, 0);
+		struct func *init_func =
+			sql_func_by_signature("_sql_stat_init", 3);
 		assert(init_func != NULL);
 		sqlVdbeAddOp4(v, OP_BuiltinFunction0, 0, stat4_reg + 1,
-			      stat4_reg, (char *)init_func, P4_FUNCDEF);
+			      stat4_reg, (char *)init_func, P4_FUNC);
 		sqlVdbeChangeP5(v, 3);
 		/*
 		 * Implementation of the following:
@@ -956,11 +955,11 @@ vdbe_emit_analyze_space(struct Parse *parse, struct space *space)
 		sqlVdbeAddOp3(v, OP_MakeRecord, stat_key_reg,
 				  pk_part_count, key_reg);
 		assert(chng_reg == (stat4_reg + 1));
-		struct FuncDef *push_func =
-			sqlFindFunction(sql_get(), "_sql_stat_push", 3, 0);
+		struct func *push_func =
+			sql_func_by_signature("_sql_stat_push", 3);
 		assert(push_func != NULL);
 		sqlVdbeAddOp4(v, OP_BuiltinFunction0, 1, stat4_reg, tmp_reg,
-			      (char *)push_func, P4_FUNCDEF);
+			      (char *)push_func, P4_FUNC);
 		sqlVdbeChangeP5(v, 3);
 		sqlVdbeAddOp2(v, OP_Next, idx_cursor, next_row_addr);
 		/* Add the entry to the stat1 table. */
@@ -1746,14 +1745,3 @@ fail:
 	box_txn_rollback();
 	return -1;
 }
-
-void
-sql_register_analyze_builtins(void)
-{
-	static FuncDef funcs[] = {
-		FUNCTION(_sql_stat_get, 2, 0, 0, statGet, FIELD_TYPE_ANY),
-		FUNCTION(_sql_stat_push, 3, 0, 0, statPush, FIELD_TYPE_ANY),
-		FUNCTION(_sql_stat_init, 3, 0, 0, statInit, FIELD_TYPE_ANY),
-	};
-	sqlInsertBuiltinFuncs(funcs, nelem(funcs));
-}
diff --git a/src/box/sql/callback.c b/src/box/sql/callback.c
index 6c272de71..290363db6 100644
--- a/src/box/sql/callback.c
+++ b/src/box/sql/callback.c
@@ -56,207 +56,3 @@ sql_get_coll_seq(Parse *parser, const char *name, uint32_t *coll_id)
 		return p->coll;
 	}
 }
-
-/* During the search for the best function definition, this procedure
- * is called to test how well the function passed as the first argument
- * matches the request for a function with nArg arguments in a system
- * that uses encoding enc. The value returned indicates how well the
- * request is matched. A higher value indicates a better match.
- *
- * If nArg is -1 that means to only return a match (non-zero) if p->nArg
- * is also -1.  In other words, we are searching for a function that
- * takes a variable number of arguments.
- *
- * If nArg is -2 that means that we are searching for any function
- * regardless of the number of arguments it uses, so return a positive
- * match score for any
- *
- * The returned value is always between 0 and 6, as follows:
- *
- * 0: Not a match.
- * 1: UTF8/16 conversion required and function takes any number of arguments.
- * 2: UTF16 byte order change required and function takes any number of args.
- * 3: encoding matches and function takes any number of arguments
- * 4: UTF8/16 conversion required - argument count matches exactly
- * 5: UTF16 byte order conversion required - argument count matches exactly
- * 6: Perfect match:  encoding and argument count match exactly.
- *
- * If nArg==(-2) then any function with a non-null xSFunc is
- * a perfect match and any function with xSFunc NULL is
- * a non-match.
- */
-#define FUNC_PERFECT_MATCH 4	/* The score for a perfect match */
-static int
-matchQuality(FuncDef * p,	/* The function we are evaluating for match quality */
-	     int nArg		/* Desired number of arguments.  (-1)==any */
-    )
-{
-	int match;
-
-	/* nArg of -2 is a special case */
-	if (nArg == (-2))
-		return (p->xSFunc == 0) ? 0 : FUNC_PERFECT_MATCH;
-
-	/* Wrong number of arguments means "no match" */
-	if (p->nArg != nArg && p->nArg >= 0)
-		return 0;
-
-	/* Give a better score to a function with a specific number of arguments
-	 * than to function that accepts any number of arguments.
-	 */
-	if (p->nArg == nArg) {
-		match = 4;
-	} else {
-		match = 1;
-	}
-
-	return match;
-}
-
-/*
- * Search a FuncDefHash for a function with the given name.  Return
- * a pointer to the matching FuncDef if found, or 0 if there is no match.
- */
-static FuncDef *
-functionSearch(int h,		/* Hash of the name */
-	       const char *zFunc	/* Name of function */
-    )
-{
-	FuncDef *p;
-	for (p = sqlBuiltinFunctions.a[h]; p; p = p->u.pHash) {
-		if (sqlStrICmp(p->zName, zFunc) == 0) {
-			return p;
-		}
-	}
-	return 0;
-}
-
-/*
- * Insert a new FuncDef into a FuncDefHash hash table.
- */
-void
-sqlInsertBuiltinFuncs(FuncDef * aDef,	/* List of global functions to be inserted */
-			  int nDef	/* Length of the apDef[] list */
-    )
-{
-	int i;
-	for (i = 0; i < nDef; i++) {
-		FuncDef *pOther;
-		const char *zName = aDef[i].zName;
-		int nName = sqlStrlen30(zName);
-		int h =
-		    (sqlUpperToLower[(u8) zName[0]] +
-		     nName) % SQL_FUNC_HASH_SZ;
-		pOther = functionSearch(h, zName);
-		if (pOther) {
-			assert(pOther != &aDef[i] && pOther->pNext != &aDef[i]);
-			aDef[i].pNext = pOther->pNext;
-			pOther->pNext = &aDef[i];
-		} else {
-			aDef[i].pNext = 0;
-			aDef[i].u.pHash = sqlBuiltinFunctions.a[h];
-			sqlBuiltinFunctions.a[h] = &aDef[i];
-		}
-	}
-}
-
-/*
- * Locate a user function given a name, a number of arguments and a flag
- * indicating whether the function prefers UTF-16 over UTF-8.  Return a
- * pointer to the FuncDef structure that defines that function, or return
- * NULL if the function does not exist.
- *
- * If the createFlag argument is true, then a new (blank) FuncDef
- * structure is created and liked into the "db" structure if a
- * no matching function previously existed.
- *
- * If nArg is -2, then the first valid function found is returned.  A
- * function is valid if xSFunc is non-zero.  The nArg==(-2)
- * case is used to see if zName is a valid function name for some number
- * of arguments.  If nArg is -2, then createFlag must be 0.
- *
- * If createFlag is false, then a function with the required name and
- * number of arguments may be returned even if the eTextRep flag does not
- * match that requested.
- */
-FuncDef *
-sqlFindFunction(sql * db,	/* An open database */
-		    const char *zName,	/* Name of the function.  zero-terminated */
-		    int nArg,	/* Number of arguments.  -1 means any number */
-		    u8 createFlag	/* Create new entry if true and does not otherwise exist */
-    )
-{
-	FuncDef *p;		/* Iterator variable */
-	FuncDef *pBest = 0;	/* Best match found so far */
-	int bestScore = 0;	/* Score of best match */
-	int h;			/* Hash value */
-	int nName;		/* Length of the name */
-
-	assert(nArg >= (-2));
-	assert(nArg >= (-1) || createFlag == 0);
-	nName = sqlStrlen30(zName);
-
-	/* First search for a match amongst the application-defined functions.
-	 */
-	p = (FuncDef *) sqlHashFind(&db->aFunc, zName);
-	while (p) {
-		int score = matchQuality(p, nArg);
-		if (score > bestScore) {
-			pBest = p;
-			bestScore = score;
-		}
-		p = p->pNext;
-	}
-
-	/* If no match is found, search the built-in functions.
-	 *
-	 * Except, if createFlag is true, that means that we are trying to
-	 * install a new function.  Whatever FuncDef structure is returned it will
-	 * have fields overwritten with new information appropriate for the
-	 * new function.  But the FuncDefs for built-in functions are read-only.
-	 * So we must not search for built-ins when creating a new function.
-	 */
-	if (!createFlag && (pBest == NULL)) {
-		bestScore = 0;
-		h = (sqlUpperToLower[(u8) zName[0]] +
-		     nName) % SQL_FUNC_HASH_SZ;
-		p = functionSearch(h, zName);
-		while (p) {
-			int score = matchQuality(p, nArg);
-			if (score > bestScore) {
-				pBest = p;
-				bestScore = score;
-			}
-			p = p->pNext;
-		}
-	}
-
-	/* If the createFlag parameter is true and the search did not reveal an
-	 * exact match for the name, number of arguments and encoding, then add a
-	 * new entry to the hash table and return it.
-	 */
-	if (createFlag && bestScore < FUNC_PERFECT_MATCH &&
-	    (pBest =
-	     sqlDbMallocZero(db, sizeof(*pBest) + nName + 1)) != 0) {
-		FuncDef *pOther;
-		pBest->zName = (const char *)&pBest[1];
-		pBest->nArg = (u16) nArg;
-		pBest->funcFlags = 0;
-		memcpy((char *)&pBest[1], zName, nName + 1);
-		pOther =
-		    (FuncDef *) sqlHashInsert(&db->aFunc, pBest->zName,
-						  pBest);
-		if (pOther == pBest) {
-			sqlDbFree(db, pBest);
-			sqlOomFault(db);
-			return 0;
-		} else {
-			pBest->pNext = pOther;
-		}
-	}
-
-	if (pBest && (pBest->xSFunc || createFlag)) {
-		return pBest;
-	}
-	return 0;
-}
diff --git a/src/box/sql/date.c b/src/box/sql/date.c
index 2e2a71ad2..dffc23616 100644
--- a/src/box/sql/date.c
+++ b/src/box/sql/date.c
@@ -1290,31 +1290,3 @@ currentTimeFunc(sql_context * context, int argc, sql_value ** argv)
 	}
 }
 #endif
-
-/*
- * This function registered all of the above C functions as SQL
- * functions.  This should be the only routine in this file with
- * external linkage.
- */
-void
-sqlRegisterDateTimeFunctions(void)
-{
-	static FuncDef aDateTimeFuncs[] = {
-#if 0
-		DFUNCTION(julianday, -1, 0, 0, juliandayFunc, FIELD_TYPE_NUMBER),
-		DFUNCTION(date, -1, 0, 0, dateFunc, FIELD_TYPE_STRING),
-		DFUNCTION(time, -1, 0, 0, timeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(datetime, -1, 0, 0, datetimeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(strftime, -1, 0, 0, strftimeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(current_time, 0, 0, 0, ctimeFunc, FIELD_TYPE_STRING),
-		DFUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc,
-			  FIELD_TYPE_STRING),
-		DFUNCTION(current_date, 0, 0, 0, cdateFunc, FIELD_TYPE_STRING),
-		STR_FUNCTION(current_time, 0, "%H:%M:%S", 0, currentTimeFunc),
-		STR_FUNCTION(current_date, 0, "%Y-%m-%d", 0, currentTimeFunc),
-		STR_FUNCTION(current_timestamp, 0, "%Y-%m-%d %H:%M:%S", 0,
-			     currentTimeFunc),
-#endif
-	};
-	sqlInsertBuiltinFuncs(aDateTimeFuncs, ArraySize(aDateTimeFuncs));
-}
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 1837817d6..f4b3c1e8c 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -328,12 +328,11 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
 		if (op == TK_FUNCTION) {
 			uint32_t arg_count = p->x.pList == NULL ? 0 :
 					     p->x.pList->nExpr;
-			struct FuncDef *func = sqlFindFunction(parse->db,
-							       p->u.zToken,
-							       arg_count, 0);
+			struct func *func =
+				sql_func_by_signature(p->u.zToken, arg_count);
 			if (func == NULL)
 				break;
-			if ((func->funcFlags & SQL_FUNC_DERIVEDCOLL) != 0) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL)) {
 				/*
 				 * Now we use quite straightforward
 				 * approach assuming that resulting
@@ -342,7 +341,7 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id,
 				 * built-in functions: trim, upper,
 				 * lower, replace, substr.
 				 */
-				assert(func->ret_type == FIELD_TYPE_STRING);
+				assert(func->def->returns == FIELD_TYPE_STRING);
 				p = p->x.pList->a->pExpr;
 				continue;
 			}
@@ -3975,11 +3974,9 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 	case TK_FUNCTION:{
 			ExprList *pFarg;	/* List of function arguments */
 			int nFarg;	/* Number of function arguments */
-			FuncDef *pDef;	/* The function definition object */
 			const char *zId;	/* The function name */
 			u32 constMask = 0;	/* Mask of function arguments that are constant */
 			int i;	/* Loop counter */
-			sql *db = pParse->db;	/* The database connection */
 			struct coll *coll = NULL;
 
 			assert(!ExprHasProperty(pExpr, EP_xIsSelect));
@@ -3991,8 +3988,8 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			nFarg = pFarg ? pFarg->nExpr : 0;
 			assert(!ExprHasProperty(pExpr, EP_IntValue));
 			zId = pExpr->u.zToken;
-			pDef = sqlFindFunction(db, zId, nFarg, 0);
-			if (pDef == 0 || pDef->xFinalize != 0) {
+			struct func *func = sql_func_by_signature(zId, nFarg);
+			if (func == NULL) {
 				diag_set(ClientError, ER_NO_SUCH_FUNCTION,
 					 zId);
 				pParse->is_aborted = true;
@@ -4002,13 +3999,13 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			 * IFNULL() functions.  This avoids unnecessary evaluation of
 			 * arguments past the first non-NULL argument.
 			 */
-			if (pDef->funcFlags & SQL_FUNC_COALESCE) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_COALESCE)) {
 				int endCoalesce = sqlVdbeMakeLabel(v);
 				if (nFarg < 2) {
 					diag_set(ClientError,
 						 ER_FUNC_WRONG_ARG_COUNT,
-						 pDef->zName, "at least two",
-						 nFarg);
+						 func->def->name,
+						 "at least two", nFarg);
 					pParse->is_aborted = true;
 					break;
 				}
@@ -4033,12 +4030,12 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			/* The UNLIKELY() function is a no-op.  The result is the value
 			 * of the first argument.
 			 */
-			if (pDef->funcFlags & SQL_FUNC_UNLIKELY) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_UNLIKELY)) {
 				if (nFarg < 1) {
 					diag_set(ClientError,
-						ER_FUNC_WRONG_ARG_COUNT,
-						pDef->zName, "at least one",
-						nFarg);
+						 ER_FUNC_WRONG_ARG_COUNT,
+						 func->def->name,
+						 "at least one", nFarg);
 					pParse->is_aborted = true;
 					break;
 				}
@@ -4063,7 +4060,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			 * is done using ANSI rules from
 			 * collations_check_compatibility().
 			 */
-			if ((pDef->funcFlags & SQL_FUNC_NEEDCOLL) != 0) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) {
 				struct coll *unused = NULL;
 				uint32_t curr_id = COLL_NONE;
 				bool is_curr_forced = false;
@@ -4110,9 +4107,8 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 				 * or OPFLAG_TYPEOFARG respectively, to avoid unnecessary data
 				 * loading.
 				 */
-				if ((pDef->
-				     funcFlags & (SQL_FUNC_LENGTH |
-						  SQL_FUNC_TYPEOF)) != 0) {
+				if (sql_func_flag_is_set(func, SQL_FUNC_LENGTH |
+							       SQL_FUNC_TYPEOF)) {
 					u8 exprOp;
 					assert(nFarg == 1);
 					assert(pFarg->a[0].pExpr != 0);
@@ -4123,14 +4119,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;
 					}
 				}
 
@@ -4142,12 +4131,15 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			} else {
 				r1 = 0;
 			}
-			if (pDef->funcFlags & SQL_FUNC_NEEDCOLL) {
+			if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) {
 				sqlVdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
 						  (char *)coll, P4_COLLSEQ);
 			}
-			sqlVdbeAddOp4(v, OP_BuiltinFunction0, constMask, r1,
-					  target, (char *)pDef, P4_FUNCDEF);
+			assert(func->def->language ==
+			       FUNC_LANGUAGE_SQL_BUILTIN);
+			int op = OP_BuiltinFunction0;
+			sqlVdbeAddOp4(v, op, constMask, r1, target,
+				      (char *)func, P4_FUNC);
 			sqlVdbeChangeP5(v, (u8) nFarg);
 			if (nFarg && constMask == 0) {
 				sqlReleaseTempRange(pParse, r1, nFarg);
@@ -5455,12 +5447,21 @@ analyzeAggregate(Walker * pWalker, Expr * pExpr)
 						pItem->iMem = ++pParse->nMem;
 						assert(!ExprHasProperty
 						       (pExpr, EP_IntValue));
-						pItem->pFunc = sqlFindFunction(
-							pParse->db,
-							pExpr->u.zToken,
-							pExpr->x.pList ?
-							pExpr->x.pList->nExpr : 0,
-							0);
+						const char *name =
+							pExpr->u.zToken;
+						uint32_t argc =
+							pExpr->x.pList != NULL ?
+							pExpr->x.pList->nExpr : 0;
+						pItem->func =
+							sql_func_by_signature(
+								name, argc);
+						assert(pItem->func != NULL);
+						assert(pItem->func->def->
+						       language ==
+						       FUNC_LANGUAGE_SQL_BUILTIN &&
+						       pItem->func->def->
+						       aggregate ==
+						       FUNC_AGGREGATE_GROUP);
 						if (pExpr->flags & EP_Distinct) {
 							pItem->iDistinct =
 								pParse->nTab++;
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 9ce645698..363768764 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -45,6 +45,8 @@
 #include <unicode/uchar.h>
 #include <unicode/ucol.h>
 #include "box/coll_id_cache.h"
+#include "box/schema.h"
+#include "box/func.h"
 
 /*
  * Return the collating function associated with a function.
@@ -79,10 +81,11 @@ minmaxFunc(sql_context * context, int argc, sql_value ** argv)
 	int i;
 	int iBest;
 	struct coll *pColl;
-	struct FuncDef *func = context->pFunc;
+	struct func_sql_builtin *func =
+		(struct func_sql_builtin *)context->func;
 
 	assert(argc > 1);
-	int mask = (func->funcFlags & SQL_FUNC_MAX) != 0 ? -1 : 0;
+	int mask = (func->flags & SQL_FUNC_MAX) != 0 ? -1 : 0;
 	pColl = sqlGetFuncCollSeq(context);
 	assert(mask == -1 || mask == 0);
 	iBest = 0;
@@ -1737,6 +1740,8 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 	Mem *pBest;
 	UNUSED_PARAMETER(NotUsed);
 
+	struct func_sql_builtin *func =
+		(struct func_sql_builtin *)context->func;
 	pBest = (Mem *) sql_aggregate_context(context, sizeof(*pBest));
 	if (!pBest)
 		return;
@@ -1753,7 +1758,7 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv)
 		 * between the two being that the sense of the
 		 * comparison is inverted.
 		 */
-		bool is_max = (context->pFunc->funcFlags & SQL_FUNC_MAX) != 0;
+		bool is_max = (func->flags & SQL_FUNC_MAX) != 0;
 		cmp = sqlMemCompare(pBest, pArg, pColl);
 		if ((is_max && cmp < 0) || (!is_max && cmp > 0)) {
 			sqlVdbeMemCopy(pBest, pArg);
@@ -1843,116 +1848,783 @@ groupConcatFinalize(sql_context * context)
 }
 
 int
-sql_is_like_func(struct sql *db, struct Expr *expr)
+sql_is_like_func(struct Expr *expr)
 {
 	if (expr->op != TK_FUNCTION || !expr->x.pList ||
 	    expr->x.pList->nExpr != 2)
 		return 0;
 	assert(!ExprHasProperty(expr, EP_xIsSelect));
-	struct FuncDef *func = sqlFindFunction(db, expr->u.zToken, 2, 0);
-	assert(func != NULL);
-	if ((func->funcFlags & SQL_FUNC_LIKE) == 0)
+	struct func *func = sql_func_by_signature(expr->u.zToken, 2);
+	if (func == NULL || !sql_func_flag_is_set(func, SQL_FUNC_LIKE))
 		return 0;
 	return 1;
 }
 
-/*
- * All of the FuncDef structures in the aBuiltinFunc[] array above
- * to the global function hash table.  This occurs at start-time (as
- * a consequence of calling sql_initialize()).
- *
- * After this routine runs
+struct func *
+sql_func_by_signature(const char *name, int argc)
+{
+	struct func *base = func_by_name(name, strlen(name));
+	if (base == NULL || !base->def->exports.sql)
+		return NULL;
+
+	if (base->def->param_count != -1 && base->def->param_count != argc)
+		return NULL;
+	return base;
+}
+
+static int
+func_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;
+}
+
+/**
+ * A sequence of SQL builtins definitions in
+ * lexicographic order.
  */
-void
-sqlRegisterBuiltinFunctions(void)
-{
-	/*
-	 * 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 struct {
+	/**
+	 * Name is used to find corresponding entry in array
+	 * sql_builtins applying binary search.
 	 */
-	static FuncDef aBuiltinFunc[] = {
-		FUNCTION(SOUNDEX, 1, 0, 0, soundexFunc, FIELD_TYPE_STRING),
-		FUNCTION2(UNLIKELY, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION2(LIKELIHOOD, 2, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION2(LIKELY, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
-			  FIELD_TYPE_BOOLEAN),
-		FUNCTION_COLL(TRIM, -1, 3, 0, trim_func),
-		FUNCTION2(LEAST, -1, 0, 1, minmaxFunc, SQL_FUNC_MIN,
-			  FIELD_TYPE_SCALAR),
-		AGGREGATE2(MIN, 1, 0, 1, minmaxStep, minMaxFinalize,
-			   SQL_FUNC_MIN, FIELD_TYPE_SCALAR),
-		FUNCTION2(greatest, -1, 1, 1, minmaxFunc, SQL_FUNC_MAX,
-			  FIELD_TYPE_SCALAR),
-		AGGREGATE2(MAX, 1, 1, 1, minmaxStep, minMaxFinalize,
-			   SQL_FUNC_MAX, FIELD_TYPE_SCALAR),
-		FUNCTION2(TYPEOF, 1, 0, 0, typeofFunc, SQL_FUNC_TYPEOF,
-			  FIELD_TYPE_STRING),
-		FUNCTION2(LENGTH, 1, 0, 0, lengthFunc, SQL_FUNC_LENGTH,
-			  FIELD_TYPE_INTEGER),
-		FUNCTION(CHAR_LENGTH, 1, 0, 0, lengthFunc, FIELD_TYPE_INTEGER),
-		FUNCTION(CHARACTER_LENGTH, 1, 0, 0, lengthFunc,
-			 FIELD_TYPE_INTEGER),
-		FUNCTION(POSITION, 2, 0, 1, position_func, FIELD_TYPE_INTEGER),
-		FUNCTION(PRINTF, -1, 0, 0, printfFunc, FIELD_TYPE_STRING),
-		FUNCTION(UNICODE, 1, 0, 0, unicodeFunc, FIELD_TYPE_STRING),
-		FUNCTION(CHAR, -1, 0, 0, charFunc, FIELD_TYPE_STRING),
-		FUNCTION(ABS, 1, 0, 0, absFunc, FIELD_TYPE_NUMBER),
-		FUNCTION(ROUND, -1, 0, 0, roundFunc, FIELD_TYPE_INTEGER),
-		FUNCTION_COLL(UPPER, 1, 0, 1, UpperICUFunc),
-		FUNCTION_COLL(LOWER, 1, 0, 1, LowerICUFunc),
-		FUNCTION(HEX, 1, 0, 0, hexFunc, FIELD_TYPE_STRING),
-		FUNCTION2(IFNULL, 2, 0, 0, noopFunc, SQL_FUNC_COALESCE,
-			  FIELD_TYPE_INTEGER),
-		VFUNCTION(RANDOM, 0, 0, 0, randomFunc, FIELD_TYPE_INTEGER),
-		VFUNCTION(RANDOMBLOB, 1, 0, 0, randomBlob, FIELD_TYPE_VARBINARY),
-		FUNCTION(NULLIF, 2, 0, 1, nullifFunc, FIELD_TYPE_SCALAR),
-		FUNCTION(VERSION, 0, 0, 0, sql_func_version, FIELD_TYPE_STRING),
-		FUNCTION(QUOTE, 1, 0, 0, quoteFunc, FIELD_TYPE_STRING),
-		VFUNCTION(ROW_COUNT, 0, 0, 0, sql_row_count, FIELD_TYPE_INTEGER),
-		FUNCTION_COLL(REPLACE, 3, 0, 0, replaceFunc),
-		FUNCTION(ZEROBLOB, 1, 0, 0, zeroblobFunc, FIELD_TYPE_VARBINARY),
-		FUNCTION_COLL(SUBSTR, -1, 0, 0, substrFunc),
-		AGGREGATE(SUM, 1, 0, 0, sum_step, sumFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE(TOTAL, 1, 0, 0, sum_step, totalFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE(AVG, 1, 0, 0, sum_step, avgFinalize,
-			  FIELD_TYPE_NUMBER),
-		AGGREGATE2(COUNT, -1, 0, 0, countStep, countFinalize,
-			  SQL_FUNC_COUNT, FIELD_TYPE_INTEGER),
-		AGGREGATE(GROUP_CONCAT, -1, 0, 0, groupConcatStep,
-			  groupConcatFinalize, FIELD_TYPE_STRING),
-		LIKEFUNC(LIKE, -1, 1, SQL_FUNC_LIKE,
-			 FIELD_TYPE_INTEGER),
-		FUNCTION2(COALESCE, -1, 0, 0, noopFunc, SQL_FUNC_COALESCE,
-			  FIELD_TYPE_SCALAR),
-	};
-	sql_register_analyze_builtins();
-	sqlRegisterDateTimeFunctions();
-	sqlInsertBuiltinFuncs(aBuiltinFunc, ArraySize(aBuiltinFunc));
+	const char *name;
+	/** Members below are related to struct func_sql_builtin. */
+	uint16_t flags;
+	void (*call)(sql_context *ctx, int argc, sql_value **argv);
+	void (*finalize)(sql_context *ctx);
+	/** Members below are related to struct func_def. */
+	bool is_deterministic;
+	int param_count;
+	enum field_type returns;
+	enum func_aggregate aggregate;
+	bool export_to_sql;
+} sql_builtins[] = {
+	{.name = "ABS",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = absFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "AVG",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .is_deterministic = false,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .flags = 0,
+	 .call = sum_step,
+	 .finalize = avgFinalize,
+	 .export_to_sql = true,
+	}, {
+	 .name = "CEIL",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "CEILING",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "CHAR",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .is_deterministic = true,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .flags = 0,
+	 .call = charFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	 }, {
+	 .name = "CHARACTER_LENGTH",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = lengthFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "CHAR_LENGTH",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = lengthFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "COALESCE",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_COALESCE,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "COUNT",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = countStep,
+	 .finalize = countFinalize,
+	 .export_to_sql = true,
+	}, {
+	 .name = "CURRENT_DATE",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "CURRENT_TIME",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "CURRENT_TIMESTAMP",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "DATE",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "DATETIME",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "EVERY",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "EXISTS",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "EXP",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "EXTRACT",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "FLOOR",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "GREATER",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "GREATEST",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MAX,
+	 .call = minmaxFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "GROUP_CONCAT",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = groupConcatStep,
+	 .finalize = groupConcatFinalize,
+	 .export_to_sql = true,
+	}, {
+	 .name = "HEX",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = hexFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "IFNULL",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_COALESCE,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "JULIANDAY",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "LEAST",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MIN,
+	 .call = minmaxFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "LENGTH",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_LENGTH,
+	 .call = lengthFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "LESSER",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "LIKE",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_LIKE,
+	 .call = likeFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "LIKELIHOOD",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_BOOLEAN,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_UNLIKELY,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "LIKELY",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_BOOLEAN,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_UNLIKELY,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "LN",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "LOWER",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
+	 .call = LowerICUFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "MAX",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MAX,
+	 .call = minmaxStep,
+	 .finalize = minMaxFinalize,
+	 .export_to_sql = true,
+	}, {
+	 .name = "MIN",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MIN,
+	 .call = minmaxStep,
+	 .finalize = minMaxFinalize,
+	 .export_to_sql = true,
+	}, {
+	 .name = "MOD",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "NULLIF",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_SCALAR,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL,
+	 .call = nullifFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "OCTET_LENGTH",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "POSITION",
+	 .param_count = 2,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_NEEDCOLL,
+	 .call = position_func,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "POWER",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "PRINTF",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = printfFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "QUOTE",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = quoteFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "RANDOM",
+	 .param_count = 0,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = randomFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "RANDOMBLOB",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_VARBINARY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = randomBlob,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "REPLACE",
+	 .param_count = 3,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL,
+	 .call = replaceFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "ROUND",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = roundFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "ROW_COUNT",
+	 .param_count = 0,
+	 .returns = FIELD_TYPE_INTEGER,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = sql_row_count,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "SOME",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "SOUNDEX",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = soundexFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "SQRT",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "STRFTIME",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "SUBSTR",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL,
+	 .call = substrFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "SUM",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = sum_step,
+	 .finalize = sumFinalize,
+	 .export_to_sql = true,
+	}, {
+	 .name = "TIME",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "TOTAL",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_NUMBER,
+	 .aggregate = FUNC_AGGREGATE_GROUP,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .call = sum_step,
+	 .finalize = totalFinalize,
+	 .export_to_sql = true,
+	}, {
+	 .name = "TRIM",
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL,
+	 .call = trim_func,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "TYPEOF",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_TYPEOF,
+	 .call = typeofFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "UNICODE",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = unicodeFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "UNLIKELY",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_BOOLEAN,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_UNLIKELY,
+	 .call = sql_builtin_stub,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "UPPER",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
+	 .call = UpperICUFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "VERSION",
+	 .param_count = 0,
+	 .returns = FIELD_TYPE_STRING,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = sql_func_version,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "ZEROBLOB",
+	 .param_count = 1,
+	 .returns = FIELD_TYPE_VARBINARY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = true,
+	 .flags = 0,
+	 .call = zeroblobFunc,
+	 .finalize = NULL,
+	 .export_to_sql = true,
+	}, {
+	 .name = "_sql_stat_get",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "_sql_stat_init",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	}, {
+	 .name = "_sql_stat_push",
+	 .call = sql_builtin_stub,
+	 .export_to_sql = false,
+	 .param_count = -1,
+	 .returns = FIELD_TYPE_ANY,
+	 .aggregate = FUNC_AGGREGATE_NONE,
+	 .is_deterministic = false,
+	 .flags = 0,
+	 .finalize = NULL,
+	},
+};
 
-#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");
+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);
+	/** 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
+	/*
+	 * All SQL built-in(s) (stubs) are defined in a snapshot.
+	 * Implementation-specific metadata is defined in
+	 * sql_builtins list. When a definition were not found
+	 * above, the function name is invalid, i.e. it is
+	 * not built-in function.
+	 */
+	if (idx == -1) {
+		diag_set(ClientError, ER_CREATE_FUNCTION, def->name,
+			 "given built-in is not predefined");
+		return NULL;
+	}
+	struct func_sql_builtin *func =
+		(struct func_sql_builtin *) calloc(1, sizeof(*func));
+	if (func == NULL) {
+		diag_set(OutOfMemory, sizeof(*func), "malloc", "func");
+		return NULL;
+	}
+	func->base.def = def;
+	func->base.vtab = &func_sql_builtin_vtab;
+	func->flags = sql_builtins[idx].flags;
+	func->call = sql_builtins[idx].call;
+	func->finalize = sql_builtins[idx].finalize;
+	def->param_count = sql_builtins[idx].param_count;
+	def->is_deterministic = sql_builtins[idx].is_deterministic;
+	def->returns = sql_builtins[idx].returns;
+	def->aggregate = sql_builtins[idx].aggregate;
+	def->exports.sql = sql_builtins[idx].export_to_sql;
+	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 = func_sql_builtin_call_stub,
+	.destroy = func_sql_builtin_destroy,
+};
diff --git a/src/box/sql/global.c b/src/box/sql/global.c
index 6cadef809..c25b83de1 100644
--- a/src/box/sql/global.c
+++ b/src/box/sql/global.c
@@ -162,13 +162,6 @@ SQL_WSD struct sqlConfig sqlConfig = {
 	0x7ffffffe		/* iOnceResetThreshold */
 };
 
-/*
- * Hash table for global functions - functions common to all
- * database connections.  After initialization, this table is
- * read-only.
- */
-FuncDefHash sqlBuiltinFunctions;
-
 /*
  * The value of the "pending" byte must be 0x40000000 (1 byte past the
  * 1-gibabyte boundary) in a compatible database.  sql never uses
diff --git a/src/box/sql/main.c b/src/box/sql/main.c
index 748dc1b8e..0b20f2132 100644
--- a/src/box/sql/main.c
+++ b/src/box/sql/main.c
@@ -131,9 +131,6 @@ sql_initialize(void)
 	if (sqlGlobalConfig.isInit == 0
 	    && sqlGlobalConfig.inProgress == 0) {
 		sqlGlobalConfig.inProgress = 1;
-		memset(&sqlBuiltinFunctions, 0,
-		       sizeof(sqlBuiltinFunctions));
-		sqlRegisterBuiltinFunctions();
 		sql_os_init();
 		sqlGlobalConfig.isInit = 1;
 		sqlGlobalConfig.inProgress = 0;
@@ -242,25 +239,6 @@ sqlCloseSavepoints(Vdbe * pVdbe)
 	pVdbe->anonymous_savepoint = NULL;
 }
 
-/*
- * Invoke the destructor function associated with FuncDef p, if any. Except,
- * if this is not the last copy of the function, do not invoke it. Multiple
- * copies of a single function are created when create_function() is called
- * with SQL_ANY as the encoding.
- */
-static void
-functionDestroy(sql * db, FuncDef * p)
-{
-	FuncDestructor *pDestructor = p->u.pDestructor;
-	if (pDestructor) {
-		pDestructor->nRef--;
-		if (pDestructor->nRef == 0) {
-			pDestructor->xDestroy(pDestructor->pUserData);
-			sqlDbFree(db, pDestructor);
-		}
-	}
-}
-
 /*
  * Rollback all database files.  If tripCode is not 0, then
  * any write cursors are invalidated ("tripped" - as in "tripping a circuit
@@ -279,121 +257,6 @@ sqlRollbackAll(Vdbe * pVdbe)
 	}
 }
 
-/*
- * This function is exactly the same as sql_create_function(), except
- * that it is designed to be called by internal code. The difference is
- * that if a malloc() fails in sql_create_function(), an error code
- * is returned and the mallocFailed flag cleared.
- */
-int
-sqlCreateFunc(sql * db,
-		  const char *zFunctionName,
-		  enum field_type type,
-		  int nArg,
-		  int flags,
-		  void *pUserData,
-		  void (*xSFunc) (sql_context *, int, sql_value **),
-		  void (*xStep) (sql_context *, int, sql_value **),
-		  void (*xFinal) (sql_context *),
-		  FuncDestructor * pDestructor)
-{
-	FuncDef *p;
-	int extraFlags;
-
-	if (zFunctionName == 0 ||
-	    (xSFunc && (xFinal || xStep)) ||
-	    (!xSFunc && (xFinal && !xStep)) ||
-	    (!xSFunc && (!xFinal && xStep)) ||
-	    (nArg < -1 || nArg > SQL_MAX_FUNCTION_ARG) ||
-	    (255 < (sqlStrlen30(zFunctionName)))) {
-		diag_set(ClientError, ER_CREATE_FUNCTION, zFunctionName,
-			 "wrong function definition");
-		return -1;
-	}
-
-	assert(SQL_FUNC_CONSTANT == SQL_DETERMINISTIC);
-	extraFlags = flags & SQL_DETERMINISTIC;
-
-
-	/* Check if an existing function is being overridden or deleted. If so,
-	 * and there are active VMs, then return an error. If a function
-	 * is being overridden/deleted but there are no active VMs, allow the
-	 * operation to continue but invalidate all precompiled statements.
-	 */
-	p = sqlFindFunction(db, zFunctionName, nArg, 0);
-	if (p && p->nArg == nArg) {
-		if (db->nVdbeActive) {
-			diag_set(ClientError, ER_CREATE_FUNCTION, zFunctionName,
-				 "unable to create function due to active "\
-				 "statements");
-			return -1;
-		} else {
-			sqlExpirePreparedStatements(db);
-		}
-	}
-
-	p = sqlFindFunction(db, zFunctionName, nArg, 1);
-	assert(p || db->mallocFailed);
-	if (p == NULL)
-		return -1;
-
-	/* If an older version of the function with a configured destructor is
-	 * being replaced invoke the destructor function here.
-	 */
-	functionDestroy(db, p);
-
-	if (pDestructor) {
-		pDestructor->nRef++;
-	}
-	p->u.pDestructor = pDestructor;
-	p->funcFlags = extraFlags;
-	testcase(p->funcFlags & SQL_DETERMINISTIC);
-	p->xSFunc = xSFunc ? xSFunc : xStep;
-	p->xFinalize = xFinal;
-	p->pUserData = pUserData;
-	p->nArg = (u16) nArg;
-	p->ret_type = type;
-	return 0;
-}
-
-int
-sql_create_function_v2(sql * db,
-			   const char *zFunc,
-			   enum field_type type,
-			   int nArg,
-			   int flags,
-			   void *p,
-			   void (*xSFunc) (sql_context *, int,
-					   sql_value **),
-			   void (*xStep) (sql_context *, int,
-					  sql_value **),
-			   void (*xFinal) (sql_context *),
-			   void (*xDestroy) (void *))
-{
-	FuncDestructor *pArg = 0;
-
-	if (xDestroy) {
-		pArg =
-		    (FuncDestructor *) sqlDbMallocZero(db,
-							   sizeof
-							   (FuncDestructor));
-		if (!pArg) {
-			xDestroy(p);
-			return -1;
-		}
-		pArg->xDestroy = xDestroy;
-		pArg->pUserData = p;
-	}
-	int rc = sqlCreateFunc(db, zFunc, type, nArg, flags, p, xSFunc, xStep,
-			       xFinal, pArg);
-	if (pArg && pArg->nRef == 0) {
-		assert(rc != 0);
-		xDestroy(p);
-		sqlDbFree(db, pArg);
-	}
-	return rc;
-}
-
 /*
  * This array defines hard upper bounds on limit values.  The
  * initializer must be kept in sync with the SQL_LIMIT_*
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 853d42b58..c9c8e26f1 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -38,6 +38,7 @@
 #include "sqlInt.h"
 #include <stdlib.h>
 #include <string.h>
+#include "box/schema.h"
 
 /*
  * Walk the expression tree pExpr and increase the aggregate function
@@ -591,83 +592,71 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 	case TK_FUNCTION:{
 			ExprList *pList = pExpr->x.pList;	/* The argument list */
 			int n = pList ? pList->nExpr : 0;	/* Number of arguments */
-			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) {
-					diag_set(ClientError,
-						 ER_NO_SUCH_FUNCTION, zId);
-				} else {
-					uint32_t argc = pDef->nArg;
-					const char *err = tt_sprintf("%d", argc);
-					diag_set(ClientError,
-						 ER_FUNC_WRONG_ARG_COUNT,
-						 pDef->zName, err, n);
-				}
+			struct func *func = func_by_name(zId, nId);
+			if (func == NULL) {
+				diag_set(ClientError, ER_NO_SUCH_FUNCTION, zId);
 				pParse->is_aborted = true;
 				pNC->nErr++;
-			} else {
-				is_agg = pDef->xFinalize != 0;
-				pExpr->type = pDef->ret_type;
-				const char *err =
-					"second argument to likelihood() must "\
-					"be a constant between 0.0 and 1.0";
-				if (pDef->funcFlags & SQL_FUNC_UNLIKELY) {
-					ExprSetProperty(pExpr,
-							EP_Unlikely | EP_Skip);
-					if (n == 2) {
-						pExpr->iTable =
-						    exprProbability(pList->a[1].
-								    pExpr);
-						if (pExpr->iTable < 0) {
-							diag_set(ClientError,
-								 ER_ILLEGAL_PARAMS,
-								 err);
-							pParse->is_aborted =
-								true;
-							pNC->nErr++;
-						}
-					} else {
-						/* EVIDENCE-OF: R-61304-29449 The unlikely(X) function is
-						 * equivalent to likelihood(X, 0.0625).
-						 * EVIDENCE-OF: R-01283-11636 The unlikely(X) function is
-						 * short-hand for likelihood(X,0.0625).
-						 * EVIDENCE-OF: R-36850-34127 The likely(X) function is short-hand
-						 * for likelihood(X,0.9375).
-						 * EVIDENCE-OF: R-53436-40973 The likely(X) function is equivalent
-						 * to likelihood(X,0.9375).
-						 */
-						/* TUNING: unlikely() probability is 0.0625.  likely() is 0.9375 */
-						pExpr->iTable =
-						    pDef->zName[0] ==
-						    'u' ? 8388608 : 125829120;
-					}
-				}
-				if ((pDef->funcFlags & SQL_FUNC_CONSTANT) != 0) {
-					/* For the purposes of the EP_ConstFunc flag, date and time
-					 * functions and other functions that change slowly are considered
-					 * constant because they are constant for the duration of one query
-					 */
-					ExprSetProperty(pExpr, EP_ConstFunc);
-				}
-				if ((pDef->funcFlags & SQL_FUNC_CONSTANT) ==
-				    0) {
-					/* Date/time functions that use 'now', and other functions
-					 * that might change over time cannot be used
-					 * in an index.
-					 */
-					assert((pNC->ncFlags & NC_IdxExpr) == 0);
+				return WRC_Abort;
+			}
+			if (!func->def->exports.sql) {
+				diag_set(ClientError, ER_SQL_PARSER_GENERIC,
+					 tt_sprintf("function %.*s() is not "
+						    "available in SQL",
+						     nId, zId));
+				pParse->is_aborted = true;
+				pNC->nErr++;
+				return WRC_Abort;
+			}
+			if (func->def->param_count != -1 &&
+			    func->def->param_count != n) {
+				uint32_t argc = func->def->param_count;
+				const char *err = tt_sprintf("%d", argc);
+				diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+					 func->def->name, err, n);
+				pParse->is_aborted = true;
+				pNC->nErr++;
+				return WRC_Abort;
+			}
+			bool is_agg = func->def->aggregate ==
+				      FUNC_AGGREGATE_GROUP;
+			assert(!is_agg || func->def->language ==
+					  FUNC_LANGUAGE_SQL_BUILTIN);
+			pExpr->type = func->def->returns;
+			if (sql_func_flag_is_set(func, SQL_FUNC_UNLIKELY) &&
+			    n == 2) {
+				ExprSetProperty(pExpr, EP_Unlikely | EP_Skip);
+				pExpr->iTable =
+					exprProbability(pList->a[1].pExpr);
+				if (pExpr->iTable < 0) {
+					diag_set(ClientError, ER_ILLEGAL_PARAMS,
+						"second argument to "
+						"likelihood() must be a "
+						"constant between 0.0 and 1.0");
+					pParse->is_aborted = true;
+					pNC->nErr++;
+					return WRC_Abort;
 				}
+			} else if (sql_func_flag_is_set(func,
+							SQL_FUNC_UNLIKELY)) {
+				ExprSetProperty(pExpr, EP_Unlikely | EP_Skip);
+				/*
+				 * unlikely() probability is
+				 * 0.0625, likely() is 0.9375
+				 */
+				pExpr->iTable = func->def->name[0] == 'u' ?
+						8388608 : 125829120;
 			}
+			assert(!func->def->is_deterministic ||
+			       (pNC->ncFlags & NC_IdxExpr) == 0);
+			if (func->def->is_deterministic)
+				ExprSetProperty(pExpr, EP_ConstFunc);
 			if (is_agg && (pNC->ncFlags & NC_AllowAgg) == 0) {
 				const char *err =
 					tt_sprintf("misuse of aggregate "\
@@ -692,13 +681,12 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 					pExpr->op2++;
 					pNC2 = pNC2->pNext;
 				}
-				assert(pDef != 0);
+				assert(func != NULL);
 				if (pNC2) {
 					pNC2->ncFlags |= NC_HasAgg;
-					if ((pDef->funcFlags &
-					    SQL_FUNC_MIN) != 0 ||
-					    (pDef->funcFlags &
-					    SQL_FUNC_MAX) != 0)
+					if (sql_func_flag_is_set(func,
+							         SQL_FUNC_MIN |
+								 SQL_FUNC_MAX))
 						pNC2->ncFlags |= NC_MinMaxAgg;
 				}
 				pNC->ncFlags |= NC_AllowAgg;
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index ddb5de87e..cdcdbaf2b 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -4381,7 +4381,9 @@ is_simple_count(struct Select *select, struct AggInfo *agg_info)
 		return NULL;
 	if (NEVER(agg_info->nFunc == 0))
 		return NULL;
-	if ((agg_info->aFunc->pFunc->funcFlags & SQL_FUNC_COUNT) == 0 ||
+	assert(agg_info->aFunc->func->def->language ==
+	       FUNC_LANGUAGE_SQL_BUILTIN);
+	if (sql_func_flag_is_set(agg_info->aFunc->func, SQL_FUNC_COUNT) ||
 	    (agg_info->aFunc->pExpr->x.pList != NULL &&
 	     agg_info->aFunc->pExpr->x.pList->nExpr > 0))
 		return NULL;
@@ -5278,7 +5280,7 @@ finalizeAggFunctions(Parse * pParse, AggInfo * pAggInfo)
 		assert(!ExprHasProperty(pF->pExpr, EP_xIsSelect));
 		sqlVdbeAddOp2(v, OP_AggFinal, pF->iMem,
 				  pList ? pList->nExpr : 0);
-		sqlVdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
+		sqlVdbeAppendP4(v, pF->func, P4_FUNC);
 	}
 }
 
@@ -5319,7 +5321,7 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 			vdbe_insert_distinct(pParse, pF->iDistinct, pF->reg_eph,
 					     addrNext, 1, regAgg);
 		}
-		if (pF->pFunc->funcFlags & SQL_FUNC_NEEDCOLL) {
+		if (sql_func_flag_is_set(pF->func, SQL_FUNC_NEEDCOLL)) {
 			struct coll *coll = NULL;
 			struct ExprList_item *pItem;
 			int j;
@@ -5338,7 +5340,7 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo)
 					  (char *)coll, P4_COLLSEQ);
 		}
 		sqlVdbeAddOp3(v, OP_AggStep0, 0, regAgg, pF->iMem);
-		sqlVdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
+		sqlVdbeAppendP4(v, pF->func, P4_FUNC);
 		sqlVdbeChangeP5(v, (u8) nArg);
 		sql_expr_type_cache_change(pParse, regAgg, nArg);
 		sqlReleaseTempRange(pParse, regAgg, nArg);
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index bfa8c1e6f..f9f996929 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1733,7 +1733,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));
@@ -1741,7 +1741,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;
@@ -1776,7 +1776,9 @@ case OP_BuiltinFunction: {
 	}
 #endif
 	pCtx->is_aborted = false;
-	(*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */
+	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
+	func->call(pCtx, pCtx->argc, pCtx->argv);
 
 	/* If the function returned an error, throw an exception */
 	if (pCtx->is_aborted)
@@ -4994,7 +4996,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));
@@ -5002,7 +5004,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;
@@ -5044,7 +5046,9 @@ case OP_AggStep: {
 	pCtx->pOut = &t;
 	pCtx->is_aborted = false;
 	pCtx->skipFlag = 0;
-	(pCtx->pFunc->xSFunc)(pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */
+	assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func;
+	func->call(pCtx, pCtx->argc, pCtx->argv);
 	if (pCtx->is_aborted) {
 		sqlVdbeMemRelease(&t);
 		goto abort_due_to_error;
@@ -5076,7 +5080,7 @@ case OP_AggFinal: {
 	assert(pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor));
 	pMem = &aMem[pOp->p1];
 	assert((pMem->flags & ~(MEM_Null|MEM_Agg))==0);
-	if (sqlVdbeMemFinalize(pMem, pOp->p4.pFunc) != 0)
+	if (sql_vdbemem_finalize(pMem, pOp->p4.func) != 0)
 		goto abort_due_to_error;
 	UPDATE_MAX_BLOBSIZE(pMem);
 	if (sqlVdbeMemTooBig(pMem)) {
diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
index dfaff9365..bf40b44fa 100644
--- a/src/box/sql/vdbeapi.c
+++ b/src/box/sql/vdbeapi.c
@@ -477,17 +477,6 @@ sql_step(sql_stmt * pStmt)
 	return sqlStep(v);
 }
 
-/*
- * Extract the user data from a sql_context structure and return a
- * pointer to it.
- */
-void *
-sql_user_data(sql_context * p)
-{
-	assert(p && p->pFunc);
-	return p->pFunc->pUserData;
-}
-
 /*
  * Extract the user data from a sql_context structure and return a
  * pointer to it.
@@ -547,7 +536,7 @@ createAggContext(sql_context * p, int nByte)
 	} else {
 		sqlVdbeMemClearAndResize(pMem, nByte);
 		pMem->flags = MEM_Agg;
-		pMem->u.pDef = p->pFunc;
+		pMem->u.func = p->func;
 		if (pMem->z) {
 			memset(pMem->z, 0, nByte);
 		}
@@ -563,7 +552,9 @@ createAggContext(sql_context * p, int nByte)
 void *
 sql_aggregate_context(sql_context * p, int nByte)
 {
-	assert(p && p->pFunc && p->pFunc->xFinalize);
+	assert(p != NULL && p->func != NULL);
+	assert(p->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	assert(p->func->def->aggregate == FUNC_AGGREGATE_GROUP);
 	testcase(nByte < 0);
 	if ((p->pMem->flags & MEM_Agg) == 0) {
 		return createAggContext(p, nByte);
diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
index 62df76c26..40918e7e7 100644
--- a/src/box/sql/vdbeaux.c
+++ b/src/box/sql/vdbeaux.c
@@ -620,24 +620,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);
 }
 
@@ -661,13 +648,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;
 	}
 }
 
@@ -1121,15 +1106,17 @@ displayP4(Op * pOp, char *zTemp, int nTemp)
 				sqlXPrintf(&x, "(binary)");
 			break;
 		}
-	case P4_FUNCDEF:{
-			FuncDef *pDef = pOp->p4.pFunc;
-			sqlXPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg);
+	case P4_FUNC:{
+			struct func *func = pOp->p4.func;
+			sqlXPrintf(&x, "%s(%d)", func->def->name,
+				   func->def->param_count);
 			break;
 		}
 #if defined(SQL_DEBUG) || defined(VDBE_PROFILE)
 	case P4_FUNCCTX:{
-			FuncDef *pDef = pOp->p4.pCtx->pFunc;
-			sqlXPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg);
+			struct func *func = pOp->p4.func;
+			sqlXPrintf(&x, "%s(%d)", func->def->name,
+				   func->def->param_count);
 			break;
 		}
 #endif
diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c
index 5516d7fb1..ac0dfa333 100644
--- a/src/box/sql/vdbemem.c
+++ b/src/box/sql/vdbemem.c
@@ -312,37 +312,29 @@ sqlVdbeMemStringify(Mem * pMem)
 	return 0;
 }
 
-/*
- * Memory cell pMem contains the context of an aggregate function.
- * This routine calls the finalize method for that function.  The
- * result of the aggregate is stored back into pMem.
- *
- * Return -1 if the finalizer reports an error. 0 otherwise.
- */
 int
-sqlVdbeMemFinalize(Mem * pMem, FuncDef * pFunc)
+sql_vdbemem_finalize(struct Mem *mem, struct func *func)
 {
-	if (ALWAYS(pFunc && pFunc->xFinalize)) {
-		sql_context ctx;
-		Mem t;
-		assert((pMem->flags & MEM_Null) != 0 || pFunc == pMem->u.pDef);
-		memset(&ctx, 0, sizeof(ctx));
-		memset(&t, 0, sizeof(t));
-		t.flags = MEM_Null;
-		t.db = pMem->db;
-		t.field_type = field_type_MAX;
-		ctx.pOut = &t;
-		ctx.pMem = pMem;
-		ctx.pFunc = pFunc;
-		pFunc->xFinalize(&ctx);	/* IMP: R-24505-23230 */
-		assert((pMem->flags & MEM_Dyn) == 0);
-		if (pMem->szMalloc > 0)
-			sqlDbFree(pMem->db, pMem->zMalloc);
-		memcpy(pMem, &t, sizeof(t));
-		if (ctx.is_aborted)
-			return -1;
-	}
-	return 0;
+	assert(func != NULL);
+	assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
+	assert(func->def->aggregate == FUNC_AGGREGATE_GROUP);
+	assert((mem->flags & MEM_Null) != 0 || func == mem->u.func);
+	sql_context ctx;
+	memset(&ctx, 0, sizeof(ctx));
+	Mem t;
+	memset(&t, 0, sizeof(t));
+	t.flags = MEM_Null;
+	t.db = mem->db;
+	t.field_type = field_type_MAX;
+	ctx.pOut = &t;
+	ctx.pMem = mem;
+	ctx.func = func;
+	((struct func_sql_builtin *)func)->finalize(&ctx);
+	assert((mem->flags & MEM_Dyn) == 0);
+	if (mem->szMalloc > 0)
+		sqlDbFree(mem->db, mem->zMalloc);
+	memcpy(mem, &t, sizeof(t));
+	return ctx.is_aborted ? -1 : 0;
 }
 
 /*
@@ -359,7 +351,7 @@ vdbeMemClearExternAndSetNull(Mem * p)
 {
 	assert(VdbeMemDynamic(p));
 	if (p->flags & MEM_Agg) {
-		sqlVdbeMemFinalize(p, p->u.pDef);
+		sql_vdbemem_finalize(p, p->u.func);
 		assert((p->flags & MEM_Agg) == 0);
 		testcase(p->flags & MEM_Dyn);
 	}
@@ -1289,7 +1281,6 @@ valueFromFunction(sql * db,	/* The database connection */
 	sql_context ctx;	/* Context object for function invocation */
 	sql_value **apVal = 0;	/* Function arguments */
 	int nVal = 0;		/* Size of apVal[] array */
-	FuncDef *pFunc = 0;	/* Function definition */
 	sql_value *pVal = 0;	/* New value */
 	int rc = 0;	/* Return code */
 	ExprList *pList = 0;	/* Function arguments */
@@ -1300,10 +1291,10 @@ valueFromFunction(sql * db,	/* The database connection */
 	pList = p->x.pList;
 	if (pList)
 		nVal = pList->nExpr;
-	pFunc = sqlFindFunction(db, p->u.zToken, nVal, 0);
-	assert(pFunc);
-	if ((pFunc->funcFlags & SQL_FUNC_CONSTANT) == 0 ||
-	    (pFunc->funcFlags & SQL_FUNC_NEEDCOLL))
+	struct func *func = sql_func_by_signature(p->u.zToken, nVal);
+	if (func == NULL || func->def->language != FUNC_LANGUAGE_SQL_BUILTIN ||
+	    !func->def->is_deterministic ||
+	    sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL))
 		return 0;
 
 	if (pList) {
@@ -1332,8 +1323,8 @@ valueFromFunction(sql * db,	/* The database connection */
 	assert(!pCtx->pParse->is_aborted);
 	memset(&ctx, 0, sizeof(ctx));
 	ctx.pOut = pVal;
-	ctx.pFunc = pFunc;
-	pFunc->xSFunc(&ctx, nVal, apVal);
+	ctx.func = func;
+	((struct func_sql_builtin *)func)->call(&ctx, nVal, apVal);
 	assert(!ctx.is_aborted);
 	sql_value_apply_type(pVal, type);
 	assert(rc == 0);
diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
index 8adf6a5f1..98615b118 100644
--- a/src/box/sql/whereexpr.c
+++ b/src/box/sql/whereexpr.c
@@ -273,7 +273,7 @@ like_optimization_is_valid(Parse *pParse, Expr *pExpr, Expr **ppPrefix,
 	/* Result code to return. */
 	int rc;
 
-	if (!sql_is_like_func(db, pExpr)) {
+	if (!sql_is_like_func(pExpr)) {
 		return 0;
 	}
 	pList = pExpr->x.pList;
diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 9bba37bcb..9d2fcea4b 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -130,7 +130,6 @@ add_library(box STATIC
     ${lua_sources}
     lua/init.c
     lua/call.c
-    lua/lua_sql.c
     lua/cfg.cc
     lua/console.c
     lua/tuple.c
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 4f2e34bf0..c0780d6da 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -2926,6 +2926,12 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event)
 				  (unsigned) old_func->def->uid,
 				  "function has references");
 		}
+		/* Can't' drop a builtin function. */
+		if (old_func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) {
+			tnt_raise(ClientError, ER_DROP_FUNCTION,
+				  (unsigned) old_func->def->uid,
+				  "function is SQL built-in");
+		}
 		struct trigger *on_commit =
 			txn_alter_trigger_new(on_drop_func_commit, old_func);
 		struct trigger *on_rollback =
diff --git a/test/box/function1.result b/test/box/function1.result
index 5b091f72b..0ece5448a 100644
--- a/test/box/function1.result
+++ b/test/box/function1.result
@@ -825,6 +825,26 @@ box.func.LUA:call({"return 1 + 1"})
 ---
 - 2
 ...
+box.schema.user.grant('guest', 'execute', 'function', 'SUM')
+---
+...
+c = net.connect(box.cfg.listen)
+---
+...
+c:call("SUM")
+---
+- error: sql builtin function does not support Lua frontend
+...
+c:close()
+---
+...
+box.schema.user.revoke('guest', 'execute', 'function', 'SUM')
+---
+...
+box.schema.func.drop("SUM")
+---
+- error: 'Can''t drop function 1: function is SQL built-in'
+...
 -- Introduce function options
 box.schema.func.create('test', {body = "function(tuple) return tuple end", is_deterministic = true, opts = {is_multikey = true}})
 ---
diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua
index f894472f8..665972eda 100644
--- a/test/box/function1.test.lua
+++ b/test/box/function1.test.lua
@@ -294,6 +294,13 @@ ok == true
 
 box.func.LUA:call({"return 1 + 1"})
 
+box.schema.user.grant('guest', 'execute', 'function', 'SUM')
+c = net.connect(box.cfg.listen)
+c:call("SUM")
+c:close()
+box.schema.user.revoke('guest', 'execute', 'function', 'SUM')
+box.schema.func.drop("SUM")
+
 -- Introduce function options
 box.schema.func.create('test', {body = "function(tuple) return tuple end", is_deterministic = true, opts = {is_multikey = true}})
 box.func['test'].is_multikey == true
-- 
2.22.1

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

* [tarantool-patches] [PATCH v4 3/5] sql: remove name overloading for SQL builtins
  2019-08-22 14:37   ` [tarantool-patches] " n.pettik
  2019-08-23 15:02     ` [tarantool-patches] [PATCH v4 4/5] " Kirill Shcherbatov
@ 2019-08-23 15:02     ` Kirill Shcherbatov
  2019-08-28 15:05       ` [tarantool-patches] " Nikita Pettik
  2019-08-23 15:02     ` [tarantool-patches] Re: [PATCH v4 3/4] sql: get rid of FuncDef function hash Kirill Shcherbatov
  2 siblings, 1 reply; 17+ messages in thread
From: Kirill Shcherbatov @ 2019-08-23 15:02 UTC (permalink / raw)
  To: tarantool-patches, n.pettik

Now all SQL builtins are unique, i.e. each function has the only
hash table entry. This technique requires to remove static argc
checks for some builtins(substr, round, like, trim, count,
group_concat). Now they raise a runtime error in case of invalid
usage.

Updated error messages correspondingly, to provide more
informative messages in such cases. Taking into account upcoming
changes, all built-in functions names are uppercased.

Needed for #2200, #4113, #2233
---
 src/box/errcode.h               |   1 +
 src/box/sql/expr.c              |  18 ++++-
 src/box/sql/func.c              | 126 ++++++++++++++++++--------------
 src/box/sql/resolve.c           |  24 +++---
 test/box/misc.result            |   1 +
 test/sql-tap/func.test.lua      |  22 +++---
 test/sql-tap/func2.test.lua     |  18 ++---
 test/sql-tap/limit.test.lua     |   4 +-
 test/sql-tap/select1.test.lua   |  12 +--
 test/sql-tap/where2.test.lua    |   4 +-
 test/sql/icu-upper-lower.result |   4 +-
 11 files changed, 130 insertions(+), 104 deletions(-)

diff --git a/src/box/errcode.h b/src/box/errcode.h
index 46b0b365a..18574c5aa 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -253,6 +253,7 @@ struct errcode_record {
 	/*198 */_(ER_FUNC_INDEX_FUNC,		"Failed to build a key for functional index '%s' of space '%s': %s") \
 	/*199 */_(ER_FUNC_INDEX_FORMAT,		"Key format doesn't match one defined in functional index '%s' of space '%s': %s") \
 	/*200 */_(ER_FUNC_INDEX_PARTS,		"Wrong functional index definition: %s") \
+	/*201 */_(ER_FUNC_WRONG_ARG_COUNT,	"Wrong number of arguments is passed to %s(): expected %s, got %d") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/sql/expr.c b/src/box/sql/expr.c
index 1f9d91705..1837817d6 100644
--- a/src/box/sql/expr.c
+++ b/src/box/sql/expr.c
@@ -4004,7 +4004,14 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			 */
 			if (pDef->funcFlags & SQL_FUNC_COALESCE) {
 				int endCoalesce = sqlVdbeMakeLabel(v);
-				assert(nFarg >= 2);
+				if (nFarg < 2) {
+					diag_set(ClientError,
+						 ER_FUNC_WRONG_ARG_COUNT,
+						 pDef->zName, "at least two",
+						 nFarg);
+					pParse->is_aborted = true;
+					break;
+				}
 				sqlExprCode(pParse, pFarg->a[0].pExpr,
 						target);
 				for (i = 1; i < nFarg; i++) {
@@ -4027,7 +4034,14 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
 			 * of the first argument.
 			 */
 			if (pDef->funcFlags & SQL_FUNC_UNLIKELY) {
-				assert(nFarg >= 1);
+				if (nFarg < 1) {
+					diag_set(ClientError,
+						ER_FUNC_WRONG_ARG_COUNT,
+						pDef->zName, "at least one",
+						nFarg);
+					pParse->is_aborted = true;
+					break;
+				}
 				return sqlExprCodeTarget(pParse,
 							     pFarg->a[0].pExpr,
 							     target);
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index a46df60ed..9ce645698 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -410,7 +410,12 @@ substrFunc(sql_context * context, int argc, sql_value ** argv)
 	i64 p1, p2;
 	int negP2 = 0;
 
-	assert(argc == 3 || argc == 2);
+	if (argc != 2 && argc != 3) {
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "SUBSTR",
+			 "1 or 2", argc);
+		context->is_aborted = true;
+		return;
+	}
 	if (sql_value_is_null(argv[1])
 	    || (argc == 3 && sql_value_is_null(argv[2]))
 	    ) {
@@ -506,7 +511,12 @@ roundFunc(sql_context * context, int argc, sql_value ** argv)
 {
 	int n = 0;
 	double r;
-	assert(argc == 1 || argc == 2);
+	if (argc != 1 && argc != 2) {
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ROUND",
+			 "1 or 2", argc);
+		context->is_aborted = true;
+		return;
+	}
 	if (argc == 2) {
 		if (sql_value_is_null(argv[1]))
 			return;
@@ -898,6 +908,12 @@ likeFunc(sql_context *context, int argc, sql_value **argv)
 {
 	u32 escape = SQL_END_OF_STRING;
 	int nPat;
+	if (argc != 2 && argc != 3) {
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+			 "LIKE", "2 or 3", argc);
+		context->is_aborted = true;
+		return;
+	}
 	sql *db = sql_context_db_handle(context);
 	int rhs_type = sql_value_type(argv[0]);
 	int lhs_type = sql_value_type(argv[1]);
@@ -1510,7 +1526,9 @@ trim_func(struct sql_context *context, int argc, sql_value **argv)
 		trim_func_three_args(context, argv[0], argv[1], argv[2]);
 		break;
 	default:
-		unreachable();
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "TRIM",
+			 "1 or 2 or 3", argc);
+		context->is_aborted = true;
 	}
 }
 
@@ -1689,6 +1707,12 @@ static void
 countStep(sql_context * context, int argc, sql_value ** argv)
 {
 	CountCtx *p;
+	if (argc != 0 && argc != 1) {
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+			 "COUNT", "0 or 1", argc);
+		context->is_aborted = true;
+		return;
+	}
 	p = sql_aggregate_context(context, sizeof(*p));
 	if ((argc == 0 || ! sql_value_is_null(argv[0])) && p) {
 		p->n++;
@@ -1765,7 +1789,12 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv)
 	StrAccum *pAccum;
 	const char *zSep;
 	int nVal, nSep;
-	assert(argc == 1 || argc == 2);
+	if (argc != 1 && argc != 2) {
+		diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT,
+			 "GROUP_CONCAT", "1 or 2", argc);
+		context->is_aborted = true;
+		return;
+	}
 	if (sql_value_is_null(argv[0]))
 		return;
 	pAccum =
@@ -1848,75 +1877,62 @@ 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, 1, 0, 0, soundexFunc, FIELD_TYPE_STRING),
+		FUNCTION2(UNLIKELY, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
 			  FIELD_TYPE_BOOLEAN),
-		FUNCTION2(likelihood, 2, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
+		FUNCTION2(LIKELIHOOD, 2, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
 			  FIELD_TYPE_BOOLEAN),
-		FUNCTION2(likely, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
+		FUNCTION2(LIKELY, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY,
 			  FIELD_TYPE_BOOLEAN),
-		FUNCTION_COLL(trim, 1, 3, 0, trim_func),
-		FUNCTION_COLL(trim, 2, 3, 0, trim_func),
-		FUNCTION_COLL(trim, 3, 3, 0, trim_func),
-		FUNCTION2(least, -1, 0, 1, minmaxFunc, SQL_FUNC_MIN,
+		FUNCTION_COLL(TRIM, -1, 3, 0, trim_func),
+		FUNCTION2(LEAST, -1, 0, 1, minmaxFunc, SQL_FUNC_MIN,
 			  FIELD_TYPE_SCALAR),
-		AGGREGATE2(min, 1, 0, 1, minmaxStep, minMaxFinalize,
+		AGGREGATE2(MIN, 1, 0, 1, minmaxStep, minMaxFinalize,
 			   SQL_FUNC_MIN, FIELD_TYPE_SCALAR),
 		FUNCTION2(greatest, -1, 1, 1, minmaxFunc, SQL_FUNC_MAX,
 			  FIELD_TYPE_SCALAR),
-		AGGREGATE2(max, 1, 1, 1, minmaxStep, minMaxFinalize,
+		AGGREGATE2(MAX, 1, 1, 1, minmaxStep, minMaxFinalize,
 			   SQL_FUNC_MAX, FIELD_TYPE_SCALAR),
-		FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQL_FUNC_TYPEOF,
+		FUNCTION2(TYPEOF, 1, 0, 0, typeofFunc, SQL_FUNC_TYPEOF,
 			  FIELD_TYPE_STRING),
-		FUNCTION2(length, 1, 0, 0, lengthFunc, SQL_FUNC_LENGTH,
+		FUNCTION2(LENGTH, 1, 0, 0, lengthFunc, SQL_FUNC_LENGTH,
 			  FIELD_TYPE_INTEGER),
-		FUNCTION(char_length, 1, 0, 0, lengthFunc, FIELD_TYPE_INTEGER),
-		FUNCTION(character_length, 1, 0, 0, lengthFunc,
+		FUNCTION(CHAR_LENGTH, 1, 0, 0, lengthFunc, FIELD_TYPE_INTEGER),
+		FUNCTION(CHARACTER_LENGTH, 1, 0, 0, lengthFunc,
 			 FIELD_TYPE_INTEGER),
-		FUNCTION(position, 2, 0, 1, position_func, FIELD_TYPE_INTEGER),
-		FUNCTION(printf, -1, 0, 0, printfFunc, FIELD_TYPE_STRING),
-		FUNCTION(unicode, 1, 0, 0, unicodeFunc, FIELD_TYPE_STRING),
-		FUNCTION(char, -1, 0, 0, charFunc, FIELD_TYPE_STRING),
-		FUNCTION(abs, 1, 0, 0, absFunc, FIELD_TYPE_NUMBER),
-		FUNCTION(round, 1, 0, 0, roundFunc, FIELD_TYPE_INTEGER),
-		FUNCTION(round, 2, 0, 0, roundFunc, FIELD_TYPE_INTEGER),
-		FUNCTION_COLL(upper, 1, 0, 1, UpperICUFunc),
-		FUNCTION_COLL(lower, 1, 0, 1, LowerICUFunc),
-		FUNCTION(hex, 1, 0, 0, hexFunc, FIELD_TYPE_STRING),
-		FUNCTION2(ifnull, 2, 0, 0, noopFunc, SQL_FUNC_COALESCE,
+		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_COLL(UPPER, 1, 0, 1, UpperICUFunc),
+		FUNCTION_COLL(LOWER, 1, 0, 1, LowerICUFunc),
+		FUNCTION(HEX, 1, 0, 0, hexFunc, FIELD_TYPE_STRING),
+		FUNCTION2(IFNULL, 2, 0, 0, noopFunc, SQL_FUNC_COALESCE,
 			  FIELD_TYPE_INTEGER),
-		VFUNCTION(random, 0, 0, 0, randomFunc, FIELD_TYPE_INTEGER),
-		VFUNCTION(randomblob, 1, 0, 0, randomBlob, FIELD_TYPE_VARBINARY),
-		FUNCTION(nullif, 2, 0, 1, nullifFunc, FIELD_TYPE_SCALAR),
-		FUNCTION(version, 0, 0, 0, sql_func_version, FIELD_TYPE_STRING),
-		FUNCTION(quote, 1, 0, 0, quoteFunc, FIELD_TYPE_STRING),
-		VFUNCTION(row_count, 0, 0, 0, sql_row_count, FIELD_TYPE_INTEGER),
-		FUNCTION_COLL(replace, 3, 0, 0, replaceFunc),
-		FUNCTION(zeroblob, 1, 0, 0, zeroblobFunc, FIELD_TYPE_VARBINARY),
-		FUNCTION_COLL(substr, 2, 0, 0, substrFunc),
-		FUNCTION_COLL(substr, 3, 0, 0, substrFunc),
-		AGGREGATE(sum, 1, 0, 0, sum_step, sumFinalize,
+		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, -1, 0, 0, substrFunc),
+		AGGREGATE(SUM, 1, 0, 0, sum_step, sumFinalize,
 			  FIELD_TYPE_NUMBER),
-		AGGREGATE(total, 1, 0, 0, sum_step, totalFinalize,
+		AGGREGATE(TOTAL, 1, 0, 0, sum_step, totalFinalize,
 			  FIELD_TYPE_NUMBER),
-		AGGREGATE(avg, 1, 0, 0, sum_step, avgFinalize,
+		AGGREGATE(AVG, 1, 0, 0, sum_step, avgFinalize,
 			  FIELD_TYPE_NUMBER),
-		AGGREGATE2(count, 0, 0, 0, countStep, countFinalize,
+		AGGREGATE2(COUNT, -1, 0, 0, countStep, countFinalize,
 			  SQL_FUNC_COUNT, FIELD_TYPE_INTEGER),
-		AGGREGATE2(count, 1, 0, 0, countStep, countFinalize,
-			   SQL_FUNC_COUNT, FIELD_TYPE_INTEGER),
-		AGGREGATE(group_concat, 1, 0, 0, groupConcatStep,
+		AGGREGATE(GROUP_CONCAT, -1, 0, 0, groupConcatStep,
 			  groupConcatFinalize, FIELD_TYPE_STRING),
-		AGGREGATE(group_concat, 2, 0, 0, groupConcatStep,
-			  groupConcatFinalize, FIELD_TYPE_STRING),
-
-		LIKEFUNC(like, 2, 1, SQL_FUNC_LIKE,
-			 FIELD_TYPE_INTEGER),
-		LIKEFUNC(like, 3, 1, SQL_FUNC_LIKE,
+		LIKEFUNC(LIKE, -1, 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, -1, 0, 0, noopFunc, SQL_FUNC_COALESCE,
 			  FIELD_TYPE_SCALAR),
 	};
 	sql_register_analyze_builtins();
diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c
index 83dff47b6..853d42b58 100644
--- a/src/box/sql/resolve.c
+++ b/src/box/sql/resolve.c
@@ -591,8 +591,6 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 	case TK_FUNCTION:{
 			ExprList *pList = pExpr->x.pList;	/* The argument list */
 			int n = pList ? pList->nExpr : 0;	/* Number of arguments */
-			int no_such_func = 0;	/* True if no such function exists */
-			int wrong_num_args = 0;	/* True if wrong number of arguments */
 			int is_agg = 0;	/* True if is an aggregate function */
 			int nId;	/* Number of characters in function name */
 			const char *zId;	/* The function name. */
@@ -606,10 +604,17 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 				pDef =
 				    sqlFindFunction(pParse->db, zId, -2,0);
 				if (pDef == 0) {
-					no_such_func = 1;
+					diag_set(ClientError,
+						 ER_NO_SUCH_FUNCTION, zId);
 				} else {
-					wrong_num_args = 1;
+					uint32_t argc = pDef->nArg;
+					const char *err = tt_sprintf("%d", argc);
+					diag_set(ClientError,
+						 ER_FUNC_WRONG_ARG_COUNT,
+						 pDef->zName, err, n);
 				}
+				pParse->is_aborted = true;
+				pNC->nErr++;
 			} else {
 				is_agg = pDef->xFinalize != 0;
 				pExpr->type = pDef->ret_type;
@@ -671,17 +676,6 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
 				pParse->is_aborted = true;
 				pNC->nErr++;
 				is_agg = 0;
-			} else if (no_such_func && pParse->db->init.busy == 0) {
-				diag_set(ClientError, ER_NO_SUCH_FUNCTION, zId);
-				pParse->is_aborted = true;
-				pNC->nErr++;
-			} else if (wrong_num_args) {
-				const char *err = "wrong number of arguments "\
-						  "to function %.*s()";
-				diag_set(ClientError, ER_SQL_PARSER_GENERIC,
-					 tt_sprintf(err, nId, zId));
-				pParse->is_aborted = true;
-				pNC->nErr++;
 			}
 			if (is_agg)
 				pNC->ncFlags &= ~NC_AllowAgg;
diff --git a/test/box/misc.result b/test/box/misc.result
index 287d84e5b..e4de2e9b3 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -530,6 +530,7 @@ t;
   198: box.error.FUNC_INDEX_FUNC
   199: box.error.FUNC_INDEX_FORMAT
   200: box.error.FUNC_INDEX_PARTS
+  201: box.error.FUNC_WRONG_ARG_COUNT
 ...
 test_run:cmd("setopt delimiter ''");
 ---
diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua
index ec06c903d..a96fc611e 100755
--- a/test/sql-tap/func.test.lua
+++ b/test/sql-tap/func.test.lua
@@ -69,7 +69,7 @@ test:do_catchsql_test(
         SELECT length(*) FROM tbl1 ORDER BY t1
     ]], {
         -- <func-1.1>
-        1, "wrong number of arguments to function LENGTH()"
+        1, "Wrong number of arguments is passed to LENGTH(): expected 1, got 0"
         -- </func-1.1>
     })
 
@@ -79,7 +79,7 @@ test:do_catchsql_test(
         SELECT length(t1,5) FROM tbl1 ORDER BY t1
     ]], {
         -- <func-1.2>
-        1, "wrong number of arguments to function LENGTH()"
+        1, "Wrong number of arguments is passed to LENGTH(): expected 1, got 2"
         -- </func-1.2>
     })
 
@@ -365,7 +365,7 @@ test:do_test(
         return test:catchsql("SELECT abs(a,b) FROM t1")
     end, {
         -- <func-4.1>
-        1, "wrong number of arguments to function ABS()"
+        1, "Wrong number of arguments is passed to ABS(): expected 1, got 2"
         -- </func-4.1>
     })
 
@@ -377,7 +377,7 @@ test:do_catchsql_test(
         SELECT abs() FROM t1
     ]], {
         -- <func-4.2>
-        1, "wrong number of arguments to function ABS()"
+        1, "Wrong number of arguments is passed to ABS(): expected 1, got 0"
         -- </func-4.2>
     })
 
@@ -428,7 +428,7 @@ test:do_catchsql_test(
         SELECT round(a,b,c) FROM t1
     ]], {
         -- <func-4.5>
-        1, "wrong number of arguments to function ROUND()"
+        1, "Wrong number of arguments is passed to ROUND(): expected 1 or 2, got 3"
         -- </func-4.5>
     })
 
@@ -488,7 +488,7 @@ test:do_catchsql_test(
         SELECT round() FROM t1 ORDER BY a
     ]], {
         -- <func-4.11>
-        1, "wrong number of arguments to function ROUND()"
+        1, "Wrong number of arguments is passed to ROUND(): expected 1 or 2, got 0"
         -- </func-4.11>
     })
 
@@ -778,7 +778,7 @@ test:do_catchsql_test(
         SELECT upper(*) FROM t2
     ]], {
         -- <func-5.5>
-        1, "wrong number of arguments to function UPPER()"
+        1, "Wrong number of arguments is passed to UPPER(): expected 1, got 0"
         -- </func-5.5>
     })
 
@@ -1782,7 +1782,7 @@ test:do_catchsql_test(
         SELECT replace(1,2);
     ]], {
         -- <func-21.1>
-        1, "wrong number of arguments to function REPLACE()"
+        1, "Wrong number of arguments is passed to REPLACE(): expected 3, got 2"
         -- </func-21.1>
     })
 
@@ -1792,7 +1792,7 @@ test:do_catchsql_test(
         SELECT replace(1,2,3,4);
     ]], {
         -- <func-21.2>
-        1, "wrong number of arguments to function REPLACE()"
+        1, "Wrong number of arguments is passed to REPLACE(): expected 3, got 4"
         -- </func-21.2>
     })
 
@@ -2540,7 +2540,7 @@ test:do_catchsql_test(
         SELECT coalesce()
     ]], {
         -- <func-27.1>
-        1, "wrong number of arguments to function COALESCE()"
+        1, "Wrong number of arguments is passed to COALESCE(): expected at least two, got 0"
         -- </func-27.1>
     })
 
@@ -2550,7 +2550,7 @@ test:do_catchsql_test(
         SELECT coalesce(1)
     ]], {
         -- <func-27.2>
-        1, "wrong number of arguments to function COALESCE()"
+        1, "Wrong number of arguments is passed to COALESCE(): expected at least two, got 1"
         -- </func-27.2>
     })
 
diff --git a/test/sql-tap/func2.test.lua b/test/sql-tap/func2.test.lua
index b70197d09..c9bd3665f 100755
--- a/test/sql-tap/func2.test.lua
+++ b/test/sql-tap/func2.test.lua
@@ -50,7 +50,7 @@ test:do_catchsql_test(
         SELECT SUBSTR()
     ]], {
         -- <func2-1.2.1>
-        1, "wrong number of arguments to function SUBSTR()"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0"
         -- </func2-1.2.1>
     })
 
@@ -60,7 +60,7 @@ test:do_catchsql_test(
         SELECT SUBSTR('Supercalifragilisticexpialidocious')
     ]], {
         -- <func2-1.2.2>
-        1, "wrong number of arguments to function SUBSTR()"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1"
         -- </func2-1.2.2>
     })
 
@@ -70,7 +70,7 @@ test:do_catchsql_test(
         SELECT SUBSTR('Supercalifragilisticexpialidocious', 1,1,1)
     ]], {
         -- <func2-1.2.3>
-        1, "wrong number of arguments to function SUBSTR()"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4"
         -- </func2-1.2.3>
     })
 
@@ -673,7 +673,7 @@ if ("ሴ" ~= "u1234")
             SELECT SUBSTR()
         ]], {
             -- <func2-2.1.2>
-            1, "wrong number of arguments to function SUBSTR()"
+            1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0"
             -- </func2-2.1.2>
         })
 
@@ -683,7 +683,7 @@ if ("ሴ" ~= "u1234")
             SELECT SUBSTR('hiሴho')
         ]], {
             -- <func2-2.1.3>
-            1, "wrong number of arguments to function SUBSTR()"
+            1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1"
             -- </func2-2.1.3>
         })
 
@@ -693,7 +693,7 @@ if ("ሴ" ~= "u1234")
             SELECT SUBSTR('hiሴho', 1,1,1)
         ]], {
             -- <func2-2.1.4>
-            1, "wrong number of arguments to function SUBSTR()"
+            1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4"
             -- </func2-2.1.4>
         })
 
@@ -1038,7 +1038,7 @@ test:do_catchsql_test(
         SELECT SUBSTR()
     ]], {
         -- <func2-3.1.2>
-        1, "wrong number of arguments to function SUBSTR()"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0"
         -- </func2-3.1.2>
     })
 
@@ -1048,7 +1048,7 @@ test:do_catchsql_test(
         SELECT SUBSTR(x'1234')
     ]], {
         -- <func2-3.1.3>
-        1, "wrong number of arguments to function SUBSTR()"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1"
         -- </func2-3.1.3>
     })
 
@@ -1058,7 +1058,7 @@ test:do_catchsql_test(
         SELECT SUBSTR(x'1234', 1,1,1)
     ]], {
         -- <func2-3.1.4>
-        1, "wrong number of arguments to function SUBSTR()"
+        1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4"
         -- </func2-3.1.4>
     })
 
diff --git a/test/sql-tap/limit.test.lua b/test/sql-tap/limit.test.lua
index 8445ab18e..870233942 100755
--- a/test/sql-tap/limit.test.lua
+++ b/test/sql-tap/limit.test.lua
@@ -771,7 +771,7 @@ test:do_catchsql_test(
         SELECT * FROM t1 LIMIT replace(1)
     ]], {
         -- <limit-12.1>
-        1, "wrong number of arguments to function REPLACE()"
+        1, "Wrong number of arguments is passed to REPLACE(): expected 3, got 1"
         -- </limit-12.1>
     })
 
@@ -781,7 +781,7 @@ test:do_catchsql_test(
         SELECT * FROM t1 LIMIT 5 OFFSET replace(1)
     ]], {
         -- <limit-12.2>
-        1, 'wrong number of arguments to function REPLACE()'
+        1, 'Wrong number of arguments is passed to REPLACE(): expected 3, got 1'
         -- </limit-12.2>
     })
 
diff --git a/test/sql-tap/select1.test.lua b/test/sql-tap/select1.test.lua
index 924c0ccb1..4bbfbd67b 100755
--- a/test/sql-tap/select1.test.lua
+++ b/test/sql-tap/select1.test.lua
@@ -244,7 +244,7 @@ test:do_catchsql_test(
         SELECT count(f1,f2) FROM test1
     ]], {
         -- <select1-2.1>
-        1, "wrong number of arguments to function COUNT()"
+        1, "Wrong number of arguments is passed to COUNT(): expected 0 or 1, got 2"
         -- </select1-2.1>
     })
 
@@ -324,7 +324,7 @@ test:do_catchsql_test(
         SELECT min(*) FROM test1
     ]], {
         -- <select1-2.6>
-        1, "wrong number of arguments to function MIN()"
+        1, "Wrong number of arguments is passed to MIN(): expected 1, got 0"
         -- </select1-2.6>
     })
 
@@ -389,7 +389,7 @@ test:do_catchsql_test(
         SELECT MAX(*) FROM test1
     ]], {
         -- <select1-2.9>
-        1, "wrong number of arguments to function MAX()"
+        1, "Wrong number of arguments is passed to MAX(): expected 1, got 0"
         -- </select1-2.9>
     })
 
@@ -469,7 +469,7 @@ test:do_catchsql_test(
         SELECT SUM(*) FROM test1
     ]], {
         -- <select1-2.14>
-        1, "wrong number of arguments to function SUM()"
+        1, "Wrong number of arguments is passed to SUM(): expected 1, got 0"
         -- </select1-2.14>
     })
 
@@ -489,7 +489,7 @@ test:do_catchsql_test(
         SELECT sum(f1,f2) FROM test1
     ]], {
         -- <select1-2.16>
-        1, "wrong number of arguments to function SUM()"
+        1, "Wrong number of arguments is passed to SUM(): expected 1, got 2"
         -- </select1-2.16>
     })
 
@@ -691,7 +691,7 @@ test:do_catchsql_test(
         SELECT f1 FROM test1 WHERE count(f1,f2)!=11
     ]], {
         -- <select1-3.9>
-        1, "wrong number of arguments to function COUNT()"
+        1, "misuse of aggregate function COUNT()"
         -- </select1-3.9>
     })
 
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>
     })
 
diff --git a/test/sql/icu-upper-lower.result b/test/sql/icu-upper-lower.result
index 88266c8c5..8ff7528f3 100644
--- a/test/sql/icu-upper-lower.result
+++ b/test/sql/icu-upper-lower.result
@@ -277,7 +277,7 @@ test_run:cmd("setopt delimiter ''");
 box.execute("select upper('1', 2)")
 ---
 - null
-- wrong number of arguments to function UPPER()
+- 'Wrong number of arguments is passed to UPPER(): expected 1, got 2'
 ...
 box.execute("select upper(\"1\")")
 ---
@@ -287,5 +287,5 @@ box.execute("select upper(\"1\")")
 box.execute("select upper()")
 ---
 - null
-- wrong number of arguments to function UPPER()
+- 'Wrong number of arguments is passed to UPPER(): expected 1, got 0'
 ...
-- 
2.22.1

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

* [tarantool-patches] Re: [PATCH v4 4/4] sql: support user-defined functions in SQL
  2019-08-22 15:23   ` [tarantool-patches] " n.pettik
@ 2019-08-23 15:02     ` Kirill Shcherbatov
  0 siblings, 0 replies; 17+ messages in thread
From: Kirill Shcherbatov @ 2019-08-23 15:02 UTC (permalink / raw)
  To: tarantool-patches, n.pettik

>> +/* Opcode: Function P1 P2 P3 P4 P5
> 
> Nit: P1 is unused. As a rule we spot unused arguments with stars (*).
> And I’d swap P1 and P5, i.e. pass range of registers in P1-P2,
> save result to P3
I've replaced P1 definition with a star sign. However, I don't like your second
proposal. The OP_Function has this interface to be basically-compatible with
legacy OP_BuiltinFunction opcode. Thus it is possible to care only about
opcode generation don't thinking about arguments order:

+                       int op = func->def->language ==
+                                FUNC_LANGUAGE_SQL_BUILTIN ?
+                                OP_BuiltinFunction0 : OP_Function;
                        sqlVdbeAddOp4(v, op, constMask, r1, target,

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

* [tarantool-patches] Re: [PATCH v4 1/4] sql: rename sql_vdbe_mem_alloc_region helper
  2019-08-22 13:04   ` [tarantool-patches] " n.pettik
@ 2019-08-23 15:02     ` Kirill Shcherbatov
  0 siblings, 0 replies; 17+ messages in thread
From: Kirill Shcherbatov @ 2019-08-23 15:02 UTC (permalink / raw)
  To: tarantool-patches, n.pettik

> You can also remove ’sql’ prefix (vdbe_ prefix implies that
> namespace is related to sql) and make it static.
Ok, done.

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

* [tarantool-patches] Re: [PATCH v4 3/4] sql: get rid of FuncDef function hash
  2019-08-22 14:37   ` [tarantool-patches] " n.pettik
  2019-08-23 15:02     ` [tarantool-patches] [PATCH v4 4/5] " Kirill Shcherbatov
  2019-08-23 15:02     ` [tarantool-patches] [PATCH v4 3/5] sql: remove name overloading for SQL builtins Kirill Shcherbatov
@ 2019-08-23 15:02     ` Kirill Shcherbatov
  2 siblings, 0 replies; 17+ messages in thread
From: Kirill Shcherbatov @ 2019-08-23 15:02 UTC (permalink / raw)
  To: tarantool-patches, n.pettik

>> +#define SQL_FUNC_MAX   0x2000
> 
> Nit: extra diff.
Fixed.
>> +	/**
>> +	 * A VDBE-memory-compatible call method.
>> +	 * SQL built-ins don't use func base class "call"
>> +	 * method to provide a best performance for SQL requests.
>> +	 * Access checks are redundant, because all SQL built-ins
>> +	 * are predefined and are executed on SQL privilege level.
> 
> Which doesn’t exist yet… I asked you to document or fix it.
> Comment in source code is OK, but it should be present in
> documentation as well.
A new DocBot request in the commit message:

    @TarantoolBot document
    Title: SQL builtins priveleges
    
    All SQL built-ins are executed on SQL privilege level that is
    undefined yet.

>> +						 ">= 2", nFarg);
> 
> -> “more than one”/“at least two”
> 
> What is more, you can move introduction of ER_FUNC_WRONG_…
> to a separate auxiliary patch.
> Nit: “at least one”
Done. As an auxiliary patch.


> Nit: personally I’d not skip members and fill in them allActually I don't like it, but I don't mind so I've implemented it.

>> +	/* Some builtins are not implemented yet. */
> 
> Please, left comment describing why we really do need this check.
> I mean the fact that it disallows user to create random built-in functions.
	/*
	 * All SQL built-in(s) (stubs) are defined in a snapshot.
	 * Implementation-specific metadata is defined in
	 * sql_builtins list. When a definition were not found
	 * above, the function name is invalid, i.e. it is
	 * not built-in function.
	 */
		    "in SQL", nId, zId));
>> 				} else {
> 
> Let’s avoid call of sql_func_by_signature(). Consider refactoring:
> etc
Okey, implemented.

> Didn’t forget check that user can’t create manually built-ins?
No, I didn't. There is corresponding test in the following commit.

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

* [tarantool-patches] Re: [PATCH v4 3/5] sql: remove name overloading for SQL builtins
  2019-08-23 15:02     ` [tarantool-patches] [PATCH v4 3/5] sql: remove name overloading for SQL builtins Kirill Shcherbatov
@ 2019-08-28 15:05       ` Nikita Pettik
  0 siblings, 0 replies; 17+ messages in thread
From: Nikita Pettik @ 2019-08-28 15:05 UTC (permalink / raw)
  To: Kirill Shcherbatov; +Cc: tarantool-patches

On Fri, Aug 23, 2019 at 06:02:12PM +0300, Kirill Shcherbatov wrote:
> Now all SQL builtins are unique, i.e. each function has the only
> hash table entry. This technique requires to remove static argc
> checks for some builtins(substr, round, like, trim, count,
> group_concat). Now they raise a runtime error in case of invalid
> usage.

LGTM.

PS Note that I've sent patch which fixes an assertion fault connected
with wrong number of arguments passed to GREATEST/LEAST functions:
https://www.freelists.org/post/tarantool-patches/PATCH-sql-make-GREATESTLEAST-builtins-accept-at-least-two-args

You can cherry-pick it on your branch, in case it doesn't get
to the master earlier.

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

* [tarantool-patches] Re: [PATCH v4 0/4] sql: uniform SQL and Lua functions subsystem
  2019-08-21 15:28 [tarantool-patches] [PATCH v4 0/4] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
                   ` (3 preceding siblings ...)
  2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 4/4] sql: support user-defined functions in SQL Kirill Shcherbatov
@ 2019-08-29 15:09 ` Nikita Pettik
  2019-08-29 17:12 ` Kirill Yukhin
  5 siblings, 0 replies; 17+ messages in thread
From: Nikita Pettik @ 2019-08-29 15:09 UTC (permalink / raw)
  To: Kirill Shcherbatov; +Cc: tarantool-patches

Thanks, now the whole patch-set looks good to me.

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

* [tarantool-patches] Re: [PATCH v4 0/4] sql: uniform SQL and Lua functions subsystem
  2019-08-21 15:28 [tarantool-patches] [PATCH v4 0/4] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
                   ` (4 preceding siblings ...)
  2019-08-29 15:09 ` [tarantool-patches] Re: [PATCH v4 0/4] sql: uniform SQL and Lua functions subsystem Nikita Pettik
@ 2019-08-29 17:12 ` Kirill Yukhin
  5 siblings, 0 replies; 17+ messages in thread
From: Kirill Yukhin @ 2019-08-29 17:12 UTC (permalink / raw)
  To: tarantool-patches; +Cc: korablev, Kirill Shcherbatov

Hello,

On 21 авг 18:28, Kirill Shcherbatov wrote:
> Branch: http://github.com/tarantool/tarantool/tree/kshch/gh-4182-persistent-functions
> Issue: https://github.com/tarantool/tarantool/issues/2233
> 
> Kirill Shcherbatov (4):
>   sql: rename sql_vdbe_mem_alloc_region helper
>   sql: replace flag MINMAX with flags MIN and MAX
>   sql: get rid of FuncDef function hash
>   sql: support user-defined functions in SQL

I've checked your patchset into master.

--
Regards, Kirill Yukhin

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

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

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-08-21 15:28 [tarantool-patches] [PATCH v4 0/4] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 1/4] sql: rename sql_vdbe_mem_alloc_region helper Kirill Shcherbatov
2019-08-22 13:04   ` [tarantool-patches] " n.pettik
2019-08-23 15:02     ` Kirill Shcherbatov
2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 2/4] sql: replace flag MINMAX with flags MIN and MAX Kirill Shcherbatov
2019-08-22 13:30   ` [tarantool-patches] " n.pettik
2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 3/4] sql: get rid of FuncDef function hash Kirill Shcherbatov
2019-08-22 14:37   ` [tarantool-patches] " n.pettik
2019-08-23 15:02     ` [tarantool-patches] [PATCH v4 4/5] " Kirill Shcherbatov
2019-08-23 15:02     ` [tarantool-patches] [PATCH v4 3/5] sql: remove name overloading for SQL builtins Kirill Shcherbatov
2019-08-28 15:05       ` [tarantool-patches] " Nikita Pettik
2019-08-23 15:02     ` [tarantool-patches] Re: [PATCH v4 3/4] sql: get rid of FuncDef function hash Kirill Shcherbatov
2019-08-21 15:28 ` [tarantool-patches] [PATCH v4 4/4] sql: support user-defined functions in SQL Kirill Shcherbatov
2019-08-22 15:23   ` [tarantool-patches] " n.pettik
2019-08-23 15:02     ` Kirill Shcherbatov
2019-08-29 15:09 ` [tarantool-patches] Re: [PATCH v4 0/4] sql: uniform SQL and Lua functions subsystem Nikita Pettik
2019-08-29 17:12 ` Kirill Yukhin

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