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 559D43053A for ; Fri, 31 May 2019 09:45:32 -0400 (EDT) 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 tGg6mgXcSdor for ; Fri, 31 May 2019 09:45:32 -0400 (EDT) Received: from smtp55.i.mail.ru (smtp55.i.mail.ru [217.69.128.35]) (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 5078E301EC for ; Fri, 31 May 2019 09:45:31 -0400 (EDT) Subject: [tarantool-patches] Re: [PATCH v5 4/6] schema: add new system space for CHECK constraints References: <788556b70e154103ed1f6131db7ee1f8cd687848.1558605591.git.kshcherbatov@tarantool.org> From: Kirill Shcherbatov Message-ID: Date: Fri, 31 May 2019 16:45:29 +0300 MIME-Version: 1.0 In-Reply-To: Content-Type: text/plain; charset="utf-8" Content-Language: en-US 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, Vladislav Shpilevoy > 1. 'thouse' word does not exist, it is clearly visible with a > spell checker, which usually highlights unknown words. Fixed. >> + /* Create or replace check constraint. */ >> + struct ck_constraint_def *ck_def = >> + ck_constraint_def_new_from_tuple(new_tuple); >> + auto ck_guard = make_scoped_guard([=] { free(ck_def); }); > > 2. Please, implement and use ck_constraint_def_delete. Free() will leak, when > ck_constraint will become more complex. > 3. The same. Use ck_constraint_def_delete. Done. struct ck_constraint_def * ck_constraint_def_new(const char *name, uint32_t name_len, const char *expr, uint32_t expr_str_len, uint32_t space_id, enum ck_constraint_language language); void ck_constraint_def_delete(struct ck_constraint_def *ck_def); >> + * @retval The size of the ck constraint definition object for >> + * given parameters. > > 4. I said it probably 2-3 times, but you still ignore. @retval takes > one parameter, @return take 0 parameters. Here you wrote, that > returned value is 'The'. Hm, I finally understood the difference between them. >> + BOX_CK_CONSTRAINT_ID = 357, > > 5. Please, leave a gap after BOX_FK_CONSTRAINT_ID. We keep > gaps to be able in future to add a view space, and to choose > recovery order. Gap is typically 7 unused IDs. The new ID > should be 364. Done. >> +/** >> + * Prepare a 0-terminated string in the wptr memory buffer that >> + * does not contain a sequence of more than one whatespace >> + * character. Routine enforces ' ' (space) as whitespace >> + * delimiter. When character ' or " was met, the sting is copied >> + * without any changes until the next (corres) ' or " . > >> + * The wptr buffer is expected to have str_len + 1 bytes >> + * (this is the expected scenario where no extra whitespace >> + * characters preset in the source string). > 6. What is 'corres'?> 7. What is 'characters preset'? /** * Prepare a 0-terminated string in the wptr memory buffer that * does not contain a sequence of more than one whatespace * character. Routine enforces ' ' (space) as whitespace * delimiter. When character ' or " was met, the sting is copied * without any changes until the next ' or " sign. * The wptr buffer is expected to have str_len + 1 bytes * (this is the expected scenario where no extra whitespace * characters in the source string). * @param wptr The destination memory buffer of size * @a str_len + 1. * @param str The source string to be copied. * @param str_len The source string @a str length. */ static void trim_space_snprintf(char *wptr, const char *str, uint32_t str_len) >> +/** >> + * Generate VDBE program to remove entry from _ck_constraint space. >> + * >> + * @param parser Parsing context. >> + * @param ck_name Name of CK constraint to be dropped. >> + * @param child_id Id of table which constraint belongs to. > > 8. There is no parameter 'child_id'. space_id. fixed. >> + sql_create_check_contraint(pParse); > > 9. What was a motivation of this rename? As I understand, > we can add many CHECK constraints in one CREATE TABLE. We > do not 'create' one single CHECK, we add multiple ones. Please, > keep the old name. Nikita asked me to do this. >> +-- Defered CK constraints are not supported. > > 10. 'Defered' word does not exist, use 'deferred'. > 'Unexistent' word does not exist as well. Non-existent is better. > 11. 'Supperted' word does not exist. Fixed. >> +--- >> +... >> +box.execute("CREATE TABLE T2(ID INT PRIMARY KEY, CONSTRAINT CK1 CHECK(ID > 0), CONSTRAINT CK1 CHECK(ID < 0))") >> +--- >> +- error: Duplicate key exists in unique index 'primary' in space '_ck_constraint' > > 12. The error is not ok. It should not expose any system details like > _ck_constraint space. Please, check duplicate names before an insertion. Ok, done. box.execute("CREATE TABLE T2(ID INT PRIMARY KEY, CONSTRAINT CK1 CHECK(ID > 0), CONSTRAINT CK1 CHECK(ID < 0))") --- - error: Constraint CK1 already exists ... ============================================================= This patch introduces a new system space to persist check constraints. The format of the new system space is _ck_constraint (space id = 364) [ UINT, STR, BOOL, STR], STR A CK constraint is local for a space, so every pair is unique (it is also the PK in the _ck_constraint space). After insertion into this space, a new instance describing check constraint is created. Check constraint holds an exspression AST. While space features some check constraints, it isn't allowed to be dropped. The :drop() space method firstly deletes all check constraints and then removes an entry from the _space. Because the space alter, the index alter and the space truncate operations cause space recreation process, a new RebuildCkConstrains object is introduced. This alter object compiles a new ck constraint object, replaces and removes an existent instances atomically (but if the assembly of some ck constraint object fails, nothing is changed). In fact, in scope of this patch we don't really need to recreate a ck_constraint object in such situations (it is enough to patch space_def pointer in AST tree like we did it before, but we are going to recompile a VDBE that represents ck constraint in further patches, and that operation is not safe). The main motivation for these changes is an ability to support ADD CHECK CONSTRAINT operation in the future. CK constraints are easier to manage as self-sustained objects: such change is managed with atomic insertion(unlike the current architecture). Finally, the xfer optimization is disabled now if some space have ck constraints. In following patches this xfer optimisation becomes impossible, so there is no reason to rewrite this code now. Needed for #3691 --- src/box/CMakeLists.txt | 1 + src/box/alter.cc | 256 ++++++++++++++++++++++++-- src/box/alter.h | 1 + src/box/bootstrap.snap | Bin 4393 -> 4444 bytes src/box/ck_constraint.c | 148 +++++++++++++++ src/box/ck_constraint.h | 205 +++++++++++++++++++++ src/box/errcode.h | 3 +- src/box/lua/schema.lua | 4 + src/box/lua/space.cc | 2 + src/box/lua/upgrade.lua | 40 ++++ src/box/schema.cc | 8 + src/box/schema_def.h | 11 ++ src/box/space.c | 2 + src/box/space.h | 5 + src/box/space_def.c | 98 +--------- src/box/space_def.h | 4 - src/box/sql.c | 86 +-------- src/box/sql.h | 40 +--- src/box/sql/build.c | 232 +++++++++++++++++++---- src/box/sql/insert.c | 55 +++--- src/box/sql/parse.y | 2 +- src/box/sql/parse_def.h | 24 +++ src/box/sql/resolve.c | 8 +- src/box/sql/select.c | 11 +- src/box/sql/sqlInt.h | 2 +- src/box/sql/tokenize.c | 1 - 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 | 3 + test/box/access_sysview.result | 6 +- test/box/alter.result | 5 +- test/box/misc.result | 1 + test/sql-tap/check.test.lua | 32 ++-- test/sql-tap/fkey2.test.lua | 4 +- test/sql-tap/sql-errors.test.lua | 2 +- test/sql-tap/table.test.lua | 4 +- test/sql/checks.result | 227 ++++++++++++++++++----- test/sql/checks.test.lua | 104 +++++++---- test/sql/errinj.result | 134 ++++++++++++++ test/sql/errinj.test.lua | 45 +++++ test/sql/gh-2981-check-autoinc.result | 8 +- test/sql/types.result | 2 +- test/sql/upgrade.result | 19 ++ test/sql/upgrade.test.lua | 5 + test/wal_off/alter.result | 2 +- 47 files changed, 1440 insertions(+), 424 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 0864c3433..481842a39 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -97,6 +97,7 @@ add_library(box STATIC space.c space_def.c sequence.c + ck_constraint.c fk_constraint.c func.c func_def.c diff --git a/src/box/alter.cc b/src/box/alter.cc index ed9e55907..bedeb71cd 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 "column_mask.h" #include "schema.h" #include "user.h" @@ -551,17 +552,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; @@ -1404,6 +1394,79 @@ UpdateSchemaVersion::alter(struct alter_space *alter) ++schema_version; } +/** + * As ck_constraint object depends on space_def we must rebuild + * all ck constraints on space alter. + * + * To perform it transactionally, we create a list of new ck + * constraint objects in ::prepare method that is fault-tolerant. + * Finally in ::alter or ::rollback methods we only swap those + * lists securely. + */ +class RebuildCkConstraints: public AlterSpaceOp +{ + void space_swap_ck_constraint(struct space *old_space, + struct space *new_space); +public: + RebuildCkConstraints(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 ~RebuildCkConstraints(); +}; + +void +RebuildCkConstraints::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 +RebuildCkConstraints::space_swap_ck_constraint(struct space *old_space, + struct space *new_space) +{ + rlist_swap(&new_space->ck_constraint, &ck_constraint); + rlist_swap(&ck_constraint, &old_space->ck_constraint); +} + +void +RebuildCkConstraints::alter(struct alter_space *alter) +{ + space_swap_ck_constraint(alter->old_space, alter->new_space); +} + +void +RebuildCkConstraints::rollback(struct alter_space *alter) +{ + space_swap_ck_constraint(alter->new_space, alter->old_space); +} + +RebuildCkConstraints::~RebuildCkConstraints() +{ + struct ck_constraint *old_ck_constraint, *tmp; + rlist_foreach_entry_safe(old_ck_constraint, &ck_constraint, link, tmp) { + /** + * Ck constraint definition is now managed by + * other Ck constraint object. Prevent it's + * destruction as a part of ck_constraint_delete + * call. + */ + old_ck_constraint->def = NULL; + ck_constraint_delete(old_ck_constraint); + } +} + /* }}} */ /** @@ -1769,6 +1832,11 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) space_name(old_space), "the space has foreign key 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 @@ -1866,6 +1934,7 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) def->field_count); (void) new CheckSpaceFormat(alter); (void) new ModifySpace(alter, def); + (void) new RebuildCkConstraints(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); @@ -2108,6 +2177,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 RebuildCkConstraints(alter); /* Add an op to update schema_version on commit. */ (void) new UpdateSchemaVersion(alter); alter_space_do(txn, alter); @@ -2176,6 +2246,7 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event) (void) new TruncateIndex(alter, old_index->def->iid); } + (void) new RebuildCkConstraints(alter); alter_space_do(txn, alter); scoped_guard.is_active = false; } @@ -4088,6 +4159,165 @@ on_replace_dd_fk_constraint(struct trigger * /* trigger*/, void *event) } } +/** Create an instance of check constraint definition by tuple. */ +static struct ck_constraint_def * +ck_constraint_def_new_from_tuple(struct tuple *tuple) +{ + 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 space_id = + tuple_field_u32_xc(tuple, BOX_CK_CONSTRAINT_FIELD_SPACE_ID); + const char *language_str = + tuple_field_cstr_xc(tuple, BOX_CK_CONSTRAINT_FIELD_LANGUAGE); + enum ck_constraint_language language = + STR2ENUM(ck_constraint_language, language_str); + if (language == ck_constraint_language_MAX) { + tnt_raise(ClientError, ER_FUNCTION_LANGUAGE, language_str, + tt_cstr(name, name_len)); + } + uint32_t expr_str_len; + const char *expr_str = + tuple_field_str_xc(tuple, BOX_CK_CONSTRAINT_FIELD_CODE, + &expr_str_len); + struct ck_constraint_def *ck_def = + ck_constraint_def_new(name, name_len, expr_str, expr_str_len, + space_id, language); + if (ck_def == NULL) + diag_raise(); + return ck_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 = (struct ck_constraint *)trigger->data; + struct space *space = NULL; + if (ck != NULL) + space = space_by_id(ck->def->space_id); + if (stmt->old_tuple != NULL && stmt->new_tuple == NULL) { + /* Rollback DELETE check constraint. */ + assert(ck != NULL); + assert(space != NULL); + assert(space_ck_constraint_by_name(space, + ck->def->name, strlen(ck->def->name)) == NULL); + rlist_add_entry(&space->ck_constraint, ck, link); + } else if (stmt->new_tuple != NULL && stmt->old_tuple == NULL) { + /* Rollback INSERT check constraint. */ + assert(space != NULL); + assert(space_ck_constraint_by_name(space, + ck->def->name, strlen(ck->def->name)) != NULL); + rlist_del_entry(ck, link); + ck_constraint_delete(ck); + } else { + /* Rollback REPLACE check constraint. */ + assert(space != NULL); + const char *name = ck->def->name; + struct ck_constraint *new_ck = + space_ck_constraint_by_name(space, name, strlen(name)); + assert(new_ck != NULL); + rlist_del_entry(new_ck, link); + rlist_add_entry(&space->ck_constraint, ck, link); + ck_constraint_delete(new_ck); + } +} + +/** + * 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 txn_stmt *stmt = txn_last_stmt((struct txn *) event); + struct ck_constraint *ck = (struct ck_constraint *)trigger->data; + if (stmt->old_tuple != NULL) + ck_constraint_delete(ck); +} + +/** 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); + 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) { + bool is_deferred = + tuple_field_bool_xc(new_tuple, + BOX_CK_CONSTRAINT_FIELD_DEFERRED); + if (is_deferred) { + tnt_raise(ClientError, ER_UNSUPPORTED, "Tarantool", + "deferred ck constraints"); + } + /* Create or replace check constraint. */ + struct ck_constraint_def *ck_def = + ck_constraint_def_new_from_tuple(new_tuple); + auto ck_guard = make_scoped_guard([=] { free(ck_def); }); + /* + * FIXME: Ck constraint creation on non-empty + * space is not implemented yet. + */ + struct index *pk = space_index(space, 0); + if (pk != NULL && index_size(pk) > 0) { + tnt_raise(ClientError, ER_CREATE_CK_CONSTRAINT, + ck_def->name, + "referencing space must be empty"); + } + struct ck_constraint *new_ck_constraint = + ck_constraint_new(ck_def, space->def); + if (new_ck_constraint == NULL) + diag_raise(); + ck_guard.is_active = false; + const char *name = new_ck_constraint->def->name; + struct ck_constraint *old_ck_constraint = + space_ck_constraint_by_name(space, name, strlen(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_tuple == NULL ? new_ck_constraint : + old_ck_constraint; + on_rollback->data = on_commit->data; + } 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 }; @@ -4156,4 +4386,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 bb8fbeba114b1e72a5585548fb7f22796931d90f..fd7c7e16920d0de05799c9265d3cc461462aca2a 100644 GIT binary patch literal 4444 diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c new file mode 100644 index 000000000..69b9793ea --- /dev/null +++ b/src/box/ck_constraint.c @@ -0,0 +1,148 @@ +/* + * 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 "box/session.h" +#include "ck_constraint.h" +#include "errcode.h" +#include "sql.h" +#include "sql/sqlInt.h" + +const char *ck_constraint_language_strs[] = {"SQL"}; + +struct ck_constraint_def * +ck_constraint_def_new(const char *name, uint32_t name_len, const char *expr_str, + uint32_t expr_str_len, uint32_t space_id, + enum ck_constraint_language language) +{ + uint32_t name_offset, expr_str_offset; + uint32_t ck_def_sz = + ck_constraint_def_sizeof(name_len, expr_str_len, &name_offset, + &expr_str_offset); + struct ck_constraint_def *ck_def = + (struct ck_constraint_def *) malloc(ck_def_sz); + if (ck_def == NULL) { + diag_set(OutOfMemory, ck_def_sz, "malloc", "ck_def"); + return NULL; + } + ck_def->name = (char *)ck_def + name_offset; + ck_def->expr_str = (char *)ck_def + expr_str_offset; + ck_def->language = language; + ck_def->space_id = space_id; + memcpy(ck_def->expr_str, expr_str, expr_str_len); + ck_def->expr_str[expr_str_len] = '\0'; + memcpy(ck_def->name, name, name_len); + ck_def->name[name_len] = '\0'; + return ck_def; +} + +void +ck_constraint_def_delete(struct ck_constraint_def *ck_def) +{ + free(ck_def); +} + +/** + * Resolve space_def references for check constraint via AST + * tree traversal. + * @param expr Check constraint AST object to update. + * @param space_def Space definition to use. + * @retval 0 On success. + * @retval -1 On error. + */ +static int +ck_constraint_resolve_field_names(struct Expr *expr, + struct space_def *space_def) +{ + struct Parse parser; + sql_parser_create(&parser, sql_get(), default_flags); + parser.parse_only = true; + sql_resolve_self_reference(&parser, space_def, NC_IsCheck, expr); + int rc = parser.is_aborted ? -1 : 0; + sql_parser_destroy(&parser); + return rc; +} + +struct ck_constraint * +ck_constraint_new(struct ck_constraint_def *ck_constraint_def, + struct space_def *space_def) +{ + if (space_def->field_count == 0) { + diag_set(ClientError, ER_UNSUPPORTED, "Tarantool", + "CK constraint for space without format"); + return NULL; + } + struct ck_constraint *ck_constraint = malloc(sizeof(*ck_constraint)); + if (ck_constraint == NULL) { + diag_set(OutOfMemory, sizeof(*ck_constraint), "malloc", + "ck_constraint"); + return NULL; + } + ck_constraint->def = NULL; + rlist_create(&ck_constraint->link); + ck_constraint->expr = + sql_expr_compile(sql_get(), ck_constraint_def->expr_str, + strlen(ck_constraint_def->expr_str)); + if (ck_constraint->expr == NULL || + ck_constraint_resolve_field_names(ck_constraint->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->def = ck_constraint_def; + 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); + ck_constraint_def_delete(ck_constraint->def); + 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..3ae3d5c91 --- /dev/null +++ b/src/box/ck_constraint.h @@ -0,0 +1,205 @@ +#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; + +/** Supported languages of ck constraint. */ +enum ck_constraint_language { + CK_CONSTRAINT_LANGUAGE_SQL, + ck_constraint_language_MAX, +}; + +/** The supported languages strings. */ +extern const char *ck_constraint_language_strs[]; + +/** + * Check constraint definition. + * See ck_constraint_def_sizeof() definition for implementation + * details and memory layout. + */ +struct ck_constraint_def { + /** + * The 0-terminated string, a name of the check + * constraint. Must be unique for a given space. + */ + char *name; + /** + * The 0-terminated string that defines check constraint + * expression. + * + * For instance: "field1 + field2 > 2 * 3". + */ + char *expr_str; + /** + * The id of the space this check constraint is + * defined for. + */ + uint32_t space_id; + /** The language of ck constraint. */ + enum ck_constraint_language language; +}; + +/** + * Structure representing ck constraint. + * See ck_constraint_new() definition. + */ +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 sql_resolve_self_reference for + * space with space[ck_constraint::space_id] definition. + */ + struct Expr *expr; + /** + * 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. + * @return The size of the ck constraint definition object for + * given parameters. + */ +static inline 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 + 1; + return *expr_str_offset + expr_str_len + 1; +} + +/** + * Create a new check constraint definition object with given + * fields. + * + * @param name The name string of a new ck constraint definition. + * @param name_len The length of @a name string. + * @param expr The check expression string. + * @param expr_str_len The length of the @a expr string. + * @param space_id The identifier of the target space. + * @param language The language of the @a expr string. + * @retval not NULL Check constraint definition object pointer + * on success. + * @retval NULL Otherwise. The diag message is set. +*/ +struct ck_constraint_def * +ck_constraint_def_new(const char *name, uint32_t name_len, const char *expr, + uint32_t expr_str_len, uint32_t space_id, + enum ck_constraint_language language); + +/** + * Destroy check constraint definition memory, release acquired + * resources. + * @param ck_def The check constraint definition object to + * destroy. + */ +void +ck_constraint_def_delete(struct ck_constraint_def *ck_def); + +/** + * Create a new check constraint object by given check constraint + * definition and definition of the space this constraint is + * related to. + * + * @param ck_constraint_def The check constraint definition object + * to use. Expected to be allocated with + * malloc. Ck constraint object manages + * this allocation in case of successful + * creation. + * @param space_def The space definition of the space this check + * constraint must be constructed for. + * @retval not NULL Check constraint object pointer on success. + * @retval NULL Otherwise. The diag message is set. +*/ +struct ck_constraint * +ck_constraint_new(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 constraint. + * @param name The check constraint name. + * @param name_len The length of the name. + * @retval Not NULL Ck constraint pointer if exists. + * @retval 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 9c15f3322..1f7c81693 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -245,8 +245,9 @@ struct errcode_record { /*190 */_(ER_INT_LITERAL_MAX, "Integer literal %s%s exceeds the supported range %lld - %lld") \ /*191 */_(ER_SQL_PARSER_LIMIT, "%s %d exceeds the limit (%d)") \ /*192 */_(ER_INDEX_DEF_UNSUPPORTED, "%s are prohibited in an index definition") \ - /*193 */_(ER_CK_DEF_UNSUPPORTED, "%s are prohibited in a CHECK constraint definition") \ + /*193 */_(ER_CK_DEF_UNSUPPORTED, "%s are prohibited in a ck constraint definition") \ /*194 */_(ER_MULTIKEY_INDEX_MISMATCH, "Field %s is used as multikey in one index and as single key in another") \ + /*195 */_(ER_CREATE_CK_CONSTRAINT, "Failed to create check constraint '%s': %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 39bd8da6d..91fae8378 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -514,6 +514,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.is_generated == true then -- Delete automatically generated sequence. @@ -525,6 +526,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.primary:pairs({space_id}) do + _ck_constraint:delete({space_id, t.name}) + end local keys = _vindex:select(space_id) for i = #keys, 1, -1 do local v = keys[i] diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc index 87adaeb16..509306eae 100644 --- a/src/box/lua/space.cc +++ b/src/box/lua/space.cc @@ -552,6 +552,8 @@ box_lua_space_init(struct lua_State *L) lua_setfield(L, -2, "TRIGGER_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 070662698..4ff2efd3a 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) @@ -774,8 +776,46 @@ local function upgrade_sequence_to_2_2_1() _space_sequence:format(format) end +local function upgrade_ck_constraint_to_2_2_1() + -- In previous Tarantool releases check constraints were + -- stored in space opts. Now we use separate space + -- _ck_constraint for this purpose. Perform legacy data + -- migration. + local MAP = setmap({}) + local _space = box.space._space + local _index = box.space._index + local _ck_constraint = box.space._ck_constraint + log.info("create space _ck_constraint") + local format = {{name='space_id', type='unsigned'}, + {name='name', type='string'}, + {name='is_deferred', type='boolean'}, + {name='language', type='str'}, {name='code', 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, 'unsigned'}, {1, 'string'}}} + + for _, space in _space:pairs() do + local flags = space.flags + if flags.checks 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({space.id, check_name, false, + 'SQL', expr_str}) + end + flags.checks = nil + _space:replace({space.id, space.owner, space.name, space.engine, + space.field_count, flags, space.format}) + end + end +end + local function upgrade_to_2_2_1() upgrade_sequence_to_2_2_1() + upgrade_ck_constraint_to_2_2_1() end -------------------------------------------------------------------------------- diff --git a/src/box/schema.cc b/src/box/schema.cc index 9a55c2f14..6d3153815 100644 --- a/src/box/schema.cc +++ b/src/box/schema.cc @@ -460,6 +460,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; /* space id */ + key_parts[0].type = FIELD_TYPE_UNSIGNED; + key_parts[1].fieldno = 1; /* constraint name */ + key_parts[1].type = FIELD_TYPE_STRING; + 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 b817b49f6..3648639bc 100644 --- a/src/box/schema_def.h +++ b/src/box/schema_def.h @@ -108,6 +108,8 @@ enum { BOX_SPACE_SEQUENCE_ID = 340, /** Space id of _fk_constraint. */ BOX_FK_CONSTRAINT_ID = 356, + /** Space id of _ck_contraint. */ + BOX_CK_CONSTRAINT_ID = 364, /** End of the reserved range of system spaces. */ BOX_SYSTEM_ID_MAX = 511, BOX_ID_NIL = 2147483647 @@ -240,6 +242,15 @@ enum { BOX_FK_CONSTRAINT_FIELD_PARENT_COLS = 8, }; +/** _ck_constraint fields. */ +enum { + BOX_CK_CONSTRAINT_FIELD_SPACE_ID = 0, + BOX_CK_CONSTRAINT_FIELD_NAME = 1, + BOX_CK_CONSTRAINT_FIELD_DEFERRED = 2, + BOX_CK_CONSTRAINT_FIELD_LANGUAGE = 3, + BOX_CK_CONSTRAINT_FIELD_CODE = 4, +}; + /* * Different objects which can be subject to access * control. diff --git a/src/box/space.c b/src/box/space.c index 243e7da2f..a42b3a64b 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 be1178b99..7ffe884b3 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -197,6 +197,11 @@ struct space { * of index id. */ struct index **index; + /** + * List of check constraints linked with + * ck_constraint::link. + */ + struct rlist ck_constraint; /** * Lists of foreign key constraints. In SQL terms child * space is the "from" table i.e. the table that contains diff --git a/src/box/space_def.c b/src/box/space_def.c index d825def75..661131295 100644 --- a/src/box/space_def.c +++ b/src/box/space_def.c @@ -36,28 +36,12 @@ #include "msgpuck.h" #include "tt_static.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[] = { @@ -65,8 +49,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, }; @@ -114,16 +97,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; } @@ -301,74 +274,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 c6639a8d8..ac6d226c5 100644 --- a/src/box/space_def.h +++ b/src/box/space_def.h @@ -40,8 +40,6 @@ extern "C" { #endif /* defined(__cplusplus) */ -struct ExprList; - /** Space options */ struct space_opts { /** @@ -71,8 +69,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 07ceec2d2..b47f7498c 100644 --- a/src/box/sql.c +++ b/src/box/sql.c @@ -1016,15 +1016,8 @@ 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; - } - mpstream_encode_map(&stream, 2 * is_view + (checks_cnt > 0)); + mpstream_encode_map(&stream, 2 * is_view); if (is_view) { assert(def->opts.sql != NULL); @@ -1033,23 +1026,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, @@ -1284,66 +1260,6 @@ 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(), default_flags); - 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; -} - /** * Initialize a new tuple_fetcher instance with given tuple * data. diff --git a/src/box/sql.h b/src/box/sql.h index a13f78ae8..bb90bf838 100644 --- a/src/box/sql.h +++ b/src/box/sql.h @@ -278,48 +278,10 @@ sql_expr_list_append(struct sql *db, struct ExprList *expr_list, * @param def The definition of space being referenced. * @param type NC_IsCheck or NC_IdxExpr. * @param expr Expression to resolve. May be NULL. - * @param expr_list Expression list to resolve. May be NUL. */ void 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); + int type, struct Expr *expr); /** * Initialize a new parser object. diff --git a/src/box/sql/build.c b/src/box/sql/build.c index e2353d8cc..09a15cb18 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -43,10 +43,12 @@ * COMMIT * ROLLBACK */ +#include #include "sqlInt.h" #include "vdbeInt.h" #include "tarantoolInt.h" #include "box/box.h" +#include "box/ck_constraint.h" #include "box/fk_constraint.h" #include "box/sequence.h" #include "box/session.h" @@ -638,40 +640,117 @@ primary_key_exit: return; } +/** + * Prepare a 0-terminated string in the wptr memory buffer that + * does not contain a sequence of more than one whatespace + * character. Routine enforces ' ' (space) as whitespace + * delimiter. When character ' or " was met, the sting is copied + * without any changes until the next ' or " sign. + * The wptr buffer is expected to have str_len + 1 bytes + * (this is the expected scenario where no extra whitespace + * characters in the source string). + * @param wptr The destination memory buffer of size + * @a str_len + 1. + * @param str The source string to be copied. + * @param str_len The source string @a str length. + */ +static void +trim_space_snprintf(char *wptr, const char *str, uint32_t str_len) +{ + const char *str_end = str + str_len; + char quote_type = '\0'; + bool is_prev_chr_space = false; + while (str < str_end) { + if (quote_type == '\0') { + if (*str == '\'' || *str == '\"') { + quote_type = *str; + } else if (isspace((unsigned char)*str)) { + if (!is_prev_chr_space) + *wptr++ = ' '; + is_prev_chr_space = true; + str++; + continue; + } + } else if (*str == quote_type) { + quote_type = '\0'; + } + is_prev_chr_space = false; + *wptr++ = *str++; + } + *wptr = '\0'; +} + void -sql_add_check_constraint(struct Parse *parser) +sql_create_check_contraint(struct Parse *parser) { - struct create_ck_def *ck_def = &parser->create_ck_def; + struct create_ck_def *create_ck_def = &parser->create_ck_def; + struct ExprSpan *expr_span = create_ck_def->expr; + sql_expr_delete(parser->db, expr_span->pExpr, false); + struct alter_entity_def *alter_def = - (struct alter_entity_def *) &parser->create_ck_def; + (struct alter_entity_def *) create_ck_def; assert(alter_def->entity_type == ENTITY_TYPE_CK); (void) alter_def; - struct Expr *expr = ck_def->expr->pExpr; struct space *space = parser->create_table_def.new_space; - if (space != NULL) { - expr->u.zToken = - sqlDbStrNDup(parser->db, - (char *) ck_def->expr->zStart, - (int) (ck_def->expr->zEnd - - ck_def->expr->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; - } - struct create_entity_def *entity_def = &ck_def->base.base; - if (entity_def->name.n > 0) { - sqlExprListSetName(parser, space->def->opts.checks, - &entity_def->name, 1); + assert(space != NULL); + + /* Prepare payload for ck constraint definition. */ + struct region *region = &parser->region; + struct Token *name_token = &create_ck_def->base.base.name; + const char *name; + if (name_token->n > 0) { + name = sql_normalized_name_region_new(region, name_token->z, + name_token->n); + if (name == NULL) { + parser->is_aborted = true; + return; } } else { -release_expr: - sql_expr_delete(parser->db, expr, false); + uint32_t ck_idx = ++parser->create_table_def.check_count; + name = tt_sprintf("CK_CONSTRAINT_%d_%s", ck_idx, + space->def->name); } + size_t name_len = strlen(name); + + uint32_t expr_str_len = (uint32_t)(expr_span->zEnd - expr_span->zStart); + const char *expr_str = expr_span->zStart; + + /* + * Allocate memory for ck constraint parse structure and + * ck constraint definition as a single memory chunk on + * region: + * + * [ck_parse][ck_def[name][expr_str]] + * |_____^ |___^ ^ + * |_________| + */ + uint32_t name_offset, expr_str_offset; + uint32_t ck_def_sz = + ck_constraint_def_sizeof(name_len, expr_str_len, &name_offset, + &expr_str_offset); + struct ck_constraint_parse *ck_parse = + region_alloc(region, sizeof(*ck_parse) + ck_def_sz); + if (ck_parse == NULL) { + diag_set(OutOfMemory, sizeof(*ck_parse) + ck_def_sz, "region", + "ck_parse"); + parser->is_aborted = true; + return; + } + struct ck_constraint_def *ck_def = + (struct ck_constraint_def *)((char *)ck_parse + + sizeof(*ck_parse)); + ck_parse->ck_def = ck_def; + rlist_create(&ck_parse->link); + + ck_def->name = (char *)ck_def + name_offset; + ck_def->expr_str = (char *)ck_def + expr_str_offset; + ck_def->language = CK_CONSTRAINT_LANGUAGE_SQL; + ck_def->space_id = BOX_ID_NIL; + trim_space_snprintf(ck_def->expr_str, expr_str, expr_str_len); + memcpy(ck_def->name, name, name_len); + ck_def->name[name_len] = '\0'; + + rlist_add_entry(&parser->create_table_def.new_check, ck_parse, link); } /* @@ -978,6 +1057,48 @@ emitNewSysSpaceSequenceRecord(Parse *pParse, int reg_space_id, int 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_def Check constraint definition to be serialized. + * @param reg_space_id The VDBE register containing space id. +*/ +static void +vdbe_emit_ck_constraint_create(struct Parse *parser, + const struct ck_constraint_def *ck_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, 6); + sqlVdbeAddOp2(v, OP_SCopy, reg_space_id, ck_constraint_reg); + sqlVdbeAddOp4(v, OP_String8, 0, ck_constraint_reg + 1, 0, + sqlDbStrDup(db, ck_def->name), P4_DYNAMIC); + sqlVdbeAddOp2(v, OP_Bool, false, ck_constraint_reg + 2); + sqlVdbeAddOp4(v, OP_String8, 0, ck_constraint_reg + 3, 0, + ck_constraint_language_strs[ck_def->language], P4_STATIC); + sqlVdbeAddOp4(v, OP_String8, 0, ck_constraint_reg + 4, 0, + sqlDbStrDup(db, ck_def->expr_str), P4_DYNAMIC); + sqlVdbeAddOp3(v, OP_MakeRecord, ck_constraint_reg, 5, + ck_constraint_reg + 5); + const char *error_msg = + tt_sprintf(tnt_errcode_desc(ER_CONSTRAINT_EXISTS), + ck_def->name); + if (vdbe_emit_halt_with_presence_test(parser, BOX_CK_CONSTRAINT_ID, 0, + ck_constraint_reg, 2, + ER_CONSTRAINT_EXISTS, error_msg, + false, OP_NoConflict) != 0) + return; + sqlVdbeAddOp3(v, OP_SInsert, BOX_CK_CONSTRAINT_ID, 0, + ck_constraint_reg + 5); + save_record(parser, BOX_CK_CONSTRAINT_ID, ck_constraint_reg, 2, + v->nOp - 1); + VdbeComment((v, "Create CK constraint %s", ck_def->name)); + sqlReleaseTempRange(parser, ck_constraint_reg, 5); +} + /** * Generate opcodes to serialize foreign key into MsgPack and * insert produced tuple into _fk_constraint space. @@ -1129,20 +1250,18 @@ resolve_link(struct Parse *parse_context, const struct space_def *def, void sqlEndTable(struct Parse *pParse) { - sql *db = pParse->db; /* The database connection */ - - assert(!db->mallocFailed); + assert(!pParse->db->mallocFailed); struct space *new_space = pParse->create_table_def.new_space; if (new_space == NULL) return; - assert(!db->init.busy); + assert(!pParse->db->init.busy); assert(!new_space->def->opts.is_view); if (sql_space_primary_key(new_space) == NULL) { diag_set(ClientError, ER_CREATE_SPACE, new_space->def->name, "PRIMARY KEY missing"); pParse->is_aborted = true; - goto cleanup; + return; } /* @@ -1232,9 +1351,12 @@ sqlEndTable(struct Parse *pParse) 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_parse *ck_parse; + rlist_foreach_entry(ck_parse, &pParse->create_table_def.new_check, + link) { + vdbe_emit_ck_constraint_create(pParse, ck_parse->ck_def, + reg_space_id); + } } void @@ -1432,6 +1554,37 @@ 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_name Name of CK constraint to be dropped. + * @param space_id Id of table which constraint belongs to. + */ +static void +vdbe_emit_ck_constraint_drop(struct Parse *parser, const char *ck_name, + uint32_t space_id) +{ + struct Vdbe *v = sqlGetVdbe(parser); + struct sql *db = v->db; + assert(v != NULL); + int key_reg = sqlGetTempRange(parser, 3); + sqlVdbeAddOp2(v, OP_Integer, space_id, key_reg); + sqlVdbeAddOp4(v, OP_String8, 0, key_reg + 1, 0, + sqlDbStrDup(db, ck_name), P4_DYNAMIC); + const char *error_msg = + tt_sprintf(tnt_errcode_desc(ER_NO_SUCH_CONSTRAINT), ck_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); + VdbeComment((v, "Delete CK constraint %s", ck_name)); + sqlReleaseTempRange(parser, key_reg, 3); +} + /** * Generate code to drop a table. * This routine includes dropping triggers, sequences, @@ -1505,6 +1658,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. @@ -2074,8 +2234,7 @@ index_fill_def(struct Parse *parse, struct index *index, } for (int i = 0; i < expr_list->nExpr; i++) { struct Expr *expr = expr_list->a[i].pExpr; - sql_resolve_self_reference(parse, space_def, NC_IdxExpr, - expr, 0); + sql_resolve_self_reference(parse, space_def, NC_IdxExpr, expr); if (parse->is_aborted) goto cleanup; @@ -2791,8 +2950,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 81feca7e0..c4b629c6c 100644 --- a/src/box/sql/insert.c +++ b/src/box/sql/insert.c @@ -36,9 +36,10 @@ #include "sqlInt.h" #include "tarantoolInt.h" #include "vdbeInt.h" -#include "box/schema.h" +#include "box/ck_constraint.h" #include "bit/bit.h" #include "box/box.h" +#include "box/schema.h" enum field_type * sql_index_type_str(struct sql *db, const struct index_def *idx_def) @@ -918,34 +919,27 @@ 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); - } - sqlVdbeResolveLabel(v, all_ok); + 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); + } 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); /* @@ -1230,14 +1224,13 @@ 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 are check + * constraints. + */ + if (!rlist_empty(&dest->ck_constraint) || + !rlist_empty(&src->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 f241b8d52..e93dfe751 100644 --- a/src/box/sql/parse.y +++ b/src/box/sql/parse.y @@ -297,7 +297,7 @@ ccons ::= check_constraint_def . check_constraint_def ::= cconsname(N) CHECK LP expr(X) RP. { create_ck_def_init(&pParse->create_ck_def, &N, &X); - sql_add_check_constraint(pParse); + sql_create_check_contraint(pParse); } ccons ::= cconsname(N) REFERENCES nm(T) eidlist_opt(TA) matcharg(M) refargs(R). { diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h index 5899a7e4e..6c1b6fddd 100644 --- a/src/box/sql/parse_def.h +++ b/src/box/sql/parse_def.h @@ -123,6 +123,22 @@ struct fk_constraint_parse { struct rlist link; }; +/** + * Structure representing check constraint appeared within + * CREATE TABLE statement. Used only during parsing. + * All allocations are performed on region, so no cleanups are + * required. + */ +struct ck_constraint_parse { + /** + * Check constraint declared in + * statement. Must be coded after space creation. + */ + struct ck_constraint_def *ck_def; + /** Organize these structs into linked list. */ + struct rlist link; +}; + /** * Possible SQL index types. Note that PK and UNIQUE constraints * are implemented as indexes and have their own types: @@ -189,6 +205,13 @@ struct create_table_def { * Foreign key constraint appeared in CREATE TABLE stmt. */ struct rlist new_fkey; + /** + * Number of CK constraints declared within + * CREATE TABLE statement. + */ + uint32_t check_count; + /** Check constraint appeared in CREATE TABLE stmt. */ + struct rlist new_check; /** True, if table to be created has AUTOINCREMENT PK. */ bool has_autoinc; }; @@ -437,6 +460,7 @@ create_table_def_init(struct create_table_def *table_def, struct Token *name, create_entity_def_init(&table_def->base, ENTITY_TYPE_TABLE, NULL, name, if_not_exists); rlist_create(&table_def->new_fkey); + rlist_create(&table_def->new_check); } static inline void diff --git a/src/box/sql/resolve.c b/src/box/sql/resolve.c index 504096e6d..ac85b85a9 100644 --- a/src/box/sql/resolve.c +++ b/src/box/sql/resolve.c @@ -1610,8 +1610,7 @@ sqlResolveSelectNames(Parse * pParse, /* The parser context */ void sql_resolve_self_reference(struct Parse *parser, struct space_def *def, - int type, struct Expr *expr, - struct ExprList *expr_list) + int type, struct Expr *expr) { /* Fake SrcList for parser->create_table_def */ SrcList sSrc; @@ -1631,8 +1630,5 @@ sql_resolve_self_reference(struct Parse *parser, struct space_def *def, sNC.pParse = parser; sNC.pSrcList = &sSrc; sNC.ncFlags = type; - if (sqlResolveExprNames(&sNC, expr) != 0) - return; - if (expr_list != NULL) - sqlResolveExprListNames(&sNC, expr_list); + sqlResolveExprNames(&sNC, expr); } diff --git a/src/box/sql/select.c b/src/box/sql/select.c index d7376ae11..22aa62830 100644 --- a/src/box/sql/select.c +++ b/src/box/sql/select.c @@ -6440,7 +6440,12 @@ 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); + /* + * Extract a copy of parsed expression. + * We cannot use EXPRDUP_REDUCE flag in sqlExprDup call + * because some compiled Expr (like Checks expressions) + * may require further resolve with sqlResolveExprNames. + */ + 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 7212553f6..abb7b9c2a 100644 --- a/src/box/sql/sqlInt.h +++ b/src/box/sql/sqlInt.h @@ -3359,7 +3359,7 @@ sqlAddPrimaryKey(struct Parse *parse); * @param parser Parsing context. */ void -sql_add_check_constraint(Parse *parser); +sql_create_check_contraint(Parse *parser); void sqlAddDefaultValue(Parse *, ExprSpan *); void sqlAddCollateType(Parse *, Token *); diff --git a/src/box/sql/tokenize.c b/src/box/sql/tokenize.c index 11ef7b97e..dbaebfefc 100644 --- a/src/box/sql/tokenize.c +++ b/src/box/sql/tokenize.c @@ -435,7 +435,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/test/app-tap/tarantoolctl.test.lua b/test/app-tap/tarantoolctl.test.lua index c1e1490ca..4777d8a6f 100755 --- a/test/app-tap/tarantoolctl.test.lua +++ b/test/app-tap/tarantoolctl.test.lua @@ -405,8 +405,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", 21) - check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 47) + check_ctlcat_snap(test_i, dir, "--space=280", "---\n", 22) + check_ctlcat_snap(test_i, dir, "--space=288", "---\n", 48) end) end) diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result index 0684914c0..21fcbea7e 100644 --- a/test/box-py/bootstrap.result +++ b/test/box-py/bootstrap.result @@ -79,6 +79,9 @@ 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'}]] + - [364, 1, '_ck_constraint', 'memtx', 0, {}, [{'name': 'space_id', 'type': 'unsigned'}, + {'name': 'name', 'type': 'string'}, {'name': 'is_deferred', 'type': 'boolean'}, + {'name': 'language', 'type': 'str'}, {'name': 'code', 'type': 'str'}]] ... box.space._index:select{} --- @@ -131,6 +134,7 @@ box.space._index:select{} - [340, 1, 'sequence', 'tree', {'unique': false}, [[1, 'unsigned']]] - [356, 0, 'primary', 'tree', {'unique': true}, [[0, 'string'], [1, 'unsigned']]] - [356, 1, 'child_id', 'tree', {'unique': false}, [[1, 'unsigned']]] + - [364, 0, 'primary', 'tree', {'unique': true}, [[0, 'unsigned'], [1, 'string']]] ... box.space._user:select{} --- diff --git a/test/box/access.result b/test/box/access.result index 44a74e17c..0ebc6a350 100644 --- a/test/box/access.result +++ b/test/box/access.result @@ -1493,6 +1493,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 ee408f53e..ad63a1016 100644 --- a/test/box/access.test.lua +++ b/test/box/access.test.lua @@ -556,6 +556,7 @@ box.schema.user.grant('tester', 'read', 'space', '_sequence') box.schema.user.grant('tester', 'read', 'space', '_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 24bdd9d63..b8ddcb53b 100644 --- a/test/box/access_misc.result +++ b/test/box/access_misc.result @@ -819,6 +819,9 @@ 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'}]] + - [364, 1, '_ck_constraint', 'memtx', 0, {}, [{'name': 'space_id', 'type': 'unsigned'}, + {'name': 'name', 'type': 'string'}, {'name': 'is_deferred', 'type': 'boolean'}, + {'name': 'language', 'type': 'str'}, {'name': 'code', 'type': 'str'}]] ... box.space._func:select() --- diff --git a/test/box/access_sysview.result b/test/box/access_sysview.result index c6a2b22ed..65a9da8a5 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{} --- -- 22 +- 23 ... #box.space._vindex:select{} --- -- 48 +- 49 ... #box.space._vuser:select{} --- @@ -262,7 +262,7 @@ box.session.su('guest') ... #box.space._vindex:select{} --- -- 48 +- 49 ... #box.space._vuser:select{} --- diff --git a/test/box/alter.result b/test/box/alter.result index e83c0b7ef..87d7290e1 100644 --- a/test/box/alter.result +++ b/test/box/alter.result @@ -92,7 +92,7 @@ space = box.space[t[1]] ... space.id --- -- 357 +- 358 ... space.field_count --- @@ -137,7 +137,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'}}} --- @@ -215,6 +215,7 @@ _index:select{} - [340, 1, 'sequence', 'tree', {'unique': false}, [[1, 'unsigned']]] - [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, 'unsigned'], [1, 'string']]] ... -- modify indexes of a system space _index:delete{_index.id, 0} diff --git a/test/box/misc.result b/test/box/misc.result index 4fcd13a78..5bf419d4f 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -523,6 +523,7 @@ t; 192: box.error.INDEX_DEF_UNSUPPORTED 193: box.error.CK_DEF_UNSUPPORTED 194: box.error.MULTIKEY_INDEX_MISMATCH + 195: box.error.CREATE_CK_CONSTRAINT ... test_run:cmd("setopt delimiter ''"); --- diff --git a/test/sql-tap/check.test.lua b/test/sql-tap/check.test.lua index b01afca7c..ede77c630 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, "Failed to execute SQL statement: CHECK constraint failed: T1" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T1" -- }) @@ -75,7 +75,7 @@ test:do_catchsql_test( INSERT INTO t1 VALUES(4,3, 2); ]], { -- - 1, "Failed to execute SQL statement: CHECK constraint failed: T1" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_2_T1" -- }) @@ -147,7 +147,7 @@ test:do_catchsql_test( UPDATE t1 SET x=7 WHERE x==2 ]], { -- - 1, "Failed to execute SQL statement: CHECK constraint failed: T1" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T1" -- }) @@ -167,7 +167,7 @@ test:do_catchsql_test( UPDATE t1 SET x=5 WHERE x==2 ]], { -- - 1, "Failed to execute SQL statement: CHECK constraint failed: T1" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T1" -- }) @@ -319,7 +319,7 @@ test:do_catchsql_test( ); ]], { -- - 1, "Failed to create space 'T3': Subqueries are prohibited in a CHECK constraint definition" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': Subqueries are prohibited in a ck constraint definition" -- }) @@ -344,7 +344,7 @@ test:do_catchsql_test( ); ]], { -- - 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'" -- }) @@ -368,7 +368,7 @@ test:do_catchsql_test( ); ]], { -- - 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" -- }) @@ -413,7 +413,7 @@ test:do_catchsql_test( INSERT INTO t3 VALUES(111,222,333); ]], { -- - 1, "Failed to execute SQL statement: CHECK constraint failed: T3" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T3" -- }) @@ -484,7 +484,7 @@ test:do_catchsql_test( UPDATE t4 SET x=0, y=1; ]], { -- - 1, "Failed to execute SQL statement: CHECK constraint failed: T4" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T4" -- }) @@ -504,7 +504,7 @@ test:do_catchsql_test( UPDATE t4 SET x=0, y=2; ]], { -- - 1, "Failed to execute SQL statement: CHECK constraint failed: T4" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T4" -- }) @@ -516,7 +516,7 @@ test:do_catchsql_test( ); ]], { -- - 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" -- }) @@ -528,7 +528,7 @@ test:do_catchsql_test( ); ]], { -- - 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" -- }) @@ -581,7 +581,7 @@ test:do_catchsql_test( UPDATE OR FAIL t1 SET x=7-x, y=y+1; ]], { -- - 1, "Failed to execute SQL statement: CHECK constraint failed: T1" + 1, "Failed to execute SQL statement: 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, "Failed to execute SQL statement: CHECK constraint failed: T1" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T1" -- }) @@ -636,7 +636,7 @@ test:do_catchsql_test( REPLACE INTO t1 VALUES(6,7, 11); ]], { -- - 1, "Failed to execute SQL statement: CHECK constraint failed: T1" + 1, "Failed to execute SQL statement: 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, "Failed to execute SQL statement: CHECK constraint failed: T6" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T6" -- }) diff --git a/test/sql-tap/fkey2.test.lua b/test/sql-tap/fkey2.test.lua index 54e5059b3..695a379a6 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, "Failed to execute SQL statement: CHECK constraint failed: EF" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_EF" -- }) @@ -382,7 +382,7 @@ test:do_catchsql_test( UPDATE ab SET a = 5; ]], { -- - 1, "Failed to execute SQL statement: CHECK constraint failed: EF" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_EF" -- }) diff --git a/test/sql-tap/sql-errors.test.lua b/test/sql-tap/sql-errors.test.lua index 9357c406b..a8d39472d 100755 --- a/test/sql-tap/sql-errors.test.lua +++ b/test/sql-tap/sql-errors.test.lua @@ -313,7 +313,7 @@ test:do_catchsql_test( CREATE TABLE t27 (i INT PRIMARY KEY, CHECK(i < (SELECT * FROM t0))); ]], { -- - 1,"Failed to create space 'T27': Subqueries are prohibited in a CHECK constraint definition" + 1,"Failed to create check constraint 'CK_CONSTRAINT_1_T27': Subqueries are prohibited in a ck constraint definition" -- }) diff --git a/test/sql-tap/table.test.lua b/test/sql-tap/table.test.lua index 5b793c0fc..066662f33 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); ]], { -- - 1, "Failed to execute SQL statement: CHECK constraint failed: T21" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T21" -- }) @@ -1231,7 +1231,7 @@ test:do_catchsql_test( INSERT INTO T21 VALUES(1, 1, -1); ]], { -- - 1, "Failed to execute SQL statement: CHECK constraint failed: T21" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_2_T21" -- }) diff --git a/test/sql/checks.result b/test/sql/checks.result index f7cddec43..efe626428 100644 --- a/test/sql/checks.result +++ b/test/sql/checks.result @@ -18,8 +18,10 @@ box.execute('pragma sql_default_engine=\''..engine..'\'') -- -- gh-3272: Move SQL CHECK into server -- --- invalid expression -opts = {checks = {{expr = 'X><5'}}} +-- Until Tarantool version 2.2 check constraints were stored in +-- space opts. +-- Make sure that now this legacy option is ignored. +opts = {checks = {{expr = 'X>5'}}} --- ... format = {{name = 'X', type = 'unsigned'}} @@ -30,89 +32,219 @@ 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') --- ... -format = {{name = 'X', type = 'unsigned'}} +-- Invalid expression test. +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X><5'}) --- +- error: 'Failed to create check constraint ''CK_CONSTRAINT_01'': Syntax error near + ''<''' ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +-- Non-existent space test. +box.space._ck_constraint:insert({550, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) --- +- error: Space '550' does not exist ... -s = box.space._space:insert(t) +-- Pass integer instead of expression. +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 666}) +--- +- error: 'Tuple field 5 type does not match one required by operation: expected string' +... +-- Deferred CK constraints are not supported. +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', true, 'SQL', 'X<5'}) +--- +- error: Tarantool does not support deferred ck constraints +... +-- The only supported language is SQL. +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'LUA', 'X<5'}) +--- +- error: Unsupported language 'LUA' specified for function 'CK_CONSTRAINT_01' +... +-- Check constraints LUA creation test. +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +--- +- [513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'] +... +box.space._ck_constraint:count({}) +--- +- 1 +... +box.execute("INSERT INTO \"test\" VALUES(5);") +--- +- error: 'Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_01' +... +box.space._ck_constraint:replace({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'}) +--- +- [513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'] +... +box.execute("INSERT INTO \"test\" VALUES(5);") +--- +- row_count: 1 +... +box.execute("INSERT INTO \"test\" VALUES(6);") --- +- error: 'Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_01' ... -box.space._space:delete(513) +-- Can't drop table with check constraints. +box.space.test:delete({5}) +--- +- [5] +... +box.space.test.index.pk:drop() +--- +... +box.space._space:delete({513}) +--- +- error: 'Can''t drop space ''test'': the space has check constraints' +... +box.space._ck_constraint:delete({513, 'CK_CONSTRAINT_01'}) +--- +- [513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'] +... +box.space._space:delete({513}) --- - [513, 1, 'test', 'memtx', 0, {'checks': [{'expr': 'X>5'}]}, [{'name': 'X', 'type': 'unsigned'}]] ... -opts = {checks = {{expr = 'X>5', name = 'ONE'}}} +-- Create table with checks in sql. +box.execute("CREATE TABLE t1(x INTEGER CONSTRAINT ONE CHECK( x<5 ), y REAL CONSTRAINT TWO CHECK( y>x ), z INTEGER PRIMARY KEY);") --- +- row_count: 1 ... -format = {{name = 'X', type = 'unsigned'}} +box.space._ck_constraint:count() --- +- 2 ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +box.execute("INSERT INTO t1 VALUES (7, 1, 1)") --- +- error: 'Failed to execute SQL statement: CHECK constraint failed: ONE' ... -s = box.space._space:insert(t) +box.execute("INSERT INTO t1 VALUES (2, 1, 1)") --- +- error: 'Failed to execute SQL statement: CHECK constraint failed: TWO' ... -box.space._space:delete(513) +box.execute("INSERT INTO t1 VALUES (2, 4, 1)") --- -- [513, 1, 'test', 'memtx', 0, {'checks': [{'name': 'ONE', 'expr': 'X>5'}]}, [{'name': 'X', - 'type': 'unsigned'}]] +- row_count: 1 ... --- extra invlalid field name -opts = {checks = {{expr = 'X>5', name = 'ONE', extra = 'TWO'}}} +box.execute("DROP TABLE t1") --- +- row_count: 1 ... -format = {{name = 'X', type = 'unsigned'}} +-- Test space creation rollback on spell error in ck constraint. +box.execute("CREATE TABLE first (id FLOAT PRIMARY KEY CHECK(id < 5), a INT CONSTRAINT ONE CHECK(a >< 5));") --- +- error: Syntax error near '<' ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +box.space.FIRST == nil --- +- true ... -s = box.space._space:insert(t) +box.space._ck_constraint:count() == 0 --- -- error: 'Wrong space options (field 5): invalid MsgPack map field ''extra''' +- true ... -opts = {checks = {{expr_invalid_label = 'X>5'}}} +-- Ck constraints are disallowed for spaces having no format. +s = box.schema.create_space('test', {engine = engine}) --- ... -format = {{name = 'X', type = 'unsigned'}} +_ = s:create_index('pk') --- ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +_ = box.space._ck_constraint:insert({s.id, 'physics', false, 'SQL', 'X10'}) +--- +- error: 'Failed to create check constraint ''conflict'': referencing space must be + empty' +... +s:truncate() +--- +... +_ = box.space._ck_constraint:insert({s.id, 'conflict', false, 'SQL', 'X>10'}) --- -- error: 'Wrong space options (field 5): invalid MsgPack map field ''name'' type' +... +box.execute("INSERT INTO \"test\" VALUES(1, 2);") +--- +- error: 'Failed to execute SQL statement: CHECK constraint failed: conflict' +... +box.execute("INSERT INTO \"test\" VALUES(11, 11);") +--- +- error: 'Failed to execute SQL statement: CHECK constraint failed: physics' +... +box.execute("INSERT INTO \"test\" VALUES(12, 11);") +--- +- row_count: 1 +... +s:drop() +--- +... +box.execute("CREATE TABLE T2(ID INT PRIMARY KEY, CONSTRAINT CK1 CHECK(ID > 0), CONSTRAINT CK1 CHECK(ID < 0))") +--- +- error: Constraint CK1 already exists +... +box.space.T2:drop() +--- +... +box.space._ck_constraint:select() +--- +- [] ... -- -- gh-3611: Segfault on table creation with check referencing this table -- box.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 are + prohibited in a ck constraint definition' ... box.execute("DROP TABLE w2;") --- @@ -123,22 +255,33 @@ box.execute("DROP TABLE w2;") -- box.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)' +- error: 'Failed to create check constraint ''CK_CONSTRAINT_1_T5'': bindings are not + allowed in DDL' ... -opts = {checks = {{expr = '?>5', name = 'ONE'}}} +-- Test trim CK constraint code correctness. +box.execute("CREATE TABLE t1(x TEXT PRIMARY KEY CHECK(x LIKE '1 a'))") --- +- row_count: 1 ... -format = {{name = 'X', type = 'unsigned'}} +box.space._ck_constraint:select()[1].code --- +- x LIKE '1 a' ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +box.execute("INSERT INTO t1 VALUES('1 a')") --- +- error: 'Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T1' ... -s = box.space._space:insert(t) +box.execute("INSERT INTO t1 VALUES('1 a')") +--- +- error: 'Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T1' +... +box.execute("INSERT INTO t1 VALUES('1 a')") +--- +- row_count: 1 +... +box.execute("DROP TABLE t1") --- -- error: 'Wrong space options (field 5): invalid expression specified (bindings are - not allowed in DDL)' +- row_count: 1 ... test_run:cmd("clear filter") --- diff --git a/test/sql/checks.test.lua b/test/sql/checks.test.lua index 5bfcf12f8..399caf134 100644 --- a/test/sql/checks.test.lua +++ b/test/sql/checks.test.lua @@ -8,41 +8,81 @@ box.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) - +-- Until Tarantool version 2.2 check constraints were stored in +-- space opts. +-- Make sure that now this legacy option is ignored. 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({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X><5'}) +-- Non-existent space test. +box.space._ck_constraint:insert({550, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +-- Pass integer instead of expression. +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 666}) +-- Deferred CK constraints are not supported. +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', true, 'SQL', 'X<5'}) +-- The only supported language is SQL. +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'LUA', 'X<5'}) --- 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) +-- Check constraints LUA creation test. +box.space._ck_constraint:insert({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +box.space._ck_constraint:count({}) -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) +box.execute("INSERT INTO \"test\" VALUES(5);") +box.space._ck_constraint:replace({513, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'}) +box.execute("INSERT INTO \"test\" VALUES(5);") +box.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({513, 'CK_CONSTRAINT_01'}) +box.space._space:delete({513}) --- 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) +-- Create table with checks in sql. +box.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.execute("INSERT INTO t1 VALUES (7, 1, 1)") +box.execute("INSERT INTO t1 VALUES (2, 1, 1)") +box.execute("INSERT INTO t1 VALUES (2, 4, 1)") +box.execute("DROP TABLE t1") +-- Test space creation rollback on spell error in ck constraint. +box.execute("CREATE TABLE first (id FLOAT PRIMARY KEY CHECK(id < 5), a INT CONSTRAINT ONE CHECK(a >< 5));") +box.space.FIRST == nil +box.space._ck_constraint:count() == 0 + +-- Ck constraints are disallowed for spaces having no format. +s = box.schema.create_space('test', {engine = engine}) +_ = s:create_index('pk') +_ = box.space._ck_constraint:insert({s.id, 'physics', false, 'SQL', 'X10'}) +s:truncate() +_ = box.space._ck_constraint:insert({s.id, 'conflict', false, 'SQL', 'X>10'}) +box.execute("INSERT INTO \"test\" VALUES(1, 2);") +box.execute("INSERT INTO \"test\" VALUES(11, 11);") +box.execute("INSERT INTO \"test\" VALUES(12, 11);") +s:drop() +box.execute("CREATE TABLE T2(ID INT PRIMARY KEY, CONSTRAINT CK1 CHECK(ID > 0), CONSTRAINT CK1 CHECK(ID < 0))") +box.space.T2:drop() +box.space._ck_constraint:select() -- -- gh-3611: Segfault on table creation with check referencing this table @@ -55,10 +95,12 @@ box.execute("DROP TABLE w2;") -- box.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 trim CK constraint code correctness. +box.execute("CREATE TABLE t1(x TEXT PRIMARY KEY CHECK(x LIKE '1 a'))") +box.space._ck_constraint:select()[1].code +box.execute("INSERT INTO t1 VALUES('1 a')") +box.execute("INSERT INTO t1 VALUES('1 a')") +box.execute("INSERT INTO t1 VALUES('1 a')") +box.execute("DROP TABLE t1") test_run:cmd("clear filter") diff --git a/test/sql/errinj.result b/test/sql/errinj.result index ee36a387b..414e3c476 100644 --- a/test/sql/errinj.result +++ b/test/sql/errinj.result @@ -463,3 +463,137 @@ errinj.set("ERRINJ_SQL_NAME_NORMALIZATION", false) --- - ok ... +-- +-- Tests which are aimed at verifying work of commit/rollback +-- triggers on _ck_constraint space. +-- +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({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +--- +- error: Failed to write to disk +... +errinj.set("ERRINJ_WAL_IO", false) +--- +- ok +... +_ = box.space._ck_constraint:insert({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +--- +... +box.execute("INSERT INTO \"test\" VALUES(5);") +--- +- error: 'Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_01' +... +errinj.set("ERRINJ_WAL_IO", true) +--- +- ok +... +_ = box.space._ck_constraint:replace({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'}) +--- +- error: Failed to write to disk +... +errinj.set("ERRINJ_WAL_IO", false) +--- +- ok +... +_ = box.space._ck_constraint:replace({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'}) +--- +... +box.execute("INSERT INTO \"test\" VALUES(5);") +--- +- row_count: 1 +... +errinj.set("ERRINJ_WAL_IO", true) +--- +- ok +... +_ = box.space._ck_constraint:delete({s.id, 'CK_CONSTRAINT_01'}) +--- +- error: Failed to write to disk +... +box.execute("INSERT INTO \"test\" VALUES(6);") +--- +- error: 'Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_01' +... +errinj.set("ERRINJ_WAL_IO", false) +--- +- ok +... +_ = box.space._ck_constraint:delete({s.id, 'CK_CONSTRAINT_01'}) +--- +... +box.execute("INSERT INTO \"test\" VALUES(6);") +--- +- row_count: 1 +... +s:drop() +--- +... +-- +-- Test that failed space alter doesn't harm ck constraints +-- +s = box.schema.create_space('test') +--- +... +_ = s:create_index('pk') +--- +... +s:format({{name='X', type='integer'}, {name='Y', type='integer'}}) +--- +... +_ = box.space._ck_constraint:insert({s.id, 'XlessY', false, 'SQL', 'X < Y'}) +--- +... +_ = box.space._ck_constraint:insert({s.id, 'Xgreater10', false, 'SQL', 'X > 10'}) +--- +... +box.execute("INSERT INTO \"test\" VALUES(1, 2);") +--- +- error: 'Failed to execute SQL statement: CHECK constraint failed: Xgreater10' +... +box.execute("INSERT INTO \"test\" VALUES(20, 10);") +--- +- error: 'Failed to execute SQL statement: CHECK constraint failed: XlessY' +... +box.execute("INSERT INTO \"test\" VALUES(20, 100);") +--- +- row_count: 1 +... +s:truncate() +--- +... +errinj.set("ERRINJ_WAL_IO", true) +--- +- ok +... +s:format({{name='Y', type='integer'}, {name='X', type='integer'}}) +--- +- error: Failed to write to disk +... +errinj.set("ERRINJ_WAL_IO", false) +--- +- ok +... +box.execute("INSERT INTO \"test\" VALUES(1, 2);") +--- +- error: 'Failed to execute SQL statement: CHECK constraint failed: Xgreater10' +... +box.execute("INSERT INTO \"test\" VALUES(20, 10);") +--- +- error: 'Failed to execute SQL statement: CHECK constraint failed: XlessY' +... +box.execute("INSERT INTO \"test\" VALUES(20, 100);") +--- +- row_count: 1 +... +s:drop() +--- +... diff --git a/test/sql/errinj.test.lua b/test/sql/errinj.test.lua index 1aff6d77e..48b80a443 100644 --- a/test/sql/errinj.test.lua +++ b/test/sql/errinj.test.lua @@ -149,3 +149,48 @@ box.execute("CREATE TABLE hello (id INT primary key,x INT,y INT);") dummy_f = function(int) return 1 end box.internal.sql_create_function("counter1", "INT", dummy_f, -1, false) errinj.set("ERRINJ_SQL_NAME_NORMALIZATION", false) + +-- +-- Tests which are aimed at verifying work of commit/rollback +-- triggers on _ck_constraint space. +-- +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({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +errinj.set("ERRINJ_WAL_IO", false) +_ = box.space._ck_constraint:insert({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<5'}) +box.execute("INSERT INTO \"test\" VALUES(5);") +errinj.set("ERRINJ_WAL_IO", true) +_ = box.space._ck_constraint:replace({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'}) +errinj.set("ERRINJ_WAL_IO", false) +_ = box.space._ck_constraint:replace({s.id, 'CK_CONSTRAINT_01', false, 'SQL', 'X<=5'}) +box.execute("INSERT INTO \"test\" VALUES(5);") +errinj.set("ERRINJ_WAL_IO", true) +_ = box.space._ck_constraint:delete({s.id, 'CK_CONSTRAINT_01'}) +box.execute("INSERT INTO \"test\" VALUES(6);") +errinj.set("ERRINJ_WAL_IO", false) +_ = box.space._ck_constraint:delete({s.id, 'CK_CONSTRAINT_01'}) +box.execute("INSERT INTO \"test\" VALUES(6);") +s:drop() + +-- +-- Test that failed space alter doesn't harm ck constraints +-- +s = box.schema.create_space('test') +_ = s:create_index('pk') +s:format({{name='X', type='integer'}, {name='Y', type='integer'}}) +_ = box.space._ck_constraint:insert({s.id, 'XlessY', false, 'SQL', 'X < Y'}) +_ = box.space._ck_constraint:insert({s.id, 'Xgreater10', false, 'SQL', 'X > 10'}) +box.execute("INSERT INTO \"test\" VALUES(1, 2);") +box.execute("INSERT INTO \"test\" VALUES(20, 10);") +box.execute("INSERT INTO \"test\" VALUES(20, 100);") +s:truncate() +errinj.set("ERRINJ_WAL_IO", true) +s:format({{name='Y', type='integer'}, {name='X', type='integer'}}) +errinj.set("ERRINJ_WAL_IO", false) +box.execute("INSERT INTO \"test\" VALUES(1, 2);") +box.execute("INSERT INTO \"test\" VALUES(20, 10);") +box.execute("INSERT INTO \"test\" VALUES(20, 100);") +s:drop() diff --git a/test/sql/gh-2981-check-autoinc.result b/test/sql/gh-2981-check-autoinc.result index 9e347ca6b..7384c81e8 100644 --- a/test/sql/gh-2981-check-autoinc.result +++ b/test/sql/gh-2981-check-autoinc.result @@ -29,7 +29,7 @@ box.execute("insert into t1 values (18, null);") ... box.execute("insert into t1(s2) values (null);") --- -- error: 'Failed to execute SQL statement: CHECK constraint failed: T1' +- error: 'Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T1' ... box.execute("insert into t2 values (18, null);") --- @@ -37,7 +37,7 @@ box.execute("insert into t2 values (18, null);") ... box.execute("insert into t2(s2) values (null);") --- -- error: 'Failed to execute SQL statement: CHECK constraint failed: T2' +- error: 'Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T2' ... box.execute("insert into t2 values (24, null);") --- @@ -45,7 +45,7 @@ box.execute("insert into t2 values (24, null);") ... box.execute("insert into t2(s2) values (null);") --- -- error: 'Failed to execute SQL statement: CHECK constraint failed: T2' +- error: 'Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T2' ... box.execute("insert into t3 values (9, null)") --- @@ -53,7 +53,7 @@ box.execute("insert into t3 values (9, null)") ... box.execute("insert into t3(s2) values (null)") --- -- error: 'Failed to execute SQL statement: CHECK constraint failed: T3' +- error: 'Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T3' ... box.execute("DROP TABLE t1") --- diff --git a/test/sql/types.result b/test/sql/types.result index a53d6f7ce..1c9ef5468 100644 --- a/test/sql/types.result +++ b/test/sql/types.result @@ -741,7 +741,7 @@ box.execute("CREATE TABLE t1 (id INT PRIMARY KEY, a BOOLEAN CHECK (a = true));") ... box.execute("INSERT INTO t1 VALUES (1, false);") --- -- error: 'Failed to execute SQL statement: CHECK constraint failed: T1' +- error: 'Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T1' ... box.execute("INSERT INTO t1 VALUES (1, true);") --- diff --git a/test/sql/upgrade.result b/test/sql/upgrade.result index 3a55f7c53..f0997e17f 100644 --- a/test/sql/upgrade.result +++ b/test/sql/upgrade.result @@ -188,6 +188,25 @@ i[1].opts.sql == nil --- - true ... +box.space._space:get(s.id).flags.checks == nil +--- +- true +... +check = box.space._ck_constraint:select()[1] +--- +... +check ~= nil +--- +- true +... +check.name +--- +- CK_CONSTRAINT_1_T5 +... +check.code +--- +- x < 2 +... s:drop() --- ... diff --git a/test/sql/upgrade.test.lua b/test/sql/upgrade.test.lua index b76a8f373..37425ae21 100644 --- a/test/sql/upgrade.test.lua +++ b/test/sql/upgrade.test.lua @@ -62,6 +62,11 @@ s ~= nil i = box.space._index:select(s.id) i ~= nil i[1].opts.sql == nil +box.space._space:get(s.id).flags.checks == nil +check = box.space._ck_constraint:select()[1] +check ~= nil +check.name +check.code s:drop() test_run:switch('default') diff --git a/test/wal_off/alter.result b/test/wal_off/alter.result index ee280fcbb..8040efa1a 100644 --- a/test/wal_off/alter.result +++ b/test/wal_off/alter.result @@ -28,7 +28,7 @@ end; ... #spaces; --- -- 65505 +- 65504 ... -- cleanup for k, v in pairs(spaces) do -- 2.21.0