From: Konstantin Osipov <kostja@tarantool.org>
To: Kirill Shcherbatov <kshcherbatov@tarantool.org>
Cc: tarantool-patches@freelists.org, korablev@tarantool.org
Subject: [tarantool-patches] Re: [PATCH v2 12/12] sql: use schema's func hash instead of FuncDef hash
Date: Wed, 10 Jul 2019 23:22:21 +0300 [thread overview]
Message-ID: <20190710202221.GM5619@atlas> (raw)
In-Reply-To: <8fa5473273ccef8386d519c19ae75a57c8ed51ae.1562756438.git.kshcherbatov@tarantool.org>
* Kirill Shcherbatov <kshcherbatov@tarantool.org> [19/07/10 14:02]:
Can you keep all functions in the same cache but avoid calling
them using the same calling convention?
I see value in killing func_def cache, but I hate adamantly the
fact that the built-ins now depend on port object. It should be
possible to call a built-in without messing with a port.
> Part of #2200
> Closes #4113
> Closes #2233
>
> @TarantoolBot document
> Title: Reworked SQL functions machinery
>
> Now all functions in Tarantool use uniform API and calling
> convention. Therefore it is possible to call C and Lua functions
> from SQL.
>
> To make a function available in SQL, you need to define it
> with list of types of it's arguments 'param_list', the type
> of returned value 'returns' and mention 'SQL' language in
> exports parameter. Deterministic functions must be defined with
> is_deterministic = true (the query planner use this information
> to build an effective vdbe code).
>
> Example:
> -- C function is called in SQL
> box.schema.func.create("function1.divide", {language = 'C',
> returns = 'number', param_list = {'number', 'number'},
> is_deterministic = true, exports = {'LUA', 'SQL'}})
> box.execute('SELECT "function1.divide"(6, 3)')
> ---
> - metadata:
> - name: '"function1.divide"(6, 3)'
> type: number
> rows:
> - [2]
> ...
>
> -- Persistent Lua function is called in SQL
> box.schema.func.create("SUMMARIZE", {language = 'LUA', returns = 'number',
> body = 'function (a, b) return a + b end',
> is_deterministic = true,
> param_list = {'number', 'number'},
> exports = {'LUA', 'SQL'}})
> box.execute('SELECT summarize(1, 2)')
> ---
> - metadata:
> - name: summarize(1, 2)
> type: number
> rows:
> - [3]
> ...
>
> Moreover there is a predefined Lua function "LUA" that allows to
> call Lua code in SQL. The argument of LUA function is a valid
> Lua code that returns some scalar type.
>
> Example:
> box.execute('SELECT lua(\'return box.cfg.memtx_memory\')')
> ---
> - metadata:
> - name: lua('return box.cfg.memtx_memory')
> type: any
> rows:
> - [268435456]
> ...
> ---
> src/box/CMakeLists.txt | 1 -
> src/box/alter.cc | 1 +
> src/box/func.c | 3 +-
> src/box/func.h | 5 +
> src/box/func_def.c | 19 +
> src/box/func_def.h | 2 +
> src/box/lua/call.c | 8 +-
> src/box/lua/lua_sql.c | 217 ----------
> src/box/lua/lua_sql.h | 39 --
> src/box/lua/schema.lua | 4 +-
> src/box/port.c | 5 +-
> src/box/sql.h | 13 +
> src/box/sql/analyze.c | 40 +-
> src/box/sql/callback.c | 208 ----------
> src/box/sql/date.c | 28 --
> src/box/sql/expr.c | 51 ++-
> src/box/sql/func.c | 697 +++++++++++++++++++++++++-------
> src/box/sql/global.c | 7 -
> src/box/sql/main.c | 134 ------
> src/box/sql/resolve.c | 55 ++-
> src/box/sql/select.c | 10 +-
> src/box/sql/sqlInt.h | 180 +--------
> src/box/sql/vdbe.c | 19 +-
> src/box/sql/vdbe.h | 6 +-
> src/box/sql/vdbeInt.h | 20 +-
> src/box/sql/vdbeapi.c | 11 +-
> src/box/sql/vdbeaux.c | 31 +-
> src/box/sql/vdbemem.c | 73 ++--
> src/box/sql/whereexpr.c | 4 +-
> src/lib/coll/coll.c | 1 +
> test/box/function1.result | 119 ++++++
> test/box/function1.test.lua | 35 ++
> test/sql-tap/alias.test.lua | 11 +-
> test/sql-tap/check.test.lua | 13 +-
> test/sql-tap/func5.test.lua | 29 +-
> test/sql-tap/lua_sql.test.lua | 120 +++---
> test/sql-tap/subquery.test.lua | 21 +-
> test/sql-tap/trigger9.test.lua | 6 +-
> test/sql-tap/where2.test.lua | 4 +-
> test/sql/errinj.result | 25 --
> test/sql/errinj.test.lua | 10 -
> test/sql/func-recreate.result | 41 +-
> test/sql/func-recreate.test.lua | 28 +-
> 43 files changed, 1109 insertions(+), 1245 deletions(-)
> delete mode 100644 src/box/lua/lua_sql.c
> delete mode 100644 src/box/lua/lua_sql.h
>
> diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
> index 481842a39..a0160912d 100644
> --- a/src/box/CMakeLists.txt
> +++ b/src/box/CMakeLists.txt
> @@ -128,7 +128,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 c92a1f710..cd85552a2 100644
> --- a/src/box/alter.cc
> +++ b/src/box/alter.cc
> @@ -2786,6 +2786,7 @@ func_def_new_from_tuple(struct tuple *tuple)
> def->exports.lua = true;
> def->param_count = 0;
> }
> + def->sql_flags = 0;
> def_guard.is_active = false;
> return def;
> }
> diff --git a/src/box/func.c b/src/box/func.c
> index e36649d15..6e7d04f25 100644
> --- a/src/box/func.c
> +++ b/src/box/func.c
> @@ -34,13 +34,13 @@
> #include "assoc.h"
> #include "lua/utils.h"
> #include "lua/call.h"
> -#include "lua/lua_sql.h"
> #include "error.h"
> #include "sql.h"
> #include "diag.h"
> #include "port.h"
> #include "schema.h"
> #include "session.h"
> +#include "small/region.h"
> #include <dlfcn.h>
>
> /**
> @@ -438,6 +438,7 @@ func_c_new(struct func_def *def)
> return NULL;
> }
> func->base.vtab = func_c_vtab;
> + rlist_create(&func->base.signature);
> func->func = NULL;
> func->module = NULL;
> return &func->base;
> diff --git a/src/box/func.h b/src/box/func.h
> index 7e4dd37a3..133172bf0 100644
> --- a/src/box/func.h
> +++ b/src/box/func.h
> @@ -82,6 +82,11 @@ struct func {
> * Cached runtime access information.
> */
> struct access access[BOX_USER_MAX];
> + /**
> + * A list of other functions with given name.
> + * Is valid for SQL builtins.
> + */
> + struct rlist signature;
> };
>
> /**
> diff --git a/src/box/func_def.c b/src/box/func_def.c
> index fb9f77df8..ffd76a514 100644
> --- a/src/box/func_def.c
> +++ b/src/box/func_def.c
> @@ -34,9 +34,28 @@ func_def_cmp(struct func_def *def1, struct func_def *def2)
> return def1->aggregate - def2->aggregate;
> if (def1->param_count != def2->param_count)
> return def1->param_count - def2->param_count;
> + if (def1->sql_flags != def2->sql_flags)
> + return def1->sql_flags - def2->sql_flags;
> if ((def1->comment != NULL) != (def2->comment != NULL))
> return def1->comment - def2->comment;
> if (def1->comment != NULL && strcmp(def1->comment, def2->comment) != 0)
> return strcmp(def1->comment, def2->comment);
> return 0;
> }
> +
> +struct func_def *
> +func_def_dup(struct func_def *def)
> +{
> + uint32_t body_offset, comment_offset;
> + uint32_t sz = func_def_sizeof(def->name_len,
> + def->body != NULL ? strlen(def->body) : 0,
> + def->comment != NULL ? strlen(def->comment) : 0,
> + &body_offset, &comment_offset);
> + struct func_def *new = (struct func_def *) malloc(sz);
> + memcpy(new, def, sz);
> + if (new->body != NULL)
> + new->body = (char *)new + body_offset;
> + if (new->comment != NULL)
> + new->comment = (char *)new + comment_offset;
> + return new;
> +}
> diff --git a/src/box/func_def.h b/src/box/func_def.h
> index 508580f78..809d74c42 100644
> --- a/src/box/func_def.h
> +++ b/src/box/func_def.h
> @@ -89,6 +89,8 @@ struct func_def {
> * available.
> */
> bool is_sandboxed;
> + /** A set of SQL_FUNCTION_* flags. */
> + uint16_t sql_flags;
> /** The count of function's input arguments. */
> int param_count;
> /** The type of the value returned by function. */
> diff --git a/src/box/lua/call.c b/src/box/lua/call.c
> index 752f05745..528c41310 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"
>
> @@ -501,13 +500,16 @@ port_lua_destroy(struct port *base)
> extern const char *
> port_lua_dump_plain(struct port *port, uint32_t *size);
>
> +extern struct sql_value *
> +port_lua_get_vdbemem(struct port *base, uint32_t *size);
> +
> static const struct port_vtab port_lua_vtab = {
> .dump_msgpack = port_lua_dump,
> .dump_msgpack_16 = port_lua_dump_16,
> .dump_lua = port_lua_dump_lua,
> .dump_plain = port_lua_dump_plain,
> .get_msgpack = port_lua_get_msgpack,
> - .get_vdbemem = NULL,
> + .get_vdbemem = port_lua_get_vdbemem,
> .get_context = NULL,
> .destroy = port_lua_destroy,
> };
> @@ -717,6 +719,7 @@ func_lua_new(struct func_def *def)
> func->lua_ref = LUA_REFNIL;
> func->base.vtab = func_lua_vtab;
> }
> + rlist_create(&func->base.signature);
> return &func->base;
> }
>
> @@ -959,7 +962,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 0c5797fa2..000000000
> --- a/src/box/lua/lua_sql.c
> +++ /dev/null
> @@ -1,217 +0,0 @@
> -/*
> - * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
> - *
> - * Redistribution and use in source and binary forms, with or
> - * without modification, are permitted provided that the following
> - * conditions are met:
> - *
> - * 1. Redistributions of source code must retain the above
> - * copyright notice, this list of conditions and the
> - * following disclaimer.
> - *
> - * 2. Redistributions in binary form must reproduce the above
> - * copyright notice, this list of conditions and the following
> - * disclaimer in the documentation and/or other materials
> - * provided with the distribution.
> - *
> - * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
> - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
> - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
> - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
> - * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
> - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
> - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
> - * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
> - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
> - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
> - * SUCH DAMAGE.
> - */
> -
> -#include "lua.h"
> -#include "lua/utils.h"
> -
> -#include "box/lua/call.h"
> -#include "box/sql/sqlInt.h"
> -#include "box/port.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 int
> -lua_sql_call(struct func *func, struct port *args, struct port *ret)
> -{
> - (void) func;
> - uint32_t argc;
> - struct Mem *argv = (struct Mem *)port_get_vdbemem(args, &argc);
> - if (argv == NULL)
> - return -1;
> - struct sql_context *ctx = (struct sql_context *) port_get_context(args);
> - assert(ctx != NULL);
> - struct Mem *val = vdbemem_alloc_on_region(1);
> - if (val == NULL)
> - return -1;
> - port_vdbemem_create(ret, (struct sql_value *)val, 1, NULL);
> -
> - 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(ctx);
> -
> - lua_rawgeti(L, LUA_REGISTRYINDEX, func_info->func_ref);
> - for (uint32_t i = 0; i < argc; i++) {
> - sql_value *param = (sql_value *)&argv[i];
> - switch (sql_value_type(param)) {
> - case MP_INT:
> - luaL_pushint64(L, sql_value_int64(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");
> - goto error;
> - }
> - }
> - if (lua_pcall(L, lua_gettop(L) - 1, 1, 0) != 0){
> - diag_set(ClientError, ER_SQL_EXECUTE, lua_tostring(L, -1));
> - goto error;
> - }
> - switch(lua_type(L, -1)) {
> - case LUA_TBOOLEAN:
> - mem_set_bool(val, lua_toboolean(L, -1));
> - break;
> - case LUA_TNUMBER:
> - sqlVdbeMemSetDouble(val, lua_tonumber(L, -1));
> - break;
> - case LUA_TSTRING:
> - if (sqlVdbeMemSetStr(val, lua_tostring(L, -1), -1,
> - 1, SQL_TRANSIENT) != 0)
> - return -1;
> - break;
> - case LUA_TNIL:
> - sqlVdbeMemSetNull(val);
> - break;
> - default:
> - diag_set(ClientError, ER_SQL_EXECUTE, "Unsupported type "\
> - "passed from Lua");
> - goto error;
> - }
> - return 0;
> -error:
> - luaL_unref(tarantool_L, LUA_REGISTRYINDEX, coro_ref);
> - return -1;
> -}
> -
> -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, "FLOAT") == 0)
> - type = FIELD_TYPE_NUMBER;
> - else if (strcmp(type_arg, "NUM") == 0)
> - type = FIELD_TYPE_NUMBER;
> - else if (strcmp(type_arg, "BLOB") == 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/lua/lua_sql.h b/src/box/lua/lua_sql.h
> deleted file mode 100644
> index b81093eca..000000000
> --- a/src/box/lua/lua_sql.h
> +++ /dev/null
> @@ -1,39 +0,0 @@
> -/*
> - * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
> - *
> - * Redistribution and use in source and binary forms, with or
> - * without modification, are permitted provided that the following
> - * conditions are met:
> - *
> - * 1. Redistributions of source code must retain the above
> - * copyright notice, this list of conditions and the
> - * following disclaimer.
> - *
> - * 2. Redistributions in binary form must reproduce the above
> - * copyright notice, this list of conditions and the following
> - * disclaimer in the documentation and/or other materials
> - * provided with the distribution.
> - *
> - * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
> - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
> - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
> - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
> - * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
> - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
> - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
> - * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
> - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
> - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
> - * SUCH DAMAGE.
> - */
> -
> -#ifndef TARANTOOL_LUA_SQL_H
> -#define TARANTOOL_LUA_SQL_H
> -
> -int
> -lbox_sql_create_function(struct lua_State *L);
> -
> -#endif //TARANTOOL_LUA_SQL_H
> -
> diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
> index aadcd3fa9..5c65eb792 100644
> --- a/src/box/lua/schema.lua
> +++ b/src/box/lua/schema.lua
> @@ -2109,7 +2109,9 @@ box.schema.func.create = function(name, opts)
> if_not_exists = 'boolean',
> language = 'string', body = 'string',
> is_deterministic = 'boolean',
> - is_sandboxed = 'boolean', comment = 'string' })
> + is_sandboxed = 'boolean', comment = 'string',
> + returns = 'string', param_list = 'table',
> + exports = 'table'})
> local _func = box.space[box.schema.FUNC_ID]
> local _vfunc = box.space[box.schema.VFUNC_ID]
> local func = _vfunc.index.name:get{name}
> diff --git a/src/box/port.c b/src/box/port.c
> index 9e4ab9453..02c6a2245 100644
> --- a/src/box/port.c
> +++ b/src/box/port.c
> @@ -140,13 +140,16 @@ port_free(void)
> mempool_destroy(&port_tuple_entry_pool);
> }
>
> +extern struct sql_value *
> +port_tuple_get_vdbemem(struct port *base, uint32_t *size);
> +
> const struct port_vtab port_tuple_vtab = {
> .dump_msgpack = port_tuple_dump_msgpack,
> .dump_msgpack_16 = port_tuple_dump_msgpack_16,
> .dump_lua = port_tuple_dump_lua,
> .dump_plain = NULL,
> .get_msgpack = NULL,
> - .get_vdbemem = NULL,
> + .get_vdbemem = port_tuple_get_vdbemem,
> .get_context = NULL,
> .destroy = port_tuple_destroy,
> };
> diff --git a/src/box/sql.h b/src/box/sql.h
> index a078bfdec..ac10ae400 100644
> --- a/src/box/sql.h
> +++ b/src/box/sql.h
> @@ -33,6 +33,7 @@
>
> #include <stdbool.h>
> #include <stdint.h>
> +#include "box/func.h"
>
> #if defined(__cplusplus)
> extern "C" {
> @@ -409,6 +410,18 @@ vdbe_field_ref_prepare_tuple(struct vdbe_field_ref *field_ref,
> struct func *
> func_sql_builtin_new(struct func_def *def);
>
> +struct func_sql_builtin {
> + /** Function object base class. */
> + struct func base;
> + /** User data to pass in call method. */
> + void *user_data;
> + /** Finilize method for aggregate function. */
> + int (*finalize)(struct func *func, struct port *args, struct port *ret);
> +};
> +
> +struct func *
> +sql_func_by_signature(const char *name, uint32_t name_len, int argc);
> +
> #if defined(__cplusplus)
> } /* extern "C" { */
> #endif
> diff --git a/src/box/sql/analyze.c b/src/box/sql/analyze.c
> index 7764b48c5..02c922314 100644
> --- a/src/box/sql/analyze.c
> +++ b/src/box/sql/analyze.c
> @@ -264,7 +264,7 @@ stat4Destructor(void *pOld)
> * return value is BLOB, but it is really just a pointer to the Stat4Accum
> * object.
> */
> -static int
> +MAYBE_UNUSED int
> sql_builtin_stat_init(struct func *func, struct port *args, struct port *ret)
> {
> (void) func;
> @@ -544,7 +544,7 @@ samplePushPrevious(Stat4Accum * p, int iChng)
> *
> * The R parameter is only used for STAT4
> */
> -static int
> +MAYBE_UNUSED int
> sql_builtin_stat_push(struct func *func, struct port *args, struct port *ret)
> {
> (void) func;
> @@ -623,7 +623,7 @@ sql_builtin_stat_push(struct func *func, struct port *args, struct port *ret)
> * The content to returned is determined by the parameter J
> * which is one of the STAT_GET_xxxx values defined above.
> */
> -static int
> +MAYBE_UNUSED int
> sql_builtin_stat_get(struct func *func, struct port *args, struct port *ret)
> {
> (void) func;
> @@ -736,11 +736,11 @@ vdbe_emit_analyze_stat_get(struct Vdbe * v, int regStat4, int iParam,
> {
> 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", strlen("_sql_stat_get"), 2);
> assert(func != NULL);
> sqlVdbeAddOp4(v, OP_Function0, 0, regStat4, regOut,
> - (char *)func, P4_FUNCDEF);
> + (char *)func, P4_FUNC);
> sqlVdbeChangeP5(v, 2);
> }
>
> @@ -876,11 +876,12 @@ 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",
> + strlen("_sql_stat_init"), 3);
> assert(init_func != NULL);
> sqlVdbeAddOp4(v, OP_Function0, 0, stat4_reg + 1, stat4_reg,
> - (char *)init_func, P4_FUNCDEF);
> + (char *)init_func, P4_FUNC);
> sqlVdbeChangeP5(v, 3);
> /*
> * Implementation of the following:
> @@ -977,11 +978,12 @@ 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",
> + strlen("_sql_stat_push"), 3);
> assert(push_func != NULL);
> sqlVdbeAddOp4(v, OP_Function0, 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. */
> @@ -1768,17 +1770,3 @@ fail:
> box_txn_rollback();
> return -1;
> }
> -
> -void
> -sql_register_analyze_builtins(void)
> -{
> - static FuncDef funcs[] = {
> - FUNCTION(_sql_stat_get, 2, 0, 0, sql_builtin_stat_get,
> - FIELD_TYPE_ANY),
> - FUNCTION(_sql_stat_push, 3, 0, 0, sql_builtin_stat_push,
> - FIELD_TYPE_ANY),
> - FUNCTION(_sql_stat_init, 3, 0, 0, sql_builtin_stat_init,
> - FIELD_TYPE_ANY),
> - };
> - sqlInsertBuiltinFuncs(funcs, nelem(funcs));
> -}
> diff --git a/src/box/sql/callback.c b/src/box/sql/callback.c
> index 42fec2c6a..290363db6 100644
> --- a/src/box/sql/callback.c
> +++ b/src/box/sql/callback.c
> @@ -56,211 +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.
> - *
> - * If the SQL_PreferBuiltin flag is set, then search the built-in
> - * functions even if a prior app-defined function was found. And give
> - * priority to built-in functions.
> - *
> - * Except, if createFlag is true, that means that we are trying to
> - * install a new function. Whatever FuncDef structure is returned it will
> - * 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 7ee3e992e..003e49840 100644
> --- a/src/box/sql/expr.c
> +++ b/src/box/sql/expr.c
> @@ -35,6 +35,8 @@
> */
> #include "box/coll_id_cache.h"
> #include "coll/coll.h"
> +#include "box/func.h"
> +#include "box/field_def.h"
> #include "sqlInt.h"
> #include "tarantoolInt.h"
> #include "box/schema.h"
> @@ -275,12 +277,13 @@ 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,
> + strlen(p->u.zToken), arg_count);
> if (func == NULL)
> break;
> - if ((func->funcFlags & SQL_FUNC_DERIVEDCOLL) != 0) {
> + if ((func->def->sql_flags &
> + SQL_FUNC_DERIVEDCOLL) != 0) {
> /*
> * Now we use quite straightforward
> * approach assuming that resulting
> @@ -289,7 +292,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;
> }
> @@ -3923,11 +3926,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));
> @@ -3939,8 +3940,10 @@ 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, strlen(zId), nFarg);
> + if (func == NULL ||
> + func->def->aggregate == FUNC_AGGREGATE_GROUP) {
> diag_set(ClientError, ER_NO_SUCH_FUNCTION,
> zId);
> pParse->is_aborted = true;
> @@ -3950,7 +3953,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
> * IFNULL() functions. This avoids unnecessary evaluation of
> * arguments past the first non-NULL argument.
> */
> - if (pDef->funcFlags & SQL_FUNC_COALESCE) {
> + if (func->def->sql_flags & SQL_FUNC_COALESCE) {
> int endCoalesce = sqlVdbeMakeLabel(v);
> assert(nFarg >= 2);
> sqlExprCode(pParse, pFarg->a[0].pExpr,
> @@ -3987,7 +3990,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 ((func->def->sql_flags & SQL_FUNC_NEEDCOLL) != 0) {
> struct coll *unused = NULL;
> uint32_t curr_id = COLL_NONE;
> bool is_curr_forced = false;
> @@ -4034,9 +4037,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 ((func->def->sql_flags & (SQL_FUNC_LENGTH |
> + SQL_FUNC_TYPEOF)) != 0) {
> u8 exprOp;
> assert(nFarg == 1);
> assert(pFarg->a[0].pExpr != 0);
> @@ -4051,8 +4053,7 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
> funcFlags &
> OPFLAG_LENGTHARG);
> pFarg->a[0].pExpr->op2 =
> - pDef->
> - funcFlags &
> + func->def->sql_flags &
> (OPFLAG_LENGTHARG |
> OPFLAG_TYPEOFARG);
> }
> @@ -4066,12 +4067,12 @@ sqlExprCodeTarget(Parse * pParse, Expr * pExpr, int target)
> } else {
> r1 = 0;
> }
> - if (pDef->funcFlags & SQL_FUNC_NEEDCOLL) {
> + if (func->def->sql_flags & SQL_FUNC_NEEDCOLL) {
> sqlVdbeAddOp4(v, OP_CollSeq, 0, 0, 0,
> (char *)coll, P4_COLLSEQ);
> }
> sqlVdbeAddOp4(v, OP_Function0, constMask, r1,
> - target, (char *)pDef, P4_FUNCDEF);
> + target, (char *)func, P4_FUNC);
> sqlVdbeChangeP5(v, (u8) nFarg);
> if (nFarg && constMask == 0) {
> sqlReleaseTempRange(pParse, r1, nFarg);
> @@ -5376,12 +5377,18 @@ analyzeAggregate(Walker * pWalker, Expr * pExpr)
> pItem->iMem = ++pParse->nMem;
> assert(!ExprHasProperty
> (pExpr, EP_IntValue));
> - pItem->pFunc = sqlFindFunction(
> - pParse->db,
> + pItem->func = sql_func_by_signature(
> pExpr->u.zToken,
> + strlen(pExpr->u.zToken),
> pExpr->x.pList ?
> - pExpr->x.pList->nExpr : 0,
> - 0);
> + pExpr->x.pList->
> + nExpr : 0);
> + 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 f79939cb6..e35b5c2a2 100644
> --- a/src/box/sql/func.c
> +++ b/src/box/sql/func.c
> @@ -39,14 +39,19 @@
> #include "version.h"
> #include "coll/coll.h"
> #include "box/func.h"
> +#include "lua/utils.h"
> +#include "box/tuple.h"
> +#include "mpstream.h"
> #include "box/port.h"
> #include "tarantoolInt.h"
> #include "box/session.h"
> +#include "box/schema.h"
> #include <unicode/ustring.h>
> #include <unicode/ucasemap.h>
> #include <unicode/ucnv.h>
> #include <unicode/uchar.h>
> #include <unicode/ucol.h>
> +#include "small/rlist.h"
>
> /*
> * Return the collating function associated with a function.
> @@ -101,17 +106,222 @@ port_vdbemem_get_context(struct port *base)
> return port->ctx;
> }
>
> +void
> +port_vdbemem_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
> +{
> + (void) is_flat;
> + struct port_vdbemem *port = (struct port_vdbemem *) base;
> + assert(is_flat == true);
> + for (uint32_t i = 0; i < port->size; i++) {
> + sql_value *param =
> + (sql_value *)((struct Mem *)port->mem + i);
> + switch (sql_value_type(param)) {
> + case MP_INT:
> + luaL_pushint64(L, sql_value_int64(param));
> + break;
> + case MP_DOUBLE:
> + lua_pushnumber(L, sql_value_double(param));
> + break;
> + case MP_STR:
> + lua_pushstring(L, (const char *) sql_value_text(param));
> + break;
> + case MP_BIN:
> + lua_pushlstring(L, sql_value_blob(param),
> + (size_t) sql_value_bytes(param));
> + break;
> + case MP_NIL:
> + lua_pushnil(L);
> + break;
> + case MP_BOOL:
> + lua_pushboolean(L, sql_value_boolean(param));
> + break;
> + default:
> + unreachable();
> + }
> + }
> +}
> +
> +const char *
> +port_vdbemem_get_msgpack(struct port *base, uint32_t *size)
> +{
> + struct port_vdbemem *port = (struct port_vdbemem *) base;
> + struct region *region = &fiber()->gc;
> + size_t region_svp = region_used(region);
> + bool is_error = false;
> + struct mpstream stream;
> + mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb,
> + set_encode_error, &is_error);
> + mpstream_encode_array(&stream, port->size);
> +
> + for (uint32_t i = 0; i < port->size && !is_error; i++) {
> + sql_value *param =
> + (sql_value *)((struct Mem *)port->mem + i);
> + switch (sql_value_type(param)) {
> + case MP_INT: {
> + sql_int64 val = sql_value_int64(param);
> + if (val < 0)
> + mpstream_encode_int(&stream, val);
> + else
> + mpstream_encode_uint(&stream, val);
> + break;
> + }
> + case MP_DOUBLE: {
> + mpstream_encode_double(&stream,
> + sql_value_double(param));
> + break;
> + }
> + case MP_STR: {
> + mpstream_encode_str(&stream,
> + (const char *) sql_value_text(param));
> + break;
> + }
> + case MP_BIN: {
> + mpstream_encode_binl(&stream, sql_value_bytes(param));
> + mpstream_memcpy(&stream, sql_value_blob(param),
> + sql_value_bytes(param));
> + break;
> + }
> + case MP_NIL: {
> + mpstream_encode_nil(&stream);
> + break;
> + }
> + case MP_BOOL: {
> + mpstream_encode_bool(&stream, sql_value_boolean(param));
> + break;
> + }
> + default:
> + unreachable();
> + }
> + }
> + mpstream_flush(&stream);
> + *size = region_used(region) - region_svp;
> + if (is_error)
> + goto error;
> + const char *ret = (char *)region_join(region, *size);
> + if (ret == NULL)
> + goto error;
> + return ret;
> +error:
> + diag_set(OutOfMemory, *size, "region", "ret");
> + return NULL;
> +}
> +
> static const struct port_vtab port_vdbemem_vtab = {
> .dump_msgpack = NULL,
> .dump_msgpack_16 = NULL,
> - .dump_lua = NULL,
> + .dump_lua = port_vdbemem_dump_lua,
> .dump_plain = NULL,
> - .get_msgpack = NULL,
> + .get_msgpack = port_vdbemem_get_msgpack,
> .get_vdbemem = port_vdbemem_get_vdbemem,
> .get_context = port_vdbemem_get_context,
> .destroy = NULL,
> };
>
> +
> +struct sql_value *
> +port_lua_get_vdbemem(struct port *base, uint32_t *size)
> +{
> + struct port_lua *port = (struct port_lua *) base;
> + struct lua_State *L = port->L;
> + int argc = lua_gettop(L);
> + *size = argc;
> + struct region *region = &fiber()->gc;
> + size_t region_svp = region_used(region);
> + struct Mem *val = vdbemem_alloc_on_region(argc);
> + if (val == NULL)
> + return NULL;
> + for (int i = 0; i < argc; i++) {
> + switch(lua_type(L, -1 - i)) {
> + case LUA_TBOOLEAN:
> + mem_set_bool(&val[i], lua_toboolean(L, -1 - i));
> + break;
> + case LUA_TNUMBER:
> + sqlVdbeMemSetDouble(&val[i], lua_tonumber(L, -1 - i));
> + break;
> + case LUA_TSTRING:
> + if (sqlVdbeMemSetStr(&val[i], lua_tostring(L, -1 - i), -1,
> + 1, SQL_TRANSIENT) != 0)
> + goto error;
> + break;
> + case LUA_TNIL:
> + sqlVdbeMemSetNull(&val[i]);
> + break;
> + default:
> + diag_set(ClientError, ER_SQL_EXECUTE,
> + "Unsupported type passed from Lua");
> + goto error;
> + }
> + }
> + return (struct sql_value *)val;
> +error:
> + for (int i = 0; i < argc; i++)
> + sqlVdbeMemRelease(&val[i]);
> + region_truncate(region, region_svp);
> + return NULL;
> +}
> +
> +struct sql_value *
> +port_tuple_get_vdbemem(struct port *base, uint32_t *size)
> +{
> + struct port_tuple *port = (struct port_tuple *)base;
> + *size = port->size;
> + struct region *region = &fiber()->gc;
> + size_t region_svp = region_used(region);
> + struct Mem *val = vdbemem_alloc_on_region(port->size);
> + if (val == NULL)
> + return NULL;
> + int i = 0;
> + struct port_tuple_entry *pe;
> + for (pe = port->first; pe != NULL; pe = pe->next) {
> + const char *data = tuple_data(pe->tuple);
> + if (mp_typeof(*data) != MP_ARRAY ||
> + mp_decode_array(&data) != 1) {
> + diag_set(ClientError, ER_SQL_EXECUTE,
> + "Unsupported type passed from C");
> + goto error;
> + }
> + uint32_t len;
> + const char *str;
> + switch (mp_typeof(*data)) {
> + case MP_BOOL:
> + mem_set_bool(&val[i], mp_decode_bool(&data));
> + break;
> + case MP_FLOAT:
> + sqlVdbeMemSetDouble(&val[i], mp_decode_float(&data));
> + break;
> + case MP_DOUBLE:
> + sqlVdbeMemSetDouble(&val[i], mp_decode_double(&data));
> + break;
> + case MP_INT:
> + sqlVdbeMemSetInt64(val, mp_decode_int(&data));
> + break;
> + case MP_UINT:
> + sqlVdbeMemSetInt64(val, mp_decode_uint(&data));
> + break;
> + case MP_STR:
> + str = mp_decode_str(&data, &len);
> + if (sqlVdbeMemSetStr(&val[i], str, len,
> + 1, SQL_TRANSIENT) != 0)
> + goto error;
> + break;
> + case MP_NIL:
> + sqlVdbeMemSetNull(val);
> + break;
> + default:
> + diag_set(ClientError, ER_SQL_EXECUTE,
> + "Unsupported type passed from C");
> + goto error;
> + }
> + i++;
> + }
> + return (struct sql_value *) val;
> +error:
> + for (int i = 0; i < port->size; i++)
> + sqlVdbeMemRelease(&val[i]);
> + region_truncate(region, region_svp);
> + return NULL;
> +}
> +
> /*
> * Implementation of the non-aggregate min() and max() functions
> */
> @@ -714,19 +924,6 @@ sql_builtin_ICU##case_type(struct func *func, struct port *args, \
> ICU_CASE_CONVERT(Lower);
> ICU_CASE_CONVERT(Upper);
>
> -
> -/*
> - * Some functions like COALESCE() and IFNULL() are implemented
> - * as VDBE code so that unused argument values do not have to be
> - * computed.
> - * However, we still need some kind of function implementation for
> - * this routines in the function table. The sql_builtin_noop macro
> - * provides this. sql_builtin_noop will never be called so it
> - * doesn't matter what the implementation is. We might as well
> - * use the "version()" function as a substitute.
> - */
> -#define sql_builtin_noop sql_builtin_version
> -
> /*
> * Implementation of random(). Return a random integer.
> */
> @@ -2028,142 +2225,302 @@ sql_builtin_group_concat_finalize(struct func *func, struct port *args,
> }
>
> int
> -sql_is_like_func(struct sql *db, struct Expr *expr, int *is_like_ci)
> +sql_is_like_func(struct Expr *expr, int *is_like_ci)
> {
> if (expr->op != TK_FUNCTION || !expr->x.pList ||
> expr->x.pList->nExpr != 2)
> return 0;
> assert(!ExprHasProperty(expr, EP_xIsSelect));
> - struct FuncDef *func = sqlFindFunction(db, expr->u.zToken, 2, 0);
> + struct func *func =
> + sql_func_by_signature(expr->u.zToken, strlen(expr->u.zToken), 2);
> assert(func != NULL);
> - if ((func->funcFlags & SQL_FUNC_LIKE) == 0)
> + if ((func->def->sql_flags & SQL_FUNC_LIKE) == 0)
> return 0;
> *is_like_ci =
> (current_session()->sql_flags & LIKE_CASE_SENS_FLAG) == 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
> - */
> -void
> -sqlRegisterBuiltinFunctions(void)
> +static struct func_vtab func_sql_builtin_vtab;
> +
> +struct func *
> +sql_func_by_signature(const char *name, uint32_t name_len, int argc)
> {
> - /*
> - * The following array holds FuncDef structures for all of the functions
> - * defined in this file.
> - *
> - * The array cannot be constant since changes are made to the
> - * FuncDef.pHash elements at start-time. The elements of this array
> - * are read-only after initialization is complete.
> - *
> - * For peak efficiency, put the most frequently used function last.
> - */
> - static FuncDef aBuiltinFunc[] = {
> - FUNCTION_COLL(trim, 1, 3, 0, sql_builtin_trim_one_arg),
> - FUNCTION_COLL(trim, 2, 3, 0, sql_builtin_trim_two_args),
> - FUNCTION_COLL(trim, 3, 3, 0, sql_builtin_trim_three_args),
> - FUNCTION(min, -1, 0, 1, sql_builtin_minmax, FIELD_TYPE_SCALAR),
> - FUNCTION(min, 0, 0, 1, 0, FIELD_TYPE_SCALAR),
> - AGGREGATE2(min, 1, 0, 1, sql_builtin_minmax_step,
> - sql_builtin_minmax_finalize, SQL_FUNC_MINMAX,
> - FIELD_TYPE_SCALAR),
> - FUNCTION(max, -1, 1, 1, sql_builtin_minmax, FIELD_TYPE_SCALAR),
> - FUNCTION(max, 0, 1, 1, 0, FIELD_TYPE_SCALAR),
> - AGGREGATE2(max, 1, 1, 1, sql_builtin_minmax_step,
> - sql_builtin_minmax_finalize, SQL_FUNC_MINMAX,
> - FIELD_TYPE_SCALAR),
> - FUNCTION2(typeof, 1, 0, 0, sql_builtin_typeof, SQL_FUNC_TYPEOF,
> - FIELD_TYPE_STRING),
> - FUNCTION2(length, 1, 0, 0, sql_builtin_length, SQL_FUNC_LENGTH,
> - FIELD_TYPE_INTEGER),
> - FUNCTION(position, 2, 0, 1, sql_builtin_position,
> - FIELD_TYPE_INTEGER),
> - FUNCTION(printf, -1, 0, 0, sql_builtin_printf,
> - FIELD_TYPE_STRING),
> - FUNCTION(unicode, 1, 0, 0, sql_builtin_unicode,
> - FIELD_TYPE_STRING),
> - FUNCTION(char, -1, 0, 0, sql_builtin_char, FIELD_TYPE_STRING),
> - FUNCTION(abs, 1, 0, 0, sql_builtin_abs, FIELD_TYPE_NUMBER),
> - FUNCTION(round, 1, 0, 0, sql_builtin_round, FIELD_TYPE_INTEGER),
> - FUNCTION(round, 2, 0, 0, sql_builtin_round, FIELD_TYPE_INTEGER),
> - FUNCTION_COLL(upper, 1, 0, 1, sql_builtin_ICUUpper),
> - FUNCTION_COLL(lower, 1, 0, 1, sql_builtin_ICULower),
> - FUNCTION(hex, 1, 0, 0, sql_builtin_hex, FIELD_TYPE_STRING),
> - FUNCTION2(ifnull, 2, 0, 0, sql_builtin_noop, SQL_FUNC_COALESCE,
> - FIELD_TYPE_INTEGER),
> - VFUNCTION(random, 0, 0, 0, sql_builtin_random,
> - FIELD_TYPE_INTEGER),
> - VFUNCTION(randomblob, 1, 0, 0, sql_builtin_random_blob,
> - FIELD_TYPE_SCALAR),
> - FUNCTION(nullif, 2, 0, 1, sql_builtin_nullif,
> - FIELD_TYPE_SCALAR),
> - FUNCTION(version, 0, 0, 0, sql_builtin_version,
> - FIELD_TYPE_STRING),
> - FUNCTION(quote, 1, 0, 0, sql_builtin_quote, FIELD_TYPE_STRING),
> - VFUNCTION(row_count, 0, 0, 0, sql_builtin_row_count,
> - FIELD_TYPE_INTEGER),
> - FUNCTION_COLL(replace, 3, 0, 0, sql_builtin_replace),
> - FUNCTION(zeroblob, 1, 0, 0, sql_builtin_zeroblob,
> - FIELD_TYPE_SCALAR),
> - FUNCTION_COLL(substr, 2, 0, 0, sql_builtin_substr),
> - FUNCTION_COLL(substr, 3, 0, 0, sql_builtin_substr),
> - AGGREGATE(sum, 1, 0, 0, sql_builtin_sum_step,
> - sql_builtin_sum_finalize, FIELD_TYPE_NUMBER),
> - AGGREGATE(total, 1, 0, 0, sql_builtin_sum_step,
> - sql_builtin_total_finalize, FIELD_TYPE_NUMBER),
> - AGGREGATE(avg, 1, 0, 0, sql_builtin_sum_step,
> - sql_builtin_avg_finalize, FIELD_TYPE_NUMBER),
> - AGGREGATE2(count, 0, 0, 0, sql_builtin_count_step,
> - sql_builtin_count_finalize, SQL_FUNC_COUNT,
> - FIELD_TYPE_INTEGER),
> - AGGREGATE(count, 1, 0, 0, sql_builtin_count_step,
> - sql_builtin_count_finalize, FIELD_TYPE_INTEGER),
> - AGGREGATE(group_concat, 1, 0, 0, sql_builtin_group_concat_step,
> - sql_builtin_group_concat_finalize, FIELD_TYPE_STRING),
> - AGGREGATE(group_concat, 2, 0, 0, sql_builtin_group_concat_step,
> - sql_builtin_group_concat_finalize, FIELD_TYPE_STRING),
> - FUNCTION2(like, 2, 1, 0, sql_builtin_like, SQL_FUNC_LIKE,
> - FIELD_TYPE_INTEGER),
> - FUNCTION2(like, 3, 1, 0, sql_builtin_like, 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, sql_builtin_noop,
> - SQL_FUNC_COALESCE, FIELD_TYPE_SCALAR),
> - };
> - sql_register_analyze_builtins();
> - sqlRegisterDateTimeFunctions();
> - sqlInsertBuiltinFuncs(aBuiltinFunc, ArraySize(aBuiltinFunc));
> -
> -#if 0 /* Enable to print out how the built-in functions are hashed */
> - {
> - int i;
> - FuncDef *p;
> - for (i = 0; i < SQL_FUNC_HASH_SZ; i++) {
> - printf("FUNC-HASH %02d:", i);
> - for (p = sqlBuiltinFunctions.a[i]; p;
> - p = p->u.pHash) {
> - int n = sqlStrlen30(p->zName);
> - int h = p->zName[0] + n;
> - printf(" %s(%d)", p->zName, h);
> - }
> - printf("\n");
> + struct func *ret = NULL;
> + struct func *main_func = func_by_name(name, name_len);
> + if (main_func == NULL || argc == -2)
> + return main_func;
> + if (main_func->def->param_count == argc) {
> + ret = main_func;
> + goto end;
> + }
> + ret = main_func->def->param_count < 0 ? main_func : NULL;
> + struct func *func;
> + rlist_foreach_entry(func, &main_func->signature, signature) {
> + /** Exact match. */
> + if (func->def->param_count == argc) {
> + ret = func;
> + goto end;
> }
> + /** Multiparam function. */
> + if (func->def->param_count < 0)
> + ret = func;
> }
> -#endif
> +end:
> + return ret != NULL && ret->def->exports.sql ? ret : NULL;
> }
>
> -struct func_sql_builtin {
> - /** Function object base class. */
> - struct func base;
> +static int
> +sql_builtin_stub(struct func *func, struct port *args, struct port *ret)
> +{
> + (void) func; (void) args; (void) ret;
> + struct Mem *val = vdbemem_alloc_on_region(1);
> + if (val == NULL)
> + return -1;
> + diag_set(ClientError, ER_SQL_EXECUTE, "function is not implemented");
> + return -1;
> +}
> +
> +struct sql_builtin_entry {
> + int param_count;
> + enum field_type returns;
> + uint16_t sql_flags;
> + int (*call)(struct func *func, struct port *args, struct port *ret);
> + int (*finalize)(struct func *func, struct port *args, struct port *ret);
> + void *user_data;
> + enum func_aggregate aggregate;
> + bool is_deterministic;
> + bool is_available;
> };
>
> -static struct func_vtab func_sql_builtin_vtab;
> +#define BUILTIN_FUNCTION_ENTRY(param_count, returns, flags, call,\
> + user_data, is_deterministic, \
> + is_available) \
> + {param_count, returns, flags, call, NULL, user_data, \
> + FUNC_AGGREGATE_NONE, is_deterministic, is_available}
> +
> +#define BUILTIN_AGGREGATE_ENTRY(param_count, returns, flags, call,\
> + finalize, user_data, is_available)\
> + {param_count, returns, flags, call, finalize, user_data, \
> + FUNC_AGGREGATE_GROUP, false, is_available}
> +
> +#define BUILTIN_STUB_ENTRY() \
> + {-1, FIELD_TYPE_ANY, 0, sql_builtin_stub, NULL, \
> + NULL, FUNC_AGGREGATE_NONE, false, false}
> +
> +/**
> + * A sequence of SQL builtins definitions in
> + * lexicographic order.
> + */
> +static struct {
> + const char *name;
> + int entries;
> + struct sql_builtin_entry entry[5];
> +} sql_builtins[] = {
> + {"ABS", 1, {
> + BUILTIN_FUNCTION_ENTRY(1, FIELD_TYPE_NUMBER, 0,
> + sql_builtin_abs, NULL, true, true),
> + }},
> + {"AVG", 1, {
> + BUILTIN_AGGREGATE_ENTRY(1, FIELD_TYPE_NUMBER, 0,
> + sql_builtin_sum_step,
> + sql_builtin_avg_finalize, NULL, true),
> + }},
> + {"CEIL", 1, {BUILTIN_STUB_ENTRY()}},
> + {"CEILING", 1, {BUILTIN_STUB_ENTRY()}},
> + {"CHAR", 1, {
> + BUILTIN_FUNCTION_ENTRY(-1, FIELD_TYPE_STRING, 0,
> + sql_builtin_char, NULL, true, true),
> + }},
> + {"CHARACTER_LENGTH", 1, {BUILTIN_STUB_ENTRY()}},
> + {"CHAR_LENGTH", 1, {BUILTIN_STUB_ENTRY()}},
> + {"COALESCE", 3, {
> + BUILTIN_FUNCTION_ENTRY(1, FIELD_TYPE_SCALAR, 0,
> + sql_builtin_stub, NULL, true, false),
> + BUILTIN_FUNCTION_ENTRY(0, FIELD_TYPE_SCALAR, 0,
> + sql_builtin_stub, NULL, true, false),
> + BUILTIN_FUNCTION_ENTRY(-1, FIELD_TYPE_SCALAR, SQL_FUNC_COALESCE,
> + sql_builtin_stub, NULL, true, true),
> + }},
> + {"COUNT", 2, {
> + BUILTIN_AGGREGATE_ENTRY(0, FIELD_TYPE_INTEGER,
> + SQL_FUNC_COUNT,
> + sql_builtin_count_step,
> + sql_builtin_count_finalize, NULL, true),
> + BUILTIN_AGGREGATE_ENTRY(1, FIELD_TYPE_INTEGER, 0,
> + sql_builtin_count_step,
> + sql_builtin_count_finalize, NULL, true),
> + }},
> + {"CURRENT_DATE", 1, {BUILTIN_STUB_ENTRY()}},
> + {"CURRENT_TIME", 1, {BUILTIN_STUB_ENTRY()}},
> + {"CURRENT_TIMESTAMP", 1, {BUILTIN_STUB_ENTRY()}},
> + {"DATE", 1, {BUILTIN_STUB_ENTRY()}},
> + {"DATETIME", 1, {BUILTIN_STUB_ENTRY()}},
> + {"EVERY", 1, {BUILTIN_STUB_ENTRY()}},
> + {"EXISTS", 1, {BUILTIN_STUB_ENTRY()}},
> + {"EXP", 1, {BUILTIN_STUB_ENTRY()}},
> + {"EXTRACT", 1, {BUILTIN_STUB_ENTRY()}},
> + {"FLOOR", 1, {BUILTIN_STUB_ENTRY()}},
> + {"GREATER", 1, {BUILTIN_STUB_ENTRY()}},
> + {"GROUP_CONCAT", 2, {
> + BUILTIN_AGGREGATE_ENTRY(1, FIELD_TYPE_STRING, 0,
> + sql_builtin_group_concat_step,
> + sql_builtin_group_concat_finalize, NULL, true),
> + BUILTIN_AGGREGATE_ENTRY(2, FIELD_TYPE_STRING, 1,
> + sql_builtin_group_concat_step,
> + sql_builtin_group_concat_finalize, NULL, true),
> + }},
> + {"HEX", 1, {
> + BUILTIN_FUNCTION_ENTRY(1, FIELD_TYPE_STRING, 0,
> + sql_builtin_hex, NULL, true, true),
> + }},
> + {"IFNULL", 1, {
> + BUILTIN_FUNCTION_ENTRY(2, FIELD_TYPE_INTEGER, SQL_FUNC_COALESCE,
> + sql_builtin_stub, NULL, true, true),
> + }},
> + {"JULIANDAY", 1, {BUILTIN_STUB_ENTRY()}},
> + {"LENGTH", 1, {
> + BUILTIN_FUNCTION_ENTRY(1, FIELD_TYPE_INTEGER, SQL_FUNC_LENGTH,
> + sql_builtin_length, NULL, true, true),
> + }},
> + {"LESSER", 1, {BUILTIN_STUB_ENTRY()}},
> + {"LIKE", 2, {
> + BUILTIN_FUNCTION_ENTRY(2, FIELD_TYPE_INTEGER, SQL_FUNC_LIKE,
> + sql_builtin_like, SQL_INT_TO_PTR(1),
> + true, true),
> + BUILTIN_FUNCTION_ENTRY(3, FIELD_TYPE_INTEGER, SQL_FUNC_LIKE,
> + sql_builtin_like, SQL_INT_TO_PTR(1),
> + true, true),
> + }},
> + {"LN", 1, {BUILTIN_STUB_ENTRY()}},
> + {"LOWER", 1, {
> + BUILTIN_FUNCTION_ENTRY(1, FIELD_TYPE_STRING,
> + SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
> + sql_builtin_ICULower, NULL, true, true),
> + }},
> + {"MAX", 3, {
> + BUILTIN_FUNCTION_ENTRY(-1, FIELD_TYPE_SCALAR, SQL_FUNC_NEEDCOLL,
> + sql_builtin_minmax, SQL_INT_TO_PTR(1),
> + true, true),
> + BUILTIN_FUNCTION_ENTRY(0, FIELD_TYPE_SCALAR, 0,
> + sql_builtin_stub, SQL_INT_TO_PTR(1),
> + true, false),
> + BUILTIN_AGGREGATE_ENTRY(1, FIELD_TYPE_SCALAR,
> + SQL_FUNC_NEEDCOLL | SQL_FUNC_MINMAX,
> + sql_builtin_minmax_step,
> + sql_builtin_minmax_finalize,
> + SQL_INT_TO_PTR(1), true),
> + }},
> + {"MIN", 3, {
> + BUILTIN_FUNCTION_ENTRY(-1, FIELD_TYPE_SCALAR, SQL_FUNC_NEEDCOLL,
> + sql_builtin_minmax, SQL_INT_TO_PTR(0),
> + true, true),
> + BUILTIN_FUNCTION_ENTRY(0, FIELD_TYPE_SCALAR, 0,
> + sql_builtin_stub, SQL_INT_TO_PTR(0),
> + true, false),
> + BUILTIN_AGGREGATE_ENTRY(1, FIELD_TYPE_SCALAR,
> + SQL_FUNC_NEEDCOLL | SQL_FUNC_MINMAX,
> + sql_builtin_minmax_step,
> + sql_builtin_minmax_finalize,
> + SQL_INT_TO_PTR(0), true),
> + }},
> + {"MOD", 1, {BUILTIN_STUB_ENTRY()}},
> + {"NULLIF", 1, {
> + BUILTIN_FUNCTION_ENTRY(2, FIELD_TYPE_SCALAR, SQL_FUNC_NEEDCOLL,
> + sql_builtin_nullif, NULL, true, true),
> + }},
> + {"OCTET_LENGTH", 1, {BUILTIN_STUB_ENTRY()}},
> + {"POSITION", 1, {
> + BUILTIN_FUNCTION_ENTRY(2, FIELD_TYPE_INTEGER, SQL_FUNC_NEEDCOLL,
> + sql_builtin_position, NULL, true, true),
> + }},
> + {"POWER", 1, {BUILTIN_STUB_ENTRY()}},
> + {"PRINTF", 1, {
> + BUILTIN_FUNCTION_ENTRY(-1, FIELD_TYPE_STRING, 0,
> + sql_builtin_printf, NULL, true, true),
> + }},
> + {"QUOTE", 1, {
> + BUILTIN_FUNCTION_ENTRY(1, FIELD_TYPE_STRING, 0,
> + sql_builtin_quote, NULL, true, true),
> + }},
> + {"RANDOM", 1, {
> + BUILTIN_FUNCTION_ENTRY(0, FIELD_TYPE_INTEGER, 0,
> + sql_builtin_random, NULL, false, true),
> + }},
> + {"RANDOMBLOB", 1, {
> + BUILTIN_FUNCTION_ENTRY(1, FIELD_TYPE_SCALAR, 0,
> + sql_builtin_random_blob, NULL, false, true),
> + }},
> + {"REPLACE", 1, {
> + BUILTIN_FUNCTION_ENTRY(3, FIELD_TYPE_STRING, SQL_FUNC_DERIVEDCOLL,
> + sql_builtin_replace, NULL, true, true),
> + }},
> + {"ROUND", 2, {
> + BUILTIN_FUNCTION_ENTRY(1, FIELD_TYPE_INTEGER, 0,
> + sql_builtin_round, NULL, true, true),
> + BUILTIN_FUNCTION_ENTRY(2, FIELD_TYPE_INTEGER, 0,
> + sql_builtin_round, NULL, true, true),
> + }},
> + {"ROW_COUNT", 1, {
> + BUILTIN_FUNCTION_ENTRY(0, FIELD_TYPE_INTEGER, 0,
> + sql_builtin_row_count, NULL, true, true),
> + }},
> + {"SOME", 1, {BUILTIN_STUB_ENTRY()}},
> + {"SQRT", 1, {BUILTIN_STUB_ENTRY()}},
> + {"STRFTIME", 1, {BUILTIN_STUB_ENTRY()}},
> + {"SUBSTR", 2, {
> + BUILTIN_FUNCTION_ENTRY(2, FIELD_TYPE_STRING, SQL_FUNC_DERIVEDCOLL,
> + sql_builtin_substr, NULL, true, true),
> + BUILTIN_FUNCTION_ENTRY(3, FIELD_TYPE_STRING, SQL_FUNC_DERIVEDCOLL,
> + sql_builtin_substr, NULL, true, true),
> + }},
> + {"SUM", 1, {
> + BUILTIN_AGGREGATE_ENTRY(1, FIELD_TYPE_NUMBER, 0,
> + sql_builtin_sum_step,
> + sql_builtin_sum_finalize, NULL, true),
> + }},
> + {"TIME", 1, {BUILTIN_STUB_ENTRY()}},
> + {"TOTAL", 1, {
> + BUILTIN_AGGREGATE_ENTRY(1, FIELD_TYPE_NUMBER, 0,
> + sql_builtin_sum_step,
> + sql_builtin_total_finalize, NULL, true),
> + }},
> + {"TRIM", 3, {
> + BUILTIN_FUNCTION_ENTRY(1, FIELD_TYPE_STRING, SQL_FUNC_DERIVEDCOLL,
> + sql_builtin_trim_one_arg, NULL,
> + true, true),
> + BUILTIN_FUNCTION_ENTRY(2, FIELD_TYPE_STRING, SQL_FUNC_DERIVEDCOLL,
> + sql_builtin_trim_two_args, NULL,
> + true, true),
> + BUILTIN_FUNCTION_ENTRY(3, FIELD_TYPE_STRING, SQL_FUNC_DERIVEDCOLL,
> + sql_builtin_trim_three_args, NULL,
> + true, true),
> + }},
> + {"TYPEOF", 1, {
> + BUILTIN_FUNCTION_ENTRY(1, FIELD_TYPE_STRING, SQL_FUNC_TYPEOF,
> + sql_builtin_typeof, NULL, true, true),
> + }},
> + {"UNICODE", 1, {
> + BUILTIN_FUNCTION_ENTRY(1, FIELD_TYPE_STRING, 0,
> + sql_builtin_unicode, NULL, true, true),
> + }},
> + {"UPPER", 1, {
> + BUILTIN_FUNCTION_ENTRY(1, FIELD_TYPE_STRING,
> + SQL_FUNC_DERIVEDCOLL | SQL_FUNC_NEEDCOLL,
> + sql_builtin_ICUUpper, NULL, true, true),
> + }},
> + {"VERSION", 1, {
> + BUILTIN_FUNCTION_ENTRY(0, FIELD_TYPE_STRING, 0,
> + sql_builtin_version, NULL, true, true),
> + }},
> + {"ZEROBLOB", 1, {
> + BUILTIN_FUNCTION_ENTRY(1, FIELD_TYPE_SCALAR, 0,
> + sql_builtin_zeroblob, NULL, true, true),
> + }},
> + {"_sql_stat_get", 1, {BUILTIN_STUB_ENTRY()}},
> + {"_sql_stat_init", 1, {BUILTIN_STUB_ENTRY()}},
> + {"_sql_stat_push", 1, {BUILTIN_STUB_ENTRY()}},
> +};
>
> struct func *
> func_sql_builtin_new(struct func_def *def)
> @@ -2175,26 +2532,88 @@ func_sql_builtin_new(struct func_def *def)
> "with SQL language");
> return NULL;
> }
> - struct func_sql_builtin *func =
> - (struct func_sql_builtin *) malloc(sizeof(*func));
> - if (func == NULL) {
> - diag_set(OutOfMemory, sizeof(*func), "malloc", "func");
> + /** 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;
> + }
> + if (idx == -1) {
> + diag_set(ClientError, ER_CREATE_FUNCTION, def->name,
> + "unknown sql builtin name");
> + return NULL;
> + }
> + int func_cnt = sql_builtins[idx].entries;
> + struct func_sql_builtin *funcs =
> + (struct func_sql_builtin *) malloc(sizeof(*funcs) * func_cnt);
> + if (funcs == NULL) {
> + diag_set(OutOfMemory, sizeof(*funcs) * func_cnt,
> + "malloc", "func");
> return NULL;
> }
> + int func_idx = 0;
> + for (; func_idx < func_cnt; func_idx++) {
> + struct func_def *curr_def;
> + if (func_idx == 0) {
> + curr_def = def;
> + rlist_create(&funcs[func_idx].base.signature);
> + } else {
> + curr_def = func_def_dup(def);
> + if (curr_def == NULL)
> + goto error;
> + rlist_add(&funcs[0].base.signature,
> + &funcs[func_idx].base.signature);
> + }
> + curr_def->is_deterministic =
> + sql_builtins[idx].entry[func_idx].is_deterministic;
> + curr_def->sql_flags =
> + sql_builtins[idx].entry[func_idx].sql_flags;
> + curr_def->param_count =
> + sql_builtins[idx].entry[func_idx].param_count;
> + curr_def->returns =
> + sql_builtins[idx].entry[func_idx].returns;
> + curr_def->aggregate =
> + sql_builtins[idx].entry[func_idx].aggregate;
> + curr_def->exports.sql =
> + sql_builtins[idx].entry[func_idx].is_available;
> + funcs[func_idx].base.def = curr_def;
> + funcs[func_idx].user_data =
> + sql_builtins[idx].entry[func_idx].user_data;
> + funcs[func_idx].finalize =
> + sql_builtins[idx].entry[func_idx].finalize;
> + funcs[func_idx].base.vtab = func_sql_builtin_vtab;
> + funcs[func_idx].base.vtab.call =
> + sql_builtins[idx].entry[func_idx].call;
> + }
> /** Don't export SQL builtins in Lua for now. */
> def->exports.lua = false;
> - func->base.vtab = func_sql_builtin_vtab;
> - return &func->base;
> + return &funcs[0].base;
> +error:
> + for (int i = 1; i < func_idx; i++)
> + free(funcs[func_idx].base.def);
> + free(funcs);
> + return NULL;
> }
>
> static void
> func_sql_builtin_destroy(struct func *base)
> {
> assert(base != NULL && base->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
> + struct func *func, *tmp;
> + rlist_foreach_entry_safe(func, &base->signature, signature, tmp)
> + free(func->def);
> free(base);
> }
>
> static struct func_vtab func_sql_builtin_vtab = {
> - .call = NULL,
> + .call = sql_builtin_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 baaf7dcc9..16505ac7f 100644
> --- a/src/box/sql/main.c
> +++ b/src/box/sql/main.c
> @@ -127,9 +127,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;
> @@ -177,25 +174,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
> @@ -214,118 +192,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(struct sql * db, const char *zFunctionName, enum field_type type,
> - int nArg, int flags, void *pUserData,
> - int (*xSFunc) (struct func *func, struct port *args,
> - struct port *ret),
> - int (*xStep) (struct func *func, struct port *args,
> - struct port *ret),
> - int (*xFinal) (struct func *func, struct port *args,
> - struct port *ret),
> - struct 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(struct sql * db, const char *zFunc, enum field_type type,
> - int nArg, int flags, void *p,
> - int (*xSFunc) (struct func *func, struct port *args,
> - struct port *ret),
> - int (*xStep) (struct func *func, struct port *args,
> - struct port *ret),
> - int (*xFinal) (struct func *func, struct port *args,
> - struct port *ret),
> - void (*xDestroy) (void *ctx))
> -{
> - 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 7c81b1980..7c67d5d56 100644
> --- a/src/box/sql/resolve.c
> +++ b/src/box/sql/resolve.c
> @@ -38,6 +38,8 @@
> #include "sqlInt.h"
> #include <stdlib.h>
> #include <string.h>
> +#include "box/func.h"
> +#include "box/field_def.h"
>
> /*
> * Walk the expression tree pExpr and increase the aggregate function
> @@ -576,36 +578,31 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
> 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) {
> + const char *name = pExpr->u.zToken;
> + uint32_t name_len = strlen(name);
> + struct func *func =
> + sql_func_by_signature(name, name_len, n);
> + if (func == 0) {
> + func = sql_func_by_signature(name, name_len, -2);
> + if (func == NULL) {
> no_such_func = 1;
> } else {
> wrong_num_args = 1;
> }
> } else {
> - is_agg = pDef->xFinalize != 0;
> - pExpr->type = pDef->ret_type;
> - if (pDef->
> - funcFlags & (SQL_FUNC_CONSTANT |
> - SQL_FUNC_SLOCHNG)) {
> + is_agg = func->def->language ==
> + FUNC_LANGUAGE_SQL_BUILTIN &&
> + func->def->aggregate ==
> + FUNC_AGGREGATE_GROUP;
> + pExpr->type = func->def->returns;
> + 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.
> @@ -614,22 +611,21 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
> }
> }
> if (is_agg && (pNC->ncFlags & NC_AllowAgg) == 0) {
> - const char *err =
> - tt_sprintf("misuse of aggregate "\
> - "function %.*s()", nId, zId);
> - diag_set(ClientError, ER_SQL_PARSER_GENERIC, err);
> + diag_set(ClientError, ER_SQL_PARSER_GENERIC,
> + tt_sprintf("misuse of aggregate "
> + "function %s()", name));
> 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);
> + diag_set(ClientError, ER_NO_SUCH_FUNCTION,
> + name);
> 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));
> + tt_sprintf("wrong number of arguments "
> + "to function %s()", name));
> pParse->is_aborted = true;
> pNC->nErr++;
> }
> @@ -648,7 +644,7 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
> pExpr->op2++;
> pNC2 = pNC2->pNext;
> }
> - assert(pDef != 0);
> + assert(func != NULL);
> if (pNC2) {
> assert(SQL_FUNC_MINMAX ==
> NC_MinMaxAgg);
> @@ -656,8 +652,7 @@ resolveExprStep(Walker * pWalker, Expr * pExpr)
> funcFlags &
> SQL_FUNC_MINMAX) != 0);
> pNC2->ncFlags |=
> - NC_HasAgg | (pDef->
> - funcFlags &
> + NC_HasAgg | (func->def->sql_flags &
> SQL_FUNC_MINMAX);
>
> }
> diff --git a/src/box/sql/select.c b/src/box/sql/select.c
> index 7c8da251e..70b39ff30 100644
> --- a/src/box/sql/select.c
> +++ b/src/box/sql/select.c
> @@ -4374,7 +4374,7 @@ is_simple_count(struct Select *select, struct AggInfo *agg_info)
> return NULL;
> if (NEVER(agg_info->nFunc == 0))
> return NULL;
> - if ((agg_info->aFunc[0].pFunc->funcFlags & SQL_FUNC_COUNT) == 0)
> + if ((agg_info->aFunc[0].func->def->sql_flags & SQL_FUNC_COUNT) == 0)
> return NULL;
> if (expr->flags & EP_Distinct)
> return NULL;
> @@ -5269,7 +5269,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);
> }
> }
>
> @@ -5310,11 +5310,11 @@ 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 ((pF->func->def->sql_flags & SQL_FUNC_NEEDCOLL) != 0) {
> struct coll *coll = NULL;
> struct ExprList_item *pItem;
> int j;
> - assert(pList != 0); /* pList!=0 if pF->pFunc has NEEDCOLL */
> + assert(pList != 0);
> bool unused;
> uint32_t id;
> for (j = 0, pItem = pList->a; coll == NULL && j < nArg;
> @@ -5329,7 +5329,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/sqlInt.h b/src/box/sql/sqlInt.h
> index ce15500e5..dc0a06f5a 100644
> --- a/src/box/sql/sqlInt.h
> +++ b/src/box/sql/sqlInt.h
> @@ -520,19 +520,6 @@ sql_initialize(void);
> #define SQL_TRACE_ROW 0x04
> #define SQL_TRACE_CLOSE 0x08
>
> -#define SQL_DETERMINISTIC 0x800
> -
> -int
> -sql_create_function_v2(struct sql * db, const char *zFunc, enum field_type type,
> - int nArg, int flags, void *p,
> - int (*xSFunc) (struct func *func, struct port *args,
> - struct port *ret),
> - int (*xStep) (struct func *func, struct port *args,
> - struct port *ret),
> - int (*xFinal) (struct func *func, struct port *args,
> - struct port *ret),
> - void (*xDestroy) (void *ctx));
> -
> #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() */
> @@ -983,9 +970,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 NameContext NameContext;
> @@ -1030,18 +1014,6 @@ typedef int VList;
> */
> #define SQL_N_LIMIT (SQL_LIMIT_TRIGGER_DEPTH+1)
>
> -/*
> - * 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.
> */
> @@ -1152,62 +1124,14 @@ 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 */
> - int (*xSFunc) (struct func *func, struct port *args, struct port *ret);
> - int (*xFinalize) (struct func *func, struct port *args, struct port *ret);
> - 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.
> + * Possible values for FuncDef.flags. Note that the _LENGTH and
> + * _TYPEOF values must correspond to OPFLAG_LENGTHARG and
> + * OPFLAG_TYPEOFARG.
> *
> * Value constraints (enforced via assert()):
> * SQL_FUNC_MINMAX == NC_MinMaxAgg == SF_MinMaxAgg
> * SQL_FUNC_LENGTH == OPFLAG_LENGTHARG
> * SQL_FUNC_TYPEOF == OPFLAG_TYPEOFARG
> - * SQL_FUNC_CONSTANT == sql_DETERMINISTIC from the API
> */
> #define SQL_FUNC_LIKE 0x0004 /* Candidate for the LIKE optimization */
> #define SQL_FUNC_EPHEM 0x0010 /* Ephemeral. Delete with VDBE */
> @@ -1229,11 +1153,7 @@ struct FuncDestructor {
> * argument.
> */
> #define SQL_FUNC_DERIVEDCOLL 0x0400
> -#define SQL_FUNC_CONSTANT 0x0800 /* Constant inputs give a constant output */
> #define SQL_FUNC_MINMAX 0x1000 /* True for min() and max() aggregates */
> -#define SQL_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a
> - * single query - might change over time
> - */
>
> /*
> * Trim side mask components. TRIM_LEADING means to trim left side
> @@ -1246,72 +1166,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.
> - *
> - * DFUNCTION(zName, nArg, iArg, bNC, xFunc)
> - * Like FUNCTION except it omits the sql_FUNC_CONSTANT flag and
> - * adds the sql_FUNC_SLOCHNG flag. Used for date & time functions,
> - * but not during a single query.
> - *
> - * AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal)
> - * 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 DFUNCTION(zName, nArg, iArg, bNC, xFunc, type) \
> - {nArg, SQL_FUNC_SLOCHNG|(bNC*SQL_FUNC_NEEDCOLL), \
> - SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
> -#define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags, type) \
> - {nArg,SQL_FUNC_CONSTANT|(bNC*SQL_FUNC_NEEDCOLL)|extraFlags,\
> - SQL_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0}, type}
> -#define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \
> - {nArg, SQL_FUNC_SLOCHNG|(bNC*SQL_FUNC_NEEDCOLL), \
> - pArg, 0, xFunc, 0, #zName, {SQL_AFF_STRING, {0}}}
> -#define LIKEFUNC(zName, nArg, arg, flags, type) \
> - {nArg, 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
> @@ -1504,7 +1358,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 */
> /**
> @@ -1654,7 +1509,8 @@ struct Expr {
> #define EP_Static 0x008000 /* Held in memory not obtained from malloc() */
> #define EP_MemToken 0x010000 /* Need to sqlDbFree() Expr.zToken */
> #define EP_NoReduce 0x020000 /* Cannot EXPRDUP_REDUCE this Expr */
> -#define EP_ConstFunc 0x080000 /* A sql_FUNC_CONSTANT or _SLOCHNG function */
> +/** A deterministic function. */
> +#define EP_ConstFunc 0x080000
> #define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */
> #define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */
> #define EP_Alias 0x400000 /* Is an alias for a result set column */
> @@ -3462,10 +3318,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.
> @@ -4037,7 +3889,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;
>
> /**
> @@ -4218,18 +4069,7 @@ sql_key_info_to_key_def(struct sql_key_info *key_info);
> * @retval 1 if LIKE optimization can be used, 0 otherwise.
> */
> int
> -sql_is_like_func(struct sql *db, struct Expr *expr, int *is_like_ci);
> -
> -int
> -sqlCreateFunc(struct sql * db, const char *zFunctionName, enum field_type type,
> - int nArg, int flags, void *pUserData,
> - int (*xSFunc) (struct func *func, struct port *args,
> - struct port *ret),
> - int (*xStep) (struct func *func, struct port *args,
> - struct port *ret),
> - int (*xFinal) (struct func *func, struct port *args,
> - struct port *ret),
> - struct FuncDestructor * pDestructor);
> +sql_is_like_func(struct Expr *expr, int *is_like_ci);
>
> /** Set OOM error flag. */
> static inline void
> @@ -4456,10 +4296,6 @@ Expr *sqlExprForVectorField(Parse *, Expr *, int);
> */
> extern int sqlSubProgramsRemaining;
>
> -/** Register built-in functions to work with ANALYZE data. */
> -void
> -sql_register_analyze_builtins(void);
> -
> /**
> * Generate VDBE code to halt execution with correct error if
> * the object with specified key is already present (or doesn't
> diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
> index 92c99877a..eb18d4b7a 100644
> --- a/src/box/sql/vdbe.c
> +++ b/src/box/sql/vdbe.c
> @@ -1722,14 +1722,14 @@ case OP_CollSeq: {
> case OP_Function0: {
> sql_context *pCtx;
>
> - assert(pOp->p4type==P4_FUNCDEF);
> + assert(pOp->p4type == P4_FUNC);
> assert(pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor));
> assert(pOp->p5 == 0 ||
> (pOp->p2 > 0 && pOp->p2 + pOp->p5 <= (p->nMem+1 - p->nCursor)+1));
> assert(pOp->p3 < pOp->p2 || pOp->p3>=pOp->p2 + pOp->p5);
> pCtx = sqlDbMallocRawNN(db, sizeof(*pCtx));
> if (pCtx==0) goto no_mem;
> - pCtx->pFunc = pOp->p4.pFunc;
> + pCtx->func = pOp->p4.func;
> pCtx->iOp = (int)(pOp - aOp);
> pCtx->pVdbe = p;
> pOp->p4type = P4_FUNCCTX;
> @@ -1764,7 +1764,7 @@ case OP_Function: {
> }
> #endif
> MemSetTypeFlag(pOut, MEM_Null);
> - rc = (*pCtx->pFunc->xSFunc)(NULL, &args, &ret);
> + rc = func_call(pCtx->func, &args, &ret);
> if (rc != 0)
> goto abort_due_to_error;
> uint32_t size;
> @@ -4969,15 +4969,18 @@ case OP_DecrJumpZero: { /* jump, in1 */
> case OP_AggStep0: {
> sql_context *pCtx;
>
> - assert(pOp->p4type == P4_FUNCDEF);
> + assert(pOp->p4type == P4_FUNC);
> assert(pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor));
> assert(pOp->p5 == 0 ||
> (pOp->p2 > 0 && pOp->p2 + pOp->p5 <= (p->nMem+1 - p->nCursor)+1));
> - assert(pOp->p3 < pOp->p2 || pOp->p3 >= pOp->p2 + pOp->p5);
> + assert(pOp->p3<pOp->p2 || pOp->p3>=pOp->p2 + pOp->p5);
> + assert(pOp->p4.func != NULL &&
> + pOp->p4.func->def->language == FUNC_LANGUAGE_SQL_BUILTIN &&
> + pOp->p4.func->def->aggregate == FUNC_AGGREGATE_GROUP);
> pCtx = sqlDbMallocRawNN(db, sizeof(*pCtx));
> 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;
> pOp->p4type = P4_FUNCCTX;
> @@ -5011,7 +5014,7 @@ case OP_AggStep: {
> sqlVdbeMemInit(&t, db, MEM_Null);
> pOut = &t;
> pCtx->skipFlag = 0;
> - rc = (*pCtx->pFunc->xSFunc)(NULL, &args, &ret);
> + rc = func_call(pCtx->func, &args, &ret);
> region_truncate(region, region_svp);
> if (rc != 0)
> goto abort_due_to_error;
> @@ -5042,7 +5045,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_finilize(pMem, pOp->p4.func) != 0)
> goto abort_due_to_error;
> UPDATE_MAX_BLOBSIZE(pMem);
> if (sqlVdbeMemTooBig(pMem)) {
> diff --git a/src/box/sql/vdbe.h b/src/box/sql/vdbe.h
> index 4f94643cb..31a0a39d7 100644
> --- a/src/box/sql/vdbe.h
> +++ b/src/box/sql/vdbe.h
> @@ -72,7 +72,8 @@ struct VdbeOp {
> char *z; /* Pointer to data for string (char array) types */
> i64 *pI64; /* Used when p4type is P4_INT64 */
> double *pReal; /* Used when p4type is P4_REAL */
> - FuncDef *pFunc; /* Used when p4type is P4_FUNCDEF */
> + /* 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 +123,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 function object. */
> +#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 4c8363a22..0d4acb0eb 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
> */
> @@ -167,7 +169,8 @@ 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 */
> + /** 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. */
> @@ -291,7 +294,8 @@ mem_apply_numeric_type(struct Mem *record);
> * (Mem) which are only defined there.
> */
> struct sql_context {
> - FuncDef *pFunc; /* Pointer to function information */
> + /** Pointer to function object. */
> + struct func *func;
> Mem *pMem; /* Memory cell used to store aggregate context */
> Vdbe *pVdbe; /* The VM that owns this context */
> int iOp; /* Instruction number of OP_Function */
> @@ -472,7 +476,17 @@ int sqlVdbeMemNumerify(Mem *);
> int sqlVdbeMemCast(Mem *, enum field_type type);
> int sqlVdbeMemFromBtree(BtCursor *, u32, u32, Mem *);
> void sqlVdbeMemRelease(Mem * p);
> -int sqlVdbeMemFinalize(Mem *, FuncDef *);
> +
> +/**
> + * Memory cell pMem contains the context of an aggregate function.
> + * This routine calls the finalize method for that function. The
> + * result of the aggregate is stored back into pMem.
> + *
> + * Returns -1 if the finalizer reports an error. 0 otherwise.
> + */
> +int
> +sql_vdbemem_finilize(struct Mem *mem, struct func *func);
> +
> const char *sqlOpcodeName(int);
> int sqlVdbeMemGrow(Mem * pMem, int n, int preserve);
> int sqlVdbeMemClearAndResize(Mem * pMem, int n);
> diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c
> index d014e8258..c59b740a7 100644
> --- a/src/box/sql/vdbeapi.c
> +++ b/src/box/sql/vdbeapi.c
> @@ -370,8 +370,9 @@ sql_step(sql_stmt * pStmt)
> void *
> sql_user_data(sql_context * p)
> {
> - assert(p && p->pFunc);
> - return p->pFunc->pUserData;
> + assert(p != NULL && p->func != NULL &&
> + p->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN);
> + return ((struct func_sql_builtin *)p->func)->user_data;
> }
>
> /*
> @@ -416,7 +417,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);
> }
> @@ -432,7 +433,9 @@ createAggContext(sql_context * p, int nByte)
> void *
> sql_aggregate_context(sql_context * p, int nByte)
> {
> - assert(p && p->pFunc && p->pFunc->xFinalize);
> + assert(p != NULL && p->func != NULL &&
> + p->func->def->language == FUNC_LANGUAGE_SQL_BUILTIN &&
> + p->func->def->aggregate == FUNC_AGGREGATE_GROUP);
> testcase(nByte < 0);
> if ((p->pMem->flags & MEM_Agg) == 0) {
> return createAggContext(p, nByte);
> diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c
> index baeeb4624..2c6f5b279 100644
> --- a/src/box/sql/vdbeaux.c
> +++ b/src/box/sql/vdbeaux.c
> @@ -39,6 +39,8 @@
> #include "box/schema.h"
> #include "box/tuple_format.h"
> #include "box/txn.h"
> +#include "box/func.h"
> +#include "box/func_def.h"
> #include "msgpuck/msgpuck.h"
> #include "sqlInt.h"
> #include "vdbeInt.h"
> @@ -650,24 +652,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);
> }
>
> @@ -690,10 +679,6 @@ 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;
> @@ -1150,15 +1135,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.pCtx->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 7bd5cdd71..b785739a6 100644
> --- a/src/box/sql/vdbemem.c
> +++ b/src/box/sql/vdbemem.c
> @@ -39,6 +39,8 @@
> #include "sqlInt.h"
> #include "vdbeInt.h"
> #include "tarantoolInt.h"
> +#include "box/func.h"
> +#include "box/func_def.h"
> #include "box/schema.h"
> #include "box/tuple.h"
> #include "box/port.h"
> @@ -306,40 +308,30 @@ sqlVdbeMemStringify(Mem * pMem, u8 bForce)
> 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_finilize(struct Mem *mem, struct func *func)
> {
> int rc = 0;
> - if (ALWAYS(pFunc && pFunc->xFinalize)) {
> - assert((pMem->flags & MEM_Null) != 0 || pFunc == pMem->u.pDef);
> - struct sql_context ctx;
> - memset(&ctx, 0, sizeof(ctx));
> - ctx.pMem = pMem;
> - ctx.pFunc = pFunc;
> - struct region *region = &fiber()->gc;
> - size_t region_svp = region_used(region);
> - struct port args, ret;
> - port_vdbemem_create(&args, NULL, 0, &ctx);
> - rc = pFunc->xFinalize(NULL, &args, &ret);
> - assert((pMem->flags & MEM_Dyn) == 0);
> - if (pMem->szMalloc > 0)
> - sqlDbFree(pMem->db, pMem->zMalloc);
> - uint32_t size;
> - struct Mem *mem = (struct Mem *)port_get_vdbemem(&ret, &size);
> - if (mem != NULL) {
> - *pMem = mem[0];
> - } else {
> - rc = -1;
> - }
> - region_truncate(region, region_svp);
> - }
> + assert(func != NULL && func->def->aggregate == FUNC_AGGREGATE_GROUP);
> + struct sql_context ctx;
> + memset(&ctx, 0, sizeof(ctx));
> + ctx.pMem = mem;
> + ctx.func = func;
> + struct region *region = &fiber()->gc;
> + size_t region_svp = region_used(region);
> + struct port args, ret;
> + port_vdbemem_create(&args, NULL, 0, &ctx);
> + rc = ((struct func_sql_builtin *)func)->finalize(func, &args, &ret);
> + assert((mem->flags & MEM_Dyn) == 0);
> + if (mem->szMalloc > 0)
> + sqlDbFree(mem->db, mem->zMalloc);
> + uint32_t size;
> + struct Mem *val = (struct Mem *)port_get_vdbemem(&ret, &size);
> + if (val != NULL)
> + *mem = val[0];
> + else
> + rc = -1;
> + region_truncate(region, region_svp);
> return rc;
> }
>
> @@ -357,7 +349,7 @@ vdbeMemClearExternAndSetNull(Mem * p)
> {
> assert(VdbeMemDynamic(p));
> if (p->flags & MEM_Agg) {
> - sqlVdbeMemFinalize(p, p->u.pDef);
> + sql_vdbemem_finilize(p, p->u.func);
> assert((p->flags & MEM_Agg) == 0);
> testcase(p->flags & MEM_Dyn);
> }
> @@ -1222,8 +1214,8 @@ valueNew(sql * db, struct ValueNewStat4Ctx *p)
> * to be a scalar SQL function. If
> *
> * * all function arguments are SQL literals,
> - * * one of the SQL_FUNC_CONSTANT or _SLOCHNG function flags is set, and
> - * * the SQL_FUNC_NEEDCOLL function flag is not set,
> + * * the function is deterministic and the SQL_FUNC_NEEDCOLL
> + * flag is not set,
> *
> * then this routine attempts to invoke the SQL function. Assuming no
> * error occurs, output parameter (*ppVal) is set to point to a value
> @@ -1247,7 +1239,6 @@ valueFromFunction(sql * db, /* The database connection */
> {
> 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 */
> @@ -1258,13 +1249,11 @@ valueFromFunction(sql * db, /* The database connection */
> pList = p->x.pList;
> if (pList)
> nVal = pList->nExpr;
> - pFunc = sqlFindFunction(db, p->u.zToken, nVal, 0);
> - assert(pFunc);
> - if ((pFunc->funcFlags & (SQL_FUNC_CONSTANT | SQL_FUNC_SLOCHNG)) ==
> - 0 || (pFunc->funcFlags & SQL_FUNC_NEEDCOLL)
> - ) {
> + struct func *func =
> + sql_func_by_signature(p->u.zToken, strlen(p->u.zToken), nVal);
> + assert(func != NULL);
> + if ((func->def->sql_flags & SQL_FUNC_NEEDCOLL) != 0)
> return 0;
> - }
> struct region *region = &fiber()->gc;
> size_t region_svp = region_used(region);
>
> @@ -1295,7 +1284,7 @@ valueFromFunction(sql * db, /* The database connection */
>
> struct port args, ret;
> port_vdbemem_create(&args, (struct sql_value *)apVal, nVal, NULL);
> - if ((*pFunc->xSFunc)(NULL, &args, &ret) != 0) {
> + if (func_call(func, &args, &ret) != 0) {
> rc = -1;
> goto value_from_function_out;
> }
> diff --git a/src/box/sql/whereexpr.c b/src/box/sql/whereexpr.c
> index befe4ce73..5736a4a0b 100644
> --- a/src/box/sql/whereexpr.c
> +++ b/src/box/sql/whereexpr.c
> @@ -259,9 +259,9 @@ like_optimization_is_valid(Parse *pParse, Expr *pExpr, Expr **ppPrefix,
> /* Result code to return. */
> int rc;
>
> - if (!sql_is_like_func(db, pExpr, pnoCase)) {
> + if (!sql_is_like_func(pExpr, pnoCase))
> return 0;
> - }
> +
> pList = pExpr->x.pList;
> pLeft = pList->a[1].pExpr;
> /* Value might be numeric */
> diff --git a/src/lib/coll/coll.c b/src/lib/coll/coll.c
> index fa27bf34e..275b1cfd7 100644
> --- a/src/lib/coll/coll.c
> +++ b/src/lib/coll/coll.c
> @@ -36,6 +36,7 @@
> #include <unicode/ucol.h>
> #include <unicode/ucnv.h>
> #include <unicode/ucasemap.h>
> +#include <unicode/ucnv.h>
> #include "tt_static.h"
>
> struct UCaseMap *icu_ucase_default_map = NULL;
> diff --git a/test/box/function1.result b/test/box/function1.result
> index c434b0067..7c3a93577 100644
> --- a/test/box/function1.result
> +++ b/test/box/function1.result
> @@ -365,6 +365,125 @@ test_run:cmd("setopt delimiter ''");
> c:close()
> ---
> ...
> +--
> +-- gh-2233: Invoke Lua functions created outside SQL.
> +--
> +box.schema.func.create("function1.divide", {language = 'C', returns = 'number', is_deterministic = true, exports = {'LUA'}})
> +---
> +...
> +box.execute('SELECT "function1.divide"()')
> +---
> +- error: wrong number of arguments to function function1.divide()
> +...
> +box.func["function1.divide"]:drop()
> +---
> +...
> +box.schema.func.create("function1.divide", {language = 'C', returns = 'number', is_deterministic = true, exports = {'LUA', 'SQL'}})
> +---
> +...
> +box.execute('SELECT "function1.divide"()')
> +---
> +- error: invalid argument
> +...
> +box.execute('SELECT "function1.divide"(6)')
> +---
> +- error: wrong number of arguments to function function1.divide()
> +...
> +box.execute('SELECT "function1.divide"(6, 3)')
> +---
> +- error: wrong number of arguments to function function1.divide()
> +...
> +box.func["function1.divide"]:drop()
> +---
> +...
> +box.schema.func.create("function1.divide", {language = 'C', returns = 'number', param_list = {'number', 'number'}, is_deterministic = true, exports = {'LUA', 'SQL'}})
> +---
> +...
> +box.execute('SELECT "function1.divide"()')
> +---
> +- error: wrong number of arguments to function function1.divide()
> +...
> +box.execute('SELECT "function1.divide"(6)')
> +---
> +- error: wrong number of arguments to function function1.divide()
> +...
> +box.execute('SELECT "function1.divide"(6, 3, 3)')
> +---
> +- error: wrong number of arguments to function function1.divide()
> +...
> +box.execute('SELECT "function1.divide"(6, 3)')
> +---
> +- metadata:
> + - name: '"function1.divide"(6, 3)'
> + type: number
> + rows:
> + - [2]
> +...
> +box.execute('SELECT "function1.divide"(5, 2)')
> +---
> +- metadata:
> + - name: '"function1.divide"(5, 2)'
> + type: number
> + rows:
> + - [2.5]
> +...
> +box.func["function1.divide"]:drop()
> +---
> +...
> +function SUMMARIZE(a, b) return a + b end
> +---
> +...
> +box.schema.func.create("SUMMARIZE", {language = 'LUA', returns = 'number', is_deterministic = true, param_list = {'number', 'number'}, exports = {'LUA', 'SQL'}})
> +---
> +...
> +box.execute('SELECT summarize(1, 2)')
> +---
> +- metadata:
> + - name: summarize(1, 2)
> + type: number
> + rows:
> + - [3]
> +...
> +box.func.SUMMARIZE:drop()
> +---
> +...
> +box.schema.func.create("SUMMARIZE", {language = 'LUA', returns = 'number', body = 'function (a, b) return a + b end', is_deterministic = true, param_list = {'number', 'number'}, exports = {'LUA', 'SQL'}})
> +---
> +...
> +box.execute('SELECT summarize(1, 2)')
> +---
> +- metadata:
> + - name: summarize(1, 2)
> + type: number
> + rows:
> + - [3]
> +...
> +box.func.SUMMARIZE:drop()
> +---
> +...
> +--
> +-- gh-4113: Valid method to use Lua from SQL
> +--
> +box.execute('SELECT lua(\'return 1 + 1\')')
> +---
> +- metadata:
> + - name: lua('return 1 + 1')
> + type: any
> + rows:
> + - [2]
> +...
> +box.execute('SELECT lua(\'return box.cfg\')')
> +---
> +- error: 'Failed to execute SQL statement: Unsupported type passed from Lua'
> +...
> +box.execute('SELECT lua(\'return box.cfg.memtx_memory\')')
> +---
> +- metadata:
> + - name: lua('return box.cfg.memtx_memory')
> + type: any
> + rows:
> + - [107374182]
> +...
> -- Test registered functions interface.
> function divide(a, b) return a / b end
> ---
> diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua
> index dbbdcf8be..69c2de685 100644
> --- a/test/box/function1.test.lua
> +++ b/test/box/function1.test.lua
> @@ -128,6 +128,41 @@ identifier.run_test(
> test_run:cmd("setopt delimiter ''");
> c:close()
>
> +--
> +-- gh-2233: Invoke Lua functions created outside SQL.
> +--
> +box.schema.func.create("function1.divide", {language = 'C', returns = 'number', is_deterministic = true, exports = {'LUA'}})
> +box.execute('SELECT "function1.divide"()')
> +box.func["function1.divide"]:drop()
> +box.schema.func.create("function1.divide", {language = 'C', returns = 'number', is_deterministic = true, exports = {'LUA', 'SQL'}})
> +box.execute('SELECT "function1.divide"()')
> +box.execute('SELECT "function1.divide"(6)')
> +box.execute('SELECT "function1.divide"(6, 3)')
> +box.func["function1.divide"]:drop()
> +box.schema.func.create("function1.divide", {language = 'C', returns = 'number', param_list = {'number', 'number'}, is_deterministic = true, exports = {'LUA', 'SQL'}})
> +box.execute('SELECT "function1.divide"()')
> +box.execute('SELECT "function1.divide"(6)')
> +box.execute('SELECT "function1.divide"(6, 3, 3)')
> +box.execute('SELECT "function1.divide"(6, 3)')
> +box.execute('SELECT "function1.divide"(5, 2)')
> +box.func["function1.divide"]:drop()
> +
> +function SUMMARIZE(a, b) return a + b end
> +box.schema.func.create("SUMMARIZE", {language = 'LUA', returns = 'number', is_deterministic = true, param_list = {'number', 'number'}, exports = {'LUA', 'SQL'}})
> +box.execute('SELECT summarize(1, 2)')
> +box.func.SUMMARIZE:drop()
> +
> +box.schema.func.create("SUMMARIZE", {language = 'LUA', returns = 'number', body = 'function (a, b) return a + b end', is_deterministic = true, param_list = {'number', 'number'}, exports = {'LUA', 'SQL'}})
> +box.execute('SELECT summarize(1, 2)')
> +box.func.SUMMARIZE:drop()
> +
> +--
> +-- gh-4113: Valid method to use Lua from SQL
> +--
> +box.execute('SELECT lua(\'return 1 + 1\')')
> +box.execute('SELECT lua(\'return box.cfg\')')
> +box.execute('SELECT lua(\'return box.cfg.memtx_memory\')')
> +
> -- Test registered functions interface.
> function divide(a, b) return a / b end
> box.schema.func.create("divide", {comment = 'Divide two values'})
> diff --git a/test/sql-tap/alias.test.lua b/test/sql-tap/alias.test.lua
> index f9e6fc9fa..75391b305 100755
> --- a/test/sql-tap/alias.test.lua
> +++ b/test/sql-tap/alias.test.lua
> @@ -25,14 +25,13 @@ test:plan(9)
> --
>
> counter = 0
> -sequence = function()
> - counter = counter + 1
> - return counter
> -end
>
> -- Function is declared as deterministic deliberately.
> -- Otherwise it would be called as much as it occurs in a query.
> -box.internal.sql_create_function("sequence", "INT", sequence, 0, true)
> +box.schema.func.create('SEQUENCE', {language = 'Lua', is_deterministic = true,
> + returns = 'unsigned',
> + body = 'function() counter = counter + 1 return counter end',
> + exports = {'LUA', 'SQL'}})
>
> test:do_test(
> "alias-1.1",
> @@ -220,6 +219,6 @@ test:do_test(
> -- -- </alias-3.1>
> -- })
>
> -
> +box.func.SEQUENCE:drop()
>
> test:finish_test()
> diff --git a/test/sql-tap/check.test.lua b/test/sql-tap/check.test.lua
> index 98f906cf4..d83aafe2c 100755
> --- a/test/sql-tap/check.test.lua
> +++ b/test/sql-tap/check.test.lua
> @@ -679,10 +679,12 @@ test:do_execsql_test(
> -- cannot be tested).
> --
> --reset_db()
> -local function myfunc(x)
> - return x < 10
> -end
> -box.internal.sql_create_function("myfunc", "INT", myfunc)
> +
> +box.schema.func.create('MYFUNC', {language = 'Lua',
> + is_deterministic = true,
> + body = 'function(x) return x < 10 end',
> + returns = 'boolean', param_list = {'number'},
> + exports = {'LUA', 'SQL'}})
>
> test:do_execsql_test(
> 7.1,
> @@ -807,5 +809,8 @@ test:do_catchsql_test(
> -- </9.3>
> })
>
> +
> +box.func.MYFUNC:drop()
> +
> test:finish_test()
>
> diff --git a/test/sql-tap/func5.test.lua b/test/sql-tap/func5.test.lua
> index f3706e631..4134d4036 100755
> --- a/test/sql-tap/func5.test.lua
> +++ b/test/sql-tap/func5.test.lua
> @@ -71,13 +71,25 @@ test:do_execsql_test(
>
> global_counter = 0
>
> -counter = function(str)
> - global_counter = global_counter + 1
> - return global_counter
> -end
> -
> -box.internal.sql_create_function("counter1", "INT", counter, -1, false)
> -box.internal.sql_create_function("counter2", "INT", counter, -1, true)
> +box.schema.func.create('COUNTER1', {language = 'Lua', is_deterministic = false,
> + param_list = {'any'}, returns = 'integer',
> + exports = {'SQL', 'LUA'},
> + body = [[
> + function(str)
> + global_counter = global_counter + 1
> + return global_counter
> + end
> + ]]})
> +
> +box.schema.func.create('COUNTER2', {language = 'Lua', is_deterministic = true,
> + param_list = {'any'}, returns = 'integer',
> + exports = {'SQL', 'LUA'},
> + body = [[
> + function(str)
> + global_counter = global_counter + 1
> + return global_counter
> + end
> + ]]})
>
> test:do_execsql_test(
> "func5-2.2",
> @@ -229,4 +241,7 @@ test:do_catchsql_test(
> }
> )
>
> +box.func.COUNTER1:drop()
> +box.func.COUNTER2:drop()
> +
> test:finish_test()
> diff --git a/test/sql-tap/lua_sql.test.lua b/test/sql-tap/lua_sql.test.lua
> index b0913e7f4..6451a2cf5 100755
> --- a/test/sql-tap/lua_sql.test.lua
> +++ b/test/sql-tap/lua_sql.test.lua
> @@ -1,19 +1,16 @@
> #!/usr/bin/env tarantool
> test = require("sqltester")
> NULL = require('msgpack').NULL
> -test:plan(24)
> -
> -local function func1(a)
> - return a
> -end
> -local function allways_2(a)
> - return 2
> -end
> +test:plan(22)
>
> test:do_test(
> "lua_sql-1.0",
> function ()
> - box.internal.sql_create_function("func1", "INT", allways_2)
> + box.schema.func.create('FUNC1', {language = 'Lua',
> + is_deterministic = true,
> + body = 'function(a) return 2 end',
> + param_list = {'any'}, returns = 'integer',
> + exports = {'LUA', 'SQL'}})
> return test:execsql("select func1(1)")
> end,
> {2})
> @@ -22,34 +19,16 @@ test:do_test(
> test:do_test(
> "lua_sql-1.1",
> function ()
> - box.internal.sql_create_function("func1", "INT", func1)
> + box.func.FUNC1:drop()
> + box.schema.func.create('FUNC1', {language = 'Lua',
> + is_deterministic = true,
> + body = 'function(a) return a end',
> + param_list = {'scalar'}, returns = 'integer',
> + exports = {'LUA', 'SQL'}})
> return test:execsql("select func1(1)")
> end,
> {1})
>
> --- try to loose memory
> -test:do_test(
> - "lua_sql-1.2",
> - function ()
> - for i = 1, 1000000, 1 do
> - box.internal.sql_create_function("func1", "INT", func1)
> - end
> - return test:execsql("select func1(1)")
> - end,
> - {1})
> -
> --- check sql polymorphism
> -test:do_test(
> - "lua_sql-1.3",
> - function ()
> - box.internal.sql_create_function("allways_2", "INT", allways_2, 1) -- specify 1 arg
> - box.internal.sql_create_function("allways_2", "INT", func1)
> - box.internal.sql_create_function("allways_2", "INT", func1, 2)
> - box.internal.sql_create_function("allways_2", "INT", func1, 3)
> - return test:execsql("select allways_2(1)")
> - end,
> - {2})
> -
> test:do_catchsql_test(
> "lua_sql-1.0",
> "select func3(1)",
> @@ -72,7 +51,7 @@ for _, val in ipairs({
> {result})
> end
>
> -local from_sql_to_lua = {
> +from_sql_to_lua = {
> [1] = {1, 1},
> [2] = {"1", 1},
> [3] = {"1.5", 1.5},
> @@ -81,14 +60,19 @@ local from_sql_to_lua = {
> [6] = {"x'0500'", "\u{0005}\u{0000}"},
> [7] = {"123123123123123", 123123123123123LL},
> }
> -local json = require("json")
> -local function check_from_sql_to_lua(i, arg)
> - if from_sql_to_lua[i][2] == arg then
> - return 1
> - end
> - return 0
> -end
> -box.internal.sql_create_function("check_from_sql_to_lua", "INT", check_from_sql_to_lua)
> +
> +box.schema.func.create('CHECK_FROM_SQL_TO_LUA', {language = 'Lua',
> + is_deterministic = true,
> + body = [[
> + function(i, arg)
> + if from_sql_to_lua[i][2] == arg then
> + return 1
> + end
> + return 0
> + end
> + ]],
> + param_list = {'integer', 'scalar'}, returns = 'integer',
> + exports = {'LUA', 'SQL'}})
>
> -- check for different types
> for i = 1, #from_sql_to_lua, 1 do
> @@ -98,17 +82,23 @@ for i = 1, #from_sql_to_lua, 1 do
> {1})
> end
>
> -local from_lua_to_sql = {
> +from_lua_to_sql = {
> [1] = {1, 1},
> [2] = {"1.5", 1.5},
> [3] = {"'1'", "1"},
> [4] = {"true", true},
> [5] = {"false", false},
> }
> -local function check_from_lua_to_sql(i)
> - return from_lua_to_sql[i][2]
> -end
> -box.internal.sql_create_function("check_from_lua_to_sql", "BLOB", check_from_lua_to_sql)
> +
> +box.schema.func.create('CHECK_FROM_LUA_TO_SQL', {language = 'Lua',
> + is_deterministic = true,
> + body = [[
> + function(i)
> + return from_lua_to_sql[i][2]
> + end
> + ]],
> + param_list = {'integer'}, returns = 'scalar',
> + exports = {'LUA', 'SQL'}})
>
> -- check for different types
> for i = 1, #from_lua_to_sql, 1 do
> @@ -118,14 +108,20 @@ for i = 1, #from_lua_to_sql, 1 do
> {1})
> end
>
> -local from_lua_to_sql_bad = {
> +from_lua_to_sql_bad = {
> [1] = NULL,
> [2] = 12LL, -- it is possible to support this type
> }
> -local function check_from_lua_to_sql_bad(i)
> - return from_lua_to_sql_bad[i]
> -end
> -box.internal.sql_create_function("check_from_lua_to_sql_bad", "BLOB", check_from_lua_to_sql_bad)
> +
> +box.schema.func.create('CHECK_FROM_LUA_TO_SQL_BAD', {language = 'Lua',
> + is_deterministic = true,
> + body = [[
> + function(i)
> + return from_lua_to_sql_bad[i]
> + end
> + ]],
> + param_list = {'integer'}, returns = 'scalar',
> + exports = {'LUA', 'SQL'}})
>
> for i = 1, #from_lua_to_sql_bad, 1 do
> test:do_catchsql_test(
> @@ -134,16 +130,26 @@ for i = 1, #from_lua_to_sql_bad, 1 do
> {1, "/Unsupported/"})
> end
>
> -local function allways_error()
> - error("my_error123")
> - return 1
> -end
> -box.internal.sql_create_function("allways_error", "INT", allways_error)
> +box.schema.func.create('ALLWAYS_ERROR', {language = 'Lua',
> + is_deterministic = true,
> + body = [[
> + function()
> + error("my_error123")
> + return 1
> + end
> + ]],
> + param_list = {}, returns = 'integer',
> + exports = {'LUA', 'SQL'}})
>
> test:do_catchsql_test(
> "lua_sql-2.6",
> "select allways_error()",
> {1, "/my_error123/"})
>
> +box.func.FUNC1:drop()
> +box.func.CHECK_FROM_SQL_TO_LUA:drop()
> +box.func.CHECK_FROM_LUA_TO_SQL:drop()
> +box.func.CHECK_FROM_LUA_TO_SQL_BAD:drop()
> +box.func.ALLWAYS_ERROR:drop()
>
> test:finish_test()
> diff --git a/test/sql-tap/subquery.test.lua b/test/sql-tap/subquery.test.lua
> index 7a3e270dc..743b7141e 100755
> --- a/test/sql-tap/subquery.test.lua
> +++ b/test/sql-tap/subquery.test.lua
> @@ -710,17 +710,20 @@ test:do_execsql_test(
> -- for a matching column name did not cause an otherwise static subquery
> -- to become a dynamic (correlated) subquery.
> --
> -local callcnt = 0
> +callcnt = 0
> test:do_test(
> "subquery-5.1",
> function()
> - local function callcntproc(n)
> - callcnt = callcnt + 1
> - return n
> - end
> -
> - callcnt = 0
> - box.internal.sql_create_function("callcnt", "INT", callcntproc)
> + box.schema.func.create('CALLCNT', {language = 'Lua',
> + is_deterministic = true,
> + param_list = {'integer'}, returns = 'integer',
> + exports = {'LUA', 'SQL'},
> + body = [[
> + function(n)
> + callcnt = callcnt + 1
> + return n
> + end
> + ]]})
> return test:execsql [[
> CREATE TABLE t4(x TEXT,y INT PRIMARY KEY);
> INSERT INTO t4 VALUES('one',1);
> @@ -791,6 +794,8 @@ test:do_test(
> return callcnt
> end, 1)
>
> +box.func.CALLCNT:drop()
> +
> --############ was disable until we get #2652 fixed
> -- Ticket #2652. Allow aggregate functions of outer queries inside
> -- a non-aggregate subquery.
> diff --git a/test/sql-tap/trigger9.test.lua b/test/sql-tap/trigger9.test.lua
> index e7e170b3d..64dcaff33 100755
> --- a/test/sql-tap/trigger9.test.lua
> +++ b/test/sql-tap/trigger9.test.lua
> @@ -46,7 +46,10 @@ local function has_rowdata(sql)
> -- X(41, "X!cmd", [=[["expr","[lsearch [execsql \"explain $sql\"] RowData]>=0"]]=])
> end
>
> -box.internal.sql_create_function('randstr', 'TEXT', test.randstr, 1)
> +box.schema.func.create('RANDSTR', {language = 'Lua',
> + body = 'function(n) return test.randstr(n) end',
> + param_list = {'integer'}, returns = 'string',
> + exports = {'LUA', 'SQL'}})
>
> -- MUST_WORK_TEST
> test:do_execsql_test(
> @@ -450,5 +453,6 @@ test:do_execsql_test(
> -- </4.3>
> })
>
> +box.func.RANDSTR:drop()
>
> test:finish_test()
> diff --git a/test/sql-tap/where2.test.lua b/test/sql-tap/where2.test.lua
> index 4116ca913..f267be8e6 100755
> --- a/test/sql-tap/where2.test.lua
> +++ b/test/sql-tap/where2.test.lua
> @@ -231,7 +231,7 @@ test:do_execsql_test(
> EXPLAIN SELECT * FROM x1, x2 WHERE x=1 ORDER BY random();
> ]], {
> -- <where2-2.5>
> - "/random/"
> + "/RANDOM/"
> -- </where2-2.5>
> })
>
> @@ -254,7 +254,7 @@ test:do_execsql_test(
> EXPLAIN SELECT * FROM x1, x2 WHERE x=1 ORDER BY abs(5);
> ]], {
> -- <where2-2.6>
> - "~/abs/"
> + "~/ABS/"
> -- </where2-2.6>
> })
>
> diff --git a/test/sql/errinj.result b/test/sql/errinj.result
> index 8846e5ee8..b4b44dda4 100644
> --- a/test/sql/errinj.result
> +++ b/test/sql/errinj.result
> @@ -439,31 +439,6 @@ errinj.set("ERRINJ_WAL_DELAY", false)
> - ok
> ...
> --
> --- gh-3931: Store regular identifiers in case-normal form
> ---
> -errinj = box.error.injection
> ----
> -...
> -errinj.set("ERRINJ_SQL_NAME_NORMALIZATION", true)
> ----
> -- ok
> -...
> -box.execute("CREATE TABLE hello (id INT primary key,x INT,y INT);")
> ----
> -- error: Failed to allocate 6 bytes in sqlDbMallocRawNN for res
> -...
> -dummy_f = function(int) return 1 end
> ----
> -...
> -box.internal.sql_create_function("counter1", "INT", dummy_f, -1, false)
> ----
> -- error: Failed to allocate 9 bytes in region_alloc for res
> -...
> -errinj.set("ERRINJ_SQL_NAME_NORMALIZATION", false)
> ----
> -- ok
> -...
> ---
> -- Tests which are aimed at verifying work of commit/rollback
> -- triggers on _ck_constraint space.
> --
> diff --git a/test/sql/errinj.test.lua b/test/sql/errinj.test.lua
> index 48b80a443..1250bf34b 100644
> --- a/test/sql/errinj.test.lua
> +++ b/test/sql/errinj.test.lua
> @@ -140,16 +140,6 @@ box.execute("UPDATE t SET id = 2;")
> -- Finish drop space.
> errinj.set("ERRINJ_WAL_DELAY", false)
>
> ---
> --- gh-3931: Store regular identifiers in case-normal form
> ---
> -errinj = box.error.injection
> -errinj.set("ERRINJ_SQL_NAME_NORMALIZATION", true)
> -box.execute("CREATE TABLE hello (id INT primary key,x INT,y INT);")
> -dummy_f = function(int) return 1 end
> -box.internal.sql_create_function("counter1", "INT", dummy_f, -1, false)
> -errinj.set("ERRINJ_SQL_NAME_NORMALIZATION", false)
> -
> --
> -- Tests which are aimed at verifying work of commit/rollback
> -- triggers on _ck_constraint space.
> diff --git a/test/sql/func-recreate.result b/test/sql/func-recreate.result
> index 73fb03cc4..da6d6e770 100644
> --- a/test/sql/func-recreate.result
> +++ b/test/sql/func-recreate.result
> @@ -12,7 +12,15 @@ box.execute('pragma sql_default_engine=\''..engine..'\'')
> fiber = require('fiber')
> ---
> ...
> -box.internal.sql_create_function('WAITFOR', 'INT', function (n) fiber.sleep(n) return n end)
> +test_run:cmd("setopt delimiter ';'")
> +---
> +- true
> +...
> +box.schema.func.create('WAITFOR', {language = 'Lua',
> + body = 'function (n) fiber.sleep(n) return n end',
> + param_list = {'integer'}, returns = 'integer',
> + exports = {'LUA', 'SQL'}})
> +test_run:cmd("setopt delimiter ''");
> ---
> ...
> ch = fiber.channel(1)
> @@ -24,10 +32,19 @@ _ = fiber.create(function () ch:put(box.execute('select WAITFOR(0.2)')) end)
> fiber.sleep(0.1)
> ---
> ...
> -box.internal.sql_create_function('WAITFOR', 'INT', function (n) require('fiber').sleep(n) return n end)
> +box.func.WAITFOR:drop()
> +---
> +...
> +test_run:cmd("setopt delimiter ';'")
> +---
> +- true
> +...
> +box.schema.func.create('WAITFOR', {language = 'Lua',
> + body = 'function (n) fiber.sleep(n) return n end',
> + param_list = {'integer'}, returns = 'integer',
> + exports = {'LUA', 'SQL'}})
> +test_run:cmd("setopt delimiter ''");
> ---
> -- error: 'Failed to create function ''WAITFOR'': unable to create function due to
> - active statements'
> ...
> ch:get()
> ---
> @@ -37,6 +54,20 @@ ch:get()
> rows:
> - [0.2]
> ...
> -box.internal.sql_create_function('WAITFOR', 'INT', function (n) require('fiber').sleep(n) return n end)
> +box.func.WAITFOR:drop()
> +---
> +...
> +test_run:cmd("setopt delimiter ';'")
> +---
> +- true
> +...
> +box.schema.func.create('WAITFOR', {language = 'Lua',
> + body = 'function (n) fiber.sleep(n) return n end',
> + param_list = {'integer'}, returns = 'integer',
> + exports = {'LUA', 'SQL'}})
> +test_run:cmd("setopt delimiter ''");
> +---
> +...
> +box.func.WAITFOR:drop()
> ---
> ...
> diff --git a/test/sql/func-recreate.test.lua b/test/sql/func-recreate.test.lua
> index 753e9ca4d..1b6e870f3 100644
> --- a/test/sql/func-recreate.test.lua
> +++ b/test/sql/func-recreate.test.lua
> @@ -4,14 +4,36 @@ box.execute('pragma sql_default_engine=\''..engine..'\'')
>
> -- Check errors during function create process
> fiber = require('fiber')
> -box.internal.sql_create_function('WAITFOR', 'INT', function (n) fiber.sleep(n) return n end)
> +
> +test_run:cmd("setopt delimiter ';'")
> +box.schema.func.create('WAITFOR', {language = 'Lua',
> + body = 'function (n) fiber.sleep(n) return n end',
> + param_list = {'integer'}, returns = 'integer',
> + exports = {'LUA', 'SQL'}})
> +test_run:cmd("setopt delimiter ''");
>
> ch = fiber.channel(1)
>
> _ = fiber.create(function () ch:put(box.execute('select WAITFOR(0.2)')) end)
> fiber.sleep(0.1)
>
> -box.internal.sql_create_function('WAITFOR', 'INT', function (n) require('fiber').sleep(n) return n end)
> +box.func.WAITFOR:drop()
> +
> +test_run:cmd("setopt delimiter ';'")
> +box.schema.func.create('WAITFOR', {language = 'Lua',
> + body = 'function (n) fiber.sleep(n) return n end',
> + param_list = {'integer'}, returns = 'integer',
> + exports = {'LUA', 'SQL'}})
> +test_run:cmd("setopt delimiter ''");
> +
> ch:get()
> -box.internal.sql_create_function('WAITFOR', 'INT', function (n) require('fiber').sleep(n) return n end)
> +box.func.WAITFOR:drop()
> +
> +test_run:cmd("setopt delimiter ';'")
> +box.schema.func.create('WAITFOR', {language = 'Lua',
> + body = 'function (n) fiber.sleep(n) return n end',
> + param_list = {'integer'}, returns = 'integer',
> + exports = {'LUA', 'SQL'}})
> +test_run:cmd("setopt delimiter ''");
>
> +box.func.WAITFOR:drop()
> --
> 2.21.0
>
--
Konstantin Osipov, Moscow, Russia
next prev parent reply other threads:[~2019-07-10 20:22 UTC|newest]
Thread overview: 35+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-07-10 11:00 [tarantool-patches] [PATCH v2 00/12] sql: uniform SQL and Lua functions subsystem Kirill Shcherbatov
2019-07-10 11:00 ` [tarantool-patches] [PATCH v2 01/12] sql: get rid of SOUNDEX, MATCH Kirill Shcherbatov
2019-07-10 18:45 ` [tarantool-patches] " Konstantin Osipov
2019-07-12 8:44 ` Kirill Yukhin
2019-07-10 11:00 ` [tarantool-patches] [PATCH v2 10/12] sql: refactor builtins signatures with port Kirill Shcherbatov
2019-07-10 18:47 ` [tarantool-patches] " Konstantin Osipov
2019-07-11 7:33 ` Kirill Shcherbatov
2019-07-10 11:00 ` [tarantool-patches] [PATCH v2 11/12] box: use own vtab per each function object Kirill Shcherbatov
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 12/12] sql: use schema's func hash instead of FuncDef hash Kirill Shcherbatov
2019-07-10 20:22 ` Konstantin Osipov [this message]
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 02/12] sql: get rid of LIKELY, UNLIKELY and LIKEHOOD Kirill Shcherbatov
2019-07-10 19:02 ` [tarantool-patches] " Konstantin Osipov
2019-07-11 7:38 ` Kirill Shcherbatov
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 03/12] sql: put analyze helpers to FuncDef cache Kirill Shcherbatov
2019-07-10 19:04 ` [tarantool-patches] " Konstantin Osipov
2019-07-12 8:47 ` Kirill Yukhin
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 04/12] sql: rework LIKE case-insensitive mode Kirill Shcherbatov
2019-07-10 19:09 ` [tarantool-patches] " Konstantin Osipov
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 05/12] sql: replace bool is_derived_coll marker with flag Kirill Shcherbatov
2019-07-10 19:10 ` [tarantool-patches] " Konstantin Osipov
2019-07-12 8:48 ` Kirill Yukhin
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 06/12] sql: remove SQL_PreferBuiltin flag Kirill Shcherbatov
2019-07-10 19:11 ` [tarantool-patches] " Konstantin Osipov
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 07/12] sql: move LIKE UConverter object to collation library Kirill Shcherbatov
2019-07-12 8:49 ` [tarantool-patches] " Kirill Yukhin
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 08/12] sql: rfc for SQL and Lua functions Kirill Shcherbatov
2019-07-10 19:17 ` [tarantool-patches] " Konstantin Osipov
2019-07-10 19:18 ` Konstantin Osipov
2019-07-11 7:40 ` Kirill Shcherbatov
2019-07-11 13:59 ` Kirill Yukhin
2019-07-10 11:01 ` [tarantool-patches] [PATCH v2 09/12] box: introduce Lua persistent functions Kirill Shcherbatov
2019-07-10 19:26 ` [tarantool-patches] " Konstantin Osipov
2019-07-12 21:49 ` Konstantin Osipov
2019-07-13 13:55 ` Kirill Yukhin
2019-07-13 14:17 ` Kirill Yukhin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20190710202221.GM5619@atlas \
--to=kostja@tarantool.org \
--cc=korablev@tarantool.org \
--cc=kshcherbatov@tarantool.org \
--cc=tarantool-patches@freelists.org \
--subject='[tarantool-patches] Re: [PATCH v2 12/12] sql: use schema'\''s func hash instead of FuncDef hash' \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox