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