[Tarantool-patches] [PATCH] sql: support constraint drop
Roman Khabibov
roman.habibov at tarantool.org
Thu Jan 9 13:15:02 MSK 2020
Extend <ALTER TABLE> statement to drop table constraints by their
names.
Closes #4120
@TarantoolBot document
Title: Drop table constraints in SQL
Now, it is possible to drop table constraints (PRIMARY KEY,
UNIQUE, FOREIGN KEY, CHECK) using
<ALTER TABLE table_name DROP CONSTRAINT constraint_name> statement
by their names.
For example:
tarantool> box.execute([[CREATE TABLE test (
a INTEGER PRIMARY KEY,
b INTEGER,
CONSTRAINT cnstr CHECK (a >= 0)
);]])
---
- row_count: 1
...
tarantool> box.execute('ALTER TABLE test DROP CONSTRAINT cnstr;')
---
- row_count: 1
...
The same for all the other constraints.
---
Branch: https://github.com/tarantool/tarantool/tree/romanhabibov/gh-4120-drop-constraint
Issue: https://github.com/tarantool/tarantool/issues/4120
src/box/errcode.h | 2 +-
src/box/sql/alter.c | 2 +-
src/box/sql/build.c | 127 ++++++++++++++++++++---------------
src/box/sql/parse.y | 4 +-
src/box/sql/parse_def.h | 15 ++---
src/box/sql/sqlInt.h | 4 +-
test/sql-tap/alter2.test.lua | 2 +-
test/sql/checks.result | 2 +-
test/sql/constraint.result | 74 ++++++++++++++++++++
test/sql/constraint.test.lua | 37 ++++++++++
10 files changed, 197 insertions(+), 72 deletions(-)
diff --git a/src/box/errcode.h b/src/box/errcode.h
index 3065b1948..9ba42dfb4 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -221,7 +221,7 @@ struct errcode_record {
/*166 */_(ER_NO_SUCH_COLLATION, "Collation '%s' does not exist") \
/*167 */_(ER_CREATE_FK_CONSTRAINT, "Failed to create foreign key constraint '%s': %s") \
/*168 */_(ER_DROP_FK_CONSTRAINT, "Failed to drop foreign key constraint '%s': %s") \
- /*169 */_(ER_NO_SUCH_CONSTRAINT, "Constraint %s does not exist") \
+ /*169 */_(ER_NO_SUCH_CONSTRAINT, "Constraint '%s' does not exist in space '%s'") \
/*170 */_(ER_CONSTRAINT_EXISTS, "%s constraint '%s' already exists in space '%s'") \
/*171 */_(ER_SQL_TYPE_MISMATCH, "Type mismatch: can not convert %s to %s") \
/*172 */_(ER_ROWID_OVERFLOW, "Rowid is overflowed: too many entries in ephemeral space") \
diff --git a/src/box/sql/alter.c b/src/box/sql/alter.c
index 973b420cf..14f6c1a97 100644
--- a/src/box/sql/alter.c
+++ b/src/box/sql/alter.c
@@ -121,7 +121,7 @@ sql_alter_ck_constraint_enable(struct Parse *parse)
int addr = sqlVdbeAddOp4Int(v, OP_Found, cursor, 0, key_reg, 2);
sqlVdbeAddOp4(v, OP_SetDiag, ER_NO_SUCH_CONSTRAINT, 0, 0,
sqlMPrintf(db, tnt_errcode_desc(ER_NO_SUCH_CONSTRAINT),
- constraint_name), P4_DYNAMIC);
+ constraint_name, tbl_name), P4_DYNAMIC);
sqlVdbeAddOp2(v, OP_Halt, -1, ON_CONFLICT_ACTION_ABORT);
sqlVdbeJumpHere(v, addr);
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index bc50ecbfa..7c4b5760e 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -1469,6 +1469,29 @@ vdbe_emit_stat_space_clear(struct Parse *parse, const char *stat_table_name,
sql_table_delete_from(parse, src_list, where);
}
+/**
+ * Generate VDBE program to remove entry with @a index_id and @a
+ * space_id from _index space.
+ */
+static void
+vdbe_emit_index_drop(struct Parse *parse_context, uint32_t index_id,
+ uint32_t space_id)
+{
+ struct Vdbe *v = sqlGetVdbe(parse_context);
+ assert(v != NULL);
+ int record_reg = ++parse_context->nMem;
+ int space_id_reg = ++parse_context->nMem;
+ int index_id_reg = ++parse_context->nMem;
+ sqlVdbeAddOp2(v, OP_Integer, space_id, space_id_reg);
+ sqlVdbeAddOp2(v, OP_Integer, index_id, index_id_reg);
+ sqlVdbeAddOp3(v, OP_MakeRecord, space_id_reg, 2, record_reg);
+ sqlVdbeAddOp2(v, OP_SDelete, BOX_INDEX_ID, record_reg);
+ if (index_id == 0)
+ VdbeComment((v, "Remove primary key", index_id));
+ else
+ VdbeComment((v, "Remove secondary index iid = %u", index_id));
+}
+
/**
* Generate VDBE program to remove entry from _fk_constraint space.
*
@@ -1488,17 +1511,6 @@ vdbe_emit_fk_constraint_drop(struct Parse *parse_context, char *constraint_name,
sqlVdbeAddOp4(vdbe, OP_String8, 0, key_reg, 0, constraint_name,
P4_DYNAMIC);
sqlVdbeAddOp2(vdbe, OP_Integer, child_id, key_reg + 1);
- const char *error_msg =
- tt_sprintf(tnt_errcode_desc(ER_NO_SUCH_CONSTRAINT),
- constraint_name);
- if (vdbe_emit_halt_with_presence_test(parse_context,
- BOX_FK_CONSTRAINT_ID, 0,
- key_reg, 2, ER_NO_SUCH_CONSTRAINT,
- error_msg, false,
- OP_Found) != 0) {
- sqlDbFree(parse_context->db, constraint_name);
- return;
- }
sqlVdbeAddOp3(vdbe, OP_MakeRecord, key_reg, 2, key_reg + 2);
sqlVdbeAddOp2(vdbe, OP_SDelete, BOX_FK_CONSTRAINT_ID, key_reg + 2);
VdbeComment((vdbe, "Delete FK constraint %s", constraint_name));
@@ -1523,12 +1535,6 @@ vdbe_emit_ck_constraint_drop(struct Parse *parser, const char *ck_name,
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));
@@ -1617,7 +1623,6 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space,
*/
int idx_rec_reg = ++parse_context->nMem;
int space_id_reg = ++parse_context->nMem;
- int index_id_reg = ++parse_context->nMem;
int space_id = space->def->id;
sqlVdbeAddOp2(v, OP_Integer, space_id, space_id_reg);
sqlVdbeAddOp1(v, OP_CheckViewReferences, space_id_reg);
@@ -1680,23 +1685,12 @@ sql_code_drop_table(struct Parse *parse_context, struct space *space,
* secondary exist.
*/
for (uint32_t i = 1; i < index_count; ++i) {
- sqlVdbeAddOp2(v, OP_Integer,
- space->index[i]->def->iid,
- index_id_reg);
- sqlVdbeAddOp3(v, OP_MakeRecord,
- space_id_reg, 2, idx_rec_reg);
- sqlVdbeAddOp2(v, OP_SDelete, BOX_INDEX_ID,
- idx_rec_reg);
- VdbeComment((v,
- "Remove secondary index iid = %u",
- space->index[i]->def->iid));
+ vdbe_emit_index_drop(parse_context,
+ space->index[i]->def->iid,
+ space_id);
}
}
- sqlVdbeAddOp2(v, OP_Integer, 0, index_id_reg);
- sqlVdbeAddOp3(v, OP_MakeRecord, space_id_reg, 2,
- idx_rec_reg);
- sqlVdbeAddOp2(v, OP_SDelete, BOX_INDEX_ID, idx_rec_reg);
- VdbeComment((v, "Remove primary index"));
+ vdbe_emit_index_drop(parse_context, 0, space_id);
}
/* Delete records about the space from the _truncate. */
sqlVdbeAddOp3(v, OP_MakeRecord, space_id_reg, 1, idx_rec_reg);
@@ -2050,28 +2044,61 @@ fk_constraint_change_defer_mode(struct Parse *parse_context, bool is_deferred)
is_deferred;
}
+/**
+ * Emit code to drop the entry from _index or _ck_contstraint or
+ * _fk_constraint space corresponding with the constraint type.
+ */
void
-sql_drop_foreign_key(struct Parse *parse_context)
+sql_drop_constraint(struct Parse *parse_context)
{
- struct drop_entity_def *drop_def = &parse_context->drop_fk_def.base;
- assert(drop_def->base.entity_type == ENTITY_TYPE_FK);
+ struct drop_entity_def *drop_def =
+ &parse_context->drop_constraint_def.base;
+ assert(drop_def->base.entity_type == ENTITY_TYPE_CONSTRAINT);
assert(drop_def->base.alter_action == ALTER_ACTION_DROP);
const char *table_name = drop_def->base.entity_name->a[0].zName;
assert(table_name != NULL);
- struct space *child = space_by_name(table_name);
- if (child == NULL) {
+ struct space *space = space_by_name(table_name);
+ if (space == NULL) {
diag_set(ClientError, ER_NO_SUCH_SPACE, table_name);
parse_context->is_aborted = true;
return;
}
- char *constraint_name =
- sql_name_from_token(parse_context->db, &drop_def->name);
- if (constraint_name == NULL) {
+ char *name = sql_name_from_token(parse_context->db, &drop_def->name);
+ if (name == NULL) {
+ parse_context->is_aborted = true;
+ return;
+ }
+ struct constraint_id *id = space_find_constraint_id(space, name);
+ if (id == NULL) {
+ diag_set(ClientError, ER_NO_SUCH_CONSTRAINT, name, table_name);
parse_context->is_aborted = true;
return;
}
- vdbe_emit_fk_constraint_drop(parse_context, constraint_name,
- child->def->id);
+ uint32_t space_id = space->def->id;
+ assert(id->type == CONSTRAINT_TYPE_PK || id->type ==
+ CONSTRAINT_TYPE_UNIQUE || id->type == CONSTRAINT_TYPE_FK ||
+ id->type == CONSTRAINT_TYPE_CK);
+ switch (id->type) {
+ case CONSTRAINT_TYPE_PK:
+ case CONSTRAINT_TYPE_UNIQUE: {
+ uint32_t index_id = box_index_id_by_name(space_id, name,
+ strlen(name));
+ /*
+ * We have already sure, that this index exists,
+ * so we don't check index_id for BOX_ID_NIL.
+ */
+ vdbe_emit_index_drop(parse_context, index_id, space_id);
+ break;
+ }
+ case CONSTRAINT_TYPE_FK:
+ vdbe_emit_fk_constraint_drop(parse_context, name, space_id);
+ break;
+ case CONSTRAINT_TYPE_CK:
+ vdbe_emit_ck_constraint_drop(parse_context, name, space_id);
+ break;
+ default:
+ unreachable();
+ }
/*
* We account changes to row count only if drop of
* foreign keys take place in a separate
@@ -2704,18 +2731,8 @@ sql_drop_index(struct Parse *parse_context)
goto exit_drop_index;
}
- /*
- * Generate code to remove entry from _index space
- * But firstly, delete statistics since schema
- * changes after DDL.
- */
- int record_reg = ++parse_context->nMem;
- int space_id_reg = ++parse_context->nMem;
- int index_id_reg = ++parse_context->nMem;
- sqlVdbeAddOp2(v, OP_Integer, space->def->id, space_id_reg);
- sqlVdbeAddOp2(v, OP_Integer, index_id, index_id_reg);
- sqlVdbeAddOp3(v, OP_MakeRecord, space_id_reg, 2, record_reg);
- sqlVdbeAddOp2(v, OP_SDelete, BOX_INDEX_ID, record_reg);
+ vdbe_emit_index_drop(parse_context, index_id, space->def->id);
+ /* Delete statistics since schema changes after DDL. */
sqlVdbeChangeP5(v, OPFLAG_NCHANGE);
exit_drop_index:
sqlSrcListDelete(db, table_list);
diff --git a/src/box/sql/parse.y b/src/box/sql/parse.y
index cfe1c0012..1a0e89703 100644
--- a/src/box/sql/parse.y
+++ b/src/box/sql/parse.y
@@ -1763,9 +1763,9 @@ cmd ::= alter_table_start(A) RENAME TO nm(N). {
}
cmd ::= ALTER TABLE fullname(X) DROP CONSTRAINT nm(Z). {
- drop_fk_def_init(&pParse->drop_fk_def, X, &Z, false);
+ drop_constraint_def_init(&pParse->drop_constraint_def, X, &Z, false);
pParse->initiateTTrans = true;
- sql_drop_foreign_key(pParse);
+ sql_drop_constraint(pParse);
}
cmd ::= alter_table_start(A) enable(E) CHECK CONSTRAINT nm(Z). {
diff --git a/src/box/sql/parse_def.h b/src/box/sql/parse_def.h
index 2f433e4c0..e6d45b256 100644
--- a/src/box/sql/parse_def.h
+++ b/src/box/sql/parse_def.h
@@ -159,10 +159,6 @@ enum entity_type {
ENTITY_TYPE_TRIGGER,
ENTITY_TYPE_CK,
ENTITY_TYPE_FK,
- /**
- * For assertion checks that constraint definition is
- * created before initialization of a term constraint.
- */
ENTITY_TYPE_CONSTRAINT,
};
@@ -265,7 +261,7 @@ struct drop_trigger_def {
struct drop_entity_def base;
};
-struct drop_fk_def {
+struct drop_constraint_def {
struct drop_entity_def base;
};
@@ -408,11 +404,12 @@ drop_trigger_def_init(struct drop_trigger_def *drop_trigger_def,
}
static inline void
-drop_fk_def_init(struct drop_fk_def *drop_fk_def, struct SrcList *parent_name,
- struct Token *name, bool if_exist)
+drop_constraint_def_init(struct drop_constraint_def *drop_constraint_def,
+ struct SrcList *parent_name, struct Token *name,
+ bool if_exist)
{
- drop_entity_def_init(&drop_fk_def->base, parent_name, name, if_exist,
- ENTITY_TYPE_FK);
+ drop_entity_def_init(&drop_constraint_def->base, parent_name, name,
+ if_exist, ENTITY_TYPE_CONSTRAINT);
}
static inline void
diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h
index d1fcf4761..2ea653c7d 100644
--- a/src/box/sql/sqlInt.h
+++ b/src/box/sql/sqlInt.h
@@ -2237,7 +2237,7 @@ struct Parse {
struct create_trigger_def create_trigger_def;
struct create_view_def create_view_def;
struct rename_entity_def rename_entity_def;
- struct drop_fk_def drop_fk_def;
+ struct drop_constraint_def drop_constraint_def;
struct drop_index_def drop_index_def;
struct drop_table_def drop_table_def;
struct drop_trigger_def drop_trigger_def;
@@ -3747,7 +3747,7 @@ sql_create_foreign_key(struct Parse *parse_context);
* @param parse_context Parsing context.
*/
void
-sql_drop_foreign_key(struct Parse *parse_context);
+sql_drop_constraint(struct Parse *parse_context);
/**
* Now our SQL implementation can't operate on spaces which
diff --git a/test/sql-tap/alter2.test.lua b/test/sql-tap/alter2.test.lua
index 3e21a5f40..759acc9c3 100755
--- a/test/sql-tap/alter2.test.lua
+++ b/test/sql-tap/alter2.test.lua
@@ -256,7 +256,7 @@ test:do_catchsql_test(
ALTER TABLE child DROP CONSTRAINT fake;
]], {
-- <alter2-5.2>
- 1, "Constraint FAKE does not exist"
+ 1, "Constraint 'FAKE' does not exist in space 'CHILD'"
-- </alter2-5.2>
})
diff --git a/test/sql/checks.result b/test/sql/checks.result
index 4ae0b4c10..7b18e5d6b 100644
--- a/test/sql/checks.result
+++ b/test/sql/checks.result
@@ -801,7 +801,7 @@ box.execute('ALTER TABLE test ADD CONSTRAINT \"some_ck\" CHECK(a < 10);')
box.execute("ALTER TABLE test DISABLE CHECK CONSTRAINT \"falsch\"")
---
- null
-- Constraint falsch does not exist
+- Constraint 'falsch' does not exist in space 'TEST'
...
box.execute("ALTER TABLE test DISABLE CHECK CONSTRAINT \"some_ck\"")
---
diff --git a/test/sql/constraint.result b/test/sql/constraint.result
index 1585c2327..73bdec45a 100644
--- a/test/sql/constraint.result
+++ b/test/sql/constraint.result
@@ -291,6 +291,80 @@ box.execute('CREATE UNIQUE INDEX d ON t2(i);')
| - Index 'D' already exists in space 'T2'
| ...
+--
+-- gh-4120: Ensure that <ALTER TABLE DROP CONSTRAINT> works
+-- correctly for each type of constraint.
+--
+-- Drop PRIMARY KEY constraint.
+box.execute('ALTER TABLE t2 DROP CONSTRAINT c;')
+ | ---
+ | - null
+ | - Can't drop primary key in space 'T2' while secondary keys exist
+ | ...
+box.space.T2.index.C ~= nil
+ | ---
+ | - true
+ | ...
+-- Drop UNIQUE constraint.
+box.execute('ALTER TABLE t2 DROP CONSTRAINT e;')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.index.E == nil
+ | ---
+ | - true
+ | ...
+-- Drop CHECK constraint.
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.ck_constraint.E ~= nil
+ | ---
+ | - true
+ | ...
+box.execute('ALTER TABLE t2 DROP CONSTRAINT e;')
+ | ---
+ | - row_count: 1
+ | ...
+box.space.T2.ck_constraint.E == nil
+ | ---
+ | - true
+ | ...
+-- Drop FOREIGN KEY constraint.
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e FOREIGN KEY(i) REFERENCES t1(i);')
+ | ---
+ | - row_count: 1
+ | ...
+box.execute('ALTER TABLE t2 DROP CONSTRAINT e;')
+ | ---
+ | - row_count: 1
+ | ...
+-- Check the error message.
+box.execute('ALTER TABLE t2 DROP CONSTRAINT e;')
+ | ---
+ | - null
+ | - Constraint 'E' does not exist in space 'T2'
+ | ...
+
+--
+-- Ensure that constraint name dropped from/added into the
+-- internal space hash table during a transaction.
+--
+box.begin() \
+box.execute('CREATE UNIQUE INDEX e ON t2(i);') \
+-- Drop UNIQUE constraint. \
+box.execute('ALTER TABLE t2 DROP CONSTRAINT e;') \
+-- Drop CHECK constraint. \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);') \
+box.execute('ALTER TABLE t2 DROP CONSTRAINT e;') \
+-- Drop FOREIGN KEY constraint. \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e FOREIGN KEY(i) REFERENCES t1(i);') \
+box.execute('ALTER TABLE t2 DROP CONSTRAINT e;') \
+box.commit()
+ | ---
+ | ...
+
--
-- Cleanup.
--
diff --git a/test/sql/constraint.test.lua b/test/sql/constraint.test.lua
index 163f4309c..0cada5f09 100755
--- a/test/sql/constraint.test.lua
+++ b/test/sql/constraint.test.lua
@@ -131,6 +131,43 @@ box.execute('CREATE UNIQUE INDEX e ON t2(i);')
-- uniqueness, index names should be unique in a space.
box.execute('CREATE UNIQUE INDEX d ON t2(i);')
+--
+-- gh-4120: Ensure that <ALTER TABLE DROP CONSTRAINT> works
+-- correctly for each type of constraint.
+--
+-- Drop PRIMARY KEY constraint.
+box.execute('ALTER TABLE t2 DROP CONSTRAINT c;')
+box.space.T2.index.C ~= nil
+-- Drop UNIQUE constraint.
+box.execute('ALTER TABLE t2 DROP CONSTRAINT e;')
+box.space.T2.index.E == nil
+-- Drop CHECK constraint.
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);')
+box.space.T2.ck_constraint.E ~= nil
+box.execute('ALTER TABLE t2 DROP CONSTRAINT e;')
+box.space.T2.ck_constraint.E == nil
+-- Drop FOREIGN KEY constraint.
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e FOREIGN KEY(i) REFERENCES t1(i);')
+box.execute('ALTER TABLE t2 DROP CONSTRAINT e;')
+-- Check the error message.
+box.execute('ALTER TABLE t2 DROP CONSTRAINT e;')
+
+--
+-- Ensure that constraint name dropped from/added into the
+-- internal space hash table during a transaction.
+--
+box.begin() \
+box.execute('CREATE UNIQUE INDEX e ON t2(i);') \
+-- Drop UNIQUE constraint. \
+box.execute('ALTER TABLE t2 DROP CONSTRAINT e;') \
+-- Drop CHECK constraint. \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e CHECK(i > 0);') \
+box.execute('ALTER TABLE t2 DROP CONSTRAINT e;') \
+-- Drop FOREIGN KEY constraint. \
+box.execute('ALTER TABLE t2 ADD CONSTRAINT e FOREIGN KEY(i) REFERENCES t1(i);') \
+box.execute('ALTER TABLE t2 DROP CONSTRAINT e;') \
+box.commit()
+
--
-- Cleanup.
--
--
2.21.0 (Apple Git-122)
More information about the Tarantool-patches
mailing list