From bfb319e628daf45d0c8357510c5af06626d9bacf Mon Sep 17 00:00:00 2001 From: Dimitry Sibiryakov Date: Thu, 25 Apr 2024 18:02:21 +0200 Subject: [PATCH] Creation of not enforced constraints --- doc/sql.extensions/README.ddl.txt | 42 ++++++++++++++++-- src/dsql/DdlNodes.epp | 13 +++++- src/dsql/DdlNodes.h | 2 + src/dsql/parse-conflicts.txt | 2 +- src/dsql/parse.y | 26 +++++++++-- src/isql/extract.epp | 72 ++++++++++++++++++++----------- 6 files changed, 125 insertions(+), 32 deletions(-) diff --git a/doc/sql.extensions/README.ddl.txt b/doc/sql.extensions/README.ddl.txt index 0b6f84fe62f..4dfe844692b 100644 --- a/doc/sql.extensions/README.ddl.txt +++ b/doc/sql.extensions/README.ddl.txt @@ -705,13 +705,49 @@ CREATE [GLOBAL] MAPPING [IF NOT EXISTS] ... ALTER TABLE ADD [IF NOT EXISTS] ... ALTER TABLE
ADD CONSTRAINT [IF NOT EXISTS] ... -3) ALTER CONSTRAINT clause for ALTER TABLE statement. +3) Non-enforced constraints. + +CREATE/ALTER TABLE supports creation of non-enforced constraints. + +Syntax: + + ::= + [CONSTRAINT constr_name] + { PRIMARY KEY [] + | UNIQUE [] + | REFERENCES other_table [(colname)] [] + [ON DELETE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}] + [ON UPDATE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}] + | CHECK () + | NOT NULL } + [] + + ::= + [CONSTRAINT constr_name] + { PRIMARY KEY () [] + | UNIQUE () [] + | FOREIGN KEY () + REFERENCES other_table [()] [] + [ON DELETE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}] + [ON UPDATE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}] + | CHECK () } + [] + + ::= + + + ::= + [ NOT ] ENFORCED + +Note: In contrast to ANSI SQL standard PRIMARY KEY and UNIQUE constraint +are allowed to be not enforced. + +Also ALTER CONSTRAINT clause is added to ALTER TABLE statement. Syntax: -ALTER TABLE ALTER CONSTRAINT [NOT] ENFORCED +ALTER TABLE ALTER CONSTRAINT -Supported for UNIQUE, PRIMARY KEY, FOREIGN KEY, CHECK AND NOT NULL constraints. Primary and unique keys cannot be deactivated if they are referenced by any active foreign key. The corresponding ALTER INDEX and ALTER TRIGGER statements are allowed as well. diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index 6ad3c9ba649..c565cef8e63 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -6714,11 +6714,18 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra constraint.create = FB_NEW_POOL(pool) Constraint(pool); constraint.create->type = Constraint::TYPE_NOT_NULL; if (clause->constraintType == AddConstraintClause::CTYPE_NOT_NULL) + { constraint.name = clause->name; + constraint.create->enforced = clause->enforced; + *notNull = clause->enforced; + } + // NOT NULL for PRIMARY KEY is always enforced } if (clause->constraintType == AddConstraintClause::CTYPE_NOT_NULL) + { break; + } // AddConstraintClause::CTYPE_PK falls into case AddConstraintClause::CTYPE_UNIQUE: @@ -6732,6 +6739,7 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra if (constraint.create->index && constraint.create->index->name.isEmpty()) constraint.create->index->name = constraint.name; constraint.create->columns = clause->columns; + constraint.create->enforced = clause->enforced; break; } @@ -6744,6 +6752,7 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra constraint.create->columns = clause->columns; constraint.create->refRelation = clause->refRelation; constraint.create->refColumns = clause->refColumns; + constraint.create->enforced = clause->enforced; // If there is a referenced table name but no referenced field names, the // primary key of the referenced table designates the referenced fields. @@ -6858,6 +6867,7 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra CreateDropConstraint& constraint = constraints.add(); constraint.create = FB_NEW_POOL(pool) Constraint(pool); constraint.create->type = Constraint::TYPE_CHECK; + constraint.create->enforced = clause->enforced; constraint.name = clause->name; defineCheckConstraint(dsqlScratch, *constraint.create, clause->check); break; @@ -6924,7 +6934,7 @@ void RelationNode::defineConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlSc definition.unique = constraint.type != Constraint::TYPE_FK; if (constraint.index->descending) definition.descending = true; - definition.inactive = false; + definition.inactive = !constraint.enforced; definition.columns = constraint.columns; definition.refRelation = constraint.refRelation; definition.refColumns = constraint.refColumns; @@ -7185,6 +7195,7 @@ void RelationNode::defineCheckConstraintTrigger(DsqlCompilerScratch* dsqlScratch trigger.type = triggerType; trigger.source = clause->source; trigger.blrData = blrWriter.getBlrData(); + trigger.active = constraint.enforced; } // Define "on delete|update set default" trigger (for referential integrity) along with its blr. diff --git a/src/dsql/DdlNodes.h b/src/dsql/DdlNodes.h index 2b83640ac81..a136a054766 100644 --- a/src/dsql/DdlNodes.h +++ b/src/dsql/DdlNodes.h @@ -1300,6 +1300,7 @@ class RelationNode : public DdlNode const char* refDeleteAction; Firebird::ObjectsArray triggers; Firebird::ObjectsArray blrWritersHolder; + bool enforced = true; }; struct CreateDropConstraint @@ -1389,6 +1390,7 @@ class RelationNode : public DdlNode NestConst refAction; NestConst check; bool createIfNotExistsOnly = false; + bool enforced = true; }; struct IdentityOptions diff --git a/src/dsql/parse-conflicts.txt b/src/dsql/parse-conflicts.txt index 58be4c6e996..ad9f0ea90d0 100644 --- a/src/dsql/parse-conflicts.txt +++ b/src/dsql/parse-conflicts.txt @@ -1 +1 @@ -115 shift/reduce conflicts, 22 reduce/reduce conflicts. +116 shift/reduce conflicts, 22 reduce/reduce conflicts. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index c0fc837ebea..400e710120e 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -2624,23 +2624,26 @@ column_constraint_def($addColumnClause) : constraint_name_opt column_constraint($addColumnClause) { if ($1) - $addColumnClause->constraints.back().name = *$1; + $2->name = *$1; } + constraint_characteristics_opt($2) ; -%type column_constraint() +%type column_constraint() column_constraint($addColumnClause) : null_constraint { setClause($addColumnClause->notNullSpecified, "NOT NULL"); RelationNode::AddConstraintClause& constraint = $addColumnClause->constraints.add(); constraint.constraintType = RelationNode::AddConstraintClause::CTYPE_NOT_NULL; + $$ = &constraint; } | check_constraint { RelationNode::AddConstraintClause& constraint = $addColumnClause->constraints.add(); constraint.constraintType = RelationNode::AddConstraintClause::CTYPE_CHECK; constraint.check = $1; + $$ = &constraint; } | REFERENCES symbol_table_name column_parens_opt referential_trigger_action constraint_index_opt @@ -2662,18 +2665,21 @@ column_constraint($addColumnClause) } constraint.index = $5; + $$ = &constraint; } | UNIQUE constraint_index_opt { RelationNode::AddConstraintClause& constraint = $addColumnClause->constraints.add(); constraint.constraintType = RelationNode::AddConstraintClause::CTYPE_UNIQUE; constraint.index = $2; + $$ = &constraint; } | PRIMARY KEY constraint_index_opt { RelationNode::AddConstraintClause& constraint = $addColumnClause->constraints.add(); constraint.constraintType = RelationNode::AddConstraintClause::CTYPE_PK; constraint.index = $3; + $$ = &constraint; } ; @@ -2688,6 +2694,10 @@ table_constraint_definition($relationNode) $2->name = *$1; $$ = $2; } + constraint_characteristics_opt($3) + { + $$ = $3; + } ; %type constraint_name_opt @@ -2786,6 +2796,15 @@ constraint_index_opt ***/ ; +%type constraint_characteristics_opt() +constraint_characteristics_opt($addConstraintClause) + : // nothing + | constraint_enforcement + { + $addConstraintClause->enforced = $1; + } + ; + %type referential_trigger_action referential_trigger_action : /* nothing */ { $$ = NULL; } @@ -4384,13 +4403,14 @@ alter_op($relationNode) const auto node = $3; node->createIfNotExistsOnly = $2; } - | ADD table_constraint($relationNode) + | ADD table_constraint($relationNode) constraint_characteristics_opt($2) | ADD CONSTRAINT if_not_exists_opt symbol_constraint_name table_constraint($relationNode) { const auto node = $5; node->name = *$4; node->createIfNotExistsOnly = $3; } + constraint_characteristics_opt($5) | col_opt alter_column_name POSITION pos_short_integer { RelationNode::AlterColPosClause* clause = newNode(); diff --git a/src/isql/extract.epp b/src/isql/extract.epp index d1aa3b2db46..1e77255482b 100644 --- a/src/isql/extract.epp +++ b/src/isql/extract.epp @@ -548,33 +548,42 @@ int EXTRACT_list_table(const SCHAR* relation_name, ** rdb$check_constraints. We hope we get at most one row back. */ - if (RFR.RDB$NULL_FLAG == 1) - { - FOR RCO IN RDB$RELATION_CONSTRAINTS CROSS - CON IN RDB$CHECK_CONSTRAINTS WITH - CON.RDB$TRIGGER_NAME = RFR.RDB$FIELD_NAME AND - CON.RDB$CONSTRAINT_NAME = RCO.RDB$CONSTRAINT_NAME AND - RCO.RDB$CONSTRAINT_TYPE EQ "NOT NULL" AND - RCO.RDB$RELATION_NAME = RFR.RDB$RELATION_NAME + bool found = false; + + FOR RCO IN RDB$RELATION_CONSTRAINTS CROSS + CON IN RDB$CHECK_CONSTRAINTS WITH + CON.RDB$TRIGGER_NAME = RFR.RDB$FIELD_NAME AND + CON.RDB$CONSTRAINT_NAME = RCO.RDB$CONSTRAINT_NAME AND + RCO.RDB$CONSTRAINT_TYPE EQ "NOT NULL" AND + RCO.RDB$RELATION_NAME = RFR.RDB$RELATION_NAME - if (!fb_utils::implicit_integrity(CON.RDB$CONSTRAINT_NAME)) + if (!fb_utils::implicit_integrity(CON.RDB$CONSTRAINT_NAME)) + { + fb_utils::exact_name(CON.RDB$CONSTRAINT_NAME); + if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION) { - fb_utils::exact_name(CON.RDB$CONSTRAINT_NAME); - if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION) - { - IUTILS_copy_SQL_id (CON.RDB$CONSTRAINT_NAME, SQL_identifier, DBL_QUOTE); - isqlGlob.printf(" CONSTRAINT %s", SQL_identifier); - } - else - isqlGlob.printf(" CONSTRAINT %s", CON.RDB$CONSTRAINT_NAME); + IUTILS_copy_SQL_id (CON.RDB$CONSTRAINT_NAME, SQL_identifier, DBL_QUOTE); + isqlGlob.printf(" CONSTRAINT %s", SQL_identifier); } - END_FOR - ON_ERROR - ISQL_errmsg (fbStatus); - return FINI_ERROR; - END_ERROR; + else + isqlGlob.printf(" CONSTRAINT %s", CON.RDB$CONSTRAINT_NAME); + } + found = true; + END_FOR + ON_ERROR + ISQL_errmsg (fbStatus); + return FINI_ERROR; + END_ERROR; + + if (found) + { isqlGlob.printf(" NOT NULL"); + + if (RFR.RDB$NULL_FLAG == 0) + { + isqlGlob.printf(" NOT ENFORCED"); + } } // Handle collations after defaults @@ -681,6 +690,11 @@ static bool extract_rel_constraints(const char* relation_name) isqlGlob.printf(" %s", IDX.RDB$INDEX_NAME); } + if (IDX.RDB$INDEX_INACTIVE == 1) + { + isqlGlob.printf(" NOT ENFORCED"); + } + END_FOR ON_ERROR ISQL_errmsg(fbStatus); @@ -1926,6 +1940,11 @@ static void list_check() if (!TRG.RDB$TRIGGER_SOURCE.NULL) SHOW_print_metadata_text_blob (isqlGlob.Out, &TRG.RDB$TRIGGER_SOURCE); + if (TRG.RDB$TRIGGER_INACTIVE == 1) + { + isqlGlob.printf(" NOT ENFORCED"); + } + isqlGlob.printf("%s%s", isqlGlob.global_Term, NEWLINE); END_FOR @@ -2828,12 +2847,14 @@ static void list_foreign() FOR RELC1 IN RDB$RELATION_CONSTRAINTS CROSS RELC2 IN RDB$RELATION_CONSTRAINTS CROSS - REFC IN RDB$REF_CONSTRAINTS WITH + REFC IN RDB$REF_CONSTRAINTS CROSS + IDX IN RDB$INDICES WITH RELC1.RDB$CONSTRAINT_TYPE EQ "FOREIGN KEY" AND REFC.RDB$CONST_NAME_UQ EQ RELC2.RDB$CONSTRAINT_NAME AND REFC.RDB$CONSTRAINT_NAME EQ RELC1.RDB$CONSTRAINT_NAME AND (RELC2.RDB$CONSTRAINT_TYPE EQ "UNIQUE" OR - RELC2.RDB$CONSTRAINT_TYPE EQ "PRIMARY KEY") + RELC2.RDB$CONSTRAINT_TYPE EQ "PRIMARY KEY") AND + IDX.RDB$INDEX_NAME = RELC1.RDB$INDEX_NAME SORTED BY RELC1.RDB$RELATION_NAME, RELC1.RDB$CONSTRAINT_NAME fb_utils::exact_name(RELC1.RDB$RELATION_NAME); @@ -2893,6 +2914,9 @@ static void list_foreign() ISQL_ri_action_print (REFC.RDB$DELETE_RULE, " ON DELETE", true); } + if (IDX.RDB$INDEX_INACTIVE == 1) + isqlGlob.printf(" NOT ENFORCED"); + isqlGlob.printf("%s%s", isqlGlob.global_Term, NEWLINE); END_FOR