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 73A1D274CC for ; Wed, 21 Aug 2019 11:28:14 -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 3FToC85repbd for ; Wed, 21 Aug 2019 11:28:14 -0400 (EDT) Received: from smtp54.i.mail.ru (smtp54.i.mail.ru [217.69.128.34]) (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 5AB7427514 for ; Wed, 21 Aug 2019 11:28:13 -0400 (EDT) From: Kirill Shcherbatov Subject: [tarantool-patches] [PATCH v4 3/4] sql: get rid of FuncDef function hash Date: Wed, 21 Aug 2019 18:28:08 +0300 Message-Id: <154ee8ea11ae260254b71d32e1299f1272438af1.1566400979.git.kshcherbatov@tarantool.org> In-Reply-To: References: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit 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: Kirill Shcherbatov Now it is possible to move all SQL builtin functions to Tarantool's function hash. An existent FuncDef function representation was replaced with func_sql_builtin class. It has a sql-specific method :call and :finalize, while port API call is not supported and protected with stubs. This patch removes FuncDef hash and sql_function_create endpoint, but doesn't introduce something instead. Therefore few affected tests are disabled. A required functionality would be fixed in the next patch. Following tests using sql_create_function are broken now. They are going to be fixed in the next commit: sql-tap/alias.test.lua sql-tap/check.test.lua sql-tap/func5.test.lua sql-tap/lua_sql.test.lua sql-tap/subquery.test.lua sql-tap/trigger9.test.lua sql/errinj.result sql/errinj.test.lua sql/func-recreate.test.lua Part of #2200, #4113, #2233 --- src/box/errcode.h | 1 + src/box/lua/lua_sql.h | 39 -- src/box/sql.h | 1 + src/box/sql/sqlInt.h | 222 +++------- src/box/sql/vdbe.h | 9 +- src/box/sql/vdbeInt.h | 23 +- src/box/func.c | 33 +- src/box/lua/call.c | 2 - src/box/lua/lua_sql.c | 205 --------- src/box/sql/analyze.c | 34 +- src/box/sql/callback.c | 204 --------- src/box/sql/date.c | 28 -- src/box/sql/expr.c | 83 ++-- src/box/sql/func.c | 760 +++++++++++++++++++++++++++----- src/box/sql/global.c | 7 - src/box/sql/main.c | 137 ------ src/box/sql/resolve.c | 71 +-- src/box/sql/select.c | 10 +- src/box/sql/vdbe.c | 20 +- src/box/sql/vdbeapi.c | 17 +- src/box/sql/vdbeaux.c | 31 +- src/box/sql/vdbemem.c | 65 ++- src/box/sql/whereexpr.c | 2 +- src/box/CMakeLists.txt | 1 - src/box/alter.cc | 6 + test/box/function1.result | 20 + test/box/function1.test.lua | 7 + test/box/misc.result | 1 + test/sql-tap/func.test.lua | 22 +- test/sql-tap/func2.test.lua | 18 +- test/sql-tap/limit.test.lua | 4 +- test/sql-tap/select1.test.lua | 12 +- test/sql-tap/where2.test.lua | 4 +- test/sql/icu-upper-lower.result | 4 +- 34 files changed, 952 insertions(+), 1151 deletions(-) delete mode 100644 src/box/lua/lua_sql.h delete mode 100644 src/box/lua/lua_sql.c diff --git a/src/box/errcode.h b/src/box/errcode.h index 817275b97..2c19046fc 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -253,6 +253,7 @@ struct errcode_record { /*198 */_(ER_FUNC_INDEX_FUNC, "Failed to build a key for functional index '%s' of space '%s': %s") \ /*199 */_(ER_FUNC_INDEX_FORMAT, "Key format doesn't match one defined in functional index '%s' of space '%s': %s") \ /*200 */_(ER_FUNC_INDEX_PARTS, "Wrong functional index definition: %s") \ + /*200 */_(ER_FUNC_WRONG_ARG_COUNT, "Wrong number of arguments is passed to %s(): expected %s, got %d") /* * !IMPORTANT! Please follow instructions at start of the file diff --git a/src/box/lua/lua_sql.h b/src/box/lua/lua_sql.h deleted file mode 100644 index b81093eca..000000000 --- a/src/box/lua/lua_sql.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * 1. Redistributions of source code must retain the above - * copyright notice, this list of conditions and the - * following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY ``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 7644051b4..b29f5aab5 100644 --- a/src/box/sql.h +++ b/src/box/sql.h @@ -68,6 +68,7 @@ struct Expr; struct Parse; struct Select; struct Table; +struct func_def; struct sql_trigger; struct space_def; diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h index d71fd06d7..d08321408 100644 --- a/src/box/sql/sqlInt.h +++ b/src/box/sql/sqlInt.h @@ -70,6 +70,8 @@ #include "box/column_mask.h" #include "parse_def.h" #include "box/field_def.h" +#include "box/func.h" +#include "box/func_def.h" #include "box/sql.h" #include "box/txn.h" #include "trivia/util.h" @@ -541,9 +543,6 @@ void sql_row_count(struct sql_context *context, MAYBE_UNUSED int unused1, MAYBE_UNUSED sql_value **unused2); -void * -sql_user_data(sql_context *); - void * sql_aggregate_context(sql_context *, int nBytes); @@ -566,26 +565,6 @@ sql_initialize(void); #define SQL_TRACE_ROW 0x04 #define SQL_TRACE_CLOSE 0x08 -#define SQL_DETERMINISTIC 0x800 - -int -sql_create_function_v2(sql * db, - const char *zFunctionName, - enum field_type type, - int nArg, - int flags, - void *pApp, - void (*xFunc) (sql_context *, - int, - sql_value **), - void (*xStep) (sql_context *, - int, - sql_value **), - void (*xFinal) - (sql_context *), - void (*xDestroy) (void *) - ); - #define SQL_OPEN_READONLY 0x00000001 /* Ok for sql_open_v2() */ #define SQL_OPEN_READWRITE 0x00000002 /* Ok for sql_open_v2() */ #define SQL_OPEN_CREATE 0x00000004 /* Ok for sql_open_v2() */ @@ -1039,9 +1018,6 @@ typedef struct Column Column; typedef struct Expr Expr; typedef struct ExprList ExprList; typedef struct ExprSpan ExprSpan; -typedef struct FuncDestructor FuncDestructor; -typedef struct FuncDef FuncDef; -typedef struct FuncDefHash FuncDefHash; typedef struct IdList IdList; typedef struct KeyClass KeyClass; typedef struct Lookaside Lookaside; @@ -1123,18 +1099,6 @@ struct LookasideSlot { LookasideSlot *pNext; /* Next buffer in the list of free buffers */ }; -/* - * A hash table for built-in function definitions. (Application-defined - * functions use a regular table table from hash.h.) - * - * Hash each FuncDef structure into one of the FuncDefHash.a[] slots. - * Collisions are on the FuncDef.u.pHash chain. - */ -#define SQL_FUNC_HASH_SZ 23 -struct FuncDefHash { - FuncDef *a[SQL_FUNC_HASH_SZ]; /* Hash table for functions */ -}; - /* * Each database connection is an instance of the following structure. */ @@ -1244,65 +1208,12 @@ struct type_def { }; /* - * Each SQL function is defined by an instance of the following - * structure. For global built-in functions (ex: substr(), max(), count()) - * a pointer to this structure is held in the sqlBuiltinFunctions object. - * For per-connection application-defined functions, a pointer to this - * structure is held in the db->aHash hash table. - * - * The u.pHash field is used by the global built-ins. The u.pDestructor - * field is used by per-connection app-def functions. - */ -struct FuncDef { - i8 nArg; /* Number of arguments. -1 means unlimited */ - u16 funcFlags; /* Some combination of sql_FUNC_* */ - void *pUserData; /* User data parameter */ - FuncDef *pNext; /* Next function with same name */ - void (*xSFunc) (sql_context *, int, sql_value **); /* func or agg-step */ - void (*xFinalize) (sql_context *); /* Agg finalizer */ - const char *zName; /* SQL name of the function. */ - union { - FuncDef *pHash; /* Next with a different name but the same hash */ - FuncDestructor *pDestructor; /* Reference counted destructor function */ - } u; - /* Return type. */ - enum field_type ret_type; -}; - -/* - * This structure encapsulates a user-function destructor callback (as - * configured using create_function_v2()) and a reference counter. When - * create_function_v2() is called to create a function with a destructor, - * a single object of this type is allocated. FuncDestructor.nRef is set to - * the number of FuncDef objects created (either 1 or 3, depending on whether - * or not the specified encoding is sql_ANY). The FuncDef.pDestructor - * member of each of the new FuncDef objects is set to point to the allocated - * FuncDestructor. - * - * Thereafter, when one of the FuncDef objects is deleted, the reference - * count on this object is decremented. When it reaches 0, the destructor - * is invoked and the FuncDestructor structure freed. - */ -struct FuncDestructor { - int nRef; - void (*xDestroy) (void *); - void *pUserData; -}; - -/* - * Possible values for FuncDef.flags. Note that the _LENGTH and _TYPEOF - * values must correspond to OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG. And - * sql_FUNC_CONSTANT must be the same as sql_DETERMINISTIC. There - * are assert() statements in the code to verify this. - * * Value constraints (enforced via assert()): * NC_MinMaxAgg == SF_MinMaxAgg * SQL_FUNC_LENGTH == OPFLAG_LENGTHARG * SQL_FUNC_TYPEOF == OPFLAG_TYPEOFARG - * SQL_FUNC_CONSTANT == sql_DETERMINISTIC from the API */ #define SQL_FUNC_LIKE 0x0004 /* Candidate for the LIKE optimization */ -#define SQL_FUNC_EPHEM 0x0010 /* Ephemeral. Delete with VDBE */ #define SQL_FUNC_NEEDCOLL 0x0020 /* sqlGetFuncCollSeq() might be called. * The flag is set when the collation * of function arguments should be @@ -1316,11 +1227,10 @@ struct FuncDestructor { #define SQL_FUNC_COUNT 0x0100 #define SQL_FUNC_COALESCE 0x0200 /* Built-in coalesce() or ifnull() */ #define SQL_FUNC_UNLIKELY 0x0400 /* Built-in unlikely() function */ -#define SQL_FUNC_CONSTANT 0x0800 /* Constant inputs give a constant output */ /** Built-in min() or least() function. */ #define SQL_FUNC_MIN 0x1000 /** Built-in max() or greatest() function. */ -#define SQL_FUNC_MAX 0x2000 +#define SQL_FUNC_MAX 0x2000 /** * If function returns string, it may require collation to be * applied on its result. For instance, result of substr() @@ -1340,61 +1250,6 @@ enum trim_side_mask { TRIM_BOTH = TRIM_LEADING | TRIM_TRAILING }; -/* - * The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are - * used to create the initializers for the FuncDef structures. - * - * FUNCTION(zName, nArg, iArg, bNC, xFunc) - * Used to create a scalar function definition of a function zName - * implemented by C function xFunc that accepts nArg arguments. The - * value passed as iArg is cast to a (void*) and made available - * as the user-data (sql_user_data()) for the function. If - * argument bNC is true, then the sql_FUNC_NEEDCOLL flag is set. - * - * FUNCTION_COLL - * Like FUNCTION except it assumes that function returns - * STRING which collation should be derived from first - * argument (trim, substr etc). - * - * VFUNCTION(zName, nArg, iArg, bNC, xFunc) - * Like FUNCTION except it omits the sql_FUNC_CONSTANT flag. - * - * AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal) - * Used to create an aggregate function definition implemented by - * the C functions xStep and xFinal. The first four parameters - * are interpreted in the same way as the first 4 parameters to - * FUNCTION(). - * - * LIKEFUNC(zName, nArg, pArg, flags) - * Used to create a scalar function definition of a function zName - * that accepts nArg arguments and is implemented by a call to C - * function likeFunc. Argument pArg is cast to a (void *) and made - * available as the function user-data (sql_user_data()). The - * FuncDef.flags variable is set to the value passed as the flags - * parameter. - */ -#define FUNCTION(zName, nArg, iArg, bNC, xFunc, type) \ - {nArg, SQL_FUNC_CONSTANT|(bNC*SQL_FUNC_NEEDCOLL), \ - SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type} -#define FUNCTION_COLL(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQL_FUNC_CONSTANT|SQL_FUNC_DERIVEDCOLL|(bNC*SQL_FUNC_NEEDCOLL), \ - SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, FIELD_TYPE_STRING} -#define VFUNCTION(zName, nArg, iArg, bNC, xFunc, type) \ - {nArg, (bNC*SQL_FUNC_NEEDCOLL), \ - SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type} -#define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags, type) \ - {nArg,SQL_FUNC_CONSTANT|(bNC*SQL_FUNC_NEEDCOLL)|extraFlags,\ - SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type} -#define LIKEFUNC(zName, nArg, arg, flags, type) \ - {nArg, SQL_FUNC_NEEDCOLL|SQL_FUNC_CONSTANT|flags, \ - (void *)(SQL_INT_TO_PTR(arg)), 0, likeFunc, 0, #zName, {0}, type} -#define AGGREGATE(zName, nArg, arg, nc, xStep, xFinal, type) \ - {nArg, (nc*SQL_FUNC_NEEDCOLL), \ - SQL_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}, type} -#define AGGREGATE2(zName, nArg, arg, nc, xStep, xFinal, extraFlags, type) \ - {nArg, (nc*SQL_FUNC_NEEDCOLL)|extraFlags, \ - SQL_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}, type} - /* * All current savepoints are stored in a linked list starting at * sql.pSavepoint. The first element in the list is the most recently @@ -1587,7 +1442,8 @@ struct AggInfo { */ struct AggInfo_func { /* For each aggregate function */ Expr *pExpr; /* Expression encoding the function */ - FuncDef *pFunc; /* The aggregate function implementation */ + /** The aggregate function implementation. */ + struct func *func; int iMem; /* Memory location that acts as accumulator */ int iDistinct; /* Ephemeral table used to enforce DISTINCT */ /** @@ -3535,10 +3391,6 @@ void sqlSelectSetName(Select *, const char *); #else #define sqlSelectSetName(A,B) #endif -void sqlInsertBuiltinFuncs(FuncDef *, int); -FuncDef *sqlFindFunction(sql *, const char *, int, u8); -void sqlRegisterBuiltinFunctions(void); -void sqlRegisterDateTimeFunctions(void); /** * Evaluate a view and store its result in an ephemeral table. @@ -4111,7 +3963,6 @@ extern const unsigned char sqlUpperToLower[]; extern const unsigned char sqlCtypeMap[]; extern const Token sqlIntTokens[]; extern SQL_WSD struct sqlConfig sqlConfig; -extern FuncDefHash sqlBuiltinFunctions; extern int sqlPendingByte; /** @@ -4247,21 +4098,13 @@ sql_key_info_to_key_def(struct sql_key_info *key_info); * Check if the function implements LIKE-style comparison & if it * is appropriate to apply a LIKE query optimization. * - * @param db database structure. * @param pExpr pointer to a function-implementing expression. * @param[out] is_like_ci true if LIKE is case insensitive. * * @retval 1 if LIKE optimization can be used, 0 otherwise. */ int -sql_is_like_func(struct sql *db, struct Expr *expr); - -int sqlCreateFunc(sql *, const char *, enum field_type, - int, int, void *, - void (*)(sql_context *, int, sql_value **), - void (*)(sql_context *, int, sql_value **), - void (*)(sql_context *), - FuncDestructor * pDestructor); +sql_is_like_func(struct Expr *expr); /** Set OOM error flag. */ static inline void @@ -4298,7 +4141,6 @@ sql_expr_new_column(struct sql *db, struct SrcList *src_list, int src_idx, int sqlExprCheckIN(Parse *, Expr *); -void sqlAnalyzeFunctions(void); int sqlStat4ProbeSetValue(Parse *, struct index_def *, UnpackedRecord **, Expr *, int, int, int *); int sqlStat4ValueFromExpr(Parse *, Expr *, enum field_type type, @@ -4489,9 +4331,55 @@ Expr *sqlExprForVectorField(Parse *, Expr *, int); */ extern int sqlSubProgramsRemaining; -/** Register built-in functions to work with ANALYZE data. */ -void -sql_register_analyze_builtins(void); +struct func_sql_builtin { + /** Function object base class. */ + struct func base; + /** A bitmask of SQL flags. */ + uint16_t flags; + /** + * A VDBE-memory-compatible call method. + * SQL built-ins don't use func base class "call" + * method to provide a best performance for SQL requests. + * Access checks are redundant, because all SQL built-ins + * are predefined and are executed on SQL privilege level. + */ + void (*call)(sql_context *ctx, int argc, sql_value **argv); + /** + * A VDBE-memory-compatible finalize method + * (is valid only for aggregate function). + */ + void (*finalize)(sql_context *ctx); +}; + +/** + * Test whether SQL-specific flag is set for given function. + * Currently only SQL Builtin Functions have such hint flags, + * so function returns false for other functions. Such approach + * decreases code complexity and allows do not distinguish + * functions by implementation details where it is unnecessary. + * + * Returns true when given flag is set for a given function and + * false otherwise. + */ +static inline bool +sql_func_flag_is_set(struct func *func, uint16_t flag) +{ + if (func->def->language != FUNC_LANGUAGE_SQL_BUILTIN) + return false; + return (((struct func_sql_builtin *)func)->flags & flag) != 0; +} + +/** + * A SQL method to find a function in a hash by its name and + * count of arguments. Only functions that have 'SQL' engine + * export field set true and have exactly the same signature + * are returned. + * + * Returns not NULL function pointer when a valid and exported + * to SQL engine function is found and NULL otherwise. + */ +struct func * +sql_func_by_signature(const char *name, int argc); /** * Generate VDBE code to halt execution with correct error if diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h index e5a85fe0e..f8dbd0fa3 100644 --- a/src/box/sql/vdbe.h +++ b/src/box/sql/vdbe.h @@ -72,7 +72,11 @@ struct VdbeOp { char *z; /* Pointer to data for string (char array) types */ i64 *pI64; /* Used when p4type is P4_INT64/UINT64 */ double *pReal; /* Used when p4type is P4_REAL */ - FuncDef *pFunc; /* Used when p4type is P4_FUNCDEF */ + /** + * A pointer to function implementation. + * Used when p4type is P4_FUNC. + */ + struct func *func; sql_context *pCtx; /* Used when p4type is P4_FUNCCTX */ struct coll *pColl; /* Used when p4type is P4_COLLSEQ */ Mem *pMem; /* Used when p4type is P4_MEM */ @@ -122,7 +126,8 @@ struct SubProgram { #define P4_DYNAMIC (-1) /* Pointer to a string obtained from sqlMalloc() */ #define P4_STATIC (-2) /* Pointer to a static string */ #define P4_COLLSEQ (-3) /* P4 is a pointer to a CollSeq structure */ -#define P4_FUNCDEF (-4) /* P4 is a pointer to a FuncDef structure */ +/** P4 is a pointer to a func structure. */ +#define P4_FUNC (-4) #define P4_MEM (-7) /* P4 is a pointer to a Mem* structure */ #define P4_TRANSIENT 0 /* P4 is a pointer to a transient string */ #define P4_REAL (-9) /* P4 is a 64-bit floating point value */ diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h index f77c019fb..af55f4cdc 100644 --- a/src/box/sql/vdbeInt.h +++ b/src/box/sql/vdbeInt.h @@ -46,6 +46,8 @@ */ typedef struct VdbeOp Op; +struct func; + /* * Boolean values */ @@ -168,7 +170,11 @@ struct Mem { bool b; /* Boolean value used when MEM_Bool is set in flags */ int nZero; /* Used when bit MEM_Zero is set in flags */ void *p; /* Generic pointer */ - FuncDef *pDef; /* Used only when flags==MEM_Agg */ + /** + * A pointer to function implementation. + * Used only when flags==MEM_Agg. + */ + struct func *func; VdbeFrame *pFrame; /* Used when flags==MEM_Frame */ } u; u32 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */ @@ -309,7 +315,8 @@ mem_apply_numeric_type(struct Mem *record); */ struct sql_context { Mem *pOut; /* The return value is stored here */ - FuncDef *pFunc; /* Pointer to function information */ + /* A pointer to function implementation. */ + struct func *func; Mem *pMem; /* Memory cell used to store aggregate context */ Vdbe *pVdbe; /* The VM that owns this context */ /** Instruction number of OP_BuiltinFunction0. */ @@ -516,7 +523,17 @@ int sqlVdbeMemNumerify(Mem *); int sqlVdbeMemCast(Mem *, enum field_type type); int sqlVdbeMemFromBtree(BtCursor *, u32, u32, Mem *); void sqlVdbeMemRelease(Mem * p); -int sqlVdbeMemFinalize(Mem *, FuncDef *); + +/** + * Memory cell mem contains the context of an aggregate function. + * This routine calls the finalize method for that function. The + * result of the aggregate is stored back into mem. + * + * Returns -1 if the finalizer reports an error. 0 otherwise. + */ +int +sql_vdbemem_finalize(struct Mem *mem, struct func *func); + const char *sqlOpcodeName(int); int sqlVdbeMemGrow(Mem * pMem, int n, int preserve); int sqlVdbeMemClearAndResize(Mem * pMem, int n); diff --git a/src/box/func.c b/src/box/func.c index b35d05dca..e8cc99081 100644 --- a/src/box/func.c +++ b/src/box/func.c @@ -381,38 +381,9 @@ restore: static struct func * func_c_new(struct func_def *def); -/** A stub object for SQL builtins to avoid name clash with UDF. */ -static struct func_vtab func_sql_builtin_vtab; - /** Construct a SQL builtin function object. */ -struct func * -func_sql_builtin_new(struct func_def *def) -{ - assert(def->language == FUNC_LANGUAGE_SQL_BUILTIN); - struct func *func = - (struct func *) malloc(sizeof(*func)); - if (func == NULL) { - diag_set(OutOfMemory, sizeof(*func), "malloc", "func"); - return NULL; - } - /** Don't export SQL builtins in Lua for now. */ - def->exports.lua = false; - func->vtab = &func_sql_builtin_vtab; - return func; -} - -static void -func_sql_builtin_destroy(struct func *func) -{ - assert(func->vtab == &func_sql_builtin_vtab); - assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN); - free(func); -} - -static struct func_vtab func_sql_builtin_vtab = { - .call = NULL, - .destroy = func_sql_builtin_destroy, -}; +extern struct func * +func_sql_builtin_new(struct func_def *def); struct func * func_new(struct func_def *def) diff --git a/src/box/lua/call.c b/src/box/lua/call.c index 0ac2eb7a6..001578b5a 100644 --- a/src/box/lua/call.c +++ b/src/box/lua/call.c @@ -44,7 +44,6 @@ #include "box/port.h" #include "box/lua/tuple.h" #include "small/obuf.h" -#include "lua_sql.h" #include "trivia/util.h" #include "mpstream.h" @@ -968,7 +967,6 @@ static struct trigger on_alter_func_in_lua = { static const struct luaL_Reg boxlib_internal[] = { {"call_loadproc", lbox_call_loadproc}, - {"sql_create_function", lbox_sql_create_function}, {"module_reload", lbox_module_reload}, {"func_call", lbox_func_call}, {NULL, NULL} diff --git a/src/box/lua/lua_sql.c b/src/box/lua/lua_sql.c deleted file mode 100644 index 67a51a82c..000000000 --- a/src/box/lua/lua_sql.c +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * 1. Redistributions of source code must retain the above - * copyright notice, this list of conditions and the - * following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY ``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..9055a0770 100644 --- a/src/box/sql/expr.c +++ b/src/box/sql/expr.c @@ -328,12 +328,11 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id, if (op == TK_FUNCTION) { uint32_t arg_count = p->x.pList == NULL ? 0 : p->x.pList->nExpr; - struct FuncDef *func = sqlFindFunction(parse->db, - p->u.zToken, - arg_count, 0); + struct func *func = + sql_func_by_signature(p->u.zToken, arg_count); if (func == NULL) break; - if ((func->funcFlags & SQL_FUNC_DERIVEDCOLL) != 0) { + if (sql_func_flag_is_set(func, SQL_FUNC_DERIVEDCOLL)) { /* * Now we use quite straightforward * approach assuming that resulting @@ -342,7 +341,7 @@ sql_expr_coll(Parse *parse, Expr *p, bool *is_explicit_coll, uint32_t *coll_id, * built-in functions: trim, upper, * lower, replace, substr. */ - assert(func->ret_type == FIELD_TYPE_STRING); + assert(func->def->returns == FIELD_TYPE_STRING); p = p->x.pList->a->pExpr; continue; } @@ -3975,11 +3974,9 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target) case TK_FUNCTION:{ ExprList *pFarg; /* List of function arguments */ int nFarg; /* Number of function arguments */ - FuncDef *pDef; /* The function definition object */ const char *zId; /* The function name */ u32 constMask = 0; /* Mask of function arguments that are constant */ int i; /* Loop counter */ - sql *db = pParse->db; /* The database connection */ struct coll *coll = NULL; assert(!ExprHasProperty(pExpr, EP_xIsSelect)); @@ -3991,8 +3988,8 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target) nFarg = pFarg ? pFarg->nExpr : 0; assert(!ExprHasProperty(pExpr, EP_IntValue)); zId = pExpr->u.zToken; - pDef = sqlFindFunction(db, zId, nFarg, 0); - if (pDef == 0 || pDef->xFinalize != 0) { + struct func *func = sql_func_by_signature(zId, nFarg); + if (func == NULL) { diag_set(ClientError, ER_NO_SUCH_FUNCTION, zId); pParse->is_aborted = true; @@ -4002,9 +3999,16 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target) * IFNULL() functions. This avoids unnecessary evaluation of * arguments past the first non-NULL argument. */ - if (pDef->funcFlags & SQL_FUNC_COALESCE) { + if (sql_func_flag_is_set(func, SQL_FUNC_COALESCE)) { int endCoalesce = sqlVdbeMakeLabel(v); - assert(nFarg >= 2); + if (nFarg < 2) { + diag_set(ClientError, + ER_FUNC_WRONG_ARG_COUNT, + func->def->name, + ">= 2", nFarg); + pParse->is_aborted = true; + break; + } sqlExprCode(pParse, pFarg->a[0].pExpr, target); for (i = 1; i < nFarg; i++) { @@ -4026,8 +4030,15 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target) /* The UNLIKELY() function is a no-op. The result is the value * of the first argument. */ - if (pDef->funcFlags & SQL_FUNC_UNLIKELY) { - assert(nFarg >= 1); + if (sql_func_flag_is_set(func, SQL_FUNC_UNLIKELY)) { + if (nFarg < 1) { + diag_set(ClientError, + ER_FUNC_WRONG_ARG_COUNT, + func->def->name, + ">= 1", nFarg); + pParse->is_aborted = true; + break; + } return sqlExprCodeTarget(pParse, pFarg->a[0].pExpr, target); @@ -4049,7 +4060,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target) * is done using ANSI rules from * collations_check_compatibility(). */ - if ((pDef->funcFlags & SQL_FUNC_NEEDCOLL) != 0) { + if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) { struct coll *unused = NULL; uint32_t curr_id = COLL_NONE; bool is_curr_forced = false; @@ -4096,9 +4107,8 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target) * or OPFLAG_TYPEOFARG respectively, to avoid unnecessary data * loading. */ - if ((pDef-> - funcFlags & (SQL_FUNC_LENGTH | - SQL_FUNC_TYPEOF)) != 0) { + if (sql_func_flag_is_set(func, SQL_FUNC_LENGTH | + SQL_FUNC_TYPEOF)) { u8 exprOp; assert(nFarg == 1); assert(pFarg->a[0].pExpr != 0); @@ -4109,14 +4119,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target) OPFLAG_LENGTHARG); assert(SQL_FUNC_TYPEOF == OPFLAG_TYPEOFARG); - testcase(pDef-> - funcFlags & - OPFLAG_LENGTHARG); - pFarg->a[0].pExpr->op2 = - pDef-> - funcFlags & - (OPFLAG_LENGTHARG | - OPFLAG_TYPEOFARG); + pFarg->a[0].pExpr->op2 = true; } } @@ -4128,12 +4131,15 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target) } else { r1 = 0; } - if (pDef->funcFlags & SQL_FUNC_NEEDCOLL) { + if (sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) { sqlVdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)coll, P4_COLLSEQ); } - sqlVdbeAddOp4(v, OP_BuiltinFunction0, constMask, r1, - target, (char *)pDef, P4_FUNCDEF); + assert(func->def->language == + FUNC_LANGUAGE_SQL_BUILTIN); + int op = OP_BuiltinFunction0; + sqlVdbeAddOp4(v, op, constMask, r1, target, + (char *)func, P4_FUNC); sqlVdbeChangeP5(v, (u8) nFarg); if (nFarg && constMask == 0) { sqlReleaseTempRange(pParse, r1, nFarg); @@ -5441,12 +5447,21 @@ analyzeAggregate(Walker * pWalker, Expr * pExpr) pItem->iMem = ++pParse->nMem; assert(!ExprHasProperty (pExpr, EP_IntValue)); - pItem->pFunc = sqlFindFunction( - pParse->db, - pExpr->u.zToken, - pExpr->x.pList ? - pExpr->x.pList->nExpr : 0, - 0); + const char *name = + pExpr->u.zToken; + uint32_t argc = + pExpr->x.pList != NULL ? + pExpr->x.pList->nExpr : 0; + pItem->func = + sql_func_by_signature( + name, argc); + assert(pItem->func != NULL); + assert(pItem->func->def-> + language == + FUNC_LANGUAGE_SQL_BUILTIN && + pItem->func->def-> + aggregate == + FUNC_AGGREGATE_GROUP); if (pExpr->flags & EP_Distinct) { pItem->iDistinct = pParse->nTab++; diff --git a/src/box/sql/func.c b/src/box/sql/func.c index a46df60ed..041183b8a 100644 --- a/src/box/sql/func.c +++ b/src/box/sql/func.c @@ -45,6 +45,8 @@ #include #include #include "box/coll_id_cache.h" +#include "box/schema.h" +#include "box/func.h" /* * Return the collating function associated with a function. @@ -79,10 +81,11 @@ minmaxFunc(sql_context * context, int argc, sql_value ** argv) int i; int iBest; struct coll *pColl; - struct FuncDef *func = context->pFunc; + struct func_sql_builtin *func = + (struct func_sql_builtin *)context->func; assert(argc > 1); - int mask = (func->funcFlags & SQL_FUNC_MAX) != 0 ? -1 : 0; + int mask = (func->flags & SQL_FUNC_MAX) != 0 ? -1 : 0; pColl = sqlGetFuncCollSeq(context); assert(mask == -1 || mask == 0); iBest = 0; @@ -410,7 +413,12 @@ substrFunc(sql_context * context, int argc, sql_value ** argv) i64 p1, p2; int negP2 = 0; - assert(argc == 3 || argc == 2); + if (argc != 2 && argc != 3) { + diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "SUBSTR", + "1 or 2", argc); + context->is_aborted = true; + return; + } if (sql_value_is_null(argv[1]) || (argc == 3 && sql_value_is_null(argv[2])) ) { @@ -506,7 +514,12 @@ roundFunc(sql_context * context, int argc, sql_value ** argv) { int n = 0; double r; - assert(argc == 1 || argc == 2); + if (argc != 1 && argc != 2) { + diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "ROUND", + "1 or 2", argc); + context->is_aborted = true; + return; + } if (argc == 2) { if (sql_value_is_null(argv[1])) return; @@ -898,6 +911,12 @@ likeFunc(sql_context *context, int argc, sql_value **argv) { u32 escape = SQL_END_OF_STRING; int nPat; + if (argc != 2 && argc != 3) { + diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, + "LIKE", "2 or 3", argc); + context->is_aborted = true; + return; + } sql *db = sql_context_db_handle(context); int rhs_type = sql_value_type(argv[0]); int lhs_type = sql_value_type(argv[1]); @@ -1510,7 +1529,9 @@ trim_func(struct sql_context *context, int argc, sql_value **argv) trim_func_three_args(context, argv[0], argv[1], argv[2]); break; default: - unreachable(); + diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, "TRIM", + "1 or 2 or 3", argc); + context->is_aborted = true; } } @@ -1689,6 +1710,12 @@ static void countStep(sql_context * context, int argc, sql_value ** argv) { CountCtx *p; + if (argc != 0 && argc != 1) { + diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, + "COUNT", "0 or 1", argc); + context->is_aborted = true; + return; + } p = sql_aggregate_context(context, sizeof(*p)); if ((argc == 0 || ! sql_value_is_null(argv[0])) && p) { p->n++; @@ -1713,6 +1740,8 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv) Mem *pBest; UNUSED_PARAMETER(NotUsed); + struct func_sql_builtin *func = + (struct func_sql_builtin *)context->func; pBest = (Mem *) sql_aggregate_context(context, sizeof(*pBest)); if (!pBest) return; @@ -1729,7 +1758,7 @@ minmaxStep(sql_context * context, int NotUsed, sql_value ** argv) * between the two being that the sense of the * comparison is inverted. */ - bool is_max = (context->pFunc->funcFlags & SQL_FUNC_MAX) != 0; + bool is_max = (func->flags & SQL_FUNC_MAX) != 0; cmp = sqlMemCompare(pBest, pArg, pColl); if ((is_max && cmp < 0) || (!is_max && cmp > 0)) { sqlVdbeMemCopy(pBest, pArg); @@ -1765,7 +1794,12 @@ groupConcatStep(sql_context * context, int argc, sql_value ** argv) StrAccum *pAccum; const char *zSep; int nVal, nSep; - assert(argc == 1 || argc == 2); + if (argc != 1 && argc != 2) { + diag_set(ClientError, ER_FUNC_WRONG_ARG_COUNT, + "GROUP_CONCAT", "1 or 2", argc); + context->is_aborted = true; + return; + } if (sql_value_is_null(argv[0])) return; pAccum = @@ -1814,129 +1848,621 @@ groupConcatFinalize(sql_context * context) } int -sql_is_like_func(struct sql *db, struct Expr *expr) +sql_is_like_func(struct Expr *expr) { if (expr->op != TK_FUNCTION || !expr->x.pList || expr->x.pList->nExpr != 2) return 0; assert(!ExprHasProperty(expr, EP_xIsSelect)); - struct FuncDef *func = sqlFindFunction(db, expr->u.zToken, 2, 0); - assert(func != NULL); - if ((func->funcFlags & SQL_FUNC_LIKE) == 0) + struct func *func = sql_func_by_signature(expr->u.zToken, 2); + if (func == NULL || !sql_func_flag_is_set(func, SQL_FUNC_LIKE)) return 0; return 1; } -/* - * All of the FuncDef structures in the aBuiltinFunc[] array above - * to the global function hash table. This occurs at start-time (as - * a consequence of calling sql_initialize()). - * - * After this routine runs +struct func * +sql_func_by_signature(const char *name, int argc) +{ + struct func *base = func_by_name(name, strlen(name)); + if (base == NULL || !base->def->exports.sql) + return NULL; + + if (base->def->param_count != -1 && base->def->param_count != argc) + return NULL; + return base; +} + +static int +func_sql_builtin_call_stub(struct func *func, struct port *args, + struct port *ret) +{ + (void) func; (void) args; (void) ret; + diag_set(ClientError, ER_UNSUPPORTED, + "sql builtin function", "Lua frontend"); + return -1; +} + +static void +sql_builtin_stub(sql_context *ctx, int argc, sql_value **argv) +{ + (void) argc; (void) argv; + diag_set(ClientError, ER_SQL_EXECUTE, + tt_sprintf("function '%s' is not implemented", + ctx->func->def->name)); + ctx->is_aborted = true; +} + +/** + * A sequence of SQL builtins definitions in + * lexicographic order. */ -void -sqlRegisterBuiltinFunctions(void) -{ - /* - * The following array holds FuncDef structures for all of the functions - * defined in this file. - * - * The array cannot be constant since changes are made to the - * FuncDef.pHash elements at start-time. The elements of this array - * are read-only after initialization is complete. - * - * For peak efficiency, put the most frequently used function last. +static struct { + /** + * Name is used to find corresponding entry in array + * sql_builtins applying binary search. */ - static FuncDef aBuiltinFunc[] = { - FUNCTION(soundex, 1, 0, 0, soundexFunc, FIELD_TYPE_STRING), - FUNCTION2(unlikely, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY, - FIELD_TYPE_BOOLEAN), - FUNCTION2(likelihood, 2, 0, 0, noopFunc, SQL_FUNC_UNLIKELY, - FIELD_TYPE_BOOLEAN), - FUNCTION2(likely, 1, 0, 0, noopFunc, SQL_FUNC_UNLIKELY, - FIELD_TYPE_BOOLEAN), - FUNCTION_COLL(trim, 1, 3, 0, trim_func), - FUNCTION_COLL(trim, 2, 3, 0, trim_func), - FUNCTION_COLL(trim, 3, 3, 0, trim_func), - FUNCTION2(least, -1, 0, 1, minmaxFunc, SQL_FUNC_MIN, - FIELD_TYPE_SCALAR), - AGGREGATE2(min, 1, 0, 1, minmaxStep, minMaxFinalize, - SQL_FUNC_MIN, FIELD_TYPE_SCALAR), - FUNCTION2(greatest, -1, 1, 1, minmaxFunc, SQL_FUNC_MAX, - FIELD_TYPE_SCALAR), - AGGREGATE2(max, 1, 1, 1, minmaxStep, minMaxFinalize, - SQL_FUNC_MAX, FIELD_TYPE_SCALAR), - FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQL_FUNC_TYPEOF, - FIELD_TYPE_STRING), - FUNCTION2(length, 1, 0, 0, lengthFunc, SQL_FUNC_LENGTH, - FIELD_TYPE_INTEGER), - FUNCTION(char_length, 1, 0, 0, lengthFunc, FIELD_TYPE_INTEGER), - FUNCTION(character_length, 1, 0, 0, lengthFunc, - FIELD_TYPE_INTEGER), - FUNCTION(position, 2, 0, 1, position_func, FIELD_TYPE_INTEGER), - FUNCTION(printf, -1, 0, 0, printfFunc, FIELD_TYPE_STRING), - FUNCTION(unicode, 1, 0, 0, unicodeFunc, FIELD_TYPE_STRING), - FUNCTION(char, -1, 0, 0, charFunc, FIELD_TYPE_STRING), - FUNCTION(abs, 1, 0, 0, absFunc, FIELD_TYPE_NUMBER), - FUNCTION(round, 1, 0, 0, roundFunc, FIELD_TYPE_INTEGER), - FUNCTION(round, 2, 0, 0, roundFunc, FIELD_TYPE_INTEGER), - FUNCTION_COLL(upper, 1, 0, 1, UpperICUFunc), - FUNCTION_COLL(lower, 1, 0, 1, LowerICUFunc), - FUNCTION(hex, 1, 0, 0, hexFunc, FIELD_TYPE_STRING), - FUNCTION2(ifnull, 2, 0, 0, noopFunc, SQL_FUNC_COALESCE, - FIELD_TYPE_INTEGER), - VFUNCTION(random, 0, 0, 0, randomFunc, FIELD_TYPE_INTEGER), - VFUNCTION(randomblob, 1, 0, 0, randomBlob, FIELD_TYPE_VARBINARY), - FUNCTION(nullif, 2, 0, 1, nullifFunc, FIELD_TYPE_SCALAR), - FUNCTION(version, 0, 0, 0, sql_func_version, FIELD_TYPE_STRING), - FUNCTION(quote, 1, 0, 0, quoteFunc, FIELD_TYPE_STRING), - VFUNCTION(row_count, 0, 0, 0, sql_row_count, FIELD_TYPE_INTEGER), - FUNCTION_COLL(replace, 3, 0, 0, replaceFunc), - FUNCTION(zeroblob, 1, 0, 0, zeroblobFunc, FIELD_TYPE_VARBINARY), - FUNCTION_COLL(substr, 2, 0, 0, substrFunc), - FUNCTION_COLL(substr, 3, 0, 0, substrFunc), - AGGREGATE(sum, 1, 0, 0, sum_step, sumFinalize, - FIELD_TYPE_NUMBER), - AGGREGATE(total, 1, 0, 0, sum_step, totalFinalize, - FIELD_TYPE_NUMBER), - AGGREGATE(avg, 1, 0, 0, sum_step, avgFinalize, - FIELD_TYPE_NUMBER), - AGGREGATE2(count, 0, 0, 0, countStep, countFinalize, - SQL_FUNC_COUNT, FIELD_TYPE_INTEGER), - AGGREGATE2(count, 1, 0, 0, countStep, countFinalize, - SQL_FUNC_COUNT, FIELD_TYPE_INTEGER), - AGGREGATE(group_concat, 1, 0, 0, groupConcatStep, - groupConcatFinalize, FIELD_TYPE_STRING), - AGGREGATE(group_concat, 2, 0, 0, groupConcatStep, - groupConcatFinalize, FIELD_TYPE_STRING), + const char *name; + /** Members below are related to struct func_sql_builtin. */ + uint16_t flags; + void (*call)(sql_context *ctx, int argc, sql_value **argv); + void (*finalize)(sql_context *ctx); + /** Members below are related to struct func_def. */ + bool is_deterministic; + int param_count; + enum field_type returns; + enum func_aggregate aggregate; + bool export_to_sql; +} sql_builtins[] = { + {.name = "ABS", + .param_count = 1, + .returns = FIELD_TYPE_NUMBER, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = 0, + .call = absFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "AVG", + .param_count = 1, + .returns = FIELD_TYPE_NUMBER, + .is_deterministic = false, + .aggregate = FUNC_AGGREGATE_GROUP, + .flags = 0, + .call = sum_step, + .finalize = avgFinalize, + .export_to_sql = true, + }, { + .name = "CEIL", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "CEILING", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "CHAR", + .param_count = -1, + .returns = FIELD_TYPE_STRING, + .is_deterministic = true, + .aggregate = FUNC_AGGREGATE_NONE, + .flags = 0, + .call = charFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "CHARACTER_LENGTH", + .param_count = 1, + .returns = FIELD_TYPE_INTEGER, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = 0, + .call = lengthFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "CHAR_LENGTH", + .param_count = 1, + .returns = FIELD_TYPE_INTEGER, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = 0, + .call = lengthFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "COALESCE", + .param_count = -1, + .returns = FIELD_TYPE_SCALAR, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_COALESCE, + .call = sql_builtin_stub, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "COUNT", + .param_count = -1, + .returns = FIELD_TYPE_INTEGER, + .aggregate = FUNC_AGGREGATE_GROUP, + .is_deterministic = false, + .flags = 0, + .call = countStep, + .finalize = countFinalize, + .export_to_sql = true, + }, { + .name = "CURRENT_DATE", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "CURRENT_TIME", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "CURRENT_TIMESTAMP", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "DATE", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "DATETIME", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "EVERY", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "EXISTS", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "EXP", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "EXTRACT", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "FLOOR", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "GREATER", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "GREATEST", + .param_count = -1, + .returns = FIELD_TYPE_SCALAR, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MAX, + .call = minmaxFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "GROUP_CONCAT", + .param_count = -1, + .returns = FIELD_TYPE_STRING, + .aggregate = FUNC_AGGREGATE_GROUP, + .is_deterministic = false, + .flags = 0, + .call = groupConcatStep, + .finalize = groupConcatFinalize, + .export_to_sql = true, + }, { + .name = "HEX", + .param_count = 1, + .returns = FIELD_TYPE_STRING, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = 0, + .call = hexFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "IFNULL", + .param_count = 2, + .returns = FIELD_TYPE_INTEGER, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_COALESCE, + .call = sql_builtin_stub, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "JULIANDAY", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "LEAST", + .param_count = -1, + .returns = FIELD_TYPE_SCALAR, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MIN, + .call = minmaxFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "LENGTH", + .param_count = 1, + .returns = FIELD_TYPE_INTEGER, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_LENGTH, + .call = lengthFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "LESSER", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "LIKE", + .param_count = -1, + .returns = FIELD_TYPE_INTEGER, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_LIKE, + .call = likeFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "LIKELIHOOD", + .param_count = 2, + .returns = FIELD_TYPE_BOOLEAN, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_UNLIKELY, + .call = sql_builtin_stub, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "LIKELY", + .param_count = 1, + .returns = FIELD_TYPE_BOOLEAN, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_UNLIKELY, + .call = sql_builtin_stub, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "LN", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "LOWER", + .param_count = 1, + .returns = FIELD_TYPE_STRING, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL, + .call = LowerICUFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "MAX", + .param_count = 1, + .returns = FIELD_TYPE_SCALAR, + .aggregate = FUNC_AGGREGATE_GROUP, + .is_deterministic = false, + .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MAX, + .call = minmaxStep, + .finalize = minMaxFinalize, + .export_to_sql = true, + }, { + .name = "MIN", + .param_count = 1, + .returns = FIELD_TYPE_SCALAR, + .aggregate = FUNC_AGGREGATE_GROUP, + .is_deterministic = false, + .flags = SQL_FUNC_NEEDCOLL | SQL_FUNC_MIN, + .call = minmaxStep, + .finalize = minMaxFinalize, + .export_to_sql = true, + }, { + .name = "MOD", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "NULLIF", + .param_count = 2, + .returns = FIELD_TYPE_SCALAR, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_NEEDCOLL, + .call = nullifFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "OCTET_LENGTH", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "POSITION", + .param_count = 2, + .returns = FIELD_TYPE_INTEGER, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_NEEDCOLL, + .call = position_func, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "POWER", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "PRINTF", + .param_count = -1, + .returns = FIELD_TYPE_STRING, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = 0, + .call = printfFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "QUOTE", + .param_count = 1, + .returns = FIELD_TYPE_STRING, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = 0, + .call = quoteFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "RANDOM", + .param_count = 0, + .returns = FIELD_TYPE_INTEGER, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = false, + .flags = 0, + .call = randomFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "RANDOMBLOB", + .param_count = 1, + .returns = FIELD_TYPE_VARBINARY, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = false, + .flags = 0, + .call = randomBlob, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "REPLACE", + .param_count = 3, + .returns = FIELD_TYPE_STRING, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_DERIVEDCOLL, + .call = replaceFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "ROUND", + .param_count = -1, + .returns = FIELD_TYPE_INTEGER, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = 0, + .call = roundFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "ROW_COUNT", + .param_count = 0, + .returns = FIELD_TYPE_INTEGER, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = 0, + .call = sql_row_count, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "SOME", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "SOUNDEX", + .param_count = 1, + .returns = FIELD_TYPE_STRING, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = 0, + .call = soundexFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "SQRT", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "STRFTIME", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "SUBSTR", + .param_count = -1, + .returns = FIELD_TYPE_STRING, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_DERIVEDCOLL, + .call = substrFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "SUM", + .param_count = 1, + .returns = FIELD_TYPE_NUMBER, + .aggregate = FUNC_AGGREGATE_GROUP, + .is_deterministic = false, + .flags = 0, + .call = sum_step, + .finalize = sumFinalize, + .export_to_sql = true, + }, { + .name = "TIME", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "TOTAL", + .param_count = 1, + .returns = FIELD_TYPE_NUMBER, + .aggregate = FUNC_AGGREGATE_GROUP, + .is_deterministic = false, + .flags = 0, + .call = sum_step, + .finalize = totalFinalize, + .export_to_sql = true, + }, { + .name = "TRIM", + .param_count = -1, + .returns = FIELD_TYPE_STRING, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_DERIVEDCOLL, + .call = trim_func, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "TYPEOF", + .param_count = 1, + .returns = FIELD_TYPE_STRING, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_TYPEOF, + .call = typeofFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "UNICODE", + .param_count = 1, + .returns = FIELD_TYPE_STRING, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = 0, + .call = unicodeFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "UNLIKELY", + .param_count = 1, + .returns = FIELD_TYPE_BOOLEAN, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_UNLIKELY, + .call = sql_builtin_stub, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "UPPER", + .param_count = 1, + .returns = FIELD_TYPE_STRING, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL, + .call = UpperICUFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "VERSION", + .param_count = 0, + .returns = FIELD_TYPE_STRING, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = 0, + .call = sql_func_version, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "ZEROBLOB", + .param_count = 1, + .returns = FIELD_TYPE_VARBINARY, + .aggregate = FUNC_AGGREGATE_NONE, + .is_deterministic = true, + .flags = 0, + .call = zeroblobFunc, + .finalize = NULL, + .export_to_sql = true, + }, { + .name = "_sql_stat_get", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "_sql_stat_init", + .call = sql_builtin_stub, + .export_to_sql = false, + }, { + .name = "_sql_stat_push", + .call = sql_builtin_stub, + .export_to_sql = false, + }, +}; - LIKEFUNC(like, 2, 1, SQL_FUNC_LIKE, - FIELD_TYPE_INTEGER), - LIKEFUNC(like, 3, 1, SQL_FUNC_LIKE, - FIELD_TYPE_INTEGER), - FUNCTION(coalesce, 1, 0, 0, 0, FIELD_TYPE_SCALAR), - FUNCTION(coalesce, 0, 0, 0, 0, FIELD_TYPE_SCALAR), - FUNCTION2(coalesce, -1, 0, 0, noopFunc, SQL_FUNC_COALESCE, - FIELD_TYPE_SCALAR), - }; - sql_register_analyze_builtins(); - sqlRegisterDateTimeFunctions(); - sqlInsertBuiltinFuncs(aBuiltinFunc, ArraySize(aBuiltinFunc)); +static struct func_vtab func_sql_builtin_vtab; -#if 0 /* Enable to print out how the built-in functions are hashed */ - { - int i; - FuncDef *p; - for (i = 0; i < SQL_FUNC_HASH_SZ; i++) { - printf("FUNC-HASH %02d:", i); - for (p = sqlBuiltinFunctions.a[i]; p; - p = p->u.pHash) { - int n = sqlStrlen30(p->zName); - int h = p->zName[0] + n; - printf(" %s(%d)", p->zName, h); - } - printf("\n"); +struct func * +func_sql_builtin_new(struct func_def *def) +{ + assert(def->language == FUNC_LANGUAGE_SQL_BUILTIN); + /** Binary search for corresponding builtin entry. */ + int idx = -1, left = 0, right = nelem(sql_builtins) - 1; + while (left <= right) { + uint32_t mid = (left + right) / 2; + int rc = strcmp(def->name, sql_builtins[mid].name); + if (rc == 0) { + idx = mid; + break; } + if (rc < 0) + right = mid - 1; + else + left = mid + 1; } -#endif + /* Some builtins are not implemented yet. */ + if (idx == -1) { + diag_set(ClientError, ER_CREATE_FUNCTION, def->name, + "given built-in is not predefined"); + return NULL; + } + struct func_sql_builtin *func = + (struct func_sql_builtin *) calloc(1, sizeof(*func)); + if (func == NULL) { + diag_set(OutOfMemory, sizeof(*func), "malloc", "func"); + return NULL; + } + func->base.def = def; + func->base.vtab = &func_sql_builtin_vtab; + func->flags = sql_builtins[idx].flags; + func->call = sql_builtins[idx].call; + func->finalize = sql_builtins[idx].finalize; + def->param_count = sql_builtins[idx].param_count; + def->is_deterministic = sql_builtins[idx].is_deterministic; + def->returns = sql_builtins[idx].returns; + def->aggregate = sql_builtins[idx].aggregate; + def->exports.sql = sql_builtins[idx].export_to_sql; + return &func->base; +} + +static void +func_sql_builtin_destroy(struct func *func) +{ + assert(func->vtab == &func_sql_builtin_vtab); + assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN); + free(func); } + +static struct func_vtab func_sql_builtin_vtab = { + .call = func_sql_builtin_call_stub, + .destroy = func_sql_builtin_destroy, +}; diff --git a/src/box/sql/global.c b/src/box/sql/global.c index 6cadef809..c25b83de1 100644 --- a/src/box/sql/global.c +++ b/src/box/sql/global.c @@ -162,13 +162,6 @@ SQL_WSD struct sqlConfig sqlConfig = { 0x7ffffffe /* iOnceResetThreshold */ }; -/* - * Hash table for global functions - functions common to all - * database connections. After initialization, this table is - * read-only. - */ -FuncDefHash sqlBuiltinFunctions; - /* * The value of the "pending" byte must be 0x40000000 (1 byte past the * 1-gibabyte boundary) in a compatible database. sql never uses diff --git a/src/box/sql/main.c b/src/box/sql/main.c index 748dc1b8e..0b20f2132 100644 --- a/src/box/sql/main.c +++ b/src/box/sql/main.c @@ -131,9 +131,6 @@ sql_initialize(void) if (sqlGlobalConfig.isInit == 0 && sqlGlobalConfig.inProgress == 0) { sqlGlobalConfig.inProgress = 1; - memset(&sqlBuiltinFunctions, 0, - sizeof(sqlBuiltinFunctions)); - sqlRegisterBuiltinFunctions(); sql_os_init(); sqlGlobalConfig.isInit = 1; sqlGlobalConfig.inProgress = 0; @@ -242,25 +239,6 @@ sqlCloseSavepoints(Vdbe * pVdbe) pVdbe->anonymous_savepoint = NULL; } -/* - * Invoke the destructor function associated with FuncDef p, if any. Except, - * if this is not the last copy of the function, do not invoke it. Multiple - * copies of a single function are created when create_function() is called - * with SQL_ANY as the encoding. - */ -static void -functionDestroy(sql * db, FuncDef * p) -{ - FuncDestructor *pDestructor = p->u.pDestructor; - if (pDestructor) { - pDestructor->nRef--; - if (pDestructor->nRef == 0) { - pDestructor->xDestroy(pDestructor->pUserData); - sqlDbFree(db, pDestructor); - } - } -} - /* * Rollback all database files. If tripCode is not 0, then * any write cursors are invalidated ("tripped" - as in "tripping a circuit @@ -279,121 +257,6 @@ sqlRollbackAll(Vdbe * pVdbe) } } -/* - * This function is exactly the same as sql_create_function(), except - * that it is designed to be called by internal code. The difference is - * that if a malloc() fails in sql_create_function(), an error code - * is returned and the mallocFailed flag cleared. - */ -int -sqlCreateFunc(sql * db, - const char *zFunctionName, - enum field_type type, - int nArg, - int flags, - void *pUserData, - void (*xSFunc) (sql_context *, int, sql_value **), - void (*xStep) (sql_context *, int, sql_value **), - void (*xFinal) (sql_context *), - FuncDestructor * pDestructor) -{ - FuncDef *p; - int extraFlags; - - if (zFunctionName == 0 || - (xSFunc && (xFinal || xStep)) || - (!xSFunc && (xFinal && !xStep)) || - (!xSFunc && (!xFinal && xStep)) || - (nArg < -1 || nArg > SQL_MAX_FUNCTION_ARG) || - (255 < (sqlStrlen30(zFunctionName)))) { - diag_set(ClientError, ER_CREATE_FUNCTION, zFunctionName, - "wrong function definition"); - return -1; - } - - assert(SQL_FUNC_CONSTANT == SQL_DETERMINISTIC); - extraFlags = flags & SQL_DETERMINISTIC; - - - /* Check if an existing function is being overridden or deleted. If so, - * and there are active VMs, then return an error. If a function - * is being overridden/deleted but there are no active VMs, allow the - * operation to continue but invalidate all precompiled statements. - */ - p = sqlFindFunction(db, zFunctionName, nArg, 0); - if (p && p->nArg == nArg) { - if (db->nVdbeActive) { - diag_set(ClientError, ER_CREATE_FUNCTION, zFunctionName, - "unable to create function due to active "\ - "statements"); - return -1; - } else { - sqlExpirePreparedStatements(db); - } - } - - p = sqlFindFunction(db, zFunctionName, nArg, 1); - assert(p || db->mallocFailed); - if (p == NULL) - return -1; - - /* If an older version of the function with a configured destructor is - * being replaced invoke the destructor function here. - */ - functionDestroy(db, p); - - if (pDestructor) { - pDestructor->nRef++; - } - p->u.pDestructor = pDestructor; - p->funcFlags = extraFlags; - testcase(p->funcFlags & SQL_DETERMINISTIC); - p->xSFunc = xSFunc ? xSFunc : xStep; - p->xFinalize = xFinal; - p->pUserData = pUserData; - p->nArg = (u16) nArg; - p->ret_type = type; - return 0; -} - -int -sql_create_function_v2(sql * db, - const char *zFunc, - enum field_type type, - int nArg, - int flags, - void *p, - void (*xSFunc) (sql_context *, int, - sql_value **), - void (*xStep) (sql_context *, int, - sql_value **), - void (*xFinal) (sql_context *), - void (*xDestroy) (void *)) -{ - FuncDestructor *pArg = 0; - - if (xDestroy) { - pArg = - (FuncDestructor *) sqlDbMallocZero(db, - sizeof - (FuncDestructor)); - if (!pArg) { - xDestroy(p); - return -1; - } - pArg->xDestroy = xDestroy; - pArg->pUserData = p; - } - int rc = sqlCreateFunc(db, zFunc, type, nArg, flags, p, xSFunc, xStep, - xFinal, pArg); - if (pArg && pArg->nRef == 0) { - assert(rc != 0); - xDestroy(p); - sqlDbFree(db, pArg); - } - return rc; -} - /* * This array defines hard upper bounds on limit values. The * initializer must be kept in sync with the SQL_LIMIT_* diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c index 83dff47b6..231c670f5 100644 --- a/src/box/sql/resolve.c +++ b/src/box/sql/resolve.c @@ -38,6 +38,7 @@ #include "sqlInt.h" #include #include +#include "box/schema.h" /* * Walk the expression tree pExpr and increase the aggregate function @@ -591,32 +592,46 @@ resolveExprStep(Walker * pWalker, Expr * pExpr) case TK_FUNCTION:{ ExprList *pList = pExpr->x.pList; /* The argument list */ int n = pList ? pList->nExpr : 0; /* Number of arguments */ - int no_such_func = 0; /* True if no such function exists */ - int wrong_num_args = 0; /* True if wrong number of arguments */ int is_agg = 0; /* True if is an aggregate function */ int nId; /* Number of characters in function name */ const char *zId; /* The function name. */ - FuncDef *pDef; /* Information about the function */ assert(!ExprHasProperty(pExpr, EP_xIsSelect)); zId = pExpr->u.zToken; nId = sqlStrlen30(zId); - pDef = sqlFindFunction(pParse->db, zId, n, 0); - if (pDef == 0) { - pDef = - sqlFindFunction(pParse->db, zId, -2,0); - if (pDef == 0) { - no_such_func = 1; + struct func *func = sql_func_by_signature(zId, n); + if (func == NULL) { + func = func_by_name(zId, nId); + if (func == NULL) { + diag_set(ClientError, + ER_NO_SUCH_FUNCTION, zId); + } else if (!func->def->exports.sql) { + diag_set(ClientError, + ER_SQL_PARSER_GENERIC, + tt_sprintf("function %.*s() " + "is not available " + "in SQL", nId, zId)); } else { - wrong_num_args = 1; + uint32_t argc = func->def->param_count; + const char *err = tt_sprintf("%d", argc); + diag_set(ClientError, + ER_FUNC_WRONG_ARG_COUNT, + func->def->name, err, n); } + pParse->is_aborted = true; + pNC->nErr++; } else { - is_agg = pDef->xFinalize != 0; - pExpr->type = pDef->ret_type; + is_agg = func->def->aggregate == + FUNC_AGGREGATE_GROUP; + assert(!is_agg || + func->def->language == + FUNC_LANGUAGE_SQL_BUILTIN); + pExpr->type = func->def->returns; const char *err = "second argument to likelihood() must "\ "be a constant between 0.0 and 1.0"; - if (pDef->funcFlags & SQL_FUNC_UNLIKELY) { + if (sql_func_flag_is_set(func, + SQL_FUNC_UNLIKELY)) { ExprSetProperty(pExpr, EP_Unlikely | EP_Skip); if (n == 2) { @@ -643,19 +658,17 @@ resolveExprStep(Walker * pWalker, Expr * pExpr) */ /* TUNING: unlikely() probability is 0.0625. likely() is 0.9375 */ pExpr->iTable = - pDef->zName[0] == - 'u' ? 8388608 : 125829120; + func->def->name[0] == 'u' ? + 8388608 : 125829120; } } - if ((pDef->funcFlags & SQL_FUNC_CONSTANT) != 0) { + if (func->def->is_deterministic) { /* For the purposes of the EP_ConstFunc flag, date and time * functions and other functions that change slowly are considered * constant because they are constant for the duration of one query */ ExprSetProperty(pExpr, EP_ConstFunc); - } - if ((pDef->funcFlags & SQL_FUNC_CONSTANT) == - 0) { + } else { /* Date/time functions that use 'now', and other functions * that might change over time cannot be used * in an index. @@ -671,17 +684,6 @@ resolveExprStep(Walker * pWalker, Expr * pExpr) pParse->is_aborted = true; pNC->nErr++; is_agg = 0; - } else if (no_such_func && pParse->db->init.busy == 0) { - diag_set(ClientError, ER_NO_SUCH_FUNCTION, zId); - pParse->is_aborted = true; - pNC->nErr++; - } else if (wrong_num_args) { - const char *err = "wrong number of arguments "\ - "to function %.*s()"; - diag_set(ClientError, ER_SQL_PARSER_GENERIC, - tt_sprintf(err, nId, zId)); - pParse->is_aborted = true; - pNC->nErr++; } if (is_agg) pNC->ncFlags &= ~NC_AllowAgg; @@ -698,13 +700,12 @@ resolveExprStep(Walker * pWalker, Expr * pExpr) pExpr->op2++; pNC2 = pNC2->pNext; } - assert(pDef != 0); + assert(func != NULL); if (pNC2) { pNC2->ncFlags |= NC_HasAgg; - if ((pDef->funcFlags & - SQL_FUNC_MIN) != 0 || - (pDef->funcFlags & - SQL_FUNC_MAX) != 0) + if (sql_func_flag_is_set(func, + SQL_FUNC_MIN | + SQL_FUNC_MAX)) pNC2->ncFlags |= NC_MinMaxAgg; } pNC->ncFlags |= NC_AllowAgg; diff --git a/src/box/sql/select.c b/src/box/sql/select.c index ddb5de87e..cdcdbaf2b 100644 --- a/src/box/sql/select.c +++ b/src/box/sql/select.c @@ -4381,7 +4381,9 @@ is_simple_count(struct Select *select, struct AggInfo *agg_info) return NULL; if (NEVER(agg_info->nFunc == 0)) return NULL; - if ((agg_info->aFunc->pFunc->funcFlags & SQL_FUNC_COUNT) == 0 || + assert(agg_info->aFunc->func->def->language == + FUNC_LANGUAGE_SQL_BUILTIN); + if (sql_func_flag_is_set(agg_info->aFunc->func, SQL_FUNC_COUNT) || (agg_info->aFunc->pExpr->x.pList != NULL && agg_info->aFunc->pExpr->x.pList->nExpr > 0)) return NULL; @@ -5278,7 +5280,7 @@ finalizeAggFunctions(Parse * pParse, AggInfo * pAggInfo) assert(!ExprHasProperty(pF->pExpr, EP_xIsSelect)); sqlVdbeAddOp2(v, OP_AggFinal, pF->iMem, pList ? pList->nExpr : 0); - sqlVdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); + sqlVdbeAppendP4(v, pF->func, P4_FUNC); } } @@ -5319,7 +5321,7 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo) vdbe_insert_distinct(pParse, pF->iDistinct, pF->reg_eph, addrNext, 1, regAgg); } - if (pF->pFunc->funcFlags & SQL_FUNC_NEEDCOLL) { + if (sql_func_flag_is_set(pF->func, SQL_FUNC_NEEDCOLL)) { struct coll *coll = NULL; struct ExprList_item *pItem; int j; @@ -5338,7 +5340,7 @@ updateAccumulator(Parse * pParse, AggInfo * pAggInfo) (char *)coll, P4_COLLSEQ); } sqlVdbeAddOp3(v, OP_AggStep0, 0, regAgg, pF->iMem); - sqlVdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); + sqlVdbeAppendP4(v, pF->func, P4_FUNC); sqlVdbeChangeP5(v, (u8) nArg); sql_expr_type_cache_change(pParse, regAgg, nArg); sqlReleaseTempRange(pParse, regAgg, nArg); diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index a2f78607a..c5d91ceee 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -41,6 +41,8 @@ */ #include "box/box.h" #include "box/error.h" +#include "box/func.h" +#include "box/func_def.h" #include "box/fk_constraint.h" #include "box/txn.h" #include "box/tuple.h" @@ -1720,7 +1722,7 @@ case OP_BuiltinFunction0: { int n; sql_context *pCtx; - assert(pOp->p4type==P4_FUNCDEF); + assert(pOp->p4type == P4_FUNC); n = pOp->p5; assert(pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor)); assert(n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1)); @@ -1728,7 +1730,7 @@ case OP_BuiltinFunction0: { pCtx = sqlDbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sql_value*)); if (pCtx==0) goto no_mem; pCtx->pOut = 0; - pCtx->pFunc = pOp->p4.pFunc; + pCtx->func = pOp->p4.func; pCtx->iOp = (int)(pOp - aOp); pCtx->pVdbe = p; pCtx->argc = n; @@ -1763,7 +1765,9 @@ case OP_BuiltinFunction: { } #endif pCtx->is_aborted = false; - (*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */ + assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN); + struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func; + func->call(pCtx, pCtx->argc, pCtx->argv); /* If the function returned an error, throw an exception */ if (pCtx->is_aborted) @@ -5021,7 +5025,7 @@ case OP_AggStep0: { int n; sql_context *pCtx; - assert(pOp->p4type==P4_FUNCDEF); + assert(pOp->p4type == P4_FUNC); n = pOp->p5; assert(pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor)); assert(n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1)); @@ -5029,7 +5033,7 @@ case OP_AggStep0: { pCtx = sqlDbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sql_value*)); if (pCtx==0) goto no_mem; pCtx->pMem = 0; - pCtx->pFunc = pOp->p4.pFunc; + pCtx->func = pOp->p4.func; pCtx->iOp = (int)(pOp - aOp); pCtx->pVdbe = p; pCtx->argc = n; @@ -5071,7 +5075,9 @@ case OP_AggStep: { pCtx->pOut = &t; pCtx->is_aborted = false; pCtx->skipFlag = 0; - (pCtx->pFunc->xSFunc)(pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */ + assert(pCtx->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN); + struct func_sql_builtin *func = (struct func_sql_builtin *)pCtx->func; + func->call(pCtx, pCtx->argc, pCtx->argv); if (pCtx->is_aborted) { sqlVdbeMemRelease(&t); goto abort_due_to_error; @@ -5103,7 +5109,7 @@ case OP_AggFinal: { assert(pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor)); pMem = &aMem[pOp->p1]; assert((pMem->flags & ~(MEM_Null|MEM_Agg))==0); - if (sqlVdbeMemFinalize(pMem, pOp->p4.pFunc) != 0) + if (sql_vdbemem_finalize(pMem, pOp->p4.func) != 0) goto abort_due_to_error; UPDATE_MAX_BLOBSIZE(pMem); if (sqlVdbeMemTooBig(pMem)) { diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c index dfaff9365..bf40b44fa 100644 --- a/src/box/sql/vdbeapi.c +++ b/src/box/sql/vdbeapi.c @@ -477,17 +477,6 @@ sql_step(sql_stmt * pStmt) return sqlStep(v); } -/* - * Extract the user data from a sql_context structure and return a - * pointer to it. - */ -void * -sql_user_data(sql_context * p) -{ - assert(p && p->pFunc); - return p->pFunc->pUserData; -} - /* * Extract the user data from a sql_context structure and return a * pointer to it. @@ -547,7 +536,7 @@ createAggContext(sql_context * p, int nByte) } else { sqlVdbeMemClearAndResize(pMem, nByte); pMem->flags = MEM_Agg; - pMem->u.pDef = p->pFunc; + pMem->u.func = p->func; if (pMem->z) { memset(pMem->z, 0, nByte); } @@ -563,7 +552,9 @@ createAggContext(sql_context * p, int nByte) void * sql_aggregate_context(sql_context * p, int nByte) { - assert(p && p->pFunc && p->pFunc->xFinalize); + assert(p != NULL && p->func != NULL); + assert(p->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN); + assert(p->func->def->aggregate == FUNC_AGGREGATE_GROUP); testcase(nByte < 0); if ((p->pMem->flags & MEM_Agg) == 0) { return createAggContext(p, nByte); diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c index 5bcf2ccd8..3bd4ff433 100644 --- a/src/box/sql/vdbeaux.c +++ b/src/box/sql/vdbeaux.c @@ -648,24 +648,11 @@ sqlVdbeJumpHere(Vdbe * p, int addr) sqlVdbeChangeP2(p, addr, p->nOp); } -/* - * If the input FuncDef structure is ephemeral, then free it. If - * the FuncDef is not ephermal, then do nothing. - */ -static void -freeEphemeralFunction(sql * db, FuncDef * pDef) -{ - if ((pDef->funcFlags & SQL_FUNC_EPHEM) != 0) { - sqlDbFree(db, pDef); - } -} - static void vdbeFreeOpArray(sql *, Op *, int); static SQL_NOINLINE void freeP4FuncCtx(sql * db, sql_context * p) { - freeEphemeralFunction(db, p->pFunc); sqlDbFree(db, p); } @@ -689,13 +676,11 @@ freeP4(sql * db, int p4type, void *p4) case P4_KEYINFO: sql_key_info_unref(p4); break; - case P4_FUNCDEF:{ - freeEphemeralFunction(db, (FuncDef *) p4); - break; - } case P4_MEM: sqlValueFree((sql_value *) p4); break; + default: + break; } } @@ -1149,15 +1134,17 @@ displayP4(Op * pOp, char *zTemp, int nTemp) sqlXPrintf(&x, "(binary)"); break; } - case P4_FUNCDEF:{ - FuncDef *pDef = pOp->p4.pFunc; - sqlXPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg); + case P4_FUNC:{ + struct func *func = pOp->p4.func; + sqlXPrintf(&x, "%s(%d)", func->def->name, + func->def->param_count); break; } #if defined(SQL_DEBUG) || defined(VDBE_PROFILE) case P4_FUNCCTX:{ - FuncDef *pDef = pOp->p4.pCtx->pFunc; - sqlXPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg); + struct func *func = pOp->p4.func; + sqlXPrintf(&x, "%s(%d)", func->def->name, + func->def->param_count); break; } #endif diff --git a/src/box/sql/vdbemem.c b/src/box/sql/vdbemem.c index 5516d7fb1..ac0dfa333 100644 --- a/src/box/sql/vdbemem.c +++ b/src/box/sql/vdbemem.c @@ -312,37 +312,29 @@ sqlVdbeMemStringify(Mem * pMem) return 0; } -/* - * Memory cell pMem contains the context of an aggregate function. - * This routine calls the finalize method for that function. The - * result of the aggregate is stored back into pMem. - * - * Return -1 if the finalizer reports an error. 0 otherwise. - */ int -sqlVdbeMemFinalize(Mem * pMem, FuncDef * pFunc) +sql_vdbemem_finalize(struct Mem *mem, struct func *func) { - if (ALWAYS(pFunc && pFunc->xFinalize)) { - sql_context ctx; - Mem t; - assert((pMem->flags & MEM_Null) != 0 || pFunc == pMem->u.pDef); - memset(&ctx, 0, sizeof(ctx)); - memset(&t, 0, sizeof(t)); - t.flags = MEM_Null; - t.db = pMem->db; - t.field_type = field_type_MAX; - ctx.pOut = &t; - ctx.pMem = pMem; - ctx.pFunc = pFunc; - pFunc->xFinalize(&ctx); /* IMP: R-24505-23230 */ - assert((pMem->flags & MEM_Dyn) == 0); - if (pMem->szMalloc > 0) - sqlDbFree(pMem->db, pMem->zMalloc); - memcpy(pMem, &t, sizeof(t)); - if (ctx.is_aborted) - return -1; - } - return 0; + assert(func != NULL); + assert(func->def->language == FUNC_LANGUAGE_SQL_BUILTIN); + assert(func->def->aggregate == FUNC_AGGREGATE_GROUP); + assert((mem->flags & MEM_Null) != 0 || func == mem->u.func); + sql_context ctx; + memset(&ctx, 0, sizeof(ctx)); + Mem t; + memset(&t, 0, sizeof(t)); + t.flags = MEM_Null; + t.db = mem->db; + t.field_type = field_type_MAX; + ctx.pOut = &t; + ctx.pMem = mem; + ctx.func = func; + ((struct func_sql_builtin *)func)->finalize(&ctx); + assert((mem->flags & MEM_Dyn) == 0); + if (mem->szMalloc > 0) + sqlDbFree(mem->db, mem->zMalloc); + memcpy(mem, &t, sizeof(t)); + return ctx.is_aborted ? -1 : 0; } /* @@ -359,7 +351,7 @@ vdbeMemClearExternAndSetNull(Mem * p) { assert(VdbeMemDynamic(p)); if (p->flags & MEM_Agg) { - sqlVdbeMemFinalize(p, p->u.pDef); + sql_vdbemem_finalize(p, p->u.func); assert((p->flags & MEM_Agg) == 0); testcase(p->flags & MEM_Dyn); } @@ -1289,7 +1281,6 @@ valueFromFunction(sql * db, /* The database connection */ sql_context ctx; /* Context object for function invocation */ sql_value **apVal = 0; /* Function arguments */ int nVal = 0; /* Size of apVal[] array */ - FuncDef *pFunc = 0; /* Function definition */ sql_value *pVal = 0; /* New value */ int rc = 0; /* Return code */ ExprList *pList = 0; /* Function arguments */ @@ -1300,10 +1291,10 @@ valueFromFunction(sql * db, /* The database connection */ pList = p->x.pList; if (pList) nVal = pList->nExpr; - pFunc = sqlFindFunction(db, p->u.zToken, nVal, 0); - assert(pFunc); - if ((pFunc->funcFlags & SQL_FUNC_CONSTANT) == 0 || - (pFunc->funcFlags & SQL_FUNC_NEEDCOLL)) + struct func *func = sql_func_by_signature(p->u.zToken, nVal); + if (func == NULL || func->def->language != FUNC_LANGUAGE_SQL_BUILTIN || + !func->def->is_deterministic || + sql_func_flag_is_set(func, SQL_FUNC_NEEDCOLL)) return 0; if (pList) { @@ -1332,8 +1323,8 @@ valueFromFunction(sql * db, /* The database connection */ assert(!pCtx->pParse->is_aborted); memset(&ctx, 0, sizeof(ctx)); ctx.pOut = pVal; - ctx.pFunc = pFunc; - pFunc->xSFunc(&ctx, nVal, apVal); + ctx.func = func; + ((struct func_sql_builtin *)func)->call(&ctx, nVal, apVal); assert(!ctx.is_aborted); sql_value_apply_type(pVal, type); assert(rc == 0); diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c index 8adf6a5f1..98615b118 100644 --- a/src/box/sql/whereexpr.c +++ b/src/box/sql/whereexpr.c @@ -273,7 +273,7 @@ like_optimization_is_valid(Parse *pParse, Expr *pExpr, Expr **ppPrefix, /* Result code to return. */ int rc; - if (!sql_is_like_func(db, pExpr)) { + if (!sql_is_like_func(pExpr)) { return 0; } pList = pExpr->x.pList; diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 9bba37bcb..9d2fcea4b 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -130,7 +130,6 @@ add_library(box STATIC ${lua_sources} lua/init.c lua/call.c - lua/lua_sql.c lua/cfg.cc lua/console.c lua/tuple.c diff --git a/src/box/alter.cc b/src/box/alter.cc index 4f2e34bf0..c0780d6da 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -2926,6 +2926,12 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event) (unsigned) old_func->def->uid, "function has references"); } + /* Can't' drop a builtin function. */ + if (old_func->def->language == FUNC_LANGUAGE_SQL_BUILTIN) { + tnt_raise(ClientError, ER_DROP_FUNCTION, + (unsigned) old_func->def->uid, + "function is SQL built-in"); + } struct trigger *on_commit = txn_alter_trigger_new(on_drop_func_commit, old_func); struct trigger *on_rollback = diff --git a/test/box/function1.result b/test/box/function1.result index 5b091f72b..0ece5448a 100644 --- a/test/box/function1.result +++ b/test/box/function1.result @@ -825,6 +825,26 @@ box.func.LUA:call({"return 1 + 1"}) --- - 2 ... +box.schema.user.grant('guest', 'execute', 'function', 'SUM') +--- +... +c = net.connect(box.cfg.listen) +--- +... +c:call("SUM") +--- +- error: sql builtin function does not support Lua frontend +... +c:close() +--- +... +box.schema.user.revoke('guest', 'execute', 'function', 'SUM') +--- +... +box.schema.func.drop("SUM") +--- +- error: 'Can''t drop function 1: function is SQL built-in' +... -- Introduce function options box.schema.func.create('test', {body = "function(tuple) return tuple end", is_deterministic = true, opts = {is_multikey = true}}) --- diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua index f894472f8..665972eda 100644 --- a/test/box/function1.test.lua +++ b/test/box/function1.test.lua @@ -294,6 +294,13 @@ ok == true box.func.LUA:call({"return 1 + 1"}) +box.schema.user.grant('guest', 'execute', 'function', 'SUM') +c = net.connect(box.cfg.listen) +c:call("SUM") +c:close() +box.schema.user.revoke('guest', 'execute', 'function', 'SUM') +box.schema.func.drop("SUM") + -- Introduce function options box.schema.func.create('test', {body = "function(tuple) return tuple end", is_deterministic = true, opts = {is_multikey = true}}) box.func['test'].is_multikey == true diff --git a/test/box/misc.result b/test/box/misc.result index 7a15dabf0..feb72f69c 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -529,6 +529,7 @@ t; 198: box.error.FUNC_INDEX_FUNC 199: box.error.FUNC_INDEX_FORMAT 200: box.error.FUNC_INDEX_PARTS + 201: box.error.FUNC_WRONG_ARG_COUNT ... test_run:cmd("setopt delimiter ''"); --- diff --git a/test/sql-tap/func.test.lua b/test/sql-tap/func.test.lua index ec06c903d..b70fac373 100755 --- a/test/sql-tap/func.test.lua +++ b/test/sql-tap/func.test.lua @@ -69,7 +69,7 @@ test:do_catchsql_test( SELECT length(*) FROM tbl1 ORDER BY t1 ]], { -- - 1, "wrong number of arguments to function LENGTH()" + 1, "Wrong number of arguments is passed to LENGTH(): expected 1, got 0" -- }) @@ -79,7 +79,7 @@ test:do_catchsql_test( SELECT length(t1,5) FROM tbl1 ORDER BY t1 ]], { -- - 1, "wrong number of arguments to function LENGTH()" + 1, "Wrong number of arguments is passed to LENGTH(): expected 1, got 2" -- }) @@ -365,7 +365,7 @@ test:do_test( return test:catchsql("SELECT abs(a,b) FROM t1") end, { -- - 1, "wrong number of arguments to function ABS()" + 1, "Wrong number of arguments is passed to ABS(): expected 1, got 2" -- }) @@ -377,7 +377,7 @@ test:do_catchsql_test( SELECT abs() FROM t1 ]], { -- - 1, "wrong number of arguments to function ABS()" + 1, "Wrong number of arguments is passed to ABS(): expected 1, got 0" -- }) @@ -428,7 +428,7 @@ test:do_catchsql_test( SELECT round(a,b,c) FROM t1 ]], { -- - 1, "wrong number of arguments to function ROUND()" + 1, "Wrong number of arguments is passed to ROUND(): expected 1 or 2, got 3" -- }) @@ -488,7 +488,7 @@ test:do_catchsql_test( SELECT round() FROM t1 ORDER BY a ]], { -- - 1, "wrong number of arguments to function ROUND()" + 1, "Wrong number of arguments is passed to ROUND(): expected 1 or 2, got 0" -- }) @@ -778,7 +778,7 @@ test:do_catchsql_test( SELECT upper(*) FROM t2 ]], { -- - 1, "wrong number of arguments to function UPPER()" + 1, "Wrong number of arguments is passed to UPPER(): expected 1, got 0" -- }) @@ -1782,7 +1782,7 @@ test:do_catchsql_test( SELECT replace(1,2); ]], { -- - 1, "wrong number of arguments to function REPLACE()" + 1, "Wrong number of arguments is passed to REPLACE(): expected 3, got 2" -- }) @@ -1792,7 +1792,7 @@ test:do_catchsql_test( SELECT replace(1,2,3,4); ]], { -- - 1, "wrong number of arguments to function REPLACE()" + 1, "Wrong number of arguments is passed to REPLACE(): expected 3, got 4" -- }) @@ -2540,7 +2540,7 @@ test:do_catchsql_test( SELECT coalesce() ]], { -- - 1, "wrong number of arguments to function COALESCE()" + 1, "Wrong number of arguments is passed to COALESCE(): expected >= 2, got 0" -- }) @@ -2550,7 +2550,7 @@ test:do_catchsql_test( SELECT coalesce(1) ]], { -- - 1, "wrong number of arguments to function COALESCE()" + 1, "Wrong number of arguments is passed to COALESCE(): expected >= 2, got 1" -- }) diff --git a/test/sql-tap/func2.test.lua b/test/sql-tap/func2.test.lua index b70197d09..c9bd3665f 100755 --- a/test/sql-tap/func2.test.lua +++ b/test/sql-tap/func2.test.lua @@ -50,7 +50,7 @@ test:do_catchsql_test( SELECT SUBSTR() ]], { -- - 1, "wrong number of arguments to function SUBSTR()" + 1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0" -- }) @@ -60,7 +60,7 @@ test:do_catchsql_test( SELECT SUBSTR('Supercalifragilisticexpialidocious') ]], { -- - 1, "wrong number of arguments to function SUBSTR()" + 1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1" -- }) @@ -70,7 +70,7 @@ test:do_catchsql_test( SELECT SUBSTR('Supercalifragilisticexpialidocious', 1,1,1) ]], { -- - 1, "wrong number of arguments to function SUBSTR()" + 1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4" -- }) @@ -673,7 +673,7 @@ if ("ሴ" ~= "u1234") SELECT SUBSTR() ]], { -- - 1, "wrong number of arguments to function SUBSTR()" + 1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0" -- }) @@ -683,7 +683,7 @@ if ("ሴ" ~= "u1234") SELECT SUBSTR('hiሴho') ]], { -- - 1, "wrong number of arguments to function SUBSTR()" + 1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1" -- }) @@ -693,7 +693,7 @@ if ("ሴ" ~= "u1234") SELECT SUBSTR('hiሴho', 1,1,1) ]], { -- - 1, "wrong number of arguments to function SUBSTR()" + 1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4" -- }) @@ -1038,7 +1038,7 @@ test:do_catchsql_test( SELECT SUBSTR() ]], { -- - 1, "wrong number of arguments to function SUBSTR()" + 1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 0" -- }) @@ -1048,7 +1048,7 @@ test:do_catchsql_test( SELECT SUBSTR(x'1234') ]], { -- - 1, "wrong number of arguments to function SUBSTR()" + 1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 1" -- }) @@ -1058,7 +1058,7 @@ test:do_catchsql_test( SELECT SUBSTR(x'1234', 1,1,1) ]], { -- - 1, "wrong number of arguments to function SUBSTR()" + 1, "Wrong number of arguments is passed to SUBSTR(): expected 1 or 2, got 4" -- }) diff --git a/test/sql-tap/limit.test.lua b/test/sql-tap/limit.test.lua index 8445ab18e..870233942 100755 --- a/test/sql-tap/limit.test.lua +++ b/test/sql-tap/limit.test.lua @@ -771,7 +771,7 @@ test:do_catchsql_test( SELECT * FROM t1 LIMIT replace(1) ]], { -- - 1, "wrong number of arguments to function REPLACE()" + 1, "Wrong number of arguments is passed to REPLACE(): expected 3, got 1" -- }) @@ -781,7 +781,7 @@ test:do_catchsql_test( SELECT * FROM t1 LIMIT 5 OFFSET replace(1) ]], { -- - 1, 'wrong number of arguments to function REPLACE()' + 1, 'Wrong number of arguments is passed to REPLACE(): expected 3, got 1' -- }) diff --git a/test/sql-tap/select1.test.lua b/test/sql-tap/select1.test.lua index 924c0ccb1..4bbfbd67b 100755 --- a/test/sql-tap/select1.test.lua +++ b/test/sql-tap/select1.test.lua @@ -244,7 +244,7 @@ test:do_catchsql_test( SELECT count(f1,f2) FROM test1 ]], { -- - 1, "wrong number of arguments to function COUNT()" + 1, "Wrong number of arguments is passed to COUNT(): expected 0 or 1, got 2" -- }) @@ -324,7 +324,7 @@ test:do_catchsql_test( SELECT min(*) FROM test1 ]], { -- - 1, "wrong number of arguments to function MIN()" + 1, "Wrong number of arguments is passed to MIN(): expected 1, got 0" -- }) @@ -389,7 +389,7 @@ test:do_catchsql_test( SELECT MAX(*) FROM test1 ]], { -- - 1, "wrong number of arguments to function MAX()" + 1, "Wrong number of arguments is passed to MAX(): expected 1, got 0" -- }) @@ -469,7 +469,7 @@ test:do_catchsql_test( SELECT SUM(*) FROM test1 ]], { -- - 1, "wrong number of arguments to function SUM()" + 1, "Wrong number of arguments is passed to SUM(): expected 1, got 0" -- }) @@ -489,7 +489,7 @@ test:do_catchsql_test( SELECT sum(f1,f2) FROM test1 ]], { -- - 1, "wrong number of arguments to function SUM()" + 1, "Wrong number of arguments is passed to SUM(): expected 1, got 2" -- }) @@ -691,7 +691,7 @@ test:do_catchsql_test( SELECT f1 FROM test1 WHERE count(f1,f2)!=11 ]], { -- - 1, "wrong number of arguments to function COUNT()" + 1, "misuse of aggregate function COUNT()" -- }) 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/" -- }) diff --git a/test/sql/icu-upper-lower.result b/test/sql/icu-upper-lower.result index 88266c8c5..8ff7528f3 100644 --- a/test/sql/icu-upper-lower.result +++ b/test/sql/icu-upper-lower.result @@ -277,7 +277,7 @@ test_run:cmd("setopt delimiter ''"); box.execute("select upper('1', 2)") --- - null -- wrong number of arguments to function UPPER() +- 'Wrong number of arguments is passed to UPPER(): expected 1, got 2' ... box.execute("select upper(\"1\")") --- @@ -287,5 +287,5 @@ box.execute("select upper(\"1\")") box.execute("select upper()") --- - null -- wrong number of arguments to function UPPER() +- 'Wrong number of arguments is passed to UPPER(): expected 1, got 0' ... -- 2.22.1