diff --git a/doc/code_snippets/test/constraints/constraint_sql_expr_test.lua b/doc/code_snippets/test/constraints/constraint_sql_expr_test.lua new file mode 100644 index 0000000000..61af6ddeda --- /dev/null +++ b/doc/code_snippets/test/constraints/constraint_sql_expr_test.lua @@ -0,0 +1,62 @@ +local fio = require('fio') +local server = require('luatest.server') +local t = require('luatest') +local g = t.group() +g.before_each(function(cg) + cg.server = server:new { + box_cfg = {}, + workdir = fio.cwd() .. '/tmp' + } + cg.server:start() +end) + +g.after_each(function(cg) + cg.server:stop() + cg.server:drop() +end) + +g.test_constraints = function(cg) + cg.server:exec(function() + -- create_constraint_start + box.schema.func.create('check_person', { + language = 'SQL_EXPR', + is_deterministic = true, + body = [["age" > 21 AND "name" != 'Admin']] + }) + -- create_constraint_end + + -- configure_space_start + local customers = box.schema.space.create('customers', { constraint = 'check_person' }) + customers:format({ + { name = 'id', type = 'number' }, + { name = 'name', type = 'string' }, + { name = 'age', type = 'number' }, + }) + customers:create_index('primary', { parts = { 1 } }) + -- configure_space_end + + -- insert_ok_start + customers:insert { 1, "Alice", 30 } + -- insert_ok_end + + local _, age_err = pcall(function() + -- insert_age_error_start + customers:insert { 2, "Bob", 18 } + -- error: Check constraint 'check_person' failed for tuple + -- insert_age_error_end + end) + + local _, name_err = pcall(function() + -- insert_name_error_start + customers:insert { 3, "Admin", 25 } + -- error: Check constraint 'check_person' failed for tuple + -- insert_name_error_end + end) + + -- Tests -- + t.assert_equals(customers:count(), 1) + t.assert_equals(customers:get(1), { 1, "Alice", 30 }) + t.assert_equals(age_err:unpack().message, 'Check constraint \'check_person\' failed for tuple') + t.assert_equals(name_err:unpack().message, 'Check constraint \'check_person\' failed for tuple') + end) +end diff --git a/doc/concepts/data_model/value_store.rst b/doc/concepts/data_model/value_store.rst index 9b03c37a8e..1419f36c58 100644 --- a/doc/concepts/data_model/value_store.rst +++ b/doc/concepts/data_model/value_store.rst @@ -598,11 +598,11 @@ a wider range of limitations. Constraint functions ~~~~~~~~~~~~~~~~~~~~ -Constraints use stored Lua functions, which must return ``true`` when the constraint +Constraints use stored Lua functions or :ref:`SQL expressions `, which must return ``true`` when the constraint is satisfied. Other return values (including ``nil``) and exceptions make the check fail and prevent tuple insertion or modification. -To create a constraint function, use :ref:`func.create with function body `. +To create a constraint function, call :ref:`box.schema.func.create() ` with the function definition specified in the ``body`` attribute. Constraint functions take two parameters: diff --git a/doc/reference/reference_lua/box_schema/func_create.rst b/doc/reference/reference_lua/box_schema/func_create.rst index 58105f02fb..41219f40a7 100644 --- a/doc/reference/reference_lua/box_schema/func_create.rst +++ b/doc/reference/reference_lua/box_schema/func_create.rst @@ -6,38 +6,235 @@ box.schema.func.create() .. module:: box.schema -.. _box_schema-func_create_without-body: +.. _box_schema-func_create_with-body: + +.. function:: box.schema.func.create(func_name [, function_options]) + + Create a function. + The created function can be used in different usage scenarios, + for example, in :ref:`field or tuple constraints ` or + :ref:`functional indexes `. + + Using the :ref:`body ` option, you can make a function *persistent*. + In this case, the function is "persistent" because its definition is stored in a snapshot (the :ref:`box.space._func ` system space) and can be recovered if the server restarts. + + :param string func_name: a name of the function, which should + conform to the :ref:`rules for object names ` + :param table function_options: see :ref:`function_options ` + + :return: nil + + .. NOTE:: + + :ref:`box.schema.user.grant() ` can be used to allow the specified user or + role to execute the created function. + + **Example 1: a non-persistent Lua function** + + The example below shows how to create a non-persistent Lua function: + + .. code-block:: lua + + box.schema.func.create('calculate') + box.schema.func.create('calculate', {if_not_exists = false}) + box.schema.func.create('calculate', {setuid = false}) + box.schema.func.create('calculate', {language = 'LUA'}) -.. function:: box.schema.func.create(func-name [, {options-without-body}]) - Create a function :ref:`tuple ` - without including the ``body`` option. - For functions created with the ``body`` option, see - :ref:`box.schema.func.create(func-name [, {options-with-body}]) `. + **Example 2: a persistent Lua function** - This is called a "not persistent" function because functions without bodies are not persistent. - This does not create the function itself -- that is done with Lua -- - but if it is necessary to grant privileges for a function, - ``box.schema.func.create`` must be done first. - For explanation of how Tarantool maintains function data, see the - reference for the :ref:`box.space._func ` space. + The example below shows how to create a persistent Lua function, + show its definition using ``box.func.{func-name}``, + and call this function using ``box.func.{func-name}:call([parameters])``: - The possible options are: + .. code-block:: tarantoolsession - * ``if_not_exists`` = ``true|false`` (default = ``false``) -- - ``true`` means there should be no error if the ``_func`` tuple already exists. + tarantool> lua_code = [[function(a, b) return a + b end]] + tarantool> box.schema.func.create('sum', {body = lua_code}) - * ``setuid`` = ``true|false`` (default = ``false``) -- with ``true`` to make Tarantool - treat the function's caller as the function's creator, with full privileges. - Remember that SETUID works only over - :ref:`binary ports `. - SETUID doesn't work if you invoke a function via an + tarantool> box.func.sum + --- + - is_sandboxed: false + is_deterministic: false + id: 2 + setuid: false + body: function(a, b) return a + b end + name: sum + language: LUA + ... + + tarantool> box.func.sum:call({1, 2}) + --- + - 3 + ... + + To call functions using ``net.box``, use :ref:`net_box:call() `. + + .. _box_schema-func_example-sql: + + **Example 3: a persistent SQL expression used in a tuple constraint** + + The code snippet below defines a function that checks a tuple's data using the SQL expression: + + .. literalinclude:: /code_snippets/test/constraints/constraint_sql_expr_test.lua + :language: lua + :start-after: create_constraint_start + :end-before: create_constraint_end + :dedent: + + Then, this function is used to create a tuple :ref:`constraint `: + + .. literalinclude:: /code_snippets/test/constraints/constraint_sql_expr_test.lua + :language: lua + :start-after: configure_space_start + :end-before: configure_space_end + :dedent: + + On an attempt to insert a tuple that doesn't meet the required criteria, an error is raised: + + .. literalinclude:: /code_snippets/test/constraints/constraint_sql_expr_test.lua + :language: lua + :start-after: insert_age_error_start + :end-before: insert_age_error_end + :dedent: + +.. _func_create_function_options: + +function_options +~~~~~~~~~~~~~~~~ + +.. class:: function_options + + A table containing options passed to the :ref:`box.schema.func.create(func-name [, function_options]) ` function. + + .. _function_options_if_not_exists: + + .. data:: if_not_exists + + Specify whether there should be no error if the function already exists. + + | Type: boolean + | Default: ``false`` + + .. _function_options_setuid: + + .. data:: setuid + + Make Tarantool treat the function's caller as the function's creator, with full privileges. + Note that ``setuid`` works only over :ref:`binary ports `. + ``setuid`` doesn't work if you invoke a function using the :ref:`admin console ` or inside a Lua script. - * ``language`` = 'LUA'|'C' (default = 'LUA'). + | Type: boolean + | Default: ``false`` + + .. _function_options_language: + + .. data:: language + + Specify the function language. + The possible values are: + + * ``LUA``: define a Lua function in the :ref:`body ` attribute. + * ``SQL_EXPR``: define an :ref:`SQL expression ` in the :ref:`body ` attribute. An SQL expression can only be used as a field or tuple :ref:`constraint `. + * ``C``: import a C function using its name from a ``.so`` file. Learn how to call C code from Lua in the :ref:`C tutorial `. + + .. NOTE:: + + To reload a C module with all its functions without restarting the server, call :ref:`box.schema.func.reload() `. - * (since version 2.10.0) ``takes_raw_args`` = ``true|false`` (default = ``false``)--- - if set to ``true`` for a Lua function and the function is called via ``net.box`` (:ref:`conn:call() `) or by ``box.func.:call()``, + | Type: string + | Default: ``LUA`` + + .. _function_options_is_sandboxed: + + .. data:: is_sandboxed + + Whether the function should be executed in an isolated environment. + This means that any operation that accesses the world outside the sandbox is forbidden or has no effect. + Therefore, a sandboxed function can only use modules and functions + that cannot affect isolation: + + `assert `_, + `assert `_, + `error `_, + `ipairs `_, + `math.* `_, + `next `_, + `pairs `_, + `pcall `_, + `print `_, + `select `_, + `string.* `_, + `table.* `_, + `tonumber `_, + `tostring `_, + `type `_, + `unpack `_, + `xpcall `_, + :ref:`utf8.* `. + + Also, a sandboxed function cannot refer to global variables -- they + are treated as local variables because the sandbox is established + with `setfenv `_. + So, a sandboxed function is stateless and deterministic. + + | Type: boolean + | Default: ``false`` + + .. _function_options_is_deterministic: + + .. data:: is_deterministic + + Specify whether a function should be deterministic. + + | Type: boolean + | Default: ``false`` + + .. _function_options_is_multikey: + + .. data:: is_multikey + + If ``true`` is set in the function definition for a functional index, the function returns multiple keys. + For details, see the :ref:`example `. + + | Type: boolean + | Default: ``false`` + + .. _function_options_body: + + .. data:: body + + Specify a function body. + You can set a function's language using the :ref:`language ` attribute. + + The code snippet below defines a :ref:`constraint ` function that checks a tuple's data using a Lua function: + + .. literalinclude:: /code_snippets/test/constraints/constraint_test.lua + :language: lua + :lines: 22-26 + :dedent: + + In the following example, an SQL expression is used to check a tuple's data: + + .. literalinclude:: /code_snippets/test/constraints/constraint_sql_expr_test.lua + :language: lua + :start-after: create_constraint_start + :end-before: create_constraint_end + :dedent: + + Example: :ref:`A persistent SQL expression used in a tuple constraint ` + + | Type: string + | Default: ``nil`` + + .. _function_options_takes_raw_args: + + .. data:: takes_raw_args + + **Since:** :doc:`2.10.0 ` + + If set to ``true`` for a Lua function and the function is called via ``net.box`` (:ref:`conn:call() `) or by ``box.func.:call()``, the function arguments are passed being wrapped in a :ref:`MsgPack object `: .. code-block:: lua @@ -52,149 +249,43 @@ box.schema.func.create() If a function forwards most of its arguments to another Tarantool instance or writes them to a database, the usage of this option can improve performance because it skips the MsgPack data decoding in Lua. - :param string func-name: name of function, which should - conform to the :ref:`rules for object names ` - :param table options: ``if_not_exists``, ``setuid``, ``language``, ``takes_raw_args``. - - :return: nil - - These functions can be called with :samp:`{function-object}:call({arguments})`; however, - unlike the case with ordinary functions, array arguments will not be correctly recognized - unless they are enclosed in braces. + | Type: boolean + | Default: ``false`` - **Example:** - .. code-block:: lua - - box.schema.func.create('calculate') - box.schema.func.create('calculate', {if_not_exists = false}) - box.schema.func.create('calculate', {setuid = false}) - box.schema.func.create('calculate', {language = 'LUA'}) - -.. _box_schema-func_create_with-body: + .. _function_options_exports: -.. function:: box.schema.func.create(func-name [, {options-with-body}]) + .. data:: exports - Create a function :ref:`tuple `. - including the ``body`` option. - (For functions created without the ``body`` option, see - :ref:`box.schema.func.create(func-name [, {options-without-body}]) `. + Specify the languages that can call the function. - This is called a "persistent" function because only functions with bodies are persistent. - This does create the function itself, the body is a function definition. - For explanation of how Tarantool maintains function data, see the - reference for the :ref:`box.space._func ` space. + Example: ``exports = {'LUA', 'SQL'}`` - The possible options are: + See also: :ref:`Calling Lua routines from SQL ` - * ``if_not_exists`` = ``true|false`` (default = ``false``) - boolean; - same as for :ref:`box.schema.func.create(func-name [, {options-without-body}]) `. + | Type: table + | Default: ``{'LUA'}`` - * ``setuid`` = ``true|false`` (default = ``false``) - boolean; - same as for :ref:`box.schema.func.create(func-name [, {options-without-body}]) `. + .. _function_options_param_list: - * ``language`` = 'LUA'|'C' (default = ‘LUA’) - string. - same as for :ref:`box.schema.func.create(func-name [, {options-without-body}]) `. + .. data:: param_list - * ``is_sandboxed`` = ``true|false`` (default = ``false``) - boolean; - whether the function should be executed in a sandbox. + Specify the Lua type names for each parameter of the function. - * ``is_deterministic`` = ``true|false`` (default = ``false``) - boolean; - ``true`` means that the function should be deterministic, - ``false`` means that the function may or may not be deterministic. + Example: ``param_list = {'number', 'number'}`` - * ``is_multikey`` = ``true|false`` (default = ``false``)--- - if ``true`` is set in the function definition for a functional index, the function returns multiple keys. - For details, see the :ref:`example `. + See also: :ref:`Calling Lua routines from SQL ` - * ``body`` = function definition (default = nil) - string; - the function definition. + | Type: table - * Additional options for SQL = See :ref:`Calling Lua routines from SQL `. + .. _function_options_returns: - * ``takes_raw_args`` -- see the option description in :ref:`box.schema.func.create(func-name [, {options-with-body}]) `. + .. data:: returns - :param string func-name: name of function, which should - conform to the :ref:`rules for object names ` - :param table options: ``if_not_exists``, ``setuid``, ``language``, - ``is_sandboxed``, ``is_deterministic``, ``body``. - - :return: nil - - C functions are imported from .so files, Lua functions can be defined within ``body``. - We will only describe Lua functions in this section. - - A function tuple with a body is "persistent" because the tuple is - stored in a snapshot and is recoverable if the server restarts. - All of the option values described in this section are visible in the - :ref:`box.space._func ` system space. - - If ``is_sandboxed`` is true, then the function will be executed - in an isolated environment: any operation that accesses the world outside - the sandbox will be forbidden or will have no effect. - Therefore a sandboxed function can only use modules and functions - which cannot affect isolation: - `assert `_, - `error `_, - `ipairs `_, - `math.* `_, - `next `_, - `pairs `_, - `pcall `_, - `print `_, - `select `_, - `string.* `_, - `table.* `_, - `tonumber `_, - `tostring `_, - `type `_, - `unpack `_, - :ref:`utf8.* `, - `xpcall `_. - Also a sandboxed function cannot refer to global variables -- they - will be treated as local variables because the sandbox is established - with `setfenv `_. - So a sandboxed function will happen to be stateless and deterministic. - - If ``is_deterministic`` is true, there is no immediate effect. - Tarantool plans to use the is_deterministic value in a future version. - A function is deterministic if it always returns the same outputs given the same - inputs. It is the function creator's responsibility to ensure that a - function is truly deterministic. - - **Using a persistent Lua function** - - After a persistent Lua function is created, it can be found in - the :ref:`box.space._func ` system space, - and it can be shown with |br| - :samp:`box.func.{func-name}` |br| - and it can be invoked by any user with - :ref:`authorization ` - to 'execute' it. The syntax for invoking is: |br| - :samp:`box.func.{func-name}:call([parameters])` |br| - or, if the connection is remote, the syntax is as in - :ref:`net_box:call() `. - - **Example:** + Specify the Lua type name for a function's return value. - .. code-block:: tarantoolsession - - tarantool> lua_code = [[function(a, b) return a + b end]] - tarantool> box.schema.func.create('sum', {body = lua_code}) + Example: ``returns = 'number'`` - tarantool> box.func.sum - --- - - is_sandboxed: false - is_deterministic: false - id: 2 - setuid: false - body: function(a, b) return a + b end - name: sum - language: LUA - ... - - tarantool> box.func.sum:call({1, 2}) - --- - - 3 - ... + See also: :ref:`Calling Lua routines from SQL ` + | Type: string diff --git a/doc/reference/reference_lua/box_space/_func.rst b/doc/reference/reference_lua/box_space/_func.rst index 979039b1ce..3d430e1693 100644 --- a/doc/reference/reference_lua/box_space/_func.rst +++ b/doc/reference/reference_lua/box_space/_func.rst @@ -8,70 +8,11 @@ box.space._func .. data:: _func - ``_func`` is a system space with function tuples made by - :ref:`box.schema.func.create() ` - or - :ref:`box.schema.func.create(func-name [, {options-with-body}]) `. + A system space containing functions created using :ref:`box.schema.func.create() `. + If a function's definition is specified in the :ref:`body ` option, + this function is *persistent*. + In this case, its definition is stored in a snapshot and can be recovered if the server restarts. - Tuples in this space contain the following fields: - - * id (integer identifier) - * owner (integer identifier) - * the function name - * the setuid flag - * a language name (optional): 'LUA' (default) or 'C' - * the body - * the is_deterministic flag - * the is_sandboxed flag - * options. - - If the function tuple was made in the older way without specification of ``body``, - then the ``_func`` space will contain default values for the body and the - is_deterministic flag and the is_sandboxed flag. - Such function tuples are called "not persistent". - You continue to create Lua functions in the usual way, by saying - ``function function_name () ... end``, without adding anything - in the ``_func`` space. The ``_func`` space only exists for storing - function tuples so that their names can be used within - :ref:`grant/revoke ` - functions. - - If the function tuple was made the newer way with specification of ``body``, - then all the fields may contain non-default values. - Such functions are called "persistent". - They should be invoked with :samp:`box.func.{func-name}:call([parameters])`. - - You can: - - * Create a ``_func`` tuple with - :doc:`/reference/reference_lua/box_schema/func_create`. - * Drop a ``_func`` tuple with - :doc:`/reference/reference_lua/box_schema/func_drop`. - * Check whether a ``_func`` tuple exists with - :doc:`/reference/reference_lua/box_schema/func_exists`. - - **Example:** - - In the following example, we create a function named ‘f7’, put it into - Tarantool's ``_func`` space and grant 'execute' privilege for this function - to 'guest' user. - - .. code-block:: tarantoolsession - - tarantool> function f7() - > box.session.uid() - > end - --- - ... - tarantool> box.schema.func.create('f7') - --- - ... - tarantool> box.schema.user.grant('guest', 'execute', 'function', 'f7') - --- - ... - tarantool> box.schema.user.revoke('guest', 'execute', 'function', 'f7') - --- - ... - - The :ref:`system space view ` for ``_func`` is ``_vfunc``. + .. NOTE:: + The :ref:`system space view ` for ``_func`` is ``_vfunc``. diff --git a/locale/ru/LC_MESSAGES/reference/reference_lua/box_schema/func_create.po b/locale/ru/LC_MESSAGES/reference/reference_lua/box_schema/func_create.po index 57b8fe8182..e16d45e999 100644 --- a/locale/ru/LC_MESSAGES/reference/reference_lua/box_schema/func_create.po +++ b/locale/ru/LC_MESSAGES/reference/reference_lua/box_schema/func_create.po @@ -156,7 +156,7 @@ msgstr "" msgid "" "If ``is_sandboxed`` is true, then the function will be executed in an " "isolated environment: any operation that accesses the world outside the " -"sandbox will be forbidden or will have no effect. Therefore a sandboxed " +"sandbox will be forbidden or will have no effect. Therefore, a sandboxed " "function can only use modules and functions which cannot affect isolation: " "`assert `_, `error " "`_, `ipairs "