[tarantool-patches] [PATCH v2 7/8] sql: get rid of FuncDef function hash

Kirill Shcherbatov kshcherbatov at tarantool.org
Thu Aug 8 17:50:51 MSK 2019


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

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

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

Part of #2200, #4113, #2233
---
 src/box/lua/lua_sql.h        |  39 -----
 src/box/sql.h                |   5 +
 src/box/sql/sqlInt.h         | 248 +++++++---------------------
 src/box/sql/vdbe.h           |   9 +-
 src/box/sql/vdbeInt.h        |  23 ++-
 src/box/func.c               |  34 +---
 src/box/lua/call.c           |   2 -
 src/box/lua/lua_sql.c        | 205 -----------------------
 src/box/sql.c                |  23 +++
 src/box/sql/analyze.c        |  35 ++--
 src/box/sql/callback.c       | 191 ----------------------
 src/box/sql/date.c           |  28 ----
 src/box/sql/expr.c           |  64 ++++----
 src/box/sql/func.c           | 309 ++++++++++++++++++++++++-----------
 src/box/sql/global.c         |   7 -
 src/box/sql/main.c           | 137 ----------------
 src/box/sql/resolve.c        |  50 +++---
 src/box/sql/select.c         |   6 +-
 src/box/sql/vdbe.c           |  20 ++-
 src/box/sql/vdbeapi.c        |  11 +-
 src/box/sql/vdbeaux.c        |  33 +---
 src/box/sql/vdbemem.c        |  50 +++---
 src/box/sql/whereexpr.c      |   2 +-
 src/box/CMakeLists.txt       |   1 -
 test/sql-tap/where2.test.lua |   4 +-
 25 files changed, 456 insertions(+), 1080 deletions(-)
 delete mode 100644 src/box/lua/lua_sql.h
 delete mode 100644 src/box/lua/lua_sql.c

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





More information about the Tarantool-patches mailing list