Tarantool development patches archive
 help / color / mirror / Atom feed
From: Kirill Shcherbatov <kshcherbatov@tarantool.org>
To: tarantool-patches@freelists.org, "n.pettik" <korablev@tarantool.org>
Subject: [tarantool-patches] Re: [PATCH v3 3/3] box: user-friendly interface to manage ck constraints
Date: Tue, 7 May 2019 12:53:48 +0300	[thread overview]
Message-ID: <2118ea18-6de3-0241-d827-e8878930c0a4@tarantool.org> (raw)
In-Reply-To: <4D153269-CCBF-414E-A21A-02EA11979838@tarantool.org>

> Why not s:create_ck_constraint() OR s:create_check().
> Let's discuss names and methods in our server chat.
> 
> So, now I won’t review code in patch since it may significantly change.

======================================================

@TarantoolBot document
Title: check constraint for Lua space

The check constraint is a type of integrity constraint which
specifies a requirement that must be met by tuple before it
is inserted into space. The constraint result must be predictable.
Expression in check constraint must be <boolean value expression>
I.e. return boolean result.

Now it is possible to create ck constraints only for empty space
having format. Constraint expression is a string that defines
relations between top-level tuple fields.
Take into account that all names are converted to an uppercase
before resolve(like SQL does), use \" sign for names of fields
that were created not with SQL.

The check constraints are fired on insertion to the Lua space
together with Lua space triggers. The execution order of
ck constraints checks and space triggers follows their creation
sequence.

