@@ -109,6 +109,8 @@ static void updateRdbFields(const TypeClause* type,
109109 SSHORT& fieldPrecisionNull, SSHORT& fieldPrecision,
110110 SSHORT& collationIdNull, SSHORT& collationId,
111111 SSHORT& segmentLengthNull, SSHORT& segmentLength);
112+ static void modifyIndex(thread_db* tdbb, jrd_tra* transaction,
113+ const char* name, bool active);
112114
113115static const char* const CHECK_CONSTRAINT_EXCEPTION = "check_constraint";
114116
@@ -177,6 +179,50 @@ void ExecInSecurityDb::executeInSecurityDb(jrd_tra* localTransaction)
177179//----------------------
178180
179181
182+ // Activate/deactivate given index
183+ static void modifyIndex(thread_db* tdbb, jrd_tra* transaction,
184+ const QualifiedName& name, bool active)
185+ {
186+ AutoCacheRequest request(tdbb, drq_m_index, DYN_REQUESTS);
187+
188+ bool found = false;
189+ FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
190+ IDX IN RDB$INDICES
191+ WITH IDX.RDB$SCHEMA_NAME EQUIV name.schema.c_str() AND
192+ IDX.RDB$INDEX_NAME EQ name.object.c_str()
193+ {
194+ found = true;
195+ MODIFY IDX
196+ IDX.RDB$INDEX_INACTIVE.NULL = FALSE;
197+ IDX.RDB$INDEX_INACTIVE = active ? FALSE : TRUE;
198+ END_MODIFY
199+ }
200+ END_FOR
201+
202+ if (!found)
203+ {
204+ // msg 48: "Index not found"
205+ status_exception::raise(Arg::PrivateDyn(48) << Arg::Gds(isc_index_name) << name.toQuotedString());
206+ }
207+ }
208+
209+ // Check if given index is referenced by active foreign key constraint
210+ static void checkIndexReferenced(thread_db* tdbb, jrd_tra* transaction, const QualifiedName& name)
211+ {
212+ AutoCacheRequest fkCheck(tdbb, drq_c_active_fk, DYN_REQUESTS);
213+
214+ FOR(REQUEST_HANDLE fkCheck TRANSACTION_HANDLE transaction)
215+ IDX IN RDB$INDICES
216+ WITH IDX.RDB$FOREIGN_KEY_SCHEMA_NAME EQ name.schema.c_str() AND
217+ IDX.RDB$FOREIGN_KEY EQ name.object.c_str() 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+ }
225+
180226// Check temporary table reference rules between given child relation and master
181227// relation (owner of given PK/UK index).
182228static void checkForeignKeyTempScope(thread_db* tdbb, jrd_tra* transaction,
@@ -3616,7 +3662,6 @@ bool TriggerDefinition::modify(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch
36163662 {
36173663 switch (TRG.RDB$SYSTEM_FLAG)
36183664 {
3619- case fb_sysflag_check_constraint:
36203665 case fb_sysflag_referential_constraint:
36213666 case fb_sysflag_view_check:
36223667 status_exception::raise(Arg::Gds(isc_dyn_cant_modify_auto_trig));
@@ -6440,6 +6485,7 @@ DdlNode* RelationNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
64406485 case Clause::TYPE_ALTER_COL_NAME:
64416486 case Clause::TYPE_ALTER_COL_NULL:
64426487 case Clause::TYPE_ALTER_COL_POS:
6488+ case Clause::TYPE_ALTER_CONSTRAINT:
64436489 case Clause::TYPE_DROP_COLUMN:
64446490 case Clause::TYPE_DROP_CONSTRAINT:
64456491 case Clause::TYPE_ALTER_SQL_SECURITY:
@@ -6888,7 +6934,12 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra
68886934 constraint.create = FB_NEW_POOL(pool) Constraint(pool);
68896935 constraint.create->type = Constraint::TYPE_NOT_NULL;
68906936 if (clause->constraintType == AddConstraintClause::CTYPE_NOT_NULL)
6937+ {
68916938 constraint.name = clause->name;
6939+ constraint.create->enforced = clause->enforced;
6940+ *notNull = clause->enforced;
6941+ }
6942+ // NOT NULL for PRIMARY KEY is always enforced
68926943 }
68936944
68946945 if (clause->constraintType == AddConstraintClause::CTYPE_NOT_NULL)
@@ -6908,6 +6959,7 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra
69086959 constraint.create->index->name = constraint.name;
69096960
69106961 constraint.create->columns = clause->columns;
6962+ constraint.create->enforced = clause->enforced;
69116963 break;
69126964 }
69136965
@@ -6920,6 +6972,7 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra
69206972 constraint.create->columns = clause->columns;
69216973 constraint.create->refRelation = clause->refRelation;
69226974 constraint.create->refColumns = clause->refColumns;
6975+ constraint.create->enforced = clause->enforced;
69236976
69246977 // If there is a referenced table name but no referenced field names, the
69256978 // primary key of the referenced table designates the referenced fields.
@@ -7035,6 +7088,7 @@ void RelationNode::makeConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScra
70357088 CreateDropConstraint& constraint = constraints.add();
70367089 constraint.create = FB_NEW_POOL(pool) Constraint(pool);
70377090 constraint.create->type = Constraint::TYPE_CHECK;
7091+ constraint.create->enforced = clause->enforced;
70387092 constraint.name = clause->name;
70397093 defineCheckConstraint(dsqlScratch, *constraint.create, clause->check);
70407094 break;
@@ -7106,7 +7160,7 @@ void RelationNode::defineConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlSc
71067160 definition.unique = constraint.type != Constraint::TYPE_FK;
71077161 if (constraint.index->descending)
71087162 definition.descending = true;
7109- definition.inactive = false ;
7163+ definition.inactive = !constraint.enforced ;
71107164 definition.columns = constraint.columns;
71117165 definition.refRelation = constraint.refRelation;
71127166 definition.refColumns = constraint.refColumns;
@@ -7381,6 +7435,7 @@ void RelationNode::defineCheckConstraintTrigger(DsqlCompilerScratch* dsqlScratch
73817435 trigger.type = triggerType;
73827436 trigger.source = clause->source;
73837437 trigger.blrData = blrWriter.getBlrData();
7438+ trigger.active = constraint.enforced;
73847439}
73857440
73867441// Define "on delete|update set default" trigger (for referential integrity) along with its blr.
@@ -8292,6 +8347,123 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc
82928347 break;
82938348 }
82948349
8350+ case Clause::TYPE_ALTER_CONSTRAINT:
8351+ {
8352+ executeBeforeTrigger();
8353+
8354+ const AlterConstraintClause* clause = static_cast<const AlterConstraintClause*>(i->getObject());
8355+ AutoCacheRequest request(tdbb, drq_get_constr_type, DYN_REQUESTS);
8356+ bool found = false;
8357+
8358+ FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
8359+ RC IN RDB$RELATION_CONSTRAINTS
8360+ WITH RC.RDB$CONSTRAINT_NAME EQ clause->name.c_str() AND
8361+ RC.RDB$SCHEMA_NAME EQ name.schema.c_str() AND
8362+ RC.RDB$RELATION_NAME EQ name.object.c_str()
8363+ {
8364+ found = true;
8365+ QualifiedName indexName(RC.RDB$INDEX_NAME, RC.RDB$SCHEMA_NAME);
8366+ fb_utils::exact_name(RC.RDB$CONSTRAINT_TYPE);
8367+ if (strcmp(RC.RDB$CONSTRAINT_TYPE, PRIMARY_KEY) == 0 ||
8368+ strcmp(RC.RDB$CONSTRAINT_TYPE, UNIQUE_CNSTRT) == 0)
8369+ {
8370+ // Deactivation of primary/unique key requires check for active foreign keys
8371+ checkIndexReferenced(tdbb, transaction, indexName);
8372+ modifyIndex(tdbb, transaction, indexName, clause->enforced);
8373+ }
8374+ else if (strcmp(RC.RDB$CONSTRAINT_TYPE, FOREIGN_KEY) == 0)
8375+ {
8376+ // Activation of foreign key requires check for active partner which is done on index activation
8377+ // so there is nothing to check here
8378+ modifyIndex(tdbb, transaction, indexName, clause->enforced);
8379+ }
8380+ else if (strcmp(RC.RDB$CONSTRAINT_TYPE, CHECK_CNSTRT) == 0)
8381+ {
8382+ AutoCacheRequest requestHandle(tdbb, drq_m_check_trgs, DYN_REQUESTS);
8383+
8384+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
8385+ TRG IN RDB$TRIGGERS CROSS
8386+ CHK IN RDB$CHECK_CONSTRAINTS
8387+ WITH TRG.RDB$SCHEMA_NAME EQ name.schema.c_str() AND
8388+ TRG.RDB$RELATION_NAME EQ name.object.c_str() AND
8389+ TRG.RDB$SCHEMA_NAME EQ CHK.RDB$SCHEMA_NAME AND
8390+ TRG.RDB$TRIGGER_NAME EQ CHK.RDB$TRIGGER_NAME AND
8391+ CHK.RDB$CONSTRAINT_NAME EQ clause->name.c_str()
8392+ {
8393+ MODIFY TRG
8394+ TRG.RDB$TRIGGER_INACTIVE = clause->enforced ? FALSE : TRUE;
8395+ END_MODIFY
8396+ }
8397+ END_FOR
8398+ }
8399+ else if (strcmp(RC.RDB$CONSTRAINT_TYPE, NOT_NULL_CNSTRT) == 0)
8400+ {
8401+ AutoRequest requestHandle;
8402+
8403+ FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
8404+ CHK IN RDB$CHECK_CONSTRAINTS CROSS
8405+ RF IN RDB$RELATION_FIELDS
8406+ WITH CHK.RDB$CONSTRAINT_NAME EQ clause->name.c_str() AND
8407+ CHK.RDB$SCHEMA_NAME EQ RF.RDB$SCHEMA_NAME AND
8408+ CHK.RDB$TRIGGER_NAME EQ RF.RDB$FIELD_NAME AND
8409+ RF.RDB$SCHEMA_NAME EQ name.schema.c_str() AND
8410+ RF.RDB$RELATION_NAME EQ name.object.c_str()
8411+ {
8412+ // Identity column cannot be NULL-able.
8413+ if (RF.RDB$IDENTITY_TYPE.NULL == FALSE)
8414+ {
8415+ fb_utils::exact_name(RF.RDB$FIELD_NAME);
8416+ // msg 274: Identity column @1 of table @2 cannot be changed to NULLable
8417+ status_exception::raise(Arg::PrivateDyn(274) << RF.RDB$FIELD_NAME << name.toQuotedString());
8418+ }
8419+
8420+ // Column of an active primary key cannot be nullable
8421+ AutoRequest request3;
8422+
8423+ FOR (REQUEST_HANDLE request3 TRANSACTION_HANDLE transaction)
8424+ ISG IN RDB$INDEX_SEGMENTS
8425+ CROSS IDX IN RDB$INDICES OVER RDB$SCHEMA_NAME, RDB$INDEX_NAME
8426+ CROSS RC2 IN RDB$RELATION_CONSTRAINTS OVER RDB$SCHEMA_NAME, RDB$INDEX_NAME
8427+ WITH ISG.RDB$FIELD_NAME EQ RF.RDB$FIELD_NAME AND
8428+ IDX.RDB$SCHEMA_NAME EQ name.schema.c_str() AND
8429+ IDX.RDB$RELATION_NAME EQ name.object.c_str() AND
8430+ (IDX.RDB$INDEX_INACTIVE EQ 0 OR IDX.RDB$INDEX_INACTIVE MISSING) AND
8431+ RC2.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY
8432+ {
8433+ status_exception::raise(Arg::Gds(isc_primary_key_notnull));
8434+ }
8435+ END_FOR
8436+
8437+ // Otherwise it is fine
8438+ MODIFY RF
8439+ if (clause->enforced)
8440+ {
8441+ RF.RDB$NULL_FLAG.NULL = FALSE;
8442+ RF.RDB$NULL_FLAG = TRUE;
8443+ }
8444+ else
8445+ {
8446+ RF.RDB$NULL_FLAG.NULL = TRUE;
8447+ RF.RDB$NULL_FLAG = FALSE; // For symmetry
8448+ }
8449+ END_MODIFY
8450+ }
8451+ END_FOR
8452+ }
8453+ else
8454+ status_exception::raise(Arg::Gds(isc_wish_list) << Arg::Gds(isc_ref_cnstrnt_update));
8455+ }
8456+ END_FOR
8457+
8458+ if (!found)
8459+ {
8460+ // msg 130: "CONSTRAINT %s does not exist."
8461+ status_exception::raise(Arg::PrivateDyn(130) << clause->name);
8462+ }
8463+
8464+ break;
8465+ }
8466+
82958467 case Clause::TYPE_ALTER_SQL_SECURITY:
82968468 {
82978469 executeBeforeTrigger();
@@ -10569,6 +10741,8 @@ void AlterIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
1056910741
1057010742 executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_INDEX, name, {});
1057110743
10744+ checkIndexReferenced(tdbb, transaction, name);
10745+
1057210746 MODIFY IDX
1057310747 IDX.RDB$INDEX_INACTIVE.NULL = FALSE;
1057410748 IDX.RDB$INDEX_INACTIVE = active ? FALSE : TRUE;
0 commit comments