-
-
Notifications
You must be signed in to change notification settings - Fork 243
Non-enforced constraints #8076
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Non-enforced constraints #8076
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -109,6 +109,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"; | ||
|
||
|
@@ -176,6 +178,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). | ||
|
@@ -3482,7 +3525,6 @@ bool TriggerDefinition::modify(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch | |
{ | ||
switch (TRG.RDB$SYSTEM_FLAG) | ||
{ | ||
case fb_sysflag_check_constraint: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (De)activation of a check constraint is done by (de)activation of the corresponding triggers. It must be allowed. |
||
case fb_sysflag_referential_constraint: | ||
case fb_sysflag_view_check: | ||
status_exception::raise(Arg::Gds(isc_dyn_cant_modify_auto_trig)); | ||
|
@@ -6605,11 +6647,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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is wrong There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If NOT NULL constraint is not enforced, notNull flag is set to false. If the constraint is enforced, notNull flag is set to true. What is wrong? |
||
} | ||
// NOT NULL for PRIMARY KEY is always enforced | ||
} | ||
|
||
if (clause->constraintType == AddConstraintClause::CTYPE_NOT_NULL) | ||
{ | ||
break; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not needed braces, remove please |
||
// AddConstraintClause::CTYPE_PK falls into | ||
|
||
case AddConstraintClause::CTYPE_UNIQUE: | ||
|
@@ -6623,6 +6672,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; | ||
} | ||
|
||
|
@@ -6635,6 +6685,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. | ||
|
@@ -6749,6 +6800,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; | ||
|
@@ -6815,7 +6867,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; | ||
|
@@ -7076,6 +7128,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. | ||
|
@@ -7936,6 +7989,118 @@ void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratc | |
break; | ||
} | ||
|
||
case Clause::TYPE_ALTER_CONSTRAINT: | ||
{ | ||
executeBeforeTrigger(); | ||
|
||
const AlterConstraintClause* clause = static_cast<const AlterConstraintClause*>(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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this code works for deactivaiton only ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, but activation of the key doesn't require the check. |
||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this and below requests be cached ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alteration of NOT NULL constraint isn't supposed to be done in mass. These requests don't worth caching. |
||
|
||
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(); | ||
|
@@ -10117,6 +10282,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()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it supposed to run when index is activated ? |
||
|
||
MODIFY IDX | ||
IDX.RDB$INDEX_INACTIVE.NULL = FALSE; | ||
IDX.RDB$INDEX_INACTIVE = active ? FALSE : TRUE; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not clear at all.