To create a new CK constraint for a space, use
s = box.schema.create_space('person')
_ = s:create_index('pk', {parts = {1, 'string'}})
s:format({{name='name', type='string'}, {name='age', type='integer'},
{name='experience', type='integer'}})
s:ck_constraint({'physics', '\"age\" > 14 and \"experience\" <
\"age\"'})
s:insert({"James Bond", 36, 36})
---
- error: 'Check constraint failed ''physics'': "age" > 14 and
  "experience" < "age"'
...
s:insert("James Bond", 36, 16) -- success
s:insert({"Bobby", 6, 0})
---
- error: 'Check constraint failed ''physics'': "age" > 14 and
  "experience" < "age"'
...

To list all ck constraints associated with space,
s:ck_constraint()

To replace ck constraint, use following syntax:
s:ck_constraint({'physics', '\"experience\" < \"age\"'}, {'physics'})
s:insert({"Bobby", 6, 0}) -- success

To drop ck constraint, use:
s:ck_constraint(nil, {'physics', '\"experience\" < \"age\"'})
---
 src/box/alter.cc         |  3 +-
 src/box/lua/schema.lua   | 34 ++++++++++++++-
 src/box/lua/space.cc     | 63 ++++++++++++++++++++++++++++
 test/sql/checks.result   | 90 ++++++++++++++++++++++++++++++++++++++++
 test/sql/checks.test.lua | 29 +++++++++++++
 5 files changed, 217 insertions(+), 2 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 9c560e85d..b7a5e3650 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -4229,6 +4229,8 @@ on_replace_ck_constraint_commit(struct trigger *trigger, void *event)
 {
 	struct txn_stmt *stmt = txn_last_stmt((struct txn *) event);
 	struct ck_constraint *ck = (struct ck_constraint *)trigger->data;
+	struct space *space = space_by_id(ck->space_id);
+	trigger_run_xc(&on_alter_space, space);
 	if (stmt->old_tuple != NULL)
 		ck_constraint_delete(ck);
 }
@@ -4250,7 +4252,6 @@ on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
 		txn_alter_trigger_new(on_replace_ck_constraint_rollback, NULL);
 	struct trigger *on_commit =
 		txn_alter_trigger_new(on_replace_ck_constraint_commit, NULL);
-
 	if (new_tuple != NULL) {
 		bool is_deferred =
 			tuple_field_bool_xc(new_tuple,
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index e01f500e6..132b2d610 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -1185,6 +1185,15 @@ local function check_primary_index(space)
 end
 box.internal.check_primary_index = check_primary_index -- for net.box
 
+-- Helper function to check ck_constraint:method() usage
+local function check_ck_constraint_arg(ck_constraint, method)
+    if type(ck_constraint) ~= 'table' or ck_constraint.name == nil then
+        local fmt = 'Use ck_constraint:%s(...) instead of ck_constraint.%s(...)'
+        error(string.format(fmt, method, method))
+    end
+end
+box.internal.check_ck_constraint_arg = check_ck_constraint_arg
+
 box.internal.schema_version = builtin.box_schema_version
 
 local function check_iterator_type(opts, key_is_nil)
@@ -1533,7 +1542,15 @@ space_mt.auto_increment = function(space, tuple)
     table.insert(tuple, 1, max + 1)
     return space:insert(tuple)
 end
-
+-- Manage space ck constraints
+space_mt.create_check_constraint = function(space, name, expr_str)
+    check_space_arg(space, 'create_constraint')
+    if name == nil or expr_str == nil then
+        box.error(box.error.PROC_LUA,
+                  "Usage: space:create_constraint(name, expr_str)")
+    end
+    return box.space._ck_constraint:insert({name, space.id, false, expr_str})
+end
 space_mt.pairs = function(space, key, opts)
     check_space_arg(space, 'pairs')
     local pk = space.index[0]
@@ -1579,10 +1596,17 @@ end
 space_mt.frommap = box.internal.space.frommap
 space_mt.__index = space_mt
 
+local ck_constraint_mt = {}
+ck_constraint_mt.drop = function(ck_constraint)
+    check_ck_constraint_arg(ck_constraint, 'drop')
+    box.space._ck_constraint:delete({ck_constraint.name, ck_constraint.space_id})
+end
+
 box.schema.index_mt = base_index_mt
 box.schema.memtx_index_mt = memtx_index_mt
 box.schema.vinyl_index_mt = vinyl_index_mt
 box.schema.space_mt = space_mt
+box.schema.ck_constraint_mt = ck_constraint_mt
 
 --
 -- Wrap a global space/index metatable into a space/index local
@@ -1628,6 +1652,14 @@ function box.schema.space.bless(space)
             end
         end
     end
+    if type(space.ck_constraint) == 'table' and space.enabled then
+        for j, ck_constraint in pairs(space.ck_constraint) do
+            if type(j) == 'string' then
+                setmetatable(ck_constraint,
+                             wrap_schema_object_mt('ck_constraint_mt'))
+            end
+        end
+    end
 end
 
 local sequence_mt = {}
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index ce4287bba..9cec1cbde 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -28,6 +28,7 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+#include "box/ck_constraint.h"
 #include "box/lua/space.h"
 #include "box/lua/tuple.h"
 #include "box/lua/key_def.h"
@@ -147,6 +148,66 @@ lbox_space_before_replace(struct lua_State *L)
 				  lbox_push_txn_stmt, lbox_pop_txn_stmt);
 }
 
+/**
+ * Make ck_constraints available in Lua, via ck_constraint[]
+ * array.
+ * Returns a new table representing a space on top of the Lua
+ * stack.
+ */
+static void
+lbox_ck_constraint(struct lua_State *L, struct space *space, int i)
+{
+	lua_getfield(L, i, "ck_constraint");
+	if (lua_isnil(L, -1)) {
+		lua_pop(L, 1);
+		lua_pushstring(L, "ck_constraint");
+		lua_newtable(L);
+		lua_settable(L, i);
+		lua_getfield(L, i, "ck_constraint");
+	} else {
+		lua_pushnil(L);
+		while (lua_next(L, -2) != 0) {
+			size_t name_len;
+			const char *name = lua_tolstring(L, -2, &name_len);
+			/*
+			 * Remove ck_constraint only if it was
+			 * deleted.
+			 */
+			if (space_ck_constraint_by_name(space, name,
+					(uint32_t)name_len) == NULL) {
+				lua_pushlstring(L, name, name_len);
+				lua_pushnil(L);
+				lua_settable(L, -5);
+			}
+			lua_pop(L, 1);
+		}
+	}
+	struct ck_constraint *ck_constraint = NULL;
+	rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) {
+		lua_getfield(L, i, ck_constraint->def->name);
+		if (lua_isnil(L, -1)) {
+			lua_pop(L, 1);
+			lua_pushstring(L, ck_constraint->def->name);
+			lua_newtable(L);
+			lua_settable(L, -3);
+			lua_getfield(L, -1, ck_constraint->def->name);
+			assert(!lua_isnil(L, -1));
+		}
+
+		lua_pushstring(L, ck_constraint->def->name);
+		lua_setfield(L, -2, "name");
+
+		lua_pushnumber(L, space->def->id);
+		lua_setfield(L, -2, "space_id");
+
+		lua_pushstring(L, ck_constraint->def->expr_str);
+		lua_setfield(L, -2, "expr_str");
+
+		lua_setfield(L, -2, ck_constraint->def->name);
+	}
+	lua_pop(L, 1);
+}
+
 /**
  * Make a single space available in Lua,
  * via box.space[] array.
@@ -337,6 +398,8 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
 
 	lua_pop(L, 1); /* pop the index field */
 
+	lbox_ck_constraint(L, space, i);
+
 	lua_getfield(L, LUA_GLOBALSINDEX, "box");
 	lua_pushstring(L, "schema");
 	lua_gettable(L, -2);
diff --git a/test/sql/checks.result b/test/sql/checks.result
index bd7b96435..44f5b2b93 100644
--- a/test/sql/checks.result
+++ b/test/sql/checks.result
@@ -449,6 +449,96 @@ s:insert({1, 666})
 s:drop()
 ---
 ...
+--
+-- Test ck constraints LUA integration.
+--
+s1 = box.schema.create_space('test1')
+---
+...
+_ = s1:create_index('pk')
+---
+...
+s1:format({{name='X', type='any'}, {name='Y', type='integer'}})
+---
+...
+s2 = box.schema.create_space('test2')
+---
+...
+_ = s2:create_index('pk')
+---
+...
+s2:format({{name='X', type='any'}, {name='Y', type='integer'}})
+---
+...
+_ = s1:create_check_constraint('physics', 'X < Y')
+---
+...
+_ = s1:create_check_constraint('greater', 'X > 20')
+---
+...
+_ = s2:create_check_constraint('physics', 'X > Y')
+---
+...
+_ = s2:create_check_constraint('greater', 'X > 20')
+---
+...
+s1.ck_constraint.physics ~= nil
+---
+- true
+...
+s1.ck_constraint.greater ~= nil
+---
+- true
+...
+s2.ck_constraint.physics ~= nil
+---
+- true
+...
+s2.ck_constraint.greater ~= nil
+---
+- true
+...
+s1:insert({2, 1})
+---
+- error: 'Check constraint failed ''greater'': X > 20'
+...
+s1:insert({21, 20})
+---
+- error: 'Check constraint failed ''physics'': X < Y'
+...
+s2:insert({1, 2})
+---
+- error: 'Check constraint failed ''greater'': X > 20'
+...
+s2:insert({21, 22})
+---
+- error: 'Check constraint failed ''physics'': X > Y'
+...
+s2.ck_constraint.greater:drop()
+---
+...
+s2.ck_constraint.physics ~= nil
+---
+- true
+...
+s2.ck_constraint.greater == nil
+---
+- true
+...
+s1:insert({2, 1})
+---
+- error: 'Check constraint failed ''greater'': X > 20'
+...
+s2:insert({1, 2})
+---
+- error: 'Check constraint failed ''physics'': X > Y'
+...
+s1:drop()
+---
+...
+s2:drop()
+---
+...
 test_run:cmd("clear filter")
 ---
 - true
diff --git a/test/sql/checks.test.lua b/test/sql/checks.test.lua
index 2a7510aa9..6f17d0612 100644
--- a/test/sql/checks.test.lua
+++ b/test/sql/checks.test.lua
@@ -154,4 +154,33 @@ s:insert({1, 3.14})
 s:insert({1, 666})
 s:drop()
 
+--
+-- Test ck constraints LUA integration.
+--
+s1 = box.schema.create_space('test1')
+_ = s1:create_index('pk')
+s1:format({{name='X', type='any'}, {name='Y', type='integer'}})
+s2 = box.schema.create_space('test2')
+_ = s2:create_index('pk')
+s2:format({{name='X', type='any'}, {name='Y', type='integer'}})
+_ = s1:create_check_constraint('physics', 'X < Y')
+_ = s1:create_check_constraint('greater', 'X > 20')
+_ = s2:create_check_constraint('physics', 'X > Y')
+_ = s2:create_check_constraint('greater', 'X > 20')
+s1.ck_constraint.physics ~= nil
+s1.ck_constraint.greater ~= nil
+s2.ck_constraint.physics ~= nil
+s2.ck_constraint.greater ~= nil
+s1:insert({2, 1})
+s1:insert({21, 20})
+s2:insert({1, 2})
+s2:insert({21, 22})
+s2.ck_constraint.greater:drop()
+s2.ck_constraint.physics ~= nil
+s2.ck_constraint.greater == nil
+s1:insert({2, 1})
+s2:insert({1, 2})
+s1:drop()
+s2:drop()
+
 test_run:cmd("clear filter")
-- 
2.21.0

  reply	other threads:[~2019-05-07  9:53 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-04-16 13:51 [tarantool-patches] [PATCH v3 0/3] box: run checks on insertions in LUA spaces Kirill Shcherbatov
2019-04-16 13:51 ` [tarantool-patches] [PATCH v3 1/3] schema: add new system space for CHECK constraints Kirill Shcherbatov
2019-04-25 20:38   ` [tarantool-patches] " n.pettik
2019-05-07  9:53     ` Kirill Shcherbatov
2019-05-12 13:45       ` n.pettik
2019-05-12 15:52         ` Kirill Shcherbatov
2019-05-12 23:04           ` n.pettik
2019-05-13  7:11             ` Kirill Shcherbatov
2019-05-13 12:29               ` n.pettik
2019-05-13 13:13                 ` Vladislav Shpilevoy
2019-04-16 13:51 ` [tarantool-patches] [PATCH v3 2/3] box: run check constraint tests on space alter Kirill Shcherbatov
2019-04-25 20:38   ` [tarantool-patches] " n.pettik
2019-05-07  9:53     ` Kirill Shcherbatov
2019-05-07 16:39       ` Konstantin Osipov
2019-05-07 17:47         ` [tarantool-patches] " Kirill Shcherbatov
2019-05-07 20:28           ` Konstantin Osipov
2019-05-11 12:15           ` n.pettik
2019-05-12 21:12             ` Konstantin Osipov
2019-05-13  7:09               ` Kirill Shcherbatov
2019-05-13  7:49                 ` Konstantin Osipov
2019-05-14 16:49       ` n.pettik
2019-04-16 13:51 ` [tarantool-patches] [PATCH v3 3/3] box: user-friendly interface to manage ck constraints Kirill Shcherbatov
2019-04-25 20:38   ` [tarantool-patches] " n.pettik
2019-05-07  9:53     ` Kirill Shcherbatov [this message]
2019-05-14 15:02 [tarantool-patches] [PATCH v3 0/3] box: run checks on insertions in LUA spaces Kirill Shcherbatov
2019-05-14 15:02 ` [tarantool-patches] [PATCH v3 3/3] box: user-friendly interface to manage ck constraints Kirill Shcherbatov
2019-05-14 20:09   ` [tarantool-patches] " Konstantin Osipov

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=2118ea18-6de3-0241-d827-e8878930c0a4@tarantool.org \
    --to=kshcherbatov@tarantool.org \
    --cc=korablev@tarantool.org \
    --cc=tarantool-patches@freelists.org \
    --subject='[tarantool-patches] Re: [PATCH v3 3/3] box: user-friendly interface to manage ck constraints' \
    /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