diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 0864c3433c1996a39489b4f57d4f0aec4980860d..481842a397e97a96e48257534aa004f2000dd1bf 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 ed9e55907530052c5f75a03b74a7ae8aeb7449e3..f72a2ed251df59e0477d954d34786e609b04ac50 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,167 @@ 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([=] { + ck_constraint_def_delete(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 +4388,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 4108fa47ccbe1699f82979bd8ebd4a4e0d461622..b9ba7b846e2e14d05d6c700e97ac9eb3a4584120 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 Binary files a/src/box/bootstrap.snap and b/src/box/bootstrap.snap differ diff --git a/src/box/ck_constraint.c b/src/box/ck_constraint.c new file mode 100644 index 0000000000000000000000000000000000000000..e25f4506e976a78de93bc44439f8b3affcb53603 --- /dev/null +++ b/src/box/ck_constraint.c @@ -0,0 +1,146 @@ +/* + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "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 expr_str_offset; + uint32_t ck_def_sz = ck_constraint_def_sizeof(name_len, expr_str_len, + &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->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 0000000000000000000000000000000000000000..a5b7b2c71a66f78313676a0645bc7e53706ea32b --- /dev/null +++ b/src/box/ck_constraint.h @@ -0,0 +1,203 @@ +#ifndef INCLUDES_BOX_CK_CONSTRAINT_H +#define INCLUDES_BOX_CK_CONSTRAINT_H +/* + * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <stdint.h> +#include "small/rlist.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +struct space; +struct space_def; +struct Expr; + +/** 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 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; + /** + * The 0-terminated string, a name of the check + * constraint. Must be unique for a given space. + */ + char name[0]; +}; + +/** + * 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] 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 *expr_str_offset) +{ + *expr_str_offset = sizeof(struct ck_constraint_def) + 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 9c15f3322560c4a9e5b3eff794ce3127d405a340..1f7c81693b2e921207c420fdf400f26fa8591e75 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 39bd8da6d1017b711f0fca5b9eb18c4f4e464723..91fae8378bbb424dfd4b6978c434d0bd8ba8a0cc 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 87adaeb163353a644f43c50253d202b84cb1d9a6..509306eaeecc30cf73a89d3be4c9497aeb08c8e6 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 07066269870fc9b83c462c7061010ceba5bab9ab..4ff2efd3a2d4a2bd1a95884f44726784bfae6bbc 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 9a55c2f1438afa516ea8b5735ad43132f513d491..6d3153815c523f4d7ff6f73665c4d6f476337a13 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 b817b49f65872ce8d49b33fafa5a28a5c8d8a07c..3648639bc773a85e0530fcfc0cca1039fa53d2cb 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 243e7da2fcb653effd1058de38ab75e706c5c130..a42b3a64b357aaba7eb4b9b9b45068629855fb86 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 be1178b99292e4343c5ef15a94875370d8409c30..7ffe884b32f9507f5839ebc47ca604701644e039 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 d825def75e0ae8eb86dd054f0da9008bb422d442..661131295a9ad23e55f51eecbf83dce46e2226f0 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 c6639a8d8c1ac51ab00ea562efb2570e4691631a..ac6d226c5b2b36151964f52356bc0930800708cb 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 f7b90ab5ce3a38697c571ae8b60ee8b20635b1d8..3d88ccbb4e3b39290161933b2d81312e93a6b563 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 vdbe_field_ref instance with given tuple * data. diff --git a/src/box/sql.h b/src/box/sql.h index 3056d8f7ecc59cdd150755805e5bcc6067042f4f..9ccecf28c244e9957793fe08eae68e7a69c57013 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 e2353d8cc159e6d44692b8a1fea1949794ab0a09..b28e7b66f50f7ce012f8f35a1f8773b64c2c4c95 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -43,10 +43,12 @@ * COMMIT * ROLLBACK */ +#include <ctype.h> #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,114 @@ sqlAddPrimaryKey(struct Parse *pParse) 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 string 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 expr_str_offset; + uint32_t ck_def_sz = ck_constraint_def_sizeof(name_len, expr_str_len, + &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->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 +1054,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 +1247,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 +1348,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 +1551,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 +1655,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 +2231,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 +2947,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 81feca7e05f8ff8b0f6ed6a7895a208463d7d132..c4b629c6c16dcb6fcef750016e64be9483aa588e 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 f241b8d522cdb0a0b973822dc2e2eebbd25b20ac..e93dfe7518b1dc7b8ae40344f0a13d65c1a1877f 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 5899a7e4e4062519d6f622d88d584a2a5b1a403a..6c1b6fddd4e70e5dbca8ff434485a5609ffb10a7 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 <CREATE TABLE ...> + * 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 504096e6dd8ca0ca42ef072994e92739ffcad409..ac85b85a95fa05f2d23a584e564d095d04bdef81 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 d7376ae1135756c7c6be3c2852a4633e07211db9..22aa628308f85c1ef21693befd65fb65a7084216 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 7212553f612901f7724b56781badf870df84b6f3..abb7b9c2a5ef84e6a1af2a058b7bc20a428b2ba4 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 11ef7b97edde07385b9f0a664ea99dad6ee05667..dbaebfefca713386895281dc373d0b016dafaf6c 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 c1e1490ca1b3c2a430b6d77336eb8afa7ed80007..4777d8a6f567e8b55c2a3d8adf91d91d5e2fd34c 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 0684914c01d95487d94ac536fc527da57a18ab22..21fcbea7e7321d0ec35bc494d1eb5401fa39c84a 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 44a74e17cbe9d6faed7e3b14e79d5cbcc0b956e9..0ebc6a350e9450c4682d15dc815d3e3dea9d1c87 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 ee408f53ef3ff76024293ae4f2f20a8e2fcb57e8..ad63a1016f169bd5111807a9f7a17ef5f5203e84 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 24bdd9d63ebedf60112d0819a894856167e568cd..b8ddcb53b14d644dd1877490e664c1c95cd7a69a 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 c6a2b22ed272e0f9839706967edc9e4121b31fab..65a9da8a5901f6ba0bbd6c27d2aafff2101d6309 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 e83c0b7effa1a2e39944f705531071232bf6be91..97dfe7c82e78e01fece6fbf3c03fb7e91d4f29fb 100644 --- a/test/box/alter.result +++ b/test/box/alter.result @@ -92,7 +92,7 @@ space = box.space[t[1]] ... space.id --- -- 357 +- 365 ... space.field_count --- @@ -137,7 +137,7 @@ space_deleted ... space:replace{0} --- -- error: Space '357' does not exist +- error: Space '365' 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']]] + - [364, 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 4fcd13a78bec449f3f0aa889cecd8c2708ef5763..5bf419d4f1de55cff0a869c83145cd1e1dee3d50 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 b01afca7ca1b530a5b43490cd2b5d000bb50f7a1..ede77c63055caae570d9fbf725d3d4065eaaee68 100755 --- a/test/sql-tap/check.test.lua +++ b/test/sql-tap/check.test.lua @@ -55,7 +55,7 @@ test:do_catchsql_test( INSERT INTO t1 VALUES(6,7, 2); ]], { -- <check-1.3> - 1, "Failed to execute SQL statement: CHECK constraint failed: T1" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T1" -- </check-1.3> }) @@ -75,7 +75,7 @@ test:do_catchsql_test( INSERT INTO t1 VALUES(4,3, 2); ]], { -- <check-1.5> - 1, "Failed to execute SQL statement: CHECK constraint failed: T1" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_2_T1" -- </check-1.5> }) @@ -147,7 +147,7 @@ test:do_catchsql_test( UPDATE t1 SET x=7 WHERE x==2 ]], { -- <check-1.12> - 1, "Failed to execute SQL statement: CHECK constraint failed: T1" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T1" -- </check-1.12> }) @@ -167,7 +167,7 @@ test:do_catchsql_test( UPDATE t1 SET x=5 WHERE x==2 ]], { -- <check-1.14> - 1, "Failed to execute SQL statement: CHECK constraint failed: T1" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T1" -- </check-1.14> }) @@ -319,7 +319,7 @@ test:do_catchsql_test( ); ]], { -- <check-3.1> - 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" -- </check-3.1> }) @@ -344,7 +344,7 @@ test:do_catchsql_test( ); ]], { -- <check-3.3> - 1, "Failed to create space 'T3': Can’t resolve field 'Q'" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': Can’t resolve field 'Q'" -- </check-3.3> }) @@ -368,7 +368,7 @@ test:do_catchsql_test( ); ]], { -- <check-3.5> - 1, "Failed to create space 'T3': Field 'X' was not found in the space 'T2' format" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T3': Field 'X' was not found in the space 'T2' format" -- </check-3.5> }) @@ -413,7 +413,7 @@ test:do_catchsql_test( INSERT INTO t3 VALUES(111,222,333); ]], { -- <check-3.9> - 1, "Failed to execute SQL statement: CHECK constraint failed: T3" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T3" -- </check-3.9> }) @@ -484,7 +484,7 @@ test:do_catchsql_test( UPDATE t4 SET x=0, y=1; ]], { -- <check-4.6> - 1, "Failed to execute SQL statement: CHECK constraint failed: T4" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T4" -- </check-4.6> }) @@ -504,7 +504,7 @@ test:do_catchsql_test( UPDATE t4 SET x=0, y=2; ]], { -- <check-4.9> - 1, "Failed to execute SQL statement: CHECK constraint failed: T4" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T4" -- </check-4.9> }) @@ -516,7 +516,7 @@ test:do_catchsql_test( ); ]], { -- <check-5.1> - 1, "Wrong space options (field 5): invalid expression specified (bindings are not allowed in DDL)" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T5': bindings are not allowed in DDL" -- </check-5.1> }) @@ -528,7 +528,7 @@ test:do_catchsql_test( ); ]], { -- <check-5.2> - 1, "Wrong space options (field 5): invalid expression specified (bindings are not allowed in DDL)" + 1, "Failed to create check constraint 'CK_CONSTRAINT_1_T5': bindings are not allowed in DDL" -- </check-5.2> }) @@ -581,7 +581,7 @@ test:do_catchsql_test( UPDATE OR FAIL t1 SET x=7-x, y=y+1; ]], { -- <check-6.5> - 1, "Failed to execute SQL statement: CHECK constraint failed: T1" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T1" -- </check-6.5> }) @@ -603,7 +603,7 @@ test:do_catchsql_test( INSERT OR ROLLBACK INTO t1 VALUES(8,40.0, 10); ]], { -- <check-6.7> - 1, "Failed to execute SQL statement: CHECK constraint failed: T1" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T1" -- </check-6.7> }) @@ -636,7 +636,7 @@ test:do_catchsql_test( REPLACE INTO t1 VALUES(6,7, 11); ]], { -- <check-6.12> - 1, "Failed to execute SQL statement: CHECK constraint failed: T1" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T1" -- </check-6.12> }) @@ -700,7 +700,7 @@ test:do_catchsql_test( 7.3, " INSERT INTO t6 VALUES(11) ", { -- <7.3> - 1, "Failed to execute SQL statement: CHECK constraint failed: T6" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T6" -- </7.3> }) diff --git a/test/sql-tap/fkey2.test.lua b/test/sql-tap/fkey2.test.lua index 54e5059b3a03b87d4427ae18ca2eaaa14e8a2dc6..695a379a62347bff577c396bfedfb7ba27934f6f 100755 --- a/test/sql-tap/fkey2.test.lua +++ b/test/sql-tap/fkey2.test.lua @@ -362,7 +362,7 @@ test:do_catchsql_test( UPDATE ab SET a = 5; ]], { -- <fkey2-3.2> - 1, "Failed to execute SQL statement: CHECK constraint failed: EF" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_EF" -- </fkey2-3.2> }) @@ -382,7 +382,7 @@ test:do_catchsql_test( UPDATE ab SET a = 5; ]], { -- <fkey2-3.4> - 1, "Failed to execute SQL statement: CHECK constraint failed: EF" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_EF" -- </fkey2-3.4> }) diff --git a/test/sql-tap/sql-errors.test.lua b/test/sql-tap/sql-errors.test.lua index 9357c406bfd64b3327bcc3ae88e34fdbc0344efd..a8d39472d2e538e652b07dcd35474e38f019b89c 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))); ]], { -- <sql-errors-1.27> - 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" -- </sql-errors-1.27> }) diff --git a/test/sql-tap/table.test.lua b/test/sql-tap/table.test.lua index 5b793c0fc98c8b1cd1854a0dcfad10e4409a16a4..066662f33ac7753e49c9420cffe4e3b6aa70be3c 100755 --- a/test/sql-tap/table.test.lua +++ b/test/sql-tap/table.test.lua @@ -1221,7 +1221,7 @@ test:do_catchsql_test( INSERT INTO T21 VALUES(1, -1, 1); ]], { -- <table-21.3> - 1, "Failed to execute SQL statement: CHECK constraint failed: T21" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_1_T21" -- </table-21.3> }) @@ -1231,7 +1231,7 @@ test:do_catchsql_test( INSERT INTO T21 VALUES(1, 1, -1); ]], { -- <table-21.4> - 1, "Failed to execute SQL statement: CHECK constraint failed: T21" + 1, "Failed to execute SQL statement: CHECK constraint failed: CK_CONSTRAINT_2_T21" -- </table-21.4> }) diff --git a/test/sql/checks.result b/test/sql/checks.result index f7cddec434268c67bb6a7de5408befa8a56e6858..efe6264288c2d41c95ddac7db5b95fb33ebff7f0 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', 'X<Y'}) --- +- error: Tarantool does not support CK constraint for space without format ... -s = box.space._space:insert(t) +s:format({{name='X', type='integer'}, {name='Y', type='integer'}}) --- -- error: 'Wrong space options (field 5): invalid MsgPack map field ''expr_invalid_label''' ... --- invalid field type -opts = {checks = {{name = 123}}} +_ = box.space._ck_constraint:insert({s.id, 'physics', false, 'SQL', 'X<Y'}) --- ... -format = {{name = 'X', type = 'unsigned'}} +box.execute("INSERT INTO \"test\" VALUES(2, 1);") --- +- error: 'Failed to execute SQL statement: CHECK constraint failed: physics' ... -t = {513, 1, 'test', 'memtx', 0, opts, format} +s:format({{name='Y', type='integer'}, {name='X', type='integer'}}) --- ... -s = box.space._space:insert(t) +box.execute("INSERT INTO \"test\" VALUES(1, 2);") +--- +- error: 'Failed to execute SQL statement: CHECK constraint failed: physics' +... +box.execute("INSERT INTO \"test\" VALUES(2, 1);") +--- +- row_count: 1 +... +s:truncate() +--- +... +box.execute("INSERT INTO \"test\" VALUES(1, 2);") +--- +- error: 'Failed to execute SQL statement: CHECK constraint failed: physics' +... +s:format({}) +--- +- error: Tarantool does not support CK constraint for space without format +... +s:format() +--- +- [{'name': 'Y', 'type': 'integer'}, {'name': 'X', 'type': 'integer'}] +... +s:format({{name='Y1', type='integer'}, {name='X1', type='integer'}}) +--- +- error: 'Failed to create check constraint ''physics'': Can’t resolve field ''X''' +... +-- Ck constraint creation is forbidden for non-empty space +s:insert({2, 1}) +--- +- [2, 1] +... +_ = box.space._ck_constraint:insert({s.id, 'conflict', false, 'SQL', 'X>10'}) +--- +- 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 5bfcf12f8dbe2a918352ffe3ff37647572aab5d6..399caf134658c669d95752034aa48ba5d9727879 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', 'X<Y'}) +s:format({{name='X', type='integer'}, {name='Y', type='integer'}}) +_ = box.space._ck_constraint:insert({s.id, 'physics', false, 'SQL', 'X<Y'}) +box.execute("INSERT INTO \"test\" VALUES(2, 1);") +s:format({{name='Y', type='integer'}, {name='X', type='integer'}}) +box.execute("INSERT INTO \"test\" VALUES(1, 2);") +box.execute("INSERT INTO \"test\" VALUES(2, 1);") +s:truncate() +box.execute("INSERT INTO \"test\" VALUES(1, 2);") +s:format({}) +s:format() +s:format({{name='Y1', type='integer'}, {name='X1', type='integer'}}) +-- Ck constraint creation is forbidden for non-empty space +s:insert({2, 1}) +_ = box.space._ck_constraint:insert({s.id, 'conflict', false, 'SQL', 'X>10'}) +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 ee36a387b8030397165a6edfb0dfa3586b5a4e68..414e3c476b84c2e0fd8ea03addcccc17570cf702 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 1aff6d77eb728ac630d15b3cab54ff25f3988e61..48b80a4430eceea4b85061392e984b89174a3314 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 9e347ca6ba3d1245cd7eb62a437dd17dcd03d89c..7384c81e8b07b69a7715f010390a4f50b88f6825 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 a53d6f7cec240adabca369e966161db21549d411..1c9ef5468ee91636487f4580a9e303350fef8731 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 3a55f7c533641c92ff79c858b774555fd9a0879c..f0997e17f80e70d8b6245a06abec2192957002f6 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 b76a8f37358784da51e2993b4f19e67173ddec99..37425ae21428718666e2f7e31f7d7c5b5972bb8c 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 ee280fcbbd1d8d488bd497d4e059a8a7168c166c..8040efa1ac71d2571342fadf7f713e1a322cb5bc 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