@@ -112,6 +112,8 @@ static void updateRdbFields(const TypeClause* type,
112112 SSHORT& fieldPrecisionNull, SSHORT& fieldPrecision,
113113 SSHORT& collationIdNull, SSHORT& collationId,
114114 SSHORT& segmentLengthNull, SSHORT& segmentLength);
115+ static void modifyIndex(thread_db* tdbb, jrd_tra* transaction,
116+ const char* name, bool active);
115117
116118static const char* const CHECK_CONSTRAINT_EXCEPTION = "check_constraint";
117119
@@ -179,6 +181,47 @@ void ExecInSecurityDb::executeInSecurityDb(jrd_tra* localTransaction)
179181
180182//----------------------
181183
184+ // Activate/deactivate given index
185+ static void modifyIndex(thread_db* tdbb, jrd_tra* transaction,
186+ const char* name, bool active)
187+ {
188+ AutoCacheRequest request(tdbb, drq_m_index, DYN_REQUESTS);
189+
190+ bool found = false;
191+ FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
192+ IDX IN RDB$INDICES
193+ WITH IDX.RDB$INDEX_NAME EQ name
194+ {
195+ found = true;
196+ MODIFY IDX
197+ IDX.RDB$INDEX_INACTIVE.NULL = FALSE;
198+ IDX.RDB$INDEX_INACTIVE = active ? FALSE : TRUE;
199+ END_MODIFY
200+ }
201+ END_FOR
202+
203+ if (!found)
204+ {
205+ // msg 48: "Index not found"
206+ status_exception::raise(Arg::PrivateDyn(48));
207+ }
208+ }
209+
210+ // Check if given index is referenced by active foreign key constraint
211+ static void checkIndexReferenced(thread_db* tdbb, jrd_tra* transaction, const char* name)
212+ {
213+ AutoCacheRequest fkCheck(tdbb, drq_c_active_fk, DYN_REQUESTS);
214+
215+ FOR(REQUEST_HANDLE fkCheck TRANSACTION_HANDLE transaction)
216+ IDX IN RDB$INDICES
217+ WITH IDX.RDB$FOREIGN_KEY EQ name AND
218+ IDX.RDB$INDEX_INACTIVE EQ 0 OR IDX.RDB$INDEX_INACTIVE MISSING
219+ {
220+ // MSG 408: "Can't deactivate index used by an integrity constraint"
221+ status_exception::raise(Arg::Gds(isc_integ_index_deactivate));
222+ }
223+ END_FOR
224+ }
182225
183226// Check temporary table reference rules between given child relation and master
184227// relation (owner of given PK/UK index).
@@ -3525,7 +3568,6 @@ bool TriggerDefinition::modify(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch
35253568 {
35263569 switch (TRG.RDB$SYSTEM_FLAG)
35273570 {
3528- case fb_sysflag_check_constraint:
35293571 case fb_sysflag_referential_constraint:
35303572 case fb_sysflag_view_check:
35313573 status_exception::raise(Arg::Gds(isc_dyn_cant_modify_auto_trig));
@@ -6648,11 +6690,18 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra
66486690 constraint.create = FB_NEW_POOL(pool) Constraint(pool);
66496691 constraint.create->type = Constraint::TYPE_NOT_NULL;
66506692 if (clause->constraintType == AddConstraintClause::CTYPE_NOT_NULL)
6693+ {
66516694 constraint.name = clause->name;
6695+ constraint.create->enforced = clause->enforced;
6696+ *notNull = clause->enforced;
6697+ }
6698+ // NOT NULL for PRIMARY KEY is always enforced
66526699 }
66536700
66546701 if (clause->constraintType == AddConstraintClause::CTYPE_NOT_NULL)
6702+ {
66556703 break;
6704+ }
66566705 // AddConstraintClause::CTYPE_PK falls into
66576706
66586707 case AddConstraintClause::CTYPE_UNIQUE:
@@ -6666,6 +6715,7 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra
66666715 if (constraint.create->index && constraint.create->index->name.isEmpty())
66676716 constraint.create->index->name = constraint.name;
66686717 constraint.create->columns = clause->columns;
6718+ constraint.create->enforced = clause->enforced;
66696719 break;
66706720 }
66716721
@@ -6678,6 +6728,7 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra
66786728 constraint.create->columns = clause->columns;
66796729 constraint.create->refRelation = clause->refRelation;
66806730 constraint.create->refColumns = clause->refColumns;
6731+ constraint.create->enforced = clause->enforced;
66816732
66826733 // If there is a referenced table name but no referenced field names, the
66836734 // primary key of the referenced table designates the referenced fields.
@@ -6792,6 +6843,7 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra
67926843 CreateDropConstraint& constraint = constraints.add();
67936844 constraint.create = FB_NEW_POOL(pool) Constraint(pool);
67946845 constraint.create->type = Constraint::TYPE_CHECK;
6846+ constraint.create->enforced = clause->enforced;
67956847 constraint.name = clause->name;
67966848 defineCheckConstraint(dsqlScratch, *constraint.create, clause->check);
67976849 break;
@@ -6858,7 +6910,7 @@ void RelationNode::defineConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlSc
68586910 definition.unique = constraint.type != Constraint::TYPE_FK;
68596911 if (constraint.index->descending)
68606912 definition.descending = true;
6861- definition.inactive = false ;
6913+ definition.inactive = !constraint.enforced ;
68626914 definition.columns = constraint.columns;
68636915 definition.refRelation = constraint.refRelation;
68646916 definition.refColumns = constraint.refColumns;
@@ -7119,6 +7171,7 @@ void RelationNode::defineCheckConstraintTrigger(DsqlCompilerScratch* dsqlScratch
71197171 trigger.type = triggerType;
71207172 trigger.source = clause->source;
71217173 trigger.blrData = blrWriter.getBlrData();
7174+ trigger.active = constraint.enforced;
71227175}
71237176
71247177// 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
79798032 break;
79808033 }
79818034
8035+ case Clause::TYPE_ALTER_CONSTRAINT:
8036+ {
8037+ executeBeforeTrigger();
8038+
8039+ const AlterConstraintClause* clause = static_cast<const AlterConstraintClause*>(i->getObject());
8040+ AutoCacheRequest request(tdbb, drq_get_constr_type, DYN_REQUESTS);
8041+ bool found = false;
8042+
8043+ FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
8044+ RC IN RDB$RELATION_CONSTRAINTS
8045+ WITH RC.RDB$CONSTRAINT_NAME EQ clause->name.c_str() AND
8046+ RC.RDB$RELATION_NAME EQ name.c_str()
8047+ {
8048+ found = true;
8049+ fb_utils::exact_name(RC.RDB$CONSTRAINT_TYPE);
8050+ if (strcmp(RC.RDB$CONSTRAINT_TYPE, PRIMARY_KEY) == 0 ||
8051+ strcmp(RC.RDB$CONSTRAINT_TYPE, UNIQUE_CNSTRT) == 0)
8052+ {
8053+ // Deactivation of primary/unique key requires check for active foreign keys
8054+ checkIndexReferenced(tdbb, transaction, RC.RDB$INDEX_NAME);
8055+ modifyIndex(tdbb, transaction, RC.RDB$INDEX_NAME, clause->enforced);
8056+ }
8057+ else if (strcmp(RC.RDB$CONSTRAINT_TYPE, FOREIGN_KEY) == 0)
8058+ {
8059+ // Activation of foreign key requires check for active partner which is done on index activation
8060+ // so there is nothing to check here
8061+ modifyIndex(tdbb, transaction, RC.RDB$INDEX_NAME, clause->enforced);
8062+ }
8063+ else if (strcmp(RC.RDB$CONSTRAINT_TYPE, CHECK_CNSTRT) == 0)
8064+ {
8065+ AutoCacheRequest requestHandle(tdbb, drq_m_check_trgs, DYN_REQUESTS);
8066+
8067+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
8068+ TRG IN RDB$TRIGGERS CROSS
8069+ CHK IN RDB$CHECK_CONSTRAINTS
8070+ WITH TRG.RDB$RELATION_NAME EQ name.c_str() AND
8071+ TRG.RDB$TRIGGER_NAME EQ CHK.RDB$TRIGGER_NAME AND
8072+ CHK.RDB$CONSTRAINT_NAME EQ clause->name.c_str()
8073+ {
8074+ MODIFY TRG
8075+ TRG.RDB$TRIGGER_INACTIVE = clause->enforced ? FALSE : TRUE;
8076+ END_MODIFY
8077+ }
8078+ END_FOR
8079+ }
8080+ else if (strcmp(RC.RDB$CONSTRAINT_TYPE, NOT_NULL_CNSTRT) == 0)
8081+ {
8082+ AutoRequest requestHandle;
8083+
8084+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
8085+ CHK IN RDB$CHECK_CONSTRAINTS CROSS
8086+ RF IN RDB$RELATION_FIELDS
8087+ WITH CHK.RDB$CONSTRAINT_NAME EQ clause->name.c_str() AND
8088+ CHK.RDB$TRIGGER_NAME EQ RF.RDB$FIELD_NAME AND
8089+ RF.RDB$RELATION_NAME EQ name.c_str()
8090+ {
8091+ // Identity column cannot be NULL-able.
8092+ if (RF.RDB$IDENTITY_TYPE.NULL == FALSE)
8093+ {
8094+ fb_utils::exact_name(RF.RDB$FIELD_NAME);
8095+ // msg 274: Identity column @1 of table @2 cannot be changed to NULLable
8096+ status_exception::raise(Arg::PrivateDyn(274) << RF.RDB$FIELD_NAME << name.c_str());
8097+ }
8098+
8099+ // Column of an active primary key cannot be nullable
8100+ AutoRequest request3;
8101+
8102+ FOR (REQUEST_HANDLE request3 TRANSACTION_HANDLE transaction)
8103+ ISG IN RDB$INDEX_SEGMENTS CROSS
8104+ IDX IN RDB$INDICES CROSS
8105+ RC2 IN RDB$RELATION_CONSTRAINTS
8106+ WITH ISG.RDB$FIELD_NAME EQ RF.RDB$FIELD_NAME AND
8107+ ISG.RDB$INDEX_NAME EQ IDX.RDB$INDEX_NAME AND
8108+ IDX.RDB$RELATION_NAME EQ name.c_str() AND
8109+ (IDX.RDB$INDEX_INACTIVE EQ 0 OR IDX.RDB$INDEX_INACTIVE MISSING) AND
8110+ RC2.RDB$INDEX_NAME EQ IDX.RDB$INDEX_NAME AND
8111+ RC2.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY
8112+ {
8113+ status_exception::raise(Arg::Gds(isc_primary_key_notnull));
8114+ }
8115+ END_FOR
8116+
8117+ // Otherwise it is fine
8118+ MODIFY RF
8119+ if (clause->enforced)
8120+ {
8121+ RF.RDB$NULL_FLAG.NULL = FALSE;
8122+ RF.RDB$NULL_FLAG = TRUE;
8123+ }
8124+ else
8125+ {
8126+ RF.RDB$NULL_FLAG.NULL = TRUE;
8127+ RF.RDB$NULL_FLAG = FALSE; // For symmetry
8128+ }
8129+ END_MODIFY
8130+ }
8131+ END_FOR
8132+ }
8133+ else
8134+ status_exception::raise(Arg::Gds(isc_wish_list) << Arg::Gds(isc_ref_cnstrnt_update));
8135+ }
8136+ END_FOR
8137+
8138+ if (!found)
8139+ {
8140+ // msg 130: "CONSTRAINT %s does not exist."
8141+ status_exception::raise(Arg::PrivateDyn(130) << clause->name);
8142+ }
8143+
8144+ break;
8145+ }
8146+
79828147 case Clause::TYPE_ALTER_SQL_SECURITY:
79838148 {
79848149 executeBeforeTrigger();
@@ -10152,6 +10317,8 @@ void AlterIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
1015210317 executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_INDEX,
1015310318 name, NULL);
1015410319
10320+ checkIndexReferenced(tdbb, transaction, name.c_str());
10321+
1015510322 MODIFY IDX
1015610323 IDX.RDB$INDEX_INACTIVE.NULL = FALSE;
1015710324 IDX.RDB$INDEX_INACTIVE = active ? FALSE : TRUE;
0 commit comments