From 9abe46efb26119b1cddbe4c807a19e8aa35b9cb0 Mon Sep 17 00:00:00 2001 From: Dimitry Sibiryakov Date: Tue, 9 Apr 2024 17:41:23 +0200 Subject: [PATCH] ALTER CONSTRAINT clause for constraints --- doc/sql.extensions/README.ddl.txt | 47 ++++++++ src/common/ParserTokens.h | 1 + src/dsql/DdlNodes.epp | 171 ++++++++++++++++++++++++++- src/dsql/DdlNodes.h | 15 +++ src/dsql/parse-conflicts.txt | 2 +- src/dsql/parse.y | 41 ++++++- src/isql/extract.epp | 72 ++++++++---- src/isql/show.epp | 27 ++++- src/jrd/SystemTriggers.epp | 184 ------------------------------ src/jrd/drq.h | 3 + src/jrd/ini.epp | 2 +- 11 files changed, 344 insertions(+), 221 deletions(-) diff --git a/doc/sql.extensions/README.ddl.txt b/doc/sql.extensions/README.ddl.txt index d508aacaf7e..9120db35f14 100644 --- a/doc/sql.extensions/README.ddl.txt +++ b/doc/sql.extensions/README.ddl.txt @@ -709,3 +709,50 @@ CREATE PACKAGE BODY [IF NOT EXISTS] ... CREATE [GLOBAL] MAPPING [IF NOT EXISTS] ... ALTER TABLE ADD [IF NOT EXISTS] ... ALTER TABLE
ADD CONSTRAINT [IF NOT EXISTS] ... + +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 + +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/common/ParserTokens.h b/src/common/ParserTokens.h index ad2b6827228..8a58cef38b7 100644 --- a/src/common/ParserTokens.h +++ b/src/common/ParserTokens.h @@ -197,6 +197,7 @@ PARSER_TOKEN(TOK_ENABLE, "ENABLE", true) PARSER_TOKEN(TOK_ENCRYPT, "ENCRYPT", true) PARSER_TOKEN(TOK_END, "END", false) PARSER_TOKEN(TOK_ENGINE, "ENGINE", true) +PARSER_TOKEN(TOK_ENFORCED, "ENFORCED", true) PARSER_TOKEN(TOK_ENTRY_POINT, "ENTRY_POINT", true) PARSER_TOKEN(TOK_ESCAPE, "ESCAPE", false) PARSER_TOKEN(TOK_EXCEPTION, "EXCEPTION", true) diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index 0c2a26992f4..32e46dfd87d 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -112,6 +112,8 @@ static void updateRdbFields(const TypeClause* type, SSHORT& fieldPrecisionNull, SSHORT& fieldPrecision, SSHORT& collationIdNull, SSHORT& collationId, SSHORT& segmentLengthNull, SSHORT& segmentLength); +static void modifyIndex(thread_db* tdbb, jrd_tra* transaction, + const char* name, bool active); static const char* const CHECK_CONSTRAINT_EXCEPTION = "check_constraint"; @@ -179,6 +181,47 @@ void ExecInSecurityDb::executeInSecurityDb(jrd_tra* localTransaction) //---------------------- +// Activate/deactivate given index +static void modifyIndex(thread_db* tdbb, jrd_tra* transaction, + const char* name, bool active) +{ + AutoCacheRequest request(tdbb, drq_m_index, DYN_REQUESTS); + + bool found = false; + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) + IDX IN RDB$INDICES + WITH IDX.RDB$INDEX_NAME EQ name + { + found = true; + MODIFY IDX + IDX.RDB$INDEX_INACTIVE.NULL = FALSE; + IDX.RDB$INDEX_INACTIVE = active ? FALSE : TRUE; + END_MODIFY + } + END_FOR + + if (!found) + { + // msg 48: "Index not found" + status_exception::raise(Arg::PrivateDyn(48)); + } +} + +// Check if given index is referenced by active foreign key constraint +static void checkIndexReferenced(thread_db* tdbb, jrd_tra* transaction, const char* name) +{ + AutoCacheRequest fkCheck(tdbb, drq_c_active_fk, DYN_REQUESTS); + + FOR(REQUEST_HANDLE fkCheck TRANSACTION_HANDLE transaction) + IDX IN RDB$INDICES + WITH IDX.RDB$FOREIGN_KEY EQ name AND + IDX.RDB$INDEX_INACTIVE EQ 0 OR IDX.RDB$INDEX_INACTIVE MISSING + { + // MSG 408: "Can't deactivate index used by an integrity constraint" + status_exception::raise(Arg::Gds(isc_integ_index_deactivate)); + } + END_FOR +} // Check temporary table reference rules between given child relation and master // relation (owner of given PK/UK index). @@ -3525,7 +3568,6 @@ bool TriggerDefinition::modify(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch { switch (TRG.RDB$SYSTEM_FLAG) { - case fb_sysflag_check_constraint: case fb_sysflag_referential_constraint: case fb_sysflag_view_check: status_exception::raise(Arg::Gds(isc_dyn_cant_modify_auto_trig)); @@ -6648,11 +6690,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: @@ -6666,6 +6715,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; } @@ -6678,6 +6728,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. @@ -6792,6 +6843,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; @@ -6858,7 +6910,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; @@ -7119,6 +7171,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. @@ -7979,6 +8032,118 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc break; } + case Clause::TYPE_ALTER_CONSTRAINT: + { + executeBeforeTrigger(); + + const AlterConstraintClause* clause = static_cast(i->getObject()); + AutoCacheRequest request(tdbb, drq_get_constr_type, DYN_REQUESTS); + bool found = false; + + FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) + RC IN RDB$RELATION_CONSTRAINTS + WITH RC.RDB$CONSTRAINT_NAME EQ clause->name.c_str() AND + RC.RDB$RELATION_NAME EQ name.c_str() + { + found = true; + fb_utils::exact_name(RC.RDB$CONSTRAINT_TYPE); + if (strcmp(RC.RDB$CONSTRAINT_TYPE, PRIMARY_KEY) == 0 || + strcmp(RC.RDB$CONSTRAINT_TYPE, UNIQUE_CNSTRT) == 0) + { + // Deactivation of primary/unique key requires check for active foreign keys + checkIndexReferenced(tdbb, transaction, RC.RDB$INDEX_NAME); + modifyIndex(tdbb, transaction, RC.RDB$INDEX_NAME, clause->enforced); + } + else if (strcmp(RC.RDB$CONSTRAINT_TYPE, FOREIGN_KEY) == 0) + { + // Activation of foreign key requires check for active partner which is done on index activation + // so there is nothing to check here + modifyIndex(tdbb, transaction, RC.RDB$INDEX_NAME, clause->enforced); + } + else if (strcmp(RC.RDB$CONSTRAINT_TYPE, CHECK_CNSTRT) == 0) + { + AutoCacheRequest requestHandle(tdbb, drq_m_check_trgs, DYN_REQUESTS); + + FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) + TRG IN RDB$TRIGGERS CROSS + CHK IN RDB$CHECK_CONSTRAINTS + WITH TRG.RDB$RELATION_NAME EQ name.c_str() AND + TRG.RDB$TRIGGER_NAME EQ CHK.RDB$TRIGGER_NAME AND + CHK.RDB$CONSTRAINT_NAME EQ clause->name.c_str() + { + MODIFY TRG + TRG.RDB$TRIGGER_INACTIVE = clause->enforced ? FALSE : TRUE; + END_MODIFY + } + END_FOR + } + else if (strcmp(RC.RDB$CONSTRAINT_TYPE, NOT_NULL_CNSTRT) == 0) + { + AutoRequest requestHandle; + + FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction) + CHK IN RDB$CHECK_CONSTRAINTS CROSS + RF IN RDB$RELATION_FIELDS + WITH CHK.RDB$CONSTRAINT_NAME EQ clause->name.c_str() AND + CHK.RDB$TRIGGER_NAME EQ RF.RDB$FIELD_NAME AND + RF.RDB$RELATION_NAME EQ name.c_str() + { + // Identity column cannot be NULL-able. + if (RF.RDB$IDENTITY_TYPE.NULL == FALSE) + { + fb_utils::exact_name(RF.RDB$FIELD_NAME); + // msg 274: Identity column @1 of table @2 cannot be changed to NULLable + status_exception::raise(Arg::PrivateDyn(274) << RF.RDB$FIELD_NAME << name.c_str()); + } + + // Column of an active primary key cannot be nullable + AutoRequest request3; + + FOR (REQUEST_HANDLE request3 TRANSACTION_HANDLE transaction) + ISG IN RDB$INDEX_SEGMENTS CROSS + IDX IN RDB$INDICES CROSS + RC2 IN RDB$RELATION_CONSTRAINTS + WITH ISG.RDB$FIELD_NAME EQ RF.RDB$FIELD_NAME AND + ISG.RDB$INDEX_NAME EQ IDX.RDB$INDEX_NAME AND + IDX.RDB$RELATION_NAME EQ name.c_str() AND + (IDX.RDB$INDEX_INACTIVE EQ 0 OR IDX.RDB$INDEX_INACTIVE MISSING) AND + RC2.RDB$INDEX_NAME EQ IDX.RDB$INDEX_NAME AND + RC2.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY + { + status_exception::raise(Arg::Gds(isc_primary_key_notnull)); + } + END_FOR + + // Otherwise it is fine + MODIFY RF + if (clause->enforced) + { + RF.RDB$NULL_FLAG.NULL = FALSE; + RF.RDB$NULL_FLAG = TRUE; + } + else + { + RF.RDB$NULL_FLAG.NULL = TRUE; + RF.RDB$NULL_FLAG = FALSE; // For symmetry + } + END_MODIFY + } + END_FOR + } + else + status_exception::raise(Arg::Gds(isc_wish_list) << Arg::Gds(isc_ref_cnstrnt_update)); + } + END_FOR + + if (!found) + { + // msg 130: "CONSTRAINT %s does not exist." + status_exception::raise(Arg::PrivateDyn(130) << clause->name); + } + + break; + } + case Clause::TYPE_ALTER_SQL_SECURITY: { executeBeforeTrigger(); @@ -10152,6 +10317,8 @@ void AlterIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_INDEX, name, NULL); + checkIndexReferenced(tdbb, transaction, name.c_str()); + MODIFY IDX IDX.RDB$INDEX_INACTIVE.NULL = FALSE; IDX.RDB$INDEX_INACTIVE = active ? FALSE : TRUE; diff --git a/src/dsql/DdlNodes.h b/src/dsql/DdlNodes.h index 554a39ca350..5169a9b0c48 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 @@ -1324,6 +1325,7 @@ class RelationNode : public DdlNode TYPE_ALTER_COL_NULL, TYPE_ALTER_COL_POS, TYPE_ALTER_COL_TYPE, + TYPE_ALTER_CONSTRAINT, TYPE_DROP_COLUMN, TYPE_DROP_CONSTRAINT, TYPE_ALTER_SQL_SECURITY, @@ -1388,6 +1390,7 @@ class RelationNode : public DdlNode NestConst refAction; NestConst check; bool createIfNotExistsOnly = false; + bool enforced = true; }; struct IdentityOptions @@ -1518,6 +1521,18 @@ class RelationNode : public DdlNode bool silent = false; }; + struct AlterConstraintClause : public Clause + { + explicit AlterConstraintClause(MemoryPool& p) + : Clause(p, TYPE_ALTER_CONSTRAINT), + name(p) + { + } + + MetaName name; + bool enforced = false; + }; + RelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode); static bool deleteLocalField(thread_db* tdbb, jrd_tra* transaction, 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 74501908c8a..e9f12672efc 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -702,6 +702,7 @@ using namespace Firebird; %token ANY_VALUE %token BTRIM %token CALL +%token ENFORCED %token FORMAT %token LTRIM %token NAMED_ARG_ASSIGN @@ -2623,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 @@ -2661,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; } ; @@ -2687,6 +2694,10 @@ table_constraint_definition($relationNode) $2->name = *$1; $$ = $2; } + constraint_characteristics_opt($3) + { + $$ = $3; + } ; %type constraint_name_opt @@ -2785,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; } @@ -4376,13 +4396,21 @@ 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) + | ALTER CONSTRAINT symbol_constraint_name constraint_enforcement + { + RelationNode::AlterConstraintClause* clause = newNode(); + clause->name = *$3; + clause->enforced = $4; + $relationNode->clauses.add(clause); + } | col_opt alter_column_name POSITION pos_short_integer { RelationNode::AlterColPosClause* clause = newNode(); @@ -4512,6 +4540,12 @@ alter_column_name : keyword_or_column ; +%type constraint_enforcement +constraint_enforcement + : NOT ENFORCED { $$ = false; } + | ENFORCED { $$ = true; } + ; + // below are reserved words that could be used as column identifiers // in the previous versions @@ -9735,6 +9769,7 @@ non_reserved_word | UNICODE_VAL // added in FB 6.0 | ANY_VALUE + | ENFORCED | FORMAT | OWNER ; 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 diff --git a/src/isql/show.epp b/src/isql/show.epp index d093e452953..cd407dbb81a 100644 --- a/src/isql/show.epp +++ b/src/isql/show.epp @@ -3030,6 +3030,10 @@ static processing_state show_check(const SCHAR* object) 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(NEWLINE); END_FOR @@ -6193,6 +6197,7 @@ static processing_state show_table(const SCHAR* relation_name, bool isView) ISQL_get_index_segments (collist, sizeof(collist), RELC1.RDB$INDEX_NAME, false); bool isPK = false; bool isUK = false; + bool isFK = false; if (!strncmp (RELC1.RDB$CONSTRAINT_TYPE, "PRIMARY", 7)) { @@ -6208,6 +6213,7 @@ static processing_state show_table(const SCHAR* relation_name, bool isView) } else if (!strncmp (RELC1.RDB$CONSTRAINT_TYPE, "FOREIGN", 7)) { + isFK = true; isqlGlob.printf("CONSTRAINT %s:%s", RELC1.RDB$CONSTRAINT_NAME, NEWLINE); isqlGlob.printf(" Foreign key (%s)", collist); @@ -6232,9 +6238,6 @@ static processing_state show_table(const SCHAR* relation_name, bool isView) IUTILS_truncate_term (REFC.RDB$DELETE_RULE, static_cast(strlen(REFC.RDB$DELETE_RULE))); ISQL_ri_action_print (REFC.RDB$DELETE_RULE, " On Delete", false); } - - isqlGlob.printf(NEWLINE); - END_FOR ON_ERROR ISQL_errmsg (fbStatus); @@ -6242,7 +6245,7 @@ static processing_state show_table(const SCHAR* relation_name, bool isView) END_ERROR; } - if (isPK || isUK) // Special handling for PRIMARY KEY and UNIQUE constraints. + if (isPK || isUK || isFK) // Special handling for RI constraints. { FOR IDX IN RDB$INDICES WITH IDX.RDB$INDEX_NAME = RELC1.RDB$INDEX_NAME @@ -6260,6 +6263,9 @@ static processing_state show_table(const SCHAR* relation_name, bool isView) if (explicit_index) isqlGlob.printf(" %s", RELC1.RDB$INDEX_NAME); + if (IDX.RDB$INDEX_INACTIVE == 1) + isqlGlob.printf(" Not enforced"); + isqlGlob.prints(NEWLINE); END_FOR ON_ERROR @@ -6275,17 +6281,26 @@ static processing_state show_table(const SCHAR* relation_name, bool isView) END_ERROR FOR R_C IN RDB$RELATION_CONSTRAINTS CROSS - C_C IN RDB$CHECK_CONSTRAINTS + C_C IN RDB$CHECK_CONSTRAINTS CROSS + R_F IN RDB$RELATION_FIELDS WITH R_C.RDB$RELATION_NAME EQ relation_name AND R_C.RDB$CONSTRAINT_TYPE EQ 'NOT NULL' AND R_C.RDB$CONSTRAINT_NAME EQ C_C.RDB$CONSTRAINT_NAME + AND R_F.RDB$FIELD_NAME EQ C_C.RDB$TRIGGER_NAME + AND R_F.RDB$RELATION_NAME EQ R_C.RDB$RELATION_NAME if (!fb_utils::implicit_integrity(R_C.RDB$CONSTRAINT_NAME)) { fb_utils::exact_name(C_C.RDB$TRIGGER_NAME); fb_utils::exact_name(R_C.RDB$CONSTRAINT_NAME); isqlGlob.printf("CONSTRAINT %s:%s", R_C.RDB$CONSTRAINT_NAME, NEWLINE); - isqlGlob.printf(" Not Null Column (%s)%s", C_C.RDB$TRIGGER_NAME, NEWLINE); + isqlGlob.printf(" Not Null Column (%s)", C_C.RDB$TRIGGER_NAME); + if (R_F.RDB$NULL_FLAG.NULL == TRUE || R_F.RDB$NULL_FLAG == FALSE) + { + // Despite of constraint existence this field allows NULLs + isqlGlob.printf(" Not enforced"); + } + isqlGlob.printf("%s", NEWLINE); } END_FOR ON_ERROR diff --git a/src/jrd/SystemTriggers.epp b/src/jrd/SystemTriggers.epp index fc3d3d407f9..d3ab19426a9 100644 --- a/src/jrd/SystemTriggers.epp +++ b/src/jrd/SystemTriggers.epp @@ -169,108 +169,6 @@ void beforeDeleteIndex(thread_db* tdbb, Record* record) END_FOR } -void beforeUpdateIndex(thread_db* tdbb, Record* orgRecord, Record* newRecord) -{ - const auto transaction = tdbb->getTransaction(); - - dsc descOldIndexName; - MetaName oldIndexName; - if (EVL_field(nullptr, orgRecord, f_idx_name, &descOldIndexName)) - MOV_get_metaname(tdbb, &descOldIndexName, oldIndexName); - - static const CachedRequestId request1CacheId; - AutoCacheRequest request1(tdbb, request1CacheId); - - FOR (REQUEST_HANDLE request1 TRANSACTION_HANDLE transaction) - RLC IN RDB$RELATION_CONSTRAINTS - WITH RLC.RDB$INDEX_NAME EQ oldIndexName.c_str() - { - dsc descNewIndexName; - MetaName newIndexName; - dsc descOldRelationName, descNewRelationName; - MetaName oldRelationName, newRelationName; - dsc descOldIndexId, descNewIndexId; - SLONG oldIndexId, newIndexId; - dsc descOldSegmentCount, descNewSegmentCount; - SLONG oldSegmentCount, newSegmentCount; - dsc descOldForeignKey, descNewForeignKey; - MetaName oldForeignKey, newForeignKey; - - if (EVL_field(nullptr, newRecord, f_idx_name, &descNewIndexName) && - EVL_field(nullptr, orgRecord, f_idx_relation, &descOldRelationName) && - EVL_field(nullptr, newRecord, f_idx_relation, &descNewRelationName) && - EVL_field(nullptr, orgRecord, f_idx_id, &descOldIndexId) && - EVL_field(nullptr, newRecord, f_idx_id, &descNewIndexId) && - EVL_field(nullptr, orgRecord, f_idx_count, &descOldSegmentCount) && - EVL_field(nullptr, newRecord, f_idx_count, &descNewSegmentCount) && - EVL_field(nullptr, orgRecord, f_idx_foreign, &descOldForeignKey) && - EVL_field(nullptr, newRecord, f_idx_foreign, &descNewForeignKey)) - { - MOV_get_metaname(tdbb, &descNewIndexName, newIndexName); - MOV_get_metaname(tdbb, &descOldRelationName, oldRelationName); - MOV_get_metaname(tdbb, &descNewRelationName, newRelationName); - oldIndexId = MOV_get_long(tdbb, &descOldIndexId, 0); - newIndexId = MOV_get_long(tdbb, &descNewIndexId, 0); - oldSegmentCount = MOV_get_long(tdbb, &descOldSegmentCount, 0); - newSegmentCount = MOV_get_long(tdbb, &descNewSegmentCount, 0); - MOV_get_metaname(tdbb, &descOldForeignKey, oldForeignKey); - MOV_get_metaname(tdbb, &descNewForeignKey, newForeignKey); - - if (oldIndexName != newIndexName || - oldRelationName != newRelationName || - oldIndexId != newIndexId || - oldSegmentCount != newSegmentCount || - oldForeignKey != newForeignKey) - { - ERR_post(Arg::Gds(isc_integ_index_mod)); - } - } - } - END_FOR - - dsc descOldIndexInactive, descNewIndexInactive; - - if (EVL_field(nullptr, newRecord, f_idx_inactive, &descNewIndexInactive) && - MOV_get_long(tdbb, &descNewIndexInactive, 0) == 1 && - (!EVL_field(nullptr, orgRecord, f_idx_inactive, &descOldIndexInactive) || - MOV_get_long(tdbb, &descOldIndexInactive, 0) == 0)) - { - static const CachedRequestId request2CacheId; - AutoCacheRequest request2(tdbb, request2CacheId); - - FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction) - RCL IN RDB$RELATION_CONSTRAINTS - CROSS IND1 IN RDB$INDICES - CROSS IND2 IN RDB$INDICES - WITH RCL.RDB$INDEX_NAME = oldIndexName.c_str() AND - IND1.RDB$INDEX_NAME = RCL.RDB$INDEX_NAME AND - IND2.RDB$FOREIGN_KEY = RCL.RDB$INDEX_NAME - { - ERR_post(Arg::Gds(isc_integ_index_deactivate)); - } - END_FOR - - static const CachedRequestId request3CacheId; - AutoCacheRequest request3(tdbb, request3CacheId); - - FOR (REQUEST_HANDLE request3 TRANSACTION_HANDLE transaction) - RCL IN RDB$RELATION_CONSTRAINTS - WITH RCL.RDB$INDEX_NAME = oldIndexName.c_str() AND - (RCL.RDB$CONSTRAINT_TYPE = PRIMARY_KEY OR - RCL.RDB$CONSTRAINT_TYPE = UNIQUE_CNSTRT OR - RCL.RDB$CONSTRAINT_TYPE = FOREIGN_KEY) - { - fb_utils::exact_name_limit(RCL.RDB$CONSTRAINT_TYPE, sizeof(RCL.RDB$CONSTRAINT_TYPE)); - - if (strcmp(RCL.RDB$CONSTRAINT_TYPE, FOREIGN_KEY) == 0) - ERR_post(Arg::Gds(isc_integ_index_deactivate)); - else - ERR_post(Arg::Gds(isc_integ_deactivate_primary)); - } - END_FOR - } -} - void beforeDeleteIndexSegment(thread_db* tdbb, Record* record) { const auto transaction = tdbb->getTransaction(); @@ -891,80 +789,6 @@ void beforeDeleteTrigger(thread_db* tdbb, Record* record) END_FOR } -void beforeUpdateTrigger(thread_db* tdbb, Record* orgRecord, Record* newRecord) -{ - const auto transaction = tdbb->getTransaction(); - dsc desc; - - if (EVL_field(nullptr, orgRecord, f_trg_sys_flag, &desc) && MOV_get_long(tdbb, &desc, 0) == 1) - ERR_post(Arg::Gds(isc_systrig_update)); - - MetaName oldTriggerName, newTriggerName; - if (EVL_field(nullptr, orgRecord, f_trg_name, &desc)) - MOV_get_metaname(tdbb, &desc, oldTriggerName); - if (EVL_field(nullptr, newRecord, f_trg_name, &desc)) - MOV_get_metaname(tdbb, &desc, newTriggerName); - - MetaName oldRelationName, newRelationName; - if (EVL_field(nullptr, orgRecord, f_trg_rname, &desc)) - MOV_get_metaname(tdbb, &desc, oldRelationName); - if (EVL_field(nullptr, newRecord, f_trg_rname, &desc)) - MOV_get_metaname(tdbb, &desc, newRelationName); - - std::optional oldTriggerSequence, newTriggerSequence; - if (EVL_field(nullptr, orgRecord, f_trg_seq, &desc)) - oldTriggerSequence = MOV_get_long(tdbb, &desc, 0); - if (EVL_field(nullptr, newRecord, f_trg_seq, &desc)) - newTriggerSequence = MOV_get_long(tdbb, &desc, 0); - - std::optional oldTriggerBlr, newTriggerBlr; - if (EVL_field(nullptr, orgRecord, f_trg_blr, &desc)) - oldTriggerBlr = *(bid*) desc.dsc_address; - if (EVL_field(nullptr, newRecord, f_trg_blr, &desc)) - newTriggerBlr = *(bid*) desc.dsc_address; - - std::optional oldTriggerInactive, newTriggerInactive; - if (EVL_field(nullptr, orgRecord, f_trg_inactive, &desc)) - oldTriggerInactive = MOV_get_long(tdbb, &desc, 0); - if (EVL_field(nullptr, newRecord, f_trg_inactive, &desc)) - newTriggerInactive = MOV_get_long(tdbb, &desc, 0); - - std::optional oldFlags, newFlags; - if (EVL_field(nullptr, orgRecord, f_trg_flags, &desc)) - oldFlags = MOV_get_long(tdbb, &desc, 0); - if (EVL_field(nullptr, newRecord, f_trg_flags, &desc)) - newFlags = MOV_get_long(tdbb, &desc, 0); - - std::optional oldDebugInfo, newDebugInfo; - if (EVL_field(nullptr, orgRecord, f_trg_debug_info, &desc)) - oldDebugInfo = *(bid*) desc.dsc_address; - if (EVL_field(nullptr, newRecord, f_trg_debug_info, &desc)) - newDebugInfo = *(bid*) desc.dsc_address; - - static const CachedRequestId requestCacheId; - AutoCacheRequest request(tdbb, requestCacheId); - - FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction) - CHK IN RDB$CHECK_CONSTRAINTS - CROSS RCL IN RDB$RELATION_CONSTRAINTS - WITH CHK.RDB$TRIGGER_NAME = oldTriggerName.c_str() AND - RCL.RDB$CONSTRAINT_NAME = CHK.RDB$CONSTRAINT_NAME AND - RCL.RDB$CONSTRAINT_TYPE = CHECK_CNSTRT - { - if (!(oldTriggerName == newTriggerName && - oldRelationName == newRelationName && - oldTriggerSequence == newTriggerSequence && - oldTriggerBlr == newTriggerBlr && - oldTriggerInactive == newTriggerInactive && - oldFlags == newFlags && - oldDebugInfo == newDebugInfo)) - { - ERR_post(Arg::Gds(isc_check_trig_update)); - } - } - END_FOR -} - void beforeDeleteUserPrivilege(thread_db* tdbb, Record* record) { const auto transaction = tdbb->getTransaction(); @@ -1363,10 +1187,6 @@ void SystemTriggers::executeBeforeUpdateTriggers(thread_db* tdbb, jrd_rel* relat beforeUpdateField(tdbb, orgRecord, newRecord); break; - case rel_indices: - beforeUpdateIndex(tdbb, orgRecord, newRecord); - break; - case rel_rfr: beforeUpdateRelationField(tdbb, orgRecord, newRecord); break; @@ -1374,9 +1194,5 @@ void SystemTriggers::executeBeforeUpdateTriggers(thread_db* tdbb, jrd_rel* relat case rel_segments: beforeUpdateIndexSegment(tdbb, orgRecord, newRecord); break; - - case rel_triggers: - beforeUpdateTrigger(tdbb, orgRecord, newRecord); - break; } } diff --git a/src/jrd/drq.h b/src/jrd/drq.h index d3b3c5a7fba..c5a7e22dbff 100644 --- a/src/jrd/drq.h +++ b/src/jrd/drq.h @@ -255,6 +255,9 @@ enum drq_type_t drq_l_pkg_name, // lookup package name drq_l_rel_con, // lookup relation constraint drq_l_rel_fld_name, // lookup relation field name + drq_get_constr_type, // get constraint type + drq_c_active_fk, // check if active FK references this index + drq_m_check_trgs, // activate/deactivate triggers for check constraint drq_MAX }; diff --git a/src/jrd/ini.epp b/src/jrd/ini.epp index d0d6527971b..8f1192a29c0 100644 --- a/src/jrd/ini.epp +++ b/src/jrd/ini.epp @@ -411,7 +411,7 @@ namespace const char* users[] = {userName, "PUBLIC"}; int grantOptions[] = {WITH_GRANT_OPTION, 0}; - for (unsigned i = 0; i < FB_NELEM(users); i++) + for (int i = 0; i < FB_NELEM(users); i++) { STORE(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction) PRIV IN RDB$USER_PRIVILEGES