[tarantool-patches] Re: [PATCH v2 5/9] schema: add new system space for CHECK constraints
Kirill Shcherbatov
kshcherbatov at tarantool.org
Tue Mar 26 13:59:27 MSK 2019
> 1. held -> hold
> 2. Sorry, the sentence is invalid. In Russian you said literally
> this: "Пока спейс не поддерживает check ограничение, он не может
> быть удален". But why? If a space does not have a check, it is ok
> to drop the space. Probably you thought that 'Until' == 'While'?
> Vice versa, rather 'While' == 'Until not'.
> 3. than -> then.
Fixed.
>> +/** BuildCkConstraints - rebuild ck_constraints on alter. */
>
> 4. Instead of the comments like you wrote below, it would be
> much better to focus your efforts on the code like this. Here
> I do not understand what is happening, especially in prepare().
> 5. Why do you need checks rebuild on an index update? As I know, checks
> depend on space format only. Strictly speaking, even on space alter you
> do not need to rebuild checks, if the space format is unchanged. However,
> I am not sure, if checks AST does not contain space pointers.
New informative comment below:
/**
* Due to the fact that ck_constraint object depends on
* space_def we must rebuild all ck constraints on space alter.
* To make it transactionally, we prepare a list of new objects
* in ::prepare method that is fault-tolerant. Later in ::alter or
* ::rollback methods we only securely swap the lists.
*/
>>+ /* Add an op to rebuild check constraints. */
>>+ (void) new BuildCkConstraints(alter);
> 6. Comments like that, and the 2 same ones above looks like this, sorry:
> https://github.com/Gerold103/tarantool-memes/blob/master/CAPTAIN.png
> How is such a comment supposed to help a reader?
But....
/* Create MoveIndex ops for all space indexes. */
alter_space_move_indexes(alter, 0, old_space->index_id_max + 1);
/* Remember to update schema_version. */
(void) new UpdateSchemaVersion(alter);
....
Okay. I've dropped my comment
>> + ++schema_version;
>
> 7. We have UpdateSchemaVersion for that. It can be touched
> direcrtly only by _space triggers.
I don't know how make it for now. I don't use alter object in on_dd_...replace_trigger
for _ck_constraint space; maybe it worth to introduce it. Do it later with review
on whole patch.
> 8. As I understand, it should be an assertion, not 'if'. When new_tuple == NULL,
> it is impossible that old_tuple == NULL too. What is more, if you reached 'else'
> branch, then 'new_tuple == NULL' is already true, and you do not need to check
> that again.
Ok.
> 9. Why a name is mandatory? As I know, I can omit the name, according to
> the standard.
User can create unnamed check, but I assign a name automatically.
This approach is already used for foreign keys.
============================================================
This patch introduces new system space to persist check
constraints. Format of the space:
_ck_constraint (space id = 357)
[<constraint name> STR, <space id> UINT, <expression string>STR]
CK constraint is local to space, so every pair <CK name, space id>
is unique (and it is PK in _ck_constraint space).
After insertion into this space, a new instance describing check
constraint is created. Check constraint hold Expr tree.
While space features check constraints, it isn't allowed to
be dropped. The :drop() space method firstly deletes all check
constraints and then removes entry from _space.
We use BuildCkConstraints Alter operation object because space
definition may be modified and check AST must be recreated.
Needed for #3691
---
src/box/CMakeLists.txt | 1 +
src/box/alter.cc | 222 ++++++++++++++++++++++++--
src/box/alter.h | 1 +
src/box/bootstrap.snap | Bin 1834 -> 1869 bytes
src/box/ck_constraint.c | 150 +++++++++++++++++
src/box/ck_constraint.h | 173 ++++++++++++++++++++
src/box/errcode.h | 3 +
src/box/lua/schema.lua | 4 +
src/box/lua/space.cc | 2 +
src/box/lua/upgrade.lua | 36 ++++-
src/box/schema.cc | 8 +
src/box/schema_def.h | 9 ++
src/box/space.c | 2 +
src/box/space.h | 5 +
src/box/space_def.c | 98 +-----------
src/box/space_def.h | 2 -
src/box/sql.c | 86 +---------
src/box/sql.h | 36 -----
src/box/sql/build.c | 153 ++++++++++++++----
src/box/sql/insert.c | 51 +++---
src/box/sql/parse.y | 4 +-
src/box/sql/prepare.c | 1 +
src/box/sql/select.c | 4 +-
src/box/sql/sqlInt.h | 12 +-
src/box/sql/tokenize.c | 1 -
src/box/sql/vdbe.c | 6 +-
test/app-tap/tarantoolctl.test.lua | 4 +-
test/box-py/bootstrap.result | 4 +
test/box/access.result | 3 +
test/box/access.test.lua | 1 +
test/box/access_misc.result | 2 +
test/box/access_sysview.result | 6 +-
test/box/alter.result | 6 +-
test/box/misc.result | 2 +
test/sql-tap/check.test.lua | 40 ++---
test/sql-tap/fkey2.test.lua | 4 +-
test/sql-tap/in1.test.lua | 2 +-
test/sql-tap/table.test.lua | 8 +-
test/sql/checks.result | 110 +++++++------
test/sql/checks.test.lua | 69 ++++----
test/sql/errinj.result | 53 ++++++
test/sql/errinj.test.lua | 20 +++
test/sql/gh-2981-check-autoinc.result | 8 +-
test/wal_off/alter.result | 2 +-
44 files changed, 986 insertions(+), 428 deletions(-)
create mode 100644 src/box/ck_constraint.c
create mode 100644 src/box/ck_constraint.h
diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 59e91b65a..070d4a9fe 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -115,6 +115,7 @@ add_library(box STATIC
applier.cc
relay.cc
journal.c
+ ck_constraint.c
sql.c
execute.c
wal.c
diff --git a/src/box/alter.cc b/src/box/alter.cc
index fb668aa4c..9aa5e3653 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -29,6 +29,7 @@
* SUCH DAMAGE.
*/
#include "alter.h"
+#include "ck_constraint.h"
#include "schema.h"
#include "user.h"
#include "space.h"
@@ -528,17 +529,6 @@ space_def_new_from_tuple(struct tuple *tuple, uint32_t errcode,
engine_name, engine_name_len, &opts, fields,
field_count);
auto def_guard = make_scoped_guard([=] { space_def_delete(def); });
- if (def->opts.checks != NULL &&
- sql_checks_resolve_space_def_reference(def->opts.checks,
- def) != 0) {
- box_error_t *err = box_error_last();
- if (box_error_code(err) != ENOMEM) {
- tnt_raise(ClientError, errcode, def->name,
- box_error_message(err));
- } else {
- diag_raise();
- }
- }
struct engine *engine = engine_find_xc(def->engine_name);
engine_check_space_def_xc(engine, def);
def_guard.is_active = false;
@@ -1349,6 +1339,62 @@ TruncateIndex::commit(struct alter_space *alter, int64_t signature)
index_commit_create(new_index, signature);
}
+/**
+ * Due to the fact that ck_constraint object depends on
+ * space_def we must rebuild all ck constraints on space alter.
+ * To make it transactionally, we prepare a list of new objects
+ * in ::prepare method that is fault-tolerant. Later in ::alter or
+ * ::rollback methods we only securely swap the lists.
+ */
+class BuildCkConstraints: public AlterSpaceOp
+{
+public:
+ BuildCkConstraints(struct alter_space *alter)
+ : AlterSpaceOp(alter),
+ ck_constraint(RLIST_HEAD_INITIALIZER(ck_constraint)) {}
+ struct rlist ck_constraint;
+ virtual void prepare(struct alter_space *alter);
+ virtual void alter(struct alter_space *alter);
+ virtual void rollback(struct alter_space *alter);
+ virtual ~BuildCkConstraints();
+};
+
+void
+BuildCkConstraints::prepare(struct alter_space *alter)
+{
+ struct ck_constraint *old_ck_constraint;
+ rlist_foreach_entry(old_ck_constraint, &alter->old_space->ck_constraint,
+ link) {
+ struct ck_constraint *new_ck_constraint =
+ ck_constraint_new(old_ck_constraint->def,
+ alter->new_space->def);
+ if (new_ck_constraint == NULL)
+ diag_raise();
+ rlist_add_entry(&ck_constraint, new_ck_constraint, link);
+ }
+}
+
+void
+BuildCkConstraints::alter(struct alter_space *alter)
+{
+ rlist_swap(&alter->new_space->ck_constraint, &ck_constraint);
+ rlist_swap(&ck_constraint, &alter->old_space->ck_constraint);
+}
+
+void
+BuildCkConstraints::rollback(struct alter_space *alter)
+{
+ rlist_swap(&alter->old_space->ck_constraint, &ck_constraint);
+ rlist_swap(&ck_constraint, &alter->new_space->ck_constraint);
+}
+
+BuildCkConstraints::~BuildCkConstraints()
+{
+ struct ck_constraint *old_ck_constraint, *tmp;
+ rlist_foreach_entry_safe(old_ck_constraint, &ck_constraint, link, tmp)
+ ck_constraint_delete(old_ck_constraint);
+}
+
/**
* UpdateSchemaVersion - increment schema_version. Used on
* in alter_space_do(), i.e. when creating or dropping
@@ -1749,6 +1795,12 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
space_name(old_space),
"the space has foreign key constraints");
}
+ /* Can't drop space having check constraints. */
+ if (!rlist_empty(&old_space->ck_constraint)) {
+ tnt_raise(ClientError, ER_DROP_SPACE,
+ space_name(old_space),
+ "the space has check constraints");
+ }
/**
* The space must be deleted from the space
* cache right away to achieve linearisable
@@ -1846,6 +1898,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
def->field_count);
(void) new CheckSpaceFormat(alter);
(void) new ModifySpace(alter, def);
+ /* Add an op to rebuild check constraints. */
+ (void) new BuildCkConstraints(alter);
def_guard.is_active = false;
/* Create MoveIndex ops for all space indexes. */
alter_space_move_indexes(alter, 0, old_space->index_id_max + 1);
@@ -2088,6 +2142,7 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
* old space.
*/
alter_space_move_indexes(alter, iid + 1, old_space->index_id_max + 1);
+ (void) new BuildCkConstraints(alter);
/* Add an op to update schema_version on commit. */
(void) new UpdateSchemaVersion(alter);
alter_space_do(txn, alter);
@@ -2155,7 +2210,7 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event)
struct index *old_index = old_space->index[i];
(void) new TruncateIndex(alter, old_index->def->iid);
}
-
+ (void) new BuildCkConstraints(alter);
alter_space_do(txn, alter);
scoped_guard.is_active = false;
}
@@ -4064,6 +4119,145 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event)
}
}
+/**
+ * Create an instance of check constraint definition from tuple
+ * on region.
+ */
+static struct ck_constraint_def *
+ck_constraint_def_decode(const struct tuple *tuple, struct region *region)
+{
+ uint32_t name_len;
+ const char *name =
+ tuple_field_str_xc(tuple, BOX_CK_CONSTRAINT_FIELD_NAME,
+ &name_len);
+ if (name_len > BOX_NAME_MAX) {
+ tnt_raise(ClientError, ER_CREATE_CK_CONSTRAINT,
+ tt_cstr(name, BOX_INVALID_NAME_MAX),
+ "check constraint name is too long");
+ }
+ identifier_check_xc(name, name_len);
+ uint32_t expr_str_len;
+ const char *expr_str =
+ tuple_field_str_xc(tuple, BOX_CK_CONSTRAINT_FIELD_EXPR_STR,
+ &expr_str_len);
+ uint32_t name_offset, expr_str_offset;
+ uint32_t sz = ck_constraint_def_sizeof(name_len, expr_str_len,
+ &name_offset, &expr_str_offset);
+ struct ck_constraint_def *ck_constraint_def =
+ (struct ck_constraint_def *)region_alloc_xc(region, sz);
+ ck_constraint_def_create(ck_constraint_def, name, name_len, expr_str,
+ expr_str_len);
+ return ck_constraint_def;
+}
+
+/** Trigger invoked on rollback in the _ck_constraint space. */
+static void
+on_replace_ck_constraint_rollback(struct trigger *trigger, void *event)
+{
+ struct txn_stmt *stmt = txn_last_stmt((struct txn*) event);
+ struct ck_constraint *ck_constraint =
+ (struct ck_constraint *)trigger->data;
+ struct space *space = NULL;
+ if (ck_constraint != NULL)
+ space = space_by_id(ck_constraint->space_id);
+ if (stmt->old_tuple != NULL && stmt->new_tuple == NULL) {
+ /* Rollback DELETE check constraint. */
+ if (ck_constraint == NULL)
+ return;
+ assert(space != NULL);
+ rlist_add_entry(&space->ck_constraint, ck_constraint, link);
+ } else if (stmt->new_tuple != NULL && stmt->old_tuple == NULL) {
+ /* Rollback INSERT check constraint. */
+ assert(space != NULL);
+ rlist_del_entry(ck_constraint, link);
+ ck_constraint_delete(ck_constraint);
+ } else {
+ /* Rollback REPLACE check constraint. */
+ assert(space != NULL);
+ const char *space_name = ck_constraint->def->name;
+ struct ck_constraint *new_ck_constraint =
+ space_ck_constraint_by_name(space, space_name,
+ strlen(space_name));
+ assert(new_ck_constraint != NULL);
+ rlist_del_entry(new_ck_constraint, link);
+ rlist_add_entry(&space->ck_constraint, ck_constraint, link);
+ ck_constraint_delete(new_ck_constraint);
+ }
+}
+
+/**
+ * Trigger invoked on commit in the _ck_constraint space.
+ * Drop useless old check constraint object if exists.
+ */
+static void
+on_replace_ck_constraint_commit(struct trigger *trigger, void * /* event */)
+{
+ struct ck_constraint *old_ck_constraint =
+ (struct ck_constraint *)trigger->data;
+ if (old_ck_constraint != NULL)
+ ck_constraint_delete(old_ck_constraint);
+ ++schema_version;
+}
+
+/** A trigger invoked on replace in the _ck_constraint space. */
+static void
+on_replace_dd_ck_constraint(struct trigger * /* trigger*/, void *event)
+{
+ struct txn *txn = (struct txn *) event;
+ txn_check_singlestatement_xc(txn, "Space _ck_constraint");
+ struct txn_stmt *stmt = txn_current_stmt(txn);
+ struct tuple *old_tuple = stmt->old_tuple;
+ struct tuple *new_tuple = stmt->new_tuple;
+ uint32_t space_id =
+ tuple_field_u32_xc(old_tuple != NULL ? old_tuple : new_tuple,
+ BOX_CK_CONSTRAINT_FIELD_SPACE_ID);
+ struct space *space = space_cache_find_xc(space_id);
+ access_check_ddl(space->def->name, space->def->id, space->def->uid,
+ SC_SPACE, PRIV_A);
+
+ struct trigger *on_rollback =
+ 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) {
+ /* Create or replace check constraint. */
+ struct ck_constraint_def *ck_constraint_def =
+ ck_constraint_def_decode(new_tuple, &fiber()->gc);
+ struct ck_constraint *new_ck_constraint =
+ ck_constraint_new(ck_constraint_def, space->def);
+ if (new_ck_constraint == NULL)
+ diag_raise();
+ const char *space_name = new_ck_constraint->def->name;
+ struct ck_constraint *old_ck_constraint =
+ space_ck_constraint_by_name(space, space_name,
+ strlen(space_name));
+ if (old_ck_constraint != NULL)
+ rlist_del_entry(old_ck_constraint, link);
+ rlist_add_entry(&space->ck_constraint, new_ck_constraint, link);
+ on_commit->data = old_ck_constraint;
+ on_rollback->data = old_tuple == NULL ? new_ck_constraint :
+ old_ck_constraint;
+ } else {
+ assert(new_tuple == NULL && old_tuple != NULL);
+ /* Drop check constraint. */
+ uint32_t name_len;
+ const char *name =
+ tuple_field_str_xc(old_tuple,
+ BOX_CK_CONSTRAINT_FIELD_NAME,
+ &name_len);
+ struct ck_constraint *old_ck_constraint =
+ space_ck_constraint_by_name(space, name, name_len);
+ assert(old_ck_constraint != NULL);
+ rlist_del_entry(old_ck_constraint, link);
+ on_commit->data = old_ck_constraint;
+ on_rollback->data = old_ck_constraint;
+ }
+
+ txn_on_rollback(txn, on_rollback);
+ txn_on_commit(txn, on_commit);
+}
+
struct trigger alter_space_on_replace_space = {
RLIST_LINK_INITIALIZER, on_replace_dd_space, NULL, NULL
};
@@ -4132,4 +4326,8 @@ struct trigger on_replace_fk_constraint = {
RLIST_LINK_INITIALIZER, on_replace_dd_fk_constraint, NULL, NULL
};
+struct trigger on_replace_ck_constraint = {
+ RLIST_LINK_INITIALIZER, on_replace_dd_ck_constraint, NULL, NULL
+};
+
/* vim: set foldmethod=marker */
diff --git a/src/box/alter.h b/src/box/alter.h
index 4108fa47c..b9ba7b846 100644
--- a/src/box/alter.h
+++ b/src/box/alter.h
@@ -46,6 +46,7 @@ extern struct trigger on_replace_sequence_data;
extern struct trigger on_replace_space_sequence;
extern struct trigger on_replace_trigger;
extern struct trigger on_replace_fk_constraint;
+extern struct trigger on_replace_ck_constraint;
extern struct trigger on_stmt_begin_space;
extern struct trigger on_stmt_begin_index;
extern struct trigger on_stmt_begin_truncate;
diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap
diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c
new file mode 100644
index 000000000..110098efc
--- /dev/null
+++ b/src/box/ck_constraint.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2010-2019, 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 <assert.h>
+#include "ck_constraint.h"
+#include "errcode.h"
+#include "small/rlist.h"
+#include "sql.h"
+#include "sql/sqlInt.h"
+
+/**
+ * Resolve space_def references for check constraint via AST
+ * tree traversal.
+ * @param expr Check constraint expression AST to resolve column
+ * references.
+ * @param ck_constraint_name Check constraint name to raise error.
+ * @param space_def Space definition to use.
+ * @retval 0 on success.
+ * @retval -1 on error.
+ */
+static int
+ck_constraint_resolve_ref(struct Expr *expr, struct space_def *space_def)
+{
+ struct Parse parser;
+ sql_parser_create(&parser, sql_get());
+ parser.parse_only = true;
+ sql_resolve_self_reference(&parser, space_def, NC_IsCheck, expr, NULL);
+ int rc = parser.is_aborted ? -1 : 0;
+ if (parser.is_aborted && parser.zErrMsg != NULL)
+ diag_set(ClientError, ER_UNKNOWN, parser.zErrMsg);
+ sql_parser_destroy(&parser);
+ return rc;
+}
+
+uint32_t
+ck_constraint_def_sizeof(uint32_t name_len, uint32_t expr_str_len,
+ uint32_t *name_offset, uint32_t *expr_str_offset)
+{
+ *name_offset = sizeof(struct ck_constraint_def);
+ *expr_str_offset = *name_offset + (name_len != 0 ? name_len + 1 : 0);
+ return *expr_str_offset + (expr_str_len != 0 ? expr_str_len + 1 : 0);
+}
+
+void
+ck_constraint_def_create(struct ck_constraint_def *ck_constraint_def,
+ const char *name, uint32_t name_len,
+ const char *expr_str, uint32_t expr_str_len)
+{
+ uint32_t name_offset, expr_str_offset;
+ (void)ck_constraint_def_sizeof(name_len, expr_str_len, &name_offset,
+ &expr_str_offset);
+ ck_constraint_def->name = (char *)ck_constraint_def + name_offset;
+ sprintf(ck_constraint_def->name, "%.*s", name_len, name);
+ ck_constraint_def->expr_str =
+ (char *)ck_constraint_def + expr_str_offset;
+ sprintf(ck_constraint_def->expr_str, "%.*s", expr_str_len, expr_str);
+ rlist_create(&ck_constraint_def->link);
+}
+
+struct ck_constraint *
+ck_constraint_new(const struct ck_constraint_def *ck_constraint_def,
+ struct space_def *space_def)
+{
+ uint32_t ck_constraint_name_len = strlen(ck_constraint_def->name);
+ uint32_t expr_str_len = strlen(ck_constraint_def->expr_str);
+ uint32_t name_offset, expr_str_offset;
+ uint32_t ck_constraint_def_sz =
+ ck_constraint_def_sizeof(ck_constraint_name_len, expr_str_len,
+ &name_offset, &expr_str_offset);
+ uint32_t ck_constraint_sz = sizeof(struct ck_constraint) +
+ ck_constraint_def_sz;
+ struct ck_constraint *ck_constraint = calloc(1, ck_constraint_sz);
+ if (ck_constraint == NULL) {
+ diag_set(OutOfMemory, ck_constraint_sz, "malloc",
+ "ck_constraint");
+ return NULL;
+ }
+ rlist_create(&ck_constraint->link);
+ ck_constraint->space_id = space_def->id;
+ ck_constraint->def =
+ (struct ck_constraint_def *)((char *)ck_constraint +
+ sizeof(struct ck_constraint));
+ ck_constraint_def_create(ck_constraint->def, ck_constraint_def->name,
+ ck_constraint_name_len,
+ ck_constraint_def->expr_str, expr_str_len);
+ struct Expr *expr =
+ sql_expr_compile(sql_get(), ck_constraint_def->expr_str,
+ expr_str_len);
+ if (expr == NULL ||
+ ck_constraint_resolve_ref(expr, space_def) != 0) {
+ diag_set(ClientError, ER_CREATE_CK_CONSTRAINT,
+ ck_constraint->def->name,
+ box_error_message(box_error_last()));
+ goto error;
+ }
+ ck_constraint->expr = expr;
+
+ return ck_constraint;
+error:
+ ck_constraint_delete(ck_constraint);
+ return NULL;
+}
+
+void
+ck_constraint_delete(struct ck_constraint *ck_constraint)
+{
+ sql_expr_delete(sql_get(), ck_constraint->expr, false);
+ TRASH(ck_constraint);
+ free(ck_constraint);
+}
+
+struct ck_constraint *
+space_ck_constraint_by_name(struct space *space, const char *name,
+ uint32_t name_len)
+{
+ struct ck_constraint *ck_constraint = NULL;
+ rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) {
+ if (strlen(ck_constraint->def->name) == name_len &&
+ memcmp(ck_constraint->def->name, name, name_len) == 0)
+ return ck_constraint;
+ }
+ return NULL;
+}
diff --git a/src/box/ck_constraint.h b/src/box/ck_constraint.h
new file mode 100644
index 000000000..02aa525ce
--- /dev/null
+++ b/src/box/ck_constraint.h
@@ -0,0 +1,173 @@
+#ifndef INCLUDES_BOX_CK_CONSTRAINT_H
+#define INCLUDES_BOX_CK_CONSTRAINT_H
+/*
+ * Copyright 2010-2019, 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 <stdint.h>
+#include "small/rlist.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct space;
+struct space_def;
+struct Expr;
+
+/**
+ * Definition of check constraint.
+ * The memory of size calculated with ck_constraint_def_sizeof
+ * must be allocated manually and must be initialized with routine
+ * ck_constraint_def_create.
+ */
+struct ck_constraint_def {
+ /**
+ * The name of the check constraint is used for error
+ * reporting. Must be unique for a given space.
+ */
+ char *name;
+ /**
+ * The string describing an check constraint expression.
+ */
+ char *expr_str;
+ /**
+ * Organize check_def structs into linked list with
+ * Parse::new_ck_constraint.
+ */
+ struct rlist link;
+};
+
+/* Structure representing check constraint object. */
+struct ck_constraint {
+ /**
+ * The check constraint definition.
+ */
+ struct ck_constraint_def *def;
+ /**
+ * The check constraint expression AST is built for
+ * ck_constraint::def::expr_str with sql_expr_compile
+ * and resolved with sqlResolveExprNames for
+ * space with space[ck_constraint::space_id] definition.
+ */
+ struct Expr *expr;
+ /**
+ * The id of the space this check constraint is
+ * built for.
+ */
+ uint32_t space_id;
+ /**
+ * Organize check constraint structs into linked list
+ * with space::ck_constraint.
+ */
+ struct rlist link;
+};
+
+/**
+ * Calculate check constraint definition memory size and fields
+ * offsets for given arguments.
+ * Alongside with struct ck_constraint_def itself, we reserve
+ * memory for string containing its name and expression string.
+ *
+ * Memory layout:
+ * +-----------------------------+ <- Allocated memory starts here
+ * | struct ck_constraint_def |
+ * |-----------------------------|
+ * | name + \0 |
+ * |-----------------------------|
+ * | expr_str + \0 |
+ * +-----------------------------+
+ * @param name_len The length of the name.
+ * @param expr_str_len The length of the expr_str.
+ * @param[out] name_offset The offset of the name string.
+ * @param[out] expr_str_offset The offset of the expr_str string.
+ */
+uint32_t
+ck_constraint_def_sizeof(uint32_t name_len, uint32_t expr_str_len,
+ uint32_t *name_offset, uint32_t *expr_str_offset);
+
+/**
+ * Initialize specified memory chunk ck_constraint_def of size
+ * calculated with ck_constraint_def_sizeof for given arguments.
+ * @param ck_constraint_def Check constraint definition to
+ * initialize.
+ * @param name The check constraint name.
+ * @param name_len The length of the name.
+ * @param expr_str The string describing check constraint
+ * expression (optional).
+ * @param expr_str_len The length of the expr_str.
+ */
+void
+ck_constraint_def_create(struct ck_constraint_def *ck_constraint_def,
+ const char *name, uint32_t name_len,
+ const char *expr_str, uint32_t expr_str_len);
+
+/**
+ * Create a new object representing check constraint object
+ * for given check constraint definition and space definition
+ * this constraint is related to.
+ * This routine manually allocates own space_def structure as
+ * a part of new memory chunk.
+ * @param ck_constraint_def The check constraint definition object
+ * to use. Must be initialized with
+ * ck_constraint_def_new.
+ * @param space_def The space definition of the space this check
+ * constraint is constructed for.
+ * @retval not NULL Check constraint object on success,
+ * NULL otherwise.
+*/
+struct ck_constraint *
+ck_constraint_new(const struct ck_constraint_def *ck_constraint_def,
+ struct space_def *space_def);
+
+/**
+ * Destroy check constraint memory, release acquired resources.
+ * @param ck_constraint The check constraint object to destroy.
+ */
+void
+ck_constraint_delete(struct ck_constraint *ck_constraint);
+
+/**
+ * Find check constraint object in space by given name and
+ * name_len.
+ * @param space The space to lookup check constarint.
+ * @param name The check constraint name.
+ * @param name_len The length of the name.
+ * @retval not NULL Check constrain if exists, NULL otherwise.
+ */
+struct ck_constraint *
+space_ck_constraint_by_name(struct space *space, const char *name,
+ uint32_t name_len);
+
+#if defined(__cplusplus)
+} /* extern "C" { */
+#endif
+
+#endif /* INCLUDES_BOX_CK_CONSTRAINT_H */
diff --git a/src/box/errcode.h b/src/box/errcode.h
index 7764aa352..83f719225 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -240,6 +240,9 @@ struct errcode_record {
/*185 */_(ER_SQL_UNKNOWN_TOKEN, "Syntax error: unrecognized token: '%.*s'") \
/*186 */_(ER_SQL_PARSER_GENERIC, "%s") \
/*187 */_(ER_SQL_ANALYZE_ARGUMENT, "ANALYZE statement argument %s is not a base table") \
+ /*188 */_(ER_CREATE_CK_CONSTRAINT, "Failed to create check constraint '%s': %s") \
+ /*189 */_(ER_CK_CONSTRAINT_FAILED, "Check constraint failed: %s") \
+
/*
* !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 6049931ab..3822d7f7e 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -513,6 +513,7 @@ box.schema.space.drop = function(space_id, space_name, opts)
local _truncate = box.space[box.schema.TRUNCATE_ID]
local _space_sequence = box.space[box.schema.SPACE_SEQUENCE_ID]
local _fk_constraint = box.space[box.schema.FK_CONSTRAINT_ID]
+ local _ck_constraint = box.space[box.schema.CK_CONSTRAINT_ID]
local sequence_tuple = _space_sequence:delete{space_id}
if sequence_tuple ~= nil and sequence_tuple[3] == true then
-- Delete automatically generated sequence.
@@ -529,6 +530,9 @@ box.schema.space.drop = function(space_id, space_name, opts)
for _, t in _fk_constraint.index.child_id:pairs({space_id}) do
_fk_constraint:delete({t.name, space_id})
end
+ for _, t in _ck_constraint.index.space_id:pairs({space_id}) do
+ _ck_constraint:delete({t.name, space_id})
+ end
revoke_object_privs('space', space_id)
_truncate:delete{space_id}
if _space:delete{space_id} == nil then
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index 9dfc97b6a..a40b53d2b 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -562,6 +562,8 @@ box_lua_space_init(struct lua_State *L)
lua_setfield(L, -2, "SQL_STAT4_ID");
lua_pushnumber(L, BOX_FK_CONSTRAINT_ID);
lua_setfield(L, -2, "FK_CONSTRAINT_ID");
+ lua_pushnumber(L, BOX_CK_CONSTRAINT_ID);
+ lua_setfield(L, -2, "CK_CONSTRAINT_ID");
lua_pushnumber(L, BOX_TRUNCATE_ID);
lua_setfield(L, -2, "TRUNCATE_ID");
lua_pushnumber(L, BOX_SEQUENCE_ID);
diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index dc7328714..492834b28 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -74,6 +74,7 @@ local function set_system_triggers(val)
box.space._schema:run_triggers(val)
box.space._cluster:run_triggers(val)
box.space._fk_constraint:run_triggers(val)
+ box.space._ck_constraint:run_triggers(val)
end
--------------------------------------------------------------------------------
@@ -94,6 +95,7 @@ local function erase()
truncate(box.space._schema)
truncate(box.space._cluster)
truncate(box.space._fk_constraint)
+ truncate(box.space._ck_constraint)
end
local function create_sysview(source_id, target_id)
@@ -627,7 +629,39 @@ local function upgrade_to_2_1_1()
if opts['sql'] ~= nil then
opts['sql'] = nil
_index:replace(box.tuple.new({index.id, index.iid, index.name,
- index.type, opts, index.parts}))
+ index.type, opts, index.parts}))
+ end
+ end
+
+ local MAP = setmap({})
+ local _space = box.space[box.schema.SPACE_ID]
+ local _ck_constraint = box.space[box.schema.CK_CONSTRAINT_ID]
+ log.info("create space _ck_constraint")
+ local format = {{name='name', type='string'},
+ {name='space_id', type='unsigned'},
+ {name='expr_str', type='str'}}
+ _space:insert{_ck_constraint.id, ADMIN, '_ck_constraint', 'memtx', 0, MAP, format}
+
+ log.info("create index primary on _ck_constraint")
+ _index:insert{_ck_constraint.id, 0, 'primary', 'tree',
+ {unique = true}, {{0, 'string'}, {1, 'unsigned'}}}
+
+ log.info("create secondary index child_id on _ck_constraint")
+ _index:insert{_ck_constraint.id, 1, 'space_id', 'tree',
+ {unique = false}, {{1, 'unsigned'}}}
+ for _, space in _space:pairs() do
+ local flags = space.flags
+ if flags['checks'] ~= nil then
+ for i, check in pairs(flags['checks']) do
+ local expr_str = check.expr
+ local check_name = check.name or
+ "CK_CONSTRAINT_"..i.."_"..space.name
+ _ck_constraint:insert({check_name, space.id, expr_str})
+ end
+ flags['checks'] = nil
+ _space:replace(box.tuple.new({space.id, space.owner, space.name,
+ space.engine, space.field_count,
+ flags, space.format}))
end
end
end
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 74d70d8d6..7b672218a 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -469,6 +469,14 @@ schema_init()
sc_space_new(BOX_FK_CONSTRAINT_ID, "_fk_constraint", key_parts, 2,
&on_replace_fk_constraint, NULL);
+ /* _ck_сonstraint - check constraints. */
+ key_parts[0].fieldno = 0; /* constraint name */
+ key_parts[0].type = FIELD_TYPE_STRING;
+ key_parts[1].fieldno = 1; /* space id */
+ key_parts[1].type = FIELD_TYPE_UNSIGNED;
+ sc_space_new(BOX_CK_CONSTRAINT_ID, "_ck_constraint", key_parts, 2,
+ &on_replace_ck_constraint, NULL);
+
/*
* _vinyl_deferred_delete - blackhole that is needed
* for writing deferred DELETE statements generated by
diff --git a/src/box/schema_def.h b/src/box/schema_def.h
index a760ecc3f..920804a28 100644
--- a/src/box/schema_def.h
+++ b/src/box/schema_def.h
@@ -111,6 +111,8 @@ enum {
BOX_SQL_STAT4_ID = 349,
/** Space id of _fk_constraint. */
BOX_FK_CONSTRAINT_ID = 356,
+ /** Space id of _ck_contraint. */
+ BOX_CK_CONSTRAINT_ID = 357,
/** End of the reserved range of system spaces. */
BOX_SYSTEM_ID_MAX = 511,
BOX_ID_NIL = 2147483647
@@ -241,6 +243,13 @@ enum {
BOX_FK_CONSTRAINT_FIELD_PARENT_COLS = 8,
};
+/** _ck_constraint fields. */
+enum {
+ BOX_CK_CONSTRAINT_FIELD_NAME = 0,
+ BOX_CK_CONSTRAINT_FIELD_SPACE_ID = 1,
+ BOX_CK_CONSTRAINT_FIELD_EXPR_STR = 2,
+};
+
/*
* Different objects which can be subject to access
* control.
diff --git a/src/box/space.c b/src/box/space.c
index e140f3d53..eda571d92 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -165,6 +165,7 @@ space_create(struct space *space, struct engine *engine,
space_fill_index_map(space);
rlist_create(&space->parent_fk_constraint);
rlist_create(&space->child_fk_constraint);
+ rlist_create(&space->ck_constraint);
return 0;
fail_free_indexes:
@@ -225,6 +226,7 @@ space_delete(struct space *space)
assert(space->sql_triggers == NULL);
assert(rlist_empty(&space->parent_fk_constraint));
assert(rlist_empty(&space->child_fk_constraint));
+ assert(rlist_empty(&space->ck_constraint));
space->vtab->destroy(space);
}
diff --git a/src/box/space.h b/src/box/space.h
index 211706d24..197ec36e2 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -194,6 +194,11 @@ struct space {
*/
struct rlist parent_fk_constraint;
struct rlist child_fk_constraint;
+ /**
+ * List of check constaints linked with
+ * ck_constraint::link.
+ */
+ struct rlist ck_constraint;
/**
* Mask indicates which fields are involved in foreign
* key constraint checking routine. Includes fields
diff --git a/src/box/space_def.c b/src/box/space_def.c
index d0864cc72..cb68cea1c 100644
--- a/src/box/space_def.c
+++ b/src/box/space_def.c
@@ -35,28 +35,12 @@
#include "sql.h"
#include "msgpuck.h"
-/**
- * Make checks from msgpack.
- * @param str pointer to array of maps
- * e.g. [{"expr": "x < y", "name": "ONE"}, ..].
- * @param len array items count.
- * @param[out] opt pointer to store parsing result.
- * @param errcode Code of error to set if something is wrong.
- * @param field_no Field number of an option in a parent element.
- * @retval 0 on success.
- * @retval not 0 on error. Also set diag message.
- */
-static int
-checks_array_decode(const char **str, uint32_t len, char *opt, uint32_t errcode,
- uint32_t field_no);
-
const struct space_opts space_opts_default = {
/* .group_id = */ 0,
/* .is_temporary = */ false,
/* .is_ephemeral = */ false,
/* .view = */ false,
/* .sql = */ NULL,
- /* .checks = */ NULL,
};
const struct opt_def space_opts_reg[] = {
@@ -64,8 +48,7 @@ const struct opt_def space_opts_reg[] = {
OPT_DEF("temporary", OPT_BOOL, struct space_opts, is_temporary),
OPT_DEF("view", OPT_BOOL, struct space_opts, is_view),
OPT_DEF("sql", OPT_STRPTR, struct space_opts, sql),
- OPT_DEF_ARRAY("checks", struct space_opts, checks,
- checks_array_decode),
+ OPT_DEF_LEGACY("checks"),
OPT_END,
};
@@ -113,16 +96,6 @@ space_def_dup_opts(struct space_def *def, const struct space_opts *opts)
return -1;
}
}
- if (opts->checks != NULL) {
- def->opts.checks = sql_expr_list_dup(sql_get(), opts->checks, 0);
- if (def->opts.checks == NULL) {
- free(def->opts.sql);
- diag_set(OutOfMemory, 0, "sql_expr_list_dup",
- "def->opts.checks");
- return -1;
- }
- sql_checks_update_space_def_reference(def->opts.checks, def);
- }
return 0;
}
@@ -300,74 +273,5 @@ void
space_opts_destroy(struct space_opts *opts)
{
free(opts->sql);
- sql_expr_list_delete(sql_get(), opts->checks);
TRASH(opts);
}
-
-static int
-checks_array_decode(const char **str, uint32_t len, char *opt, uint32_t errcode,
- uint32_t field_no)
-{
- char *errmsg = tt_static_buf();
- struct ExprList *checks = NULL;
- const char **map = str;
- struct sql *db = sql_get();
- for (uint32_t i = 0; i < len; i++) {
- checks = sql_expr_list_append(db, checks, NULL);
- if (checks == NULL) {
- diag_set(OutOfMemory, 0, "sql_expr_list_append",
- "checks");
- goto error;
- }
- const char *expr_name = NULL;
- const char *expr_str = NULL;
- uint32_t expr_name_len = 0;
- uint32_t expr_str_len = 0;
- uint32_t map_size = mp_decode_map(map);
- for (uint32_t j = 0; j < map_size; j++) {
- if (mp_typeof(**map) != MP_STR) {
- diag_set(ClientError, errcode, field_no,
- "key must be a string");
- goto error;
- }
- uint32_t key_len;
- const char *key = mp_decode_str(map, &key_len);
- if (mp_typeof(**map) != MP_STR) {
- snprintf(errmsg, TT_STATIC_BUF_LEN,
- "invalid MsgPack map field '%.*s' type",
- key_len, key);
- diag_set(ClientError, errcode, field_no, errmsg);
- goto error;
- }
- if (key_len == 4 && memcmp(key, "expr", key_len) == 0) {
- expr_str = mp_decode_str(map, &expr_str_len);
- } else if (key_len == 4 &&
- memcmp(key, "name", key_len) == 0) {
- expr_name = mp_decode_str(map, &expr_name_len);
- } else {
- snprintf(errmsg, TT_STATIC_BUF_LEN,
- "invalid MsgPack map field '%.*s'",
- key_len, key);
- diag_set(ClientError, errcode, field_no, errmsg);
- goto error;
- }
- }
- if (sql_check_list_item_init(checks, i, expr_name, expr_name_len,
- expr_str, expr_str_len) != 0) {
- box_error_t *err = box_error_last();
- if (box_error_code(err) != ENOMEM) {
- snprintf(errmsg, TT_STATIC_BUF_LEN,
- "invalid expression specified (%s)",
- box_error_message(err));
- diag_set(ClientError, errcode, field_no,
- errmsg);
- }
- goto error;
- }
- }
- *(struct ExprList **)opt = checks;
- return 0;
-error:
- sql_expr_list_delete(db, checks);
- return -1;
-}
diff --git a/src/box/space_def.h b/src/box/space_def.h
index 52ff56764..71d168342 100644
--- a/src/box/space_def.h
+++ b/src/box/space_def.h
@@ -71,8 +71,6 @@ struct space_opts {
bool is_view;
/** SQL statement that produced this space. */
char *sql;
- /** SQL Checks expressions list. */
- struct ExprList *checks;
};
extern const struct space_opts space_opts_default;
diff --git a/src/box/sql.c b/src/box/sql.c
index 4fac020b0..fc469126e 100644
--- a/src/box/sql.c
+++ b/src/box/sql.c
@@ -1037,16 +1037,9 @@ sql_encode_table_opts(struct region *region, struct space_def *def,
bool is_error = false;
mpstream_init(&stream, region, region_reserve_cb, region_alloc_cb,
set_encode_error, &is_error);
- int checks_cnt = 0;
- struct ExprList_item *a;
bool is_view = def->opts.is_view;
- struct ExprList *checks = def->opts.checks;
- if (checks != NULL) {
- checks_cnt = checks->nExpr;
- a = checks->a;
- }
assert(is_view || sql == NULL);
- mpstream_encode_map(&stream, 2 * is_view + (checks_cnt > 0));
+ mpstream_encode_map(&stream, 2 * is_view);
if (is_view) {
mpstream_encode_str(&stream, "sql");
@@ -1054,23 +1047,6 @@ sql_encode_table_opts(struct region *region, struct space_def *def,
mpstream_encode_str(&stream, "view");
mpstream_encode_bool(&stream, true);
}
- if (checks_cnt > 0) {
- mpstream_encode_str(&stream, "checks");
- mpstream_encode_array(&stream, checks_cnt);
- }
- for (int i = 0; i < checks_cnt && !is_error; ++i, ++a) {
- int items = (a->pExpr != NULL) + (a->zName != NULL);
- mpstream_encode_map(&stream, items);
- assert(a->pExpr != NULL);
- struct Expr *pExpr = a->pExpr;
- assert(pExpr->u.zToken != NULL);
- mpstream_encode_str(&stream, "expr");
- mpstream_encode_str(&stream, pExpr->u.zToken);
- if (a->zName != NULL) {
- mpstream_encode_str(&stream, "name");
- mpstream_encode_str(&stream, a->zName);
- }
- }
mpstream_flush(&stream);
if (is_error) {
diag_set(OutOfMemory, stream.pos - stream.buf,
@@ -1304,63 +1280,3 @@ sql_ephemeral_space_new(Parse *parser, const char *name)
return space;
}
-
-int
-sql_check_list_item_init(struct ExprList *expr_list, int column,
- const char *expr_name, uint32_t expr_name_len,
- const char *expr_str, uint32_t expr_str_len)
-{
- assert(column < expr_list->nExpr);
- struct ExprList_item *item = &expr_list->a[column];
- memset(item, 0, sizeof(*item));
- if (expr_name != NULL) {
- item->zName = sqlDbStrNDup(db, expr_name, expr_name_len);
- if (item->zName == NULL) {
- diag_set(OutOfMemory, expr_name_len, "sqlDbStrNDup",
- "item->zName");
- return -1;
- }
- }
- if (expr_str != NULL) {
- item->pExpr = sql_expr_compile(db, expr_str, expr_str_len);
- /* The item->zName would be released later. */
- if (item->pExpr == NULL)
- return -1;
- }
- return 0;
-}
-
-static int
-update_space_def_callback(Walker *walker, Expr *expr)
-{
- if (expr->op == TK_COLUMN && ExprHasProperty(expr, EP_Resolved))
- expr->space_def = walker->u.space_def;
- return WRC_Continue;
-}
-
-void
-sql_checks_update_space_def_reference(ExprList *expr_list,
- struct space_def *def)
-{
- assert(expr_list != NULL);
- Walker w;
- memset(&w, 0, sizeof(w));
- w.xExprCallback = update_space_def_callback;
- w.u.space_def = def;
- for (int i = 0; i < expr_list->nExpr; i++)
- sqlWalkExpr(&w, expr_list->a[i].pExpr);
-}
-
-int
-sql_checks_resolve_space_def_reference(ExprList *expr_list,
- struct space_def *def)
-{
- Parse parser;
- sql_parser_create(&parser, sql_get());
- parser.parse_only = true;
-
- sql_resolve_self_reference(&parser, def, NC_IsCheck, NULL, expr_list);
- int rc = parser.is_aborted ? -1 : 0;
- sql_parser_destroy(&parser);
- return rc;
-}
diff --git a/src/box/sql.h b/src/box/sql.h
index 400360f59..f191e9f90 100644
--- a/src/box/sql.h
+++ b/src/box/sql.h
@@ -280,42 +280,6 @@ sql_resolve_self_reference(struct Parse *parser, struct space_def *def,
int type, struct Expr *expr,
struct ExprList *expr_list);
-/**
- * Initialize check_list_item.
- * @param expr_list ExprList with item.
- * @param column index.
- * @param expr_name expression name (optional).
- * @param expr_name_len expresson name length (optional).
- * @param expr_str expression to build string.
- * @param expr_str_len expression to build string length.
- * @retval 0 on success.
- * @retval -1 on error.
- */
-int
-sql_check_list_item_init(struct ExprList *expr_list, int column,
- const char *expr_name, uint32_t expr_name_len,
- const char *expr_str, uint32_t expr_str_len);
-
-/**
- * Resolve space_def references checks for expr_list.
- * @param expr_list to modify.
- * @param def to refer to.
- * @retval 0 on success.
- * @retval -1 on error.
- */
-int
-sql_checks_resolve_space_def_reference(struct ExprList *expr_list,
- struct space_def *def);
-
-/**
- * Update space_def references for expr_list.
- * @param expr_list to modify.
- * @param def to refer to.
- */
-void
-sql_checks_update_space_def_reference(struct ExprList *expr_list,
- struct space_def *def);
-
/**
* Initialize a new parser object.
* A number of service allocations are performed on the region,
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index 0c0655543..3197cde0c 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -661,31 +661,55 @@ primary_key_exit:
}
void
-sql_add_check_constraint(struct Parse *parser, struct ExprSpan *span)
+sql_add_ck_constraint(struct Parse *parser, struct ExprSpan *span)
{
struct Expr *expr = span->pExpr;
- if (parser->new_space != NULL) {
- struct space *space = parser->new_space;
- expr->u.zToken =
- sqlDbStrNDup(parser->db, (char *)span->zStart,
- (int)(span->zEnd - span->zStart));
- if (expr->u.zToken == NULL)
- goto release_expr;
- space->def->opts.checks =
- sql_expr_list_append(parser->db,
- space->def->opts.checks, expr);
- if (space->def->opts.checks == NULL) {
- sqlDbFree(parser->db, expr->u.zToken);
- goto release_expr;
- }
- if (parser->constraintName.n != 0) {
- sqlExprListSetName(parser, space->def->opts.checks,
- &parser->constraintName, 1);
+ struct space *space = parser->new_space;
+ assert(space != NULL);
+
+ struct region *region = &parser->region;
+ uint32_t expr_str_len = (uint32_t)(span->zEnd - span->zStart);
+ const char *expr_str = span->zStart;
+
+ const char *ck_constraint_name = NULL;
+ if (parser->constraintName.n != 0) {
+ ck_constraint_name =
+ region_alloc(region, parser->constraintName.n + 1);
+ if (ck_constraint_name == NULL) {
+ diag_set(OutOfMemory, parser->constraintName.n + 1,
+ "region_alloc", "ck_constraint_name");
+ parser->is_aborted = true;
+ goto out;
}
+ sprintf((char *)ck_constraint_name, "%.*s",
+ parser->constraintName.n, parser->constraintName.z);
+ sqlNormalizeName((char *)ck_constraint_name);
} else {
-release_expr:
- sql_expr_delete(parser->db, expr, false);
- }
+ ck_constraint_name = tt_sprintf("CK_CONSTRAINT_%d_%s",
+ ++parser->ck_constraint_count,
+ space->def->name);
+ }
+ uint32_t ck_constraint_name_len = strlen(ck_constraint_name);
+
+ uint32_t name_offset, expr_str_offset;
+ uint32_t ck_constraint_def_sz =
+ ck_constraint_def_sizeof(ck_constraint_name_len, expr_str_len,
+ &name_offset, &expr_str_offset);
+ struct ck_constraint_def *ck_constraint_def =
+ region_alloc(region, ck_constraint_def_sz);
+ if (ck_constraint_def == NULL) {
+ diag_set(OutOfMemory, ck_constraint_def_sz, "region_alloc",
+ "ck_constraint_def");
+ parser->is_aborted = true;
+ goto out;
+ }
+ ck_constraint_def_create(ck_constraint_def, ck_constraint_name,
+ ck_constraint_name_len, expr_str,
+ expr_str_len);
+ rlist_add_entry(&parser->new_ck_constraint, ck_constraint_def, link);
+out:
+ sql_expr_delete(parser->db, expr, false);
+ return;
}
/*
@@ -974,6 +998,40 @@ emitNewSysSpaceSequenceRecord(Parse *pParse, int space_id, const char reg_seq_id
return first_col;
}
+/**
+ * Generate opcodes to serialize check constraint definition into
+ * MsgPack and insert produced tuple into _ck_constraint space.
+ * @param parser Parsing context.
+ * @param ck_constraint_def Check constraint definition to be
+ * serialized.
+ * @param reg_space_id The VDBE containing space id.
+*/
+static void
+vdbe_emit_ck_constraint_create(struct Parse *parser,
+ const struct ck_constraint_def *ck_constraint_def,
+ uint32_t reg_space_id)
+{
+ struct sql *db = parser->db;
+ struct Vdbe *v = sqlGetVdbe(parser);
+ assert(v != NULL);
+ int ck_constraint_reg = sqlGetTempRange(parser, 4);
+ sqlVdbeAddOp4(v, OP_String8, 0, ck_constraint_reg, 0,
+ sqlDbStrDup(db, ck_constraint_def->name),
+ P4_DYNAMIC);
+ sqlVdbeAddOp2(v, OP_SCopy, reg_space_id, ck_constraint_reg + 1);
+ sqlVdbeAddOp4(v, OP_String8, 0, ck_constraint_reg + 2, 0,
+ sqlDbStrDup(db, ck_constraint_def->expr_str),
+ P4_DYNAMIC);
+ sqlVdbeAddOp3(v, OP_MakeRecord, ck_constraint_reg, 3,
+ ck_constraint_reg + 3);
+ sqlVdbeAddOp3(v, OP_SInsert, BOX_CK_CONSTRAINT_ID, 0,
+ ck_constraint_reg + 3);
+ save_record(parser, BOX_CK_CONSTRAINT_ID, ck_constraint_reg, 2,
+ v->nOp - 1);
+ sqlReleaseTempRange(parser, ck_constraint_reg, 4);
+ return;
+}
+
/**
* Generate opcodes to serialize foreign key into MsgPack and
* insert produced tuple into _fk_constraint space.
@@ -1147,7 +1205,7 @@ sqlEndTable(Parse * pParse, /* Parse context */
sqlErrorMsg(pParse,
"PRIMARY KEY missing on table %s",
new_space->def->name);
- goto cleanup;
+ return;
}
}
@@ -1252,9 +1310,12 @@ sqlEndTable(Parse * pParse, /* Parse context */
fk_def->child_id = reg_space_id;
vdbe_emit_fk_constraint_create(pParse, fk_def);
}
-cleanup:
- sql_expr_list_delete(db, new_space->def->opts.checks);
- new_space->def->opts.checks = NULL;
+ struct ck_constraint_def *ck_constraint_def;
+ rlist_foreach_entry(ck_constraint_def, &pParse->new_ck_constraint,
+ link) {
+ vdbe_emit_ck_constraint_create(pParse, ck_constraint_def,
+ reg_space_id);
+ }
}
void
@@ -1456,6 +1517,38 @@ vdbe_emit_fk_constraint_drop(struct Parse *parse_context, char *constraint_name,
sqlReleaseTempRange(parse_context, key_reg, 3);
}
+/**
+ * Generate VDBE program to remove entry from _ck_constraint space.
+ *
+ * @param parser Parsing context.
+ * @param ck_constraint_name Name of CK constraint to be dropped.
+ * @param child_id Id of table which constraint belongs to.
+ */
+static void
+vdbe_emit_ck_constraint_drop(struct Parse *parser,
+ const char *ck_constraint_name, uint32_t space_id)
+{
+ struct Vdbe *v = sqlGetVdbe(parser);
+ struct sql *db = v->db;
+ assert(v != NULL);
+ int key_reg = sqlGetTempRange(parser, 3);
+ sqlVdbeAddOp4(v, OP_String8, 0, key_reg, 0,
+ sqlDbStrDup(db, ck_constraint_name),
+ P4_DYNAMIC);
+ sqlVdbeAddOp2(v, OP_Integer, space_id, key_reg + 1);
+ const char *error_msg =
+ tt_sprintf(tnt_errcode_desc(ER_NO_SUCH_CONSTRAINT),
+ ck_constraint_name);
+ if (vdbe_emit_halt_with_presence_test(parser, BOX_CK_CONSTRAINT_ID, 0,
+ key_reg, 2, ER_NO_SUCH_CONSTRAINT,
+ error_msg, false,
+ OP_Found) != 0)
+ return;
+ sqlVdbeAddOp3(v, OP_MakeRecord, key_reg, 2, key_reg + 2);
+ sqlVdbeAddOp2(v, OP_SDelete, BOX_CK_CONSTRAINT_ID, key_reg + 2);
+ sqlReleaseTempRange(parser, key_reg, 3);
+}
+
/**
* Generate code to drop a table.
* This routine includes dropping triggers, sequences,
@@ -1528,6 +1621,13 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space,
return;
vdbe_emit_fk_constraint_drop(parse_context, fk_name_dup, space_id);
}
+ /* Delete all CK constraints. */
+ struct ck_constraint *ck_constraint;
+ rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) {
+ vdbe_emit_ck_constraint_drop(parse_context,
+ ck_constraint->def->name,
+ space_id);
+ }
/*
* Drop all _space and _index entries that refer to the
* table.
@@ -2763,8 +2863,7 @@ sqlSrcListDelete(sql * db, SrcList * pList)
*/
assert(pItem->space == NULL ||
!pItem->space->def->opts.is_temporary ||
- (pItem->space->index == NULL &&
- pItem->space->def->opts.checks == NULL));
+ pItem->space->index == NULL);
sql_select_delete(db, pItem->pSelect);
sql_expr_delete(db, pItem->pOn, false);
sqlIdListDelete(db, pItem->pUsing);
diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c
index 6f7f02040..2fe74a027 100644
--- a/src/box/sql/insert.c
+++ b/src/box/sql/insert.c
@@ -919,34 +919,29 @@ vdbe_emit_constraint_checks(struct Parse *parse_context, struct space *space,
if (on_conflict == ON_CONFLICT_ACTION_DEFAULT)
on_conflict = ON_CONFLICT_ACTION_ABORT;
/* Test all CHECK constraints. */
- struct ExprList *checks = def->opts.checks;
enum on_conflict_action on_conflict_check = on_conflict;
if (on_conflict == ON_CONFLICT_ACTION_REPLACE)
on_conflict_check = ON_CONFLICT_ACTION_ABORT;
- if (checks != NULL) {
+ if (!rlist_empty(&space->ck_constraint))
parse_context->ckBase = new_tuple_reg;
- for (int i = 0; i < checks->nExpr; i++) {
- struct Expr *expr = checks->a[i].pExpr;
- if (is_update &&
- checkConstraintUnchanged(expr, upd_cols))
- continue;
- int all_ok = sqlVdbeMakeLabel(v);
- sqlExprIfTrue(parse_context, expr, all_ok,
- SQL_JUMPIFNULL);
- if (on_conflict == ON_CONFLICT_ACTION_IGNORE) {
- sqlVdbeGoto(v, ignore_label);
- } else {
- char *name = checks->a[i].zName;
- if (name == NULL)
- name = def->name;
- sqlHaltConstraint(parse_context,
- SQL_CONSTRAINT_CHECK,
- on_conflict_check, name,
- P4_TRANSIENT,
- P5_ConstraintCheck);
- }
+ struct ck_constraint *ck_constraint;
+ rlist_foreach_entry(ck_constraint, &space->ck_constraint, link) {
+ struct Expr *expr = ck_constraint->expr;
+ if (is_update && checkConstraintUnchanged(expr, upd_cols) != 0)
+ continue;
+ int all_ok = sqlVdbeMakeLabel(v);
+ sqlExprIfTrue(parse_context, expr, all_ok, SQL_JUMPIFNULL);
+ if (on_conflict == ON_CONFLICT_ACTION_IGNORE) {
+ sqlVdbeGoto(v, ignore_label);
sqlVdbeResolveLabel(v, all_ok);
+ } else {
+ char *name = ck_constraint->def->name;
+ sqlHaltConstraint(parse_context,
+ SQL_CONSTRAINT_CHECK,
+ on_conflict_check, name,
+ P4_TRANSIENT, P5_ConstraintCheck);
}
+ sqlVdbeResolveLabel(v, all_ok);
}
sql_emit_table_types(v, space->def, new_tuple_reg);
/*
@@ -1231,14 +1226,12 @@ xferOptimization(Parse * pParse, /* Parser context */
if (pSrcIdx == NULL)
return 0;
}
- /* Get server checks. */
- ExprList *pCheck_src = src->def->opts.checks;
- ExprList *pCheck_dest = dest->def->opts.checks;
- if (pCheck_dest != NULL &&
- sqlExprListCompare(pCheck_src, pCheck_dest, -1) != 0) {
- /* Tables have different CHECK constraints. Ticket #2252 */
+ /*
+ * Dissallow the transfer optimization if the destination
+ * table contains any check constraints.
+ */
+ if (!rlist_empty(&dest->ck_constraint))
return 0;
- }
/* Disallow the transfer optimization if the destination table constains
* any foreign key constraints. This is more restrictive than necessary.
* So the extra complication to make this rule less restrictive is probably
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index b27651c3b..a419fd689 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -270,7 +270,7 @@ ccons ::= PRIMARY KEY sortorder(Z) autoinc(I).
ccons ::= UNIQUE. {sql_create_index(pParse,0,0,0,0,
SORT_ORDER_ASC, false,
SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
-ccons ::= CHECK LP expr(X) RP. {sql_add_check_constraint(pParse,&X);}
+ccons ::= CHECK LP expr(X) RP. {sql_add_ck_constraint(pParse,&X);}
ccons ::= REFERENCES nm(T) eidlist_opt(TA) matcharg(M) refargs(R).
{sql_create_foreign_key(pParse, NULL, NULL, NULL, &T, TA, false, M, R);}
ccons ::= defer_subclause(D). {fk_constraint_change_defer_mode(pParse, D);}
@@ -328,7 +328,7 @@ tcons ::= UNIQUE LP sortlist(X) RP.
SORT_ORDER_ASC,false,
SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);}
tcons ::= CHECK LP expr(E) RP .
- {sql_add_check_constraint(pParse,&E);}
+ {sql_add_ck_constraint(pParse,&E);}
tcons ::= FOREIGN KEY LP eidlist(FA) RP
REFERENCES nm(T) eidlist_opt(TA) matcharg(M) refargs(R) defer_subclause_opt(D). {
sql_create_foreign_key(pParse, NULL, NULL, FA, &T, TA, D, M, R);
diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c
index 85385ee29..7dd75a059 100644
--- a/src/box/sql/prepare.c
+++ b/src/box/sql/prepare.c
@@ -290,6 +290,7 @@ sql_parser_create(struct Parse *parser, sql *db)
memset(parser, 0, sizeof(struct Parse));
parser->db = db;
rlist_create(&parser->new_fk_constraint);
+ rlist_create(&parser->new_ck_constraint);
rlist_create(&parser->record_list);
region_create(&parser->region, &cord()->slabc);
}
diff --git a/src/box/sql/select.c b/src/box/sql/select.c
index 5195656af..6c031c2d8 100644
--- a/src/box/sql/select.c
+++ b/src/box/sql/select.c
@@ -6422,7 +6422,5 @@ sql_expr_extract_select(struct Parse *parser, struct Select *select)
struct ExprList *expr_list = select->pEList;
assert(expr_list->nExpr == 1);
parser->parsed_ast_type = AST_TYPE_EXPR;
- parser->parsed_ast.expr = sqlExprDup(parser->db,
- expr_list->a->pExpr,
- EXPRDUP_REDUCE);
+ parser->parsed_ast.expr = sqlExprDup(parser->db, expr_list->a->pExpr, 0);
}
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index 8967ea3e0..3c58ac649 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -69,6 +69,7 @@
#include "box/field_def.h"
#include "box/sql.h"
+#include "box/ck_constraint.h"
#include "box/txn.h"
#include "trivia/util.h"
@@ -2739,6 +2740,15 @@ struct Parse {
* Foreign key constraint appeared in CREATE TABLE stmt.
*/
struct rlist new_fk_constraint;
+ /**
+ * Number of check constraints declared within
+ * CREATE TABLE statement.
+ */
+ uint32_t ck_constraint_count;
+ /**
+ * Check constraint appeared in CREATE TABLE stmt.
+ */
+ struct rlist new_ck_constraint;
/**
* List of all records that were inserted in system spaces
* in current statement.
@@ -3326,7 +3336,7 @@ void sqlAddPrimaryKey(Parse *, ExprList *, int, enum sort_order);
* @param span Expression span object.
*/
void
-sql_add_check_constraint(Parse *parser, ExprSpan *span);
+sql_add_ck_constraint(struct Parse *parser, struct ExprSpan *span);
void sqlAddDefaultValue(Parse *, ExprSpan *);
void sqlAddCollateType(Parse *, Token *);
diff --git a/src/box/sql/tokenize.c b/src/box/sql/tokenize.c
index de0f282ae..e5e79ecda 100644
--- a/src/box/sql/tokenize.c
+++ b/src/box/sql/tokenize.c
@@ -434,7 +434,6 @@ parser_space_delete(struct sql *db, struct space *space)
assert(space->def->opts.is_temporary);
for (uint32_t i = 0; i < space->index_count; ++i)
index_def_delete(space->index[i]->def);
- sql_expr_list_delete(db, space->def->opts.checks);
}
/**
diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c
index ed7bf8870..cbccec95e 100644
--- a/src/box/sql/vdbe.c
+++ b/src/box/sql/vdbe.c
@@ -1028,7 +1028,7 @@ case OP_HaltIfNull: { /* in3 */
* 0: (no change)
* 1: NOT NULL contraint failed: P4
* 2: UNIQUE constraint failed: P4
- * 3: CHECK constraint failed: P4
+ * 3: Check constraint failed: P4
* 4: FOREIGN KEY constraint failed: P4
*
* If P5 is not zero and P4 is NULL, then everything after the
@@ -1077,8 +1077,8 @@ case OP_Halt: {
pOp->p4.z);
}
} else if (pOp->p5 != 0) {
- static const char * const azType[] = { "NOT NULL", "UNIQUE", "CHECK",
- "FOREIGN KEY" };
+ static const char * const azType[] =
+ {"NOT NULL", "UNIQUE", "Check", "FOREIGN KEY" };
testcase( pOp->p5==1);
testcase( pOp->p5==2);
testcase( pOp->p5==3);
diff --git a/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua
index db046e03f..cb373dae0 100755
--- a/test/app-tap/tarantoolctl.test.lua
+++ b/test/app-tap/tarantoolctl.test.lua
@@ -388,8 +388,8 @@ do
check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1", "\n", 3)
check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 1 --replica 2", "\n", 3)
check_ctlcat_xlog(test_i, dir, "--from=3 --to=6 --format=json --show-system --replica 2", "\n", 0)
- check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 23)
- check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 49)
+ check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 24)
+ check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 51)
end)
end)
diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result
index cdf07117e..2afb0d8aa 100644
--- a/test/box-py/bootstrap.result
+++ b/test/box-py/bootstrap.result
@@ -83,6 +83,8 @@ box.space._space:select{}
{'name': 'is_deferred', 'type': 'boolean'}, {'name': 'match', 'type': 'string'},
{'name': 'on_delete', 'type': 'string'}, {'name': 'on_update', 'type': 'string'},
{'name': 'child_cols', 'type': 'array'}, {'name': 'parent_cols', 'type': 'array'}]]
+ - [357, 1, '_ck_constraint', 'memtx', 0, {}, [{'name': 'name', 'type': 'string'},
+ {'name': 'space_id', 'type': 'unsigned'}, {'name': 'expr_str', 'type': 'str'}]]
...
box.space._index:select{}
---
@@ -138,6 +140,8 @@ box.space._index:select{}
5, 'scalar']]]
- [356, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]]
- [356, 1, 'child_id', 'tree', {'unique': false}, [[1, 'unsigned']]]
+ - [357, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]]
+ - [357, 1, 'space_id', 'tree', {'unique': false}, [[1, 'unsigned']]]
...
box.space._user:select{}
---
diff --git a/test/box/access.result b/test/box/access.result
index 9c190240f..801112799 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -1487,6 +1487,9 @@ box.schema.user.grant('tester', 'read', 'space', '_trigger')
box.schema.user.grant('tester', 'read', 'space', '_fk_constraint')
---
...
+box.schema.user.grant('tester', 'read', 'space', '_ck_constraint')
+---
+...
box.session.su("tester")
---
...
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index 4baeb2ef6..2f62d6f53 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -554,6 +554,7 @@ box.schema.user.grant('tester', 'create' , 'sequence')
box.schema.user.grant('tester', 'read', 'space', '_sequence')
box.schema.user.grant('tester', 'read', 'space', '_trigger')
box.schema.user.grant('tester', 'read', 'space', '_fk_constraint')
+box.schema.user.grant('tester', 'read', 'space', '_ck_constraint')
box.session.su("tester")
-- successful create
s1 = box.schema.space.create("test_space")
diff --git a/test/box/access_misc.result b/test/box/access_misc.result
index 4ffeb386a..93d4c4cc2 100644
--- a/test/box/access_misc.result
+++ b/test/box/access_misc.result
@@ -823,6 +823,8 @@ box.space._space:select()
{'name': 'is_deferred', 'type': 'boolean'}, {'name': 'match', 'type': 'string'},
{'name': 'on_delete', 'type': 'string'}, {'name': 'on_update', 'type': 'string'},
{'name': 'child_cols', 'type': 'array'}, {'name': 'parent_cols', 'type': 'array'}]]
+ - [357, 1, '_ck_constraint', 'memtx', 0, {}, [{'name': 'name', 'type': 'string'},
+ {'name': 'space_id', 'type': 'unsigned'}, {'name': 'expr_str', 'type': 'str'}]]
...
box.space._func:select()
---
diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result
index fd8b14248..c0b75b235 100644
--- a/test/box/access_sysview.result
+++ b/test/box/access_sysview.result
@@ -230,11 +230,11 @@ box.session.su('guest')
...
#box.space._vspace:select{}
---
-- 24
+- 25
...
#box.space._vindex:select{}
---
-- 50
+- 52
...
#box.space._vuser:select{}
---
@@ -262,7 +262,7 @@ box.session.su('guest')
...
#box.space._vindex:select{}
---
-- 50
+- 52
...
#box.space._vuser:select{}
---
diff --git a/test/box/alter.result b/test/box/alter.result
index 9a1086e0c..9c4262687 100644
--- a/test/box/alter.result
+++ b/test/box/alter.result
@@ -107,7 +107,7 @@ space = box.space[t[1]]
...
space.id
---
-- 357
+- 358
...
space.field_count
---
@@ -152,7 +152,7 @@ space_deleted
...
space:replace{0}
---
-- error: Space '357' does not exist
+- error: Space '358' does not exist
...
_index:insert{_space.id, 0, 'primary', 'tree', {unique=true}, {{0, 'unsigned'}}}
---
@@ -233,6 +233,8 @@ _index:select{}
5, 'scalar']]]
- [356, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]]
- [356, 1, 'child_id', 'tree', {'unique': false}, [[1, 'unsigned']]]
+ - [357, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]]
+ - [357, 1, 'space_id', 'tree', {'unique': false}, [[1, 'unsigned']]]
...
-- modify indexes of a system space
_index:delete{_index.id, 0}
diff --git a/test/box/misc.result b/test/box/misc.result
index c350bbd73..09a98af99 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -516,6 +516,8 @@ t;
185: box.error.SQL_UNKNOWN_TOKEN
186: box.error.SQL_PARSER_GENERIC
187: box.error.SQL_ANALYZE_ARGUMENT
+ 188: box.error.CREATE_CK_CONSTRAINT
+ 189: box.error.CK_CONSTRAINT_FAILED
...
test_run:cmd("setopt delimiter ''");
---
diff --git a/test/sql-tap/check.test.lua b/test/sql-tap/check.test.lua
index 0d8bf15a7..c2c0b6a80 100755
--- a/test/sql-tap/check.test.lua
+++ b/test/sql-tap/check.test.lua
@@ -55,7 +55,7 @@ test:do_catchsql_test(
INSERT INTO t1 VALUES(6,7, 2);
]], {
-- <check-1.3>
- 1, "CHECK constraint failed: T1"
+ 1, "Check constraint failed: CK_CONSTRAINT_1_T1"
-- </check-1.3>
})
@@ -75,7 +75,7 @@ test:do_catchsql_test(
INSERT INTO t1 VALUES(4,3, 2);
]], {
-- <check-1.5>
- 1, "CHECK constraint failed: T1"
+ 1, "Check constraint failed: CK_CONSTRAINT_2_T1"
-- </check-1.5>
})
@@ -147,7 +147,7 @@ test:do_catchsql_test(
UPDATE t1 SET x=7 WHERE x==2
]], {
-- <check-1.12>
- 1, "CHECK constraint failed: T1"
+ 1, "Check constraint failed: CK_CONSTRAINT_1_T1"
-- </check-1.12>
})
@@ -167,7 +167,7 @@ test:do_catchsql_test(
UPDATE t1 SET x=5 WHERE x==2
]], {
-- <check-1.14>
- 1, "CHECK constraint failed: T1"
+ 1, "Check constraint failed: CK_CONSTRAINT_1_T1"
-- </check-1.14>
})
@@ -246,7 +246,7 @@ test:do_catchsql_test(
INSERT INTO t2 VALUES(3, 1.1, NULL, NULL);
]], {
-- <check-2.4>
- 1, "CHECK constraint failed: ONE"
+ 1, "Check constraint failed: ONE"
-- </check-2.4>
})
@@ -256,7 +256,7 @@ test:do_catchsql_test(
INSERT INTO t2 VALUES(4, NULL, 5, NULL);
]], {
-- <check-2.5>
- 1, "CHECK constraint failed: TWO"
+ 1, "Check constraint failed: TWO"
-- </check-2.5>
})
@@ -266,7 +266,7 @@ test:do_catchsql_test(
INSERT INTO t2 VALUES(5, NULL, NULL, 3.14159);
]], {
-- <check-2.6>
- 1, "CHECK constraint failed: THREE"
+ 1, "Check constraint failed: THREE"
-- </check-2.6>
})
@@ -319,7 +319,7 @@ test:do_catchsql_test(
);
]], {
-- <check-3.1>
- 1, "Failed to create space 'T3': subqueries prohibited in CHECK constraints"
+ 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': subqueries prohibited in CHECK constraints"
-- </check-3.1>
})
@@ -344,7 +344,7 @@ test:do_catchsql_test(
);
]], {
-- <check-3.3>
- 1, "Failed to create space 'T3': Can’t resolve field 'Q'"
+ 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': Can’t resolve field 'Q'"
-- </check-3.3>
})
@@ -368,7 +368,7 @@ test:do_catchsql_test(
);
]], {
-- <check-3.5>
- 1, "Failed to create space 'T3': Field 'X' was not found in the space 'T2' format"
+ 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': Field 'X' was not found in the space 'T2' format"
-- </check-3.5>
})
@@ -413,7 +413,7 @@ test:do_catchsql_test(
INSERT INTO t3 VALUES(111,222,333);
]], {
-- <check-3.9>
- 1, "CHECK constraint failed: T3"
+ 1, "Check constraint failed: CK_CONSTRAINT_1_T3"
-- </check-3.9>
})
@@ -484,7 +484,7 @@ test:do_catchsql_test(
UPDATE t4 SET x=0, y=1;
]], {
-- <check-4.6>
- 1, "CHECK constraint failed: T4"
+ 1, "Check constraint failed: CK_CONSTRAINT_1_T4"
-- </check-4.6>
})
@@ -504,7 +504,7 @@ test:do_catchsql_test(
UPDATE t4 SET x=0, y=2;
]], {
-- <check-4.9>
- 1, "CHECK constraint failed: T4"
+ 1, "Check constraint failed: CK_CONSTRAINT_1_T4"
-- </check-4.9>
})
@@ -516,7 +516,7 @@ test:do_catchsql_test(
);
]], {
-- <check-5.1>
- 1, "Wrong space options (field 5): invalid expression specified (bindings are not allowed in DDL)"
+ 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T5': bindings are not allowed in DDL"
-- </check-5.1>
})
@@ -528,7 +528,7 @@ test:do_catchsql_test(
);
]], {
-- <check-5.2>
- 1, "Wrong space options (field 5): invalid expression specified (bindings are not allowed in DDL)"
+ 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T5': bindings are not allowed in DDL"
-- </check-5.2>
})
@@ -581,7 +581,7 @@ test:do_catchsql_test(
UPDATE OR FAIL t1 SET x=7-x, y=y+1;
]], {
-- <check-6.5>
- 1, "CHECK constraint failed: T1"
+ 1, "Check constraint failed: CK_CONSTRAINT_1_T1"
-- </check-6.5>
})
@@ -603,7 +603,7 @@ test:do_catchsql_test(
INSERT OR ROLLBACK INTO t1 VALUES(8,40.0, 10);
]], {
-- <check-6.7>
- 1, "CHECK constraint failed: T1"
+ 1, "Check constraint failed: CK_CONSTRAINT_1_T1"
-- </check-6.7>
})
@@ -636,7 +636,7 @@ test:do_catchsql_test(
REPLACE INTO t1 VALUES(6,7, 11);
]], {
-- <check-6.12>
- 1, "CHECK constraint failed: T1"
+ 1, "Check constraint failed: CK_CONSTRAINT_1_T1"
-- </check-6.12>
})
@@ -700,7 +700,7 @@ test:do_catchsql_test(
7.3,
" INSERT INTO t6 VALUES(11) ", {
-- <7.3>
- 1, "CHECK constraint failed: T6"
+ 1, "Check constraint failed: CK_CONSTRAINT_1_T6"
-- </7.3>
})
@@ -755,7 +755,7 @@ test:do_test(
return test:catchsql(" INSERT INTO t6 VALUES(12) ", "db2")
end, {
-- <7.8>
- 1, "CHECK constraint failed: T6"
+ 1, "Check constraint failed: T6"
-- </7.8>
})
end
diff --git a/test/sql-tap/fkey2.test.lua b/test/sql-tap/fkey2.test.lua
index d347e5a5c..153ff42d0 100755
--- a/test/sql-tap/fkey2.test.lua
+++ b/test/sql-tap/fkey2.test.lua
@@ -362,7 +362,7 @@ test:do_catchsql_test(
UPDATE ab SET a = 5;
]], {
-- <fkey2-3.2>
- 1, "CHECK constraint failed: EF"
+ 1, "Check constraint failed: CK_CONSTRAINT_1_EF"
-- </fkey2-3.2>
})
@@ -382,7 +382,7 @@ test:do_catchsql_test(
UPDATE ab SET a = 5;
]], {
-- <fkey2-3.4>
- 1, "CHECK constraint failed: EF"
+ 1, "Check constraint failed: CK_CONSTRAINT_1_EF"
-- </fkey2-3.4>
})
diff --git a/test/sql-tap/in1.test.lua b/test/sql-tap/in1.test.lua
index 835c10dd5..2de7747e1 100755
--- a/test/sql-tap/in1.test.lua
+++ b/test/sql-tap/in1.test.lua
@@ -615,7 +615,7 @@ test:do_catchsql_test(
-- catchsql {
-- INSERT INTO t5 VALUES(4);
-- }
--- } {1 {CHECK constraint failed: t5}}
+-- } {1 {Check constraint failed: t5}}
-- Ticket #1821
--
-- Type affinity applied to the right-hand side of an IN operator.
diff --git a/test/sql-tap/table.test.lua b/test/sql-tap/table.test.lua
index c89d89044..92900239a 100755
--- a/test/sql-tap/table.test.lua
+++ b/test/sql-tap/table.test.lua
@@ -1221,7 +1221,7 @@ test:do_catchsql_test(
INSERT INTO T21 VALUES(1, -1, 1);
]], {
-- <table-21.3>
- 1, "CHECK constraint failed: T21"
+ 1, "Check constraint failed: CK_CONSTRAINT_1_T21"
-- </table-21.3>
})
@@ -1231,7 +1231,7 @@ test:do_catchsql_test(
INSERT INTO T21 VALUES(1, 1, -1);
]], {
-- <table-21.4>
- 1, "CHECK constraint failed: T21"
+ 1, "Check constraint failed: CK_CONSTRAINT_2_T21"
-- </table-21.4>
})
@@ -1372,7 +1372,7 @@ test:do_catchsql_test(
INSERT INTO T28 VALUES(0);
]], {
-- <table-22.10>
- 1, "CHECK constraint failed: CHECK1"
+ 1, "Check constraint failed: CHECK1"
-- </table-22.10>
})
@@ -1382,7 +1382,7 @@ test:do_catchsql_test(
INSERT INTO T28 VALUES(9);
]], {
-- <table-22.11>
- 1, "CHECK constraint failed: CHECK2"
+ 1, "Check constraint failed: CHECK2"
-- </table-22.11>
})
diff --git a/test/sql/checks.result b/test/sql/checks.result
index 42df65711..aeacd09cb 100644
--- a/test/sql/checks.result
+++ b/test/sql/checks.result
@@ -17,8 +17,8 @@ box.sql.execute('pragma sql_default_engine=\''..engine..'\'')
--
-- gh-3272: Move SQL CHECK into server
--
--- invalid expression
-opts = {checks = {{expr = 'X><5'}}}
+-- Legacy data in _space (insertion on bootrap) test.
+opts = {checks = {{expr = 'X>5'}}}
---
...
format = {{name = 'X', type = 'unsigned'}}
@@ -29,89 +29,107 @@ t = {513, 1, 'test', 'memtx', 0, opts, format}
...
s = box.space._space:insert(t)
---
-- error: 'Wrong space options (field 5): invalid expression specified (Syntax error
- near ''<'')'
...
-opts = {checks = {{expr = 'X>5'}}}
+box.space.test:create_index('pk')
---
+- unique: true
+ parts:
+ - type: unsigned
+ is_nullable: false
+ fieldno: 1
+ id: 0
+ space_id: 513
+ name: pk
+ type: TREE
...
-format = {{name = 'X', type = 'unsigned'}}
+-- Invalid expression test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X><5'})
---
+- error: 'Failed to create check constraint ''CK_CONSTRAINT_01'': Syntax error near
+ ''<'''
...
-t = {513, 1, 'test', 'memtx', 0, opts, format}
+-- Unexistent space test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 'X<5'})
---
+- error: Space '550' does not exist
...
-s = box.space._space:insert(t)
+-- Field type test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 666})
---
+- error: 'Tuple field 3 type does not match one required by operation: expected string'
...
-box.space._space:delete(513)
+-- Check constraints LUA creation test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X<5'})
---
-- [513, 1, 'test', 'memtx', 0, {'checks': [{'expr': 'X>5'}]}, [{'name': 'X', 'type': 'unsigned'}]]
+- ['CK_CONSTRAINT_01', 513, 'X<5']
...
-opts = {checks = {{expr = 'X>5', name = 'ONE'}}}
+box.space._ck_constraint:count({})
---
+- 1
...
-format = {{name = 'X', type = 'unsigned'}}
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
---
+- error: 'Check constraint failed: CK_CONSTRAINT_01'
...
-t = {513, 1, 'test', 'memtx', 0, opts, format}
+box.space._ck_constraint:replace({'CK_CONSTRAINT_01', 513, 'X<=5'})
---
+- ['CK_CONSTRAINT_01', 513, 'X<=5']
...
-s = box.space._space:insert(t)
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
---
...
-box.space._space:delete(513)
+box.sql.execute("INSERT INTO \"test\" VALUES(6);")
---
-- [513, 1, 'test', 'memtx', 0, {'checks': [{'name': 'ONE', 'expr': 'X>5'}]}, [{'name': 'X',
- 'type': 'unsigned'}]]
+- error: 'Check constraint failed: CK_CONSTRAINT_01'
...
--- extra invlalid field name
-opts = {checks = {{expr = 'X>5', name = 'ONE', extra = 'TWO'}}}
+-- Can't drop table with check constraints.
+box.space.test:delete({5})
---
+- [5]
...
-format = {{name = 'X', type = 'unsigned'}}
+box.space.test.index.pk:drop()
---
...
-t = {513, 1, 'test', 'memtx', 0, opts, format}
+box.space._space:delete({513})
---
+- error: 'Can''t drop space ''test'': the space has check constraints'
...
-s = box.space._space:insert(t)
+box.space._ck_constraint:delete({'CK_CONSTRAINT_01', 513})
---
-- error: 'Wrong space options (field 5): invalid MsgPack map field ''extra'''
+- ['CK_CONSTRAINT_01', 513, 'X<=5']
...
-opts = {checks = {{expr_invalid_label = 'X>5'}}}
+box.space.test:drop()
---
...
-format = {{name = 'X', type = 'unsigned'}}
----
-...
-t = {513, 1, 'test', 'memtx', 0, opts, format}
+-- Create table with checks in sql.
+box.sql.execute("CREATE TABLE t1(x INTEGER CONSTRAINT ONE CHECK( x<5 ), y REAL CONSTRAINT TWO CHECK( y>x ), z INTEGER PRIMARY KEY);")
---
...
-s = box.space._space:insert(t)
+box.space._ck_constraint:count()
---
-- error: 'Wrong space options (field 5): invalid MsgPack map field ''expr_invalid_label'''
+- 2
...
--- invalid field type
-opts = {checks = {{name = 123}}}
+box.sql.execute("INSERT INTO t1 VALUES (7, 1, 1)")
---
+- error: 'Check constraint failed: ONE'
...
-format = {{name = 'X', type = 'unsigned'}}
+box.sql.execute("INSERT INTO t1 VALUES (2, 1, 1)")
---
+- error: 'Check constraint failed: TWO'
...
-t = {513, 1, 'test', 'memtx', 0, opts, format}
+box.sql.execute("INSERT INTO t1 VALUES (2, 4, 1)")
---
...
-s = box.space._space:insert(t)
+box.sql.execute("DROP TABLE t1")
---
-- error: 'Wrong space options (field 5): invalid MsgPack map field ''name'' type'
...
--
-- gh-3611: Segfault on table creation with check referencing this table
--
box.sql.execute("CREATE TABLE w2 (s1 INT PRIMARY KEY, CHECK ((SELECT COUNT(*) FROM w2) = 0));")
---
-- error: 'Failed to create space ''W2'': Space ''W2'' does not exist'
+- error: 'Failed to create check constraint ''CK_CONSTRAINT_1_W2'': subqueries prohibited
+ in CHECK constraints'
...
box.sql.execute("DROP TABLE w2;")
---
@@ -122,22 +140,8 @@ box.sql.execute("DROP TABLE w2;")
--
box.sql.execute("CREATE TABLE t5(x INT PRIMARY KEY, y INT, CHECK( x*y < ? ));")
---
-- error: 'Wrong space options (field 5): invalid expression specified (bindings are
- not allowed in DDL)'
-...
-opts = {checks = {{expr = '?>5', name = 'ONE'}}}
----
-...
-format = {{name = 'X', type = 'unsigned'}}
----
-...
-t = {513, 1, 'test', 'memtx', 0, opts, format}
----
-...
-s = box.space._space:insert(t)
----
-- error: 'Wrong space options (field 5): invalid expression specified (bindings are
- not allowed in DDL)'
+- error: 'Failed to create check constraint ''CK_CONSTRAINT_1_T5'': bindings are not
+ allowed in DDL'
...
test_run:cmd("clear filter")
---
diff --git a/test/sql/checks.test.lua b/test/sql/checks.test.lua
index 0582bbb63..9a7e5faf4 100644
--- a/test/sql/checks.test.lua
+++ b/test/sql/checks.test.lua
@@ -8,41 +8,42 @@ box.sql.execute('pragma sql_default_engine=\''..engine..'\'')
-- gh-3272: Move SQL CHECK into server
--
--- invalid expression
-opts = {checks = {{expr = 'X><5'}}}
-format = {{name = 'X', type = 'unsigned'}}
-t = {513, 1, 'test', 'memtx', 0, opts, format}
-s = box.space._space:insert(t)
-
+-- Legacy data in _space (insertion on bootrap) test.
opts = {checks = {{expr = 'X>5'}}}
format = {{name = 'X', type = 'unsigned'}}
t = {513, 1, 'test', 'memtx', 0, opts, format}
s = box.space._space:insert(t)
-box.space._space:delete(513)
-
-opts = {checks = {{expr = 'X>5', name = 'ONE'}}}
-format = {{name = 'X', type = 'unsigned'}}
-t = {513, 1, 'test', 'memtx', 0, opts, format}
-s = box.space._space:insert(t)
-box.space._space:delete(513)
-
--- extra invlalid field name
-opts = {checks = {{expr = 'X>5', name = 'ONE', extra = 'TWO'}}}
-format = {{name = 'X', type = 'unsigned'}}
-t = {513, 1, 'test', 'memtx', 0, opts, format}
-s = box.space._space:insert(t)
-
-opts = {checks = {{expr_invalid_label = 'X>5'}}}
-format = {{name = 'X', type = 'unsigned'}}
-t = {513, 1, 'test', 'memtx', 0, opts, format}
-s = box.space._space:insert(t)
-
--- invalid field type
-opts = {checks = {{name = 123}}}
-format = {{name = 'X', type = 'unsigned'}}
-t = {513, 1, 'test', 'memtx', 0, opts, format}
-s = box.space._space:insert(t)
-
+box.space.test:create_index('pk')
+
+-- Invalid expression test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X><5'})
+-- Unexistent space test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 'X<5'})
+-- Field type test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 550, 666})
+
+-- Check constraints LUA creation test.
+box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X<5'})
+box.space._ck_constraint:count({})
+
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
+box.space._ck_constraint:replace({'CK_CONSTRAINT_01', 513, 'X<=5'})
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
+box.sql.execute("INSERT INTO \"test\" VALUES(6);")
+-- Can't drop table with check constraints.
+box.space.test:delete({5})
+box.space.test.index.pk:drop()
+box.space._space:delete({513})
+box.space._ck_constraint:delete({'CK_CONSTRAINT_01', 513})
+box.space.test:drop()
+
+-- Create table with checks in sql.
+box.sql.execute("CREATE TABLE t1(x INTEGER CONSTRAINT ONE CHECK( x<5 ), y REAL CONSTRAINT TWO CHECK( y>x ), z INTEGER PRIMARY KEY);")
+box.space._ck_constraint:count()
+box.sql.execute("INSERT INTO t1 VALUES (7, 1, 1)")
+box.sql.execute("INSERT INTO t1 VALUES (2, 1, 1)")
+box.sql.execute("INSERT INTO t1 VALUES (2, 4, 1)")
+box.sql.execute("DROP TABLE t1")
--
-- gh-3611: Segfault on table creation with check referencing this table
@@ -55,10 +56,4 @@ box.sql.execute("DROP TABLE w2;")
--
box.sql.execute("CREATE TABLE t5(x INT PRIMARY KEY, y INT, CHECK( x*y < ? ));")
-opts = {checks = {{expr = '?>5', name = 'ONE'}}}
-format = {{name = 'X', type = 'unsigned'}}
-t = {513, 1, 'test', 'memtx', 0, opts, format}
-s = box.space._space:insert(t)
-
-
test_run:cmd("clear filter")
diff --git a/test/sql/errinj.result b/test/sql/errinj.result
index a1e7cc4a3..44b454a7c 100644
--- a/test/sql/errinj.result
+++ b/test/sql/errinj.result
@@ -388,3 +388,56 @@ errinj.set("ERRINJ_WAL_DELAY", false)
---
- ok
...
+--
+-- Tests which are aimed at verifying work of commit/rollback
+-- triggers on _ck_constraint space.
+--
+errinj = box.error.injection
+---
+...
+s = box.schema.space.create('test', {format = {{name = 'X', type = 'unsigned'}}})
+---
+...
+pk = box.space.test:create_index('pk')
+---
+...
+errinj.set("ERRINJ_WAL_IO", true)
+---
+- ok
+...
+_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'})
+---
+- error: Failed to write to disk
+...
+errinj.set("ERRINJ_WAL_IO", false)
+---
+- ok
+...
+_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'})
+---
+...
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
+---
+- error: 'Check constraint failed: CK_CONSTRAINT_01'
+...
+errinj.set("ERRINJ_WAL_IO", true)
+---
+- ok
+...
+_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'})
+---
+- error: Failed to write to disk
+...
+errinj.set("ERRINJ_WAL_IO", false)
+---
+- ok
+...
+_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'})
+---
+...
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
+---
+...
+s:drop()
+---
+...
diff --git a/test/sql/errinj.test.lua b/test/sql/errinj.test.lua
index d8833feb4..f5e78664c 100644
--- a/test/sql/errinj.test.lua
+++ b/test/sql/errinj.test.lua
@@ -139,3 +139,23 @@ box.sql.execute("INSERT INTO t VALUES (2);")
box.sql.execute("UPDATE t SET id = 2;")
-- Finish drop space.
errinj.set("ERRINJ_WAL_DELAY", false)
+
+--
+-- Tests which are aimed at verifying work of commit/rollback
+-- triggers on _ck_constraint space.
+--
+errinj = box.error.injection
+s = box.schema.space.create('test', {format = {{name = 'X', type = 'unsigned'}}})
+pk = box.space.test:create_index('pk')
+
+errinj.set("ERRINJ_WAL_IO", true)
+_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'})
+errinj.set("ERRINJ_WAL_IO", false)
+_ = box.space._ck_constraint:insert({'CK_CONSTRAINT_01', s.id, 'X<5'})
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
+errinj.set("ERRINJ_WAL_IO", true)
+_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'})
+errinj.set("ERRINJ_WAL_IO", false)
+_ = box.space._ck_constraint:replace({'CK_CONSTRAINT_01', s.id, 'X<=5'})
+box.sql.execute("INSERT INTO \"test\" VALUES(5);")
+s:drop()
diff --git a/test/sql/gh-2981-check-autoinc.result b/test/sql/gh-2981-check-autoinc.result
index b0f55e61d..69c722064 100644
--- a/test/sql/gh-2981-check-autoinc.result
+++ b/test/sql/gh-2981-check-autoinc.result
@@ -24,28 +24,28 @@ box.sql.execute("insert into t1 values (18, null);")
...
box.sql.execute("insert into t1(s2) values (null);")
---
-- error: 'CHECK constraint failed: T1'
+- error: 'Check constraint failed: CK_CONSTRAINT_1_T1'
...
box.sql.execute("insert into t2 values (18, null);")
---
...
box.sql.execute("insert into t2(s2) values (null);")
---
-- error: 'CHECK constraint failed: T2'
+- error: 'Check constraint failed: CK_CONSTRAINT_1_T2'
...
box.sql.execute("insert into t2 values (24, null);")
---
...
box.sql.execute("insert into t2(s2) values (null);")
---
-- error: 'CHECK constraint failed: T2'
+- error: 'Check constraint failed: CK_CONSTRAINT_1_T2'
...
box.sql.execute("insert into t3 values (9, null)")
---
...
box.sql.execute("insert into t3(s2) values (null)")
---
-- error: 'CHECK constraint failed: T3'
+- error: 'Check constraint failed: CK_CONSTRAINT_1_T3'
...
box.sql.execute("DROP TABLE t1")
---
diff --git a/test/wal_off/alter.result b/test/wal_off/alter.result
index b4c6a928a..d62cc8e19 100644
--- a/test/wal_off/alter.result
+++ b/test/wal_off/alter.result
@@ -28,7 +28,7 @@ end;
...
#spaces;
---
-- 65509
+- 65508
...
-- cleanup
for k, v in pairs(spaces) do
--
2.21.0
More information about the Tarantool-patches
mailing list