From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from localhost (localhost [127.0.0.1]) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTP id B11612623D for ; Wed, 30 Jan 2019 03:59:21 -0500 (EST) Received: from turing.freelists.org ([127.0.0.1]) by localhost (turing.freelists.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id Bftkn0rRUFPJ for ; Wed, 30 Jan 2019 03:59:21 -0500 (EST) Received: from smtp54.i.mail.ru (smtp54.i.mail.ru [217.69.128.34]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTPS id D428826192 for ; Wed, 30 Jan 2019 03:59:20 -0500 (EST) From: Kirill Shcherbatov Subject: [tarantool-patches] [PATCH v2 5/9] schema: add new system space for CHECK constraints Date: Wed, 30 Jan 2019 11:59:12 +0300 Message-Id: <186b109d1f6049b1413fe0cdded43e5fad29a895.1548838034.git.kshcherbatov@tarantool.org> In-Reply-To: References: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit Sender: tarantool-patches-bounce@freelists.org Errors-to: tarantool-patches-bounce@freelists.org Reply-To: tarantool-patches@freelists.org List-help: List-unsubscribe: List-software: Ecartis version 1.0.0 List-Id: tarantool-patches List-subscribe: List-owner: List-post: List-archive: To: tarantool-patches@freelists.org, korablev@tarantool.org Cc: Kirill Shcherbatov This patch introduces new system space to persist check constraints. Format of the space: _ck_constraint (space id = 357) [ STR, UINT, STR] CK constraint is local to space, so every pair 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 held Expr tree. Until space features check constraints, it isn't allowed to be dropped. The :drop() space method firstly deletes all check constraints and than 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 | 217 ++++++++++++++++++++++++-- src/box/alter.h | 1 + src/box/bootstrap.snap | Bin 1914 -> 1955 bytes src/box/ck_constraint.c | 162 +++++++++++++++++++ src/box/ck_constraint.h | 173 ++++++++++++++++++++ src/box/errcode.h | 4 +- src/box/lua/schema.lua | 4 + src/box/lua/space.cc | 2 + src/box/lua/upgrade.lua | 38 +++++ 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 | 97 +----------- src/box/space_def.h | 2 - src/box/sql.c | 97 +----------- src/box/sql.h | 44 ------ src/box/sql/build.c | 160 ++++++++++++++----- src/box/sql/insert.c | 52 +++--- src/box/sql/parse.y | 4 +- src/box/sql/prepare.c | 1 + src/box/sql/select.c | 3 +- src/box/sql/sqliteInt.h | 12 +- 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 | 96 +++++++----- test/sql/checks.test.lua | 55 +++---- test/sql/errinj.result | 52 ++++++ test/sql/errinj.test.lua | 19 +++ test/sql/gh-2981-check-autoinc.result | 8 +- test/sql/upgrade.result | 9 +- test/wal_off/alter.result | 2 +- 44 files changed, 991 insertions(+), 436 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 5521e489e..5019127eb 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 eff3524cf..3ba604ca6 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" @@ -532,17 +533,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; @@ -1351,6 +1341,56 @@ TruncateIndex::commit(struct alter_space *alter, int64_t signature) index_commit_create(new_index, signature); } +/** BuildCkConstraints - rebuild ck_constraints on alter. */ +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 @@ -1757,6 +1797,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 @@ -1854,6 +1900,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); @@ -2096,6 +2144,8 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event) * old space. */ alter_space_move_indexes(alter, iid + 1, old_space->index_id_max + 1); + /* Add an op to rebuild check constraints. */ + (void) new BuildCkConstraints(alter); /* Add an op to update schema_version on commit. */ (void) new UpdateSchemaVersion(alter); alter_space_do(txn, alter); @@ -2163,7 +2213,8 @@ 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); } - + /* Add an op to rebuild check constraints. */ + (void) new BuildCkConstraints(alter); alter_space_do(txn, alter); scoped_guard.is_active = false; } @@ -4051,6 +4102,144 @@ 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 if (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 }; @@ -4119,4 +4308,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 index 662d6411544a3374ab97cf83dd8912ba7e05f6bd..2f5111e79d17a4fb9fd9aaf5093b5fe63d3dc240 100644 GIT binary patch delta 1950 diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c new file mode 100644 index 000000000..044416a4f --- /dev/null +++ b/src/box/ck_constraint.c @@ -0,0 +1,162 @@ +/* + * 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 ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include +#include "ck_constraint.h" +#include "errcode.h" +#include "small/rlist.h" +#include "sql.h" +#include "sql/sqliteInt.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_column_reference(struct Expr *expr, + const char *ck_constraint_name, + const struct space_def *space_def) +{ + struct Parse parser; + sql_parser_create(&parser, sql_get()); + parser.parse_only = true; + + struct Table dummy_table; + memset(&dummy_table, 0, sizeof(dummy_table)); + dummy_table.def = (struct space_def *)space_def; + + sql_resolve_self_reference(&parser, &dummy_table, NC_IsCheck, + expr, NULL); + int rc = 0; + if (parser.rc != SQLITE_OK) { + /* Tarantool error may be already set with diag. */ + if (parser.rc != SQL_TARANTOOL_ERROR) { + diag_set(ClientError, ER_CREATE_CK_CONSTRAINT, + ck_constraint_name, parser.zErrMsg); + } + rc = -1; + } + 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, + const 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) + goto error; + if (ck_constraint_resolve_column_reference(expr, ck_constraint_def->name, + space_def) != 0) + 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..b63c7cb43 --- /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 ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#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 sqlite3ResolveExprNames 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, + const 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 f7dbb948e..bae6f71e7 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -200,12 +200,12 @@ struct errcode_record { /*145 */_(ER_NO_SUCH_SEQUENCE, "Sequence '%s' does not exist") \ /*146 */_(ER_SEQUENCE_EXISTS, "Sequence '%s' already exists") \ /*147 */_(ER_SEQUENCE_OVERFLOW, "Sequence '%s' has overflowed") \ - /*148 */_(ER_UNUSED5, "") \ + /*148 */_(ER_CREATE_CK_CONSTRAINT, "Failed to create check constraint '%s': %s") \ /*149 */_(ER_SPACE_FIELD_IS_DUPLICATE, "Space field '%s' is duplicate") \ /*150 */_(ER_CANT_CREATE_COLLATION, "Failed to initialize collation: %s.") \ /*151 */_(ER_WRONG_COLLATION_OPTIONS, "Wrong collation options (field %u): %s") \ /*152 */_(ER_NULLABLE_PRIMARY, "Primary index of the space '%s' can not contain nullable parts") \ - /*153 */_(ER_UNUSED, "") \ + /*153 */_(ER_CK_CONSTRAINT_FAILED, "Check constraint failed: %s") \ /*154 */_(ER_TRANSACTION_YIELD, "Transaction has been aborted by a fiber yield") \ /*155 */_(ER_NO_SUCH_GROUP, "Replication group '%s' does not exist") \ /*156 */_(ER_SQL_BIND_VALUE, "Bind value for parameter %s is out of range for type %s") \ diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 8a804f0ba..0e5feb7e6 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 7cae436f1..a12ee1830 100644 --- a/src/box/lua/space.cc +++ b/src/box/lua/space.cc @@ -555,6 +555,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 cc172dc15..a304290a2 100644 --- a/src/box/lua/upgrade.lua +++ b/src/box/lua/upgrade.lua @@ -72,6 +72,7 @@ local function set_system_triggers(val) box.space._trigger:run_triggers(val) box.space._collation:run_triggers(val) box.space._fk_constraint:run_triggers(val) + box.space._ck_constraint:run_triggers(val) end -------------------------------------------------------------------------------- @@ -90,6 +91,7 @@ local function erase() truncate(box.space._collation) truncate(box.space._trigger) truncate(box.space._fk_constraint) + truncate(box.space._ck_constraint) --truncate(box.space._schema) box.space._schema:delete('version') box.space._schema:delete('max_id') @@ -629,10 +631,46 @@ local function upgrade_priv_to_2_1_1() index.type, opts, index.parts})) end end + local _space = box.space[box.schema.SPACE_ID] + local _ck_constraint = box.space[box.schema.CK_CONSTRAINT_ID] + 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 local function upgrade_to_2_1_1() log.info("started upgrade_to_2_1_1") + local _space = box.space[box.schema.SPACE_ID] + local _index = box.space[box.schema.INDEX_ID] + local _ck_constraint = box.space[box.schema.CK_CONSTRAINT_ID] + local MAP = setmap({}) + + 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'}}} + upgrade_priv_to_2_1_1() end diff --git a/src/box/schema.cc b/src/box/schema.cc index 8625d92ea..84875639a 100644 --- a/src/box/schema.cc +++ b/src/box/schema.cc @@ -468,6 +468,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 316b34b92..6b3158a25 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_fkey); rlist_create(&space->child_fkey); + 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_fkey)); assert(rlist_empty(&space->child_fkey)); + assert(rlist_empty(&space->ck_constraint)); space->vtab->destroy(space); } diff --git a/src/box/space.h b/src/box/space.h index 7eb7ae292..fccca4b44 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -194,6 +194,11 @@ struct space { */ struct rlist parent_fkey; struct rlist child_fkey; + /** + * 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 c5b5ec295..cf2d29431 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,6 @@ 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 sqlite3 *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 387da7b3d..7755e6c09 100644 --- a/src/box/sql.c +++ b/src/box/sql.c @@ -1041,16 +1041,9 @@ sql_encode_table_opts(struct region *region, struct Table *table, 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 = table->def->opts.is_view; - struct ExprList *checks = table->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"); @@ -1058,23 +1051,6 @@ sql_encode_table_opts(struct region *region, struct Table *table, 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, @@ -1302,74 +1278,3 @@ sql_table_def_rebuild(struct sqlite3 *db, struct Table *pTable) pTable->def->opts.is_temporary = false; return 0; } - -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 = sqlite3DbStrNDup(db, expr_name, expr_name_len); - if (item->zName == NULL) { - diag_set(OutOfMemory, expr_name_len, "sqlite3DbStrNDup", - "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++) - sqlite3WalkExpr(&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; - - Table dummy_table; - memset(&dummy_table, 0, sizeof(dummy_table)); - dummy_table.def = def; - - sql_resolve_self_reference(&parser, &dummy_table, NC_IsCheck, NULL, - expr_list); - int rc = 0; - if (parser.rc != SQLITE_OK) { - /* Tarantool error may be already set with diag. */ - if (parser.rc != SQL_TARANTOOL_ERROR) - diag_set(ClientError, ER_SQL, parser.zErrMsg); - rc = -1; - } - sql_parser_destroy(&parser); - return rc; -} diff --git a/src/box/sql.h b/src/box/sql.h index 028a15245..30bab3eb1 100644 --- a/src/box/sql.h +++ b/src/box/sql.h @@ -172,14 +172,6 @@ sql_expr_extract_select(struct Parse *parser, struct Select *select); struct Expr* space_column_default_expr(uint32_t space_id, uint32_t fieldno); -/** - * Get server checks list by space_id. - * @param space_id Space ID. - * @retval Checks list. - */ -struct ExprList * -space_checks_expr_list(uint32_t space_id); - /** * Return the number of bytes required to create a duplicate of the * expression passed as the first argument. The second argument is a @@ -310,42 +302,6 @@ void sql_resolve_self_reference(struct Parse *parser, struct Table *table, 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 49b90b5d0..235b78a31 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -275,8 +275,6 @@ table_delete(struct sqlite3 *db, struct Table *tab) if (tab->def->opts.is_temporary) { for (uint32_t i = 0; i < tab->space->index_count; ++i) index_def_delete(tab->space->index[i]->def); - /* Do not delete table->def allocated on region. */ - sql_expr_list_delete(db, tab->def->opts.checks); } else if (tab->def->id == 0) { space_def_delete(tab->def); } @@ -757,31 +755,57 @@ 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; struct Table *table = parser->pNewTable; - if (table != NULL) { - expr->u.zToken = - sqlite3DbStrNDup(parser->db, (char *)span->zStart, - (int)(span->zEnd - span->zStart)); - if (expr->u.zToken == NULL) - goto release_expr; - table->def->opts.checks = - sql_expr_list_append(parser->db, - table->def->opts.checks, expr); - if (table->def->opts.checks == NULL) { - sqlite3DbFree(parser->db, expr->u.zToken); - goto release_expr; - } - if (parser->constraintName.n) { - sqlite3ExprListSetName(parser, table->def->opts.checks, - &parser->constraintName, 1); + assert(table != 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"); + goto error; } + sprintf((char *)ck_constraint_name, "%.*s", + parser->constraintName.n, parser->constraintName.z); + sqlite3NormalizeName((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, + table->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"); + goto error; } + 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; +error: + parser->rc = SQL_TARANTOOL_ERROR; + parser->nErr++; + goto out; } /* @@ -845,16 +869,6 @@ sql_column_collation(struct space_def *def, uint32_t column, uint32_t *coll_id) return field->coll; } -struct ExprList * -space_checks_expr_list(uint32_t space_id) -{ - struct space *space; - space = space_by_id(space_id); - assert(space != NULL); - assert(space->def != NULL); - return space->def->opts.checks; -} - int vdbe_emit_open_cursor(struct Parse *parse_context, int cursor, int index_id, struct space *space) @@ -1085,6 +1099,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 sqlite3 *db = parser->db; + struct Vdbe *v = sqlite3GetVdbe(parser); + assert(v != NULL); + int ck_constraint_reg = sqlite3GetTempRange(parser, 4); + sqlite3VdbeAddOp4(v, OP_String8, 0, ck_constraint_reg, 0, + sqlite3DbStrDup(db, ck_constraint_def->name), + P4_DYNAMIC); + sqlite3VdbeAddOp2(v, OP_SCopy, reg_space_id, ck_constraint_reg + 1); + sqlite3VdbeAddOp4(v, OP_String8, 0, ck_constraint_reg + 2, 0, + sqlite3DbStrDup(db, ck_constraint_def->expr_str), + P4_DYNAMIC); + sqlite3VdbeAddOp3(v, OP_MakeRecord, ck_constraint_reg, 3, + ck_constraint_reg + 3); + sqlite3VdbeAddOp3(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); + sqlite3ReleaseTempRange(parser, ck_constraint_reg, 4); + return; +} + /** * Generate opcodes to serialize foreign key into MsgPack and * insert produced tuple into _fk_constraint space. @@ -1269,7 +1317,7 @@ sqlite3EndTable(Parse * pParse, /* Parse context */ sqlite3ErrorMsg(pParse, "PRIMARY KEY missing on table %s", p->def->name); - goto cleanup; + return; } } @@ -1374,9 +1422,12 @@ sqlite3EndTable(Parse * pParse, /* Parse context */ fk->child_id = reg_space_id; vdbe_emit_fkey_create(pParse, fk); } -cleanup: - sql_expr_list_delete(db, p->def->opts.checks); - p->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 @@ -1579,6 +1630,38 @@ vdbe_emit_fkey_drop(struct Parse *parse_context, char *constraint_name, sqlite3ReleaseTempRange(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 = sqlite3GetVdbe(parser); + struct sqlite3 *db = v->db; + assert(v != NULL); + int key_reg = sqlite3GetTempRange(parser, 3); + sqlite3VdbeAddOp4(v, OP_String8, 0, key_reg, 0, + sqlite3DbStrDup(db, ck_constraint_name), + P4_DYNAMIC); + sqlite3VdbeAddOp2(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; + sqlite3VdbeAddOp3(v, OP_MakeRecord, key_reg, 2, key_reg + 2); + sqlite3VdbeAddOp2(v, OP_SDelete, BOX_CK_CONSTRAINT_ID, key_reg + 2); + sqlite3ReleaseTempRange(parser, key_reg, 3); +} + /** * Generate code to drop a table. * This routine includes dropping triggers, sequences, @@ -1649,6 +1732,13 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space, return; vdbe_emit_fkey_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. diff --git a/src/box/sql/insert.c b/src/box/sql/insert.c index f147f6a50..786ac6990 100644 --- a/src/box/sql/insert.c +++ b/src/box/sql/insert.c @@ -935,34 +935,30 @@ vdbe_emit_constraint_checks(struct Parse *parse_context, struct Table *tab, if (on_conflict == ON_CONFLICT_ACTION_DEFAULT) on_conflict = ON_CONFLICT_ACTION_ABORT; /* Test all CHECK constraints. */ - struct ExprList *checks = space_checks_expr_list(def->id); 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 = sqlite3VdbeMakeLabel(v); - sqlite3ExprIfTrue(parse_context, expr, all_ok, - SQLITE_JUMPIFNULL); - if (on_conflict == ON_CONFLICT_ACTION_IGNORE) { - sqlite3VdbeGoto(v, ignore_label); - } else { - char *name = checks->a[i].zName; - if (name == NULL) - name = def->name; - sqlite3HaltConstraint(parse_context, - SQLITE_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 = sqlite3VdbeMakeLabel(v); + sqlite3ExprIfTrue(parse_context, expr, all_ok, + SQLITE_JUMPIFNULL); + if (on_conflict == ON_CONFLICT_ACTION_IGNORE) { + sqlite3VdbeGoto(v, ignore_label); sqlite3VdbeResolveLabel(v, all_ok); + } else { + char *name = ck_constraint->def->name; + sqlite3HaltConstraint(parse_context, + SQLITE_CONSTRAINT_CHECK, + on_conflict_check, name, + P4_TRANSIENT, P5_ConstraintCheck); } + sqlite3VdbeResolveLabel(v, all_ok); } sql_emit_table_affinity(v, tab->def, new_tuple_reg); /* @@ -1248,14 +1244,12 @@ xferOptimization(Parse * pParse, /* Parser context */ if (pSrcIdx == NULL) return 0; } - /* Get server checks. */ - ExprList *pCheck_src = space_checks_expr_list(src->def->id); - ExprList *pCheck_dest = space_checks_expr_list(dest->def->id); - if (pCheck_dest != NULL && - sqlite3ExprListCompare(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 8e21b6fca..31715bf88 100644 --- a/src/box/sql/parse.y +++ b/src/box/sql/parse.y @@ -265,7 +265,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) refargs(R). {sql_create_foreign_key(pParse, NULL, NULL, NULL, &T, TA, false, R);} ccons ::= defer_subclause(D). {fkey_change_defer_mode(pParse, D);} @@ -317,7 +317,7 @@ tcons ::= UNIQUE LP sortlist(X) RP. SORT_ORDER_ASC,false, SQL_INDEX_TYPE_CONSTRAINT_UNIQUE);} tcons ::= CHECK LP expr(E) RP onconf. - {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) refargs(R) defer_subclause_opt(D). { sql_create_foreign_key(pParse, NULL, NULL, FA, &T, TA, D, R); diff --git a/src/box/sql/prepare.c b/src/box/sql/prepare.c index 824578e45..703ec5aae 100644 --- a/src/box/sql/prepare.c +++ b/src/box/sql/prepare.c @@ -274,6 +274,7 @@ sql_parser_create(struct Parse *parser, sqlite3 *db) memset(parser, 0, sizeof(struct Parse)); parser->db = db; rlist_create(&parser->new_fkey); + 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 02ee225f1..22ddbb50c 100644 --- a/src/box/sql/select.c +++ b/src/box/sql/select.c @@ -6408,6 +6408,5 @@ sql_expr_extract_select(struct Parse *parser, struct Select *select) assert(expr_list->nExpr == 1); parser->parsed_ast_type = AST_TYPE_EXPR; parser->parsed_ast.expr = sqlite3ExprDup(parser->db, - expr_list->a->pExpr, - EXPRDUP_REDUCE); + expr_list->a->pExpr, 0); } diff --git a/src/box/sql/sqliteInt.h b/src/box/sql/sqliteInt.h index ee24e0337..c6678e884 100644 --- a/src/box/sql/sqliteInt.h +++ b/src/box/sql/sqliteInt.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" @@ -2790,6 +2791,15 @@ struct Parse { * Foreign key constraint appeared in CREATE TABLE stmt. */ struct rlist new_fkey; + /** + * 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. @@ -3385,7 +3395,7 @@ void sqlite3AddPrimaryKey(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 sqlite3AddDefaultValue(Parse *, ExprSpan *); void sqlite3AddCollateType(Parse *, Token *); diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index 9fc362f0a..4b2b45766 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -1011,7 +1011,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 @@ -1060,8 +1060,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 2532b704a..0cb492904 100644 --- a/test/box-py/bootstrap.result +++ b/test/box-py/bootstrap.result @@ -84,6 +84,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{} --- @@ -139,6 +141,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 c3cabcc8a..dd51539a6 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -477,10 +477,12 @@ t; 145: box.error.NO_SUCH_SEQUENCE 146: box.error.SEQUENCE_EXISTS 147: box.error.SEQUENCE_OVERFLOW + 148: box.error.CREATE_CK_CONSTRAINT 149: box.error.SPACE_FIELD_IS_DUPLICATE 150: box.error.CANT_CREATE_COLLATION 151: box.error.WRONG_COLLATION_OPTIONS 152: box.error.NULLABLE_PRIMARY + 153: box.error.CK_CONSTRAINT_FAILED 154: box.error.TRANSACTION_YIELD 155: box.error.NO_SUCH_GROUP 156: box.error.SQL_BIND_VALUE diff --git a/test/sql-tap/check.test.lua b/test/sql-tap/check.test.lua index 1f369fb02..c419e535d 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); ]], { -- - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- }) @@ -75,7 +75,7 @@ test:do_catchsql_test( INSERT INTO t1 VALUES(4,3, 2); ]], { -- - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_2_T1" -- }) @@ -147,7 +147,7 @@ test:do_catchsql_test( UPDATE t1 SET x=7 WHERE x==2 ]], { -- - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- }) @@ -167,7 +167,7 @@ test:do_catchsql_test( UPDATE t1 SET x=5 WHERE x==2 ]], { -- - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- }) @@ -246,7 +246,7 @@ test:do_catchsql_test( INSERT INTO t2 VALUES(3, 1.1, NULL, NULL); ]], { -- - 1, "CHECK constraint failed: ONE" + 1, "Check constraint failed: ONE" -- }) @@ -256,7 +256,7 @@ test:do_catchsql_test( INSERT INTO t2 VALUES(4, NULL, 5, NULL); ]], { -- - 1, "CHECK constraint failed: TWO" + 1, "Check constraint failed: TWO" -- }) @@ -266,7 +266,7 @@ test:do_catchsql_test( INSERT INTO t2 VALUES(5, NULL, NULL, 3.14159); ]], { -- - 1, "CHECK constraint failed: THREE" + 1, "Check constraint failed: THREE" -- }) @@ -319,7 +319,7 @@ test:do_catchsql_test( ); ]], { -- - 1, "Failed to create space 'T3': SQL error: subqueries prohibited in CHECK constraints" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': subqueries prohibited in CHECK constraints" -- }) @@ -344,7 +344,7 @@ test:do_catchsql_test( ); ]], { -- - 1, "Failed to create space 'T3': SQL error: no such column: Q" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': no such column: Q" -- }) @@ -368,7 +368,7 @@ test:do_catchsql_test( ); ]], { -- - 1, "Failed to create space 'T3': SQL error: no such column: T2.X" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': no such column: T2.X" -- }) @@ -413,7 +413,7 @@ test:do_catchsql_test( INSERT INTO t3 VALUES(111,222,333); ]], { -- - 1, "CHECK constraint failed: T3" + 1, "Check constraint failed: CK_CONSTRAINT_1_T3" -- }) @@ -484,7 +484,7 @@ test:do_catchsql_test( UPDATE t4 SET x=0, y=1; ]], { -- - 1, "CHECK constraint failed: T4" + 1, "Check constraint failed: CK_CONSTRAINT_1_T4" -- }) @@ -504,7 +504,7 @@ test:do_catchsql_test( UPDATE t4 SET x=0, y=2; ]], { -- - 1, "CHECK constraint failed: T4" + 1, "Check constraint failed: CK_CONSTRAINT_1_T4" -- }) @@ -516,7 +516,7 @@ test:do_catchsql_test( ); ]], { -- - 1, "Wrong space options (field 5): invalid expression specified (SQL error: bindings are not allowed in DDL)" + 1, "SQL error: bindings are not allowed in DDL" -- }) @@ -528,7 +528,7 @@ test:do_catchsql_test( ); ]], { -- - 1, "Wrong space options (field 5): invalid expression specified (SQL error: bindings are not allowed in DDL)" + 1, "SQL error: bindings are not allowed in DDL" -- }) @@ -581,7 +581,7 @@ test:do_catchsql_test( UPDATE OR FAIL t1 SET x=7-x, y=y+1; ]], { -- - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- }) @@ -603,7 +603,7 @@ test:do_catchsql_test( INSERT OR ROLLBACK INTO t1 VALUES(8,40.0, 10); ]], { -- - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- }) @@ -636,7 +636,7 @@ test:do_catchsql_test( REPLACE INTO t1 VALUES(6,7, 11); ]], { -- - 1, "CHECK constraint failed: T1" + 1, "Check constraint failed: CK_CONSTRAINT_1_T1" -- }) @@ -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" -- }) @@ -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" -- }) end diff --git a/test/sql-tap/fkey2.test.lua b/test/sql-tap/fkey2.test.lua index 03bf025f3..72b301713 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; ]], { -- - 1, "CHECK constraint failed: EF" + 1, "Check constraint failed: CK_CONSTRAINT_1_EF" -- }) @@ -382,7 +382,7 @@ test:do_catchsql_test( UPDATE ab SET a = 5; ]], { -- - 1, "CHECK constraint failed: EF" + 1, "Check constraint failed: CK_CONSTRAINT_1_EF" -- }) diff --git a/test/sql-tap/in1.test.lua b/test/sql-tap/in1.test.lua index b938ff17f..2729fcc0a 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 7057f6b0f..cb54e719c 100755 --- a/test/sql-tap/table.test.lua +++ b/test/sql-tap/table.test.lua @@ -1217,7 +1217,7 @@ test:do_catchsql_test( INSERT INTO T21 VALUES(1, -1, 1); ]], { -- - 1, "CHECK constraint failed: T21" + 1, "Check constraint failed: CK_CONSTRAINT_1_T21" -- }) @@ -1227,7 +1227,7 @@ test:do_catchsql_test( INSERT INTO T21 VALUES(1, 1, -1); ]], { -- - 1, "CHECK constraint failed: T21" + 1, "Check constraint failed: CK_CONSTRAINT_2_T21" -- }) @@ -1368,7 +1368,7 @@ test:do_catchsql_test( INSERT INTO T28 VALUES(0); ]], { -- - 1, "CHECK constraint failed: CHECK1" + 1, "Check constraint failed: CHECK1" -- }) @@ -1378,7 +1378,7 @@ test:do_catchsql_test( INSERT INTO T28 VALUES(9); ]], { -- - 1, "CHECK constraint failed: CHECK2" + 1, "Check constraint failed: CHECK2" -- }) diff --git a/test/sql/checks.result b/test/sql/checks.result index 12a3aa14c..636fa5b5f 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,106 @@ t = {513, 1, 'test', 'memtx', 0, opts, format} ... s = box.space._space:insert(t) --- -- error: 'Wrong space options (field 5): invalid expression specified (SQL error: - near "<": syntax error)' ... -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: 'SQL error: near "<": syntax error' ... -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'}} +-- 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);") --- ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +box.space._ck_constraint:count() --- +- 2 ... -s = box.space._space:insert(t) +box.sql.execute("INSERT INTO t1 VALUES (7, 1, 1)") --- -- error: 'Wrong space options (field 5): invalid MsgPack map field ''expr_invalid_label''' +- error: 'Check constraint failed: ONE' ... --- invalid field type -opts = {checks = {{name = 123}}} +box.sql.execute("INSERT INTO t1 VALUES (2, 1, 1)") --- +- error: 'Check constraint failed: TWO' ... -format = {{name = 'X', type = 'unsigned'}} +box.sql.execute("INSERT INTO t1 VALUES (2, 4, 1)") --- ... -t = {513, 1, 'test', 'memtx', 0, opts, format} ---- -... -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'': SQL error: no such table: W2' +- error: 'Failed to create check constraint ''CK_CONSTRAINT_1_W2'': subqueries prohibited + in CHECK constraints' ... box.sql.execute("DROP TABLE w2;") --- @@ -122,8 +139,7 @@ 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 (SQL error: - bindings are not allowed in DDL)' +- error: 'SQL error: bindings are not allowed in DDL' ... opts = {checks = {{expr = '?>5', name = 'ONE'}}} --- @@ -136,8 +152,6 @@ t = {513, 1, 'test', 'memtx', 0, opts, format} ... s = box.space._space:insert(t) --- -- error: 'Wrong space options (field 5): invalid expression specified (SQL error: - 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..0de0b5e75 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) +box.space.test:create_index('pk') -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) +-- 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}) --- 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) +-- Check constraints LUA creation test. +box.space._ck_constraint:insert({'CK_CONSTRAINT_01', 513, 'X<5'}) +box.space._ck_constraint:count({}) --- 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.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 diff --git a/test/sql/errinj.result b/test/sql/errinj.result index acce52e8a..56e103e4a 100644 --- a/test/sql/errinj.result +++ b/test/sql/errinj.result @@ -340,3 +340,55 @@ errinj.set("ERRINJ_WAL_IO", false) box.sql.execute("DROP TABLE t3;") --- ... +-- 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);") +--- +... +box.space.test:drop() +--- +... diff --git a/test/sql/errinj.test.lua b/test/sql/errinj.test.lua index fc19c859b..e4f064ff1 100644 --- a/test/sql/errinj.test.lua +++ b/test/sql/errinj.test.lua @@ -117,3 +117,22 @@ box.sql.execute("ALTER TABLE t3 DROP CONSTRAINT fk1;") box.sql.execute("INSERT INTO t3 VALUES(1, 1, 3);") errinj.set("ERRINJ_WAL_IO", false) box.sql.execute("DROP TABLE t3;") + +-- 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);") +box.space.test: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/sql/upgrade.result b/test/sql/upgrade.result index d3392e88b..7b9126a82 100644 --- a/test/sql/upgrade.result +++ b/test/sql/upgrade.result @@ -180,11 +180,10 @@ s = box.space._space.index['name']:get('T2') s --- - [512, 1, 'T2', 'memtx', 3, {'sql': 'CREATE TABLE t2(x INTEGER CONSTRAINT aaa CHECK - ( x<5 ), y REAL CHECK( y>x ), z INT primary key)', 'checks': [{'name': 'AAA', - 'expr': 'x<5'}, {'expr': 'y>x'}]}, [{'affinity': 68, 'type': 'scalar', 'nullable_action': 'none', - 'name': 'X', 'is_nullable': true}, {'affinity': 69, 'type': 'scalar', 'nullable_action': 'none', - 'name': 'Y', 'is_nullable': true}, {'affinity': 68, 'type': 'integer', 'nullable_action': 'abort', - 'name': 'Z', 'is_nullable': false}]] + ( x<5 ), y REAL CHECK( y>x ), z INT primary key)'}, [{'affinity': 68, 'type': 'scalar', + 'is_nullable': true, 'name': 'X', 'nullable_action': 'none'}, {'affinity': 69, + 'type': 'scalar', 'is_nullable': true, 'name': 'Y', 'nullable_action': 'none'}, + {'affinity': 68, 'type': 'integer', 'is_nullable': false, 'name': 'Z', 'nullable_action': 'abort'}]] ... i = box.space._index:select(s.id) --- 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.19.2