Skip to content

Commit 9abe46e

Browse files
committed
ALTER CONSTRAINT clause for constraints
1 parent 0616d8b commit 9abe46e

File tree

11 files changed

+344
-221
lines changed

11 files changed

+344
-221
lines changed

doc/sql.extensions/README.ddl.txt

+47
Original file line numberDiff line numberDiff line change
@@ -709,3 +709,50 @@ CREATE PACKAGE BODY [IF NOT EXISTS] ...
709709
CREATE [GLOBAL] MAPPING [IF NOT EXISTS] ...
710710
ALTER TABLE <table> ADD [IF NOT EXISTS] <column name> ...
711711
ALTER TABLE <table> ADD CONSTRAINT [IF NOT EXISTS] <constraint name> ...
712+
713+
3) Non-enforced constraints.
714+
715+
CREATE/ALTER TABLE supports creation of non-enforced constraints.
716+
717+
Syntax:
718+
719+
<col_constraint> ::=
720+
[CONSTRAINT constr_name]
721+
{ PRIMARY KEY [<using_index>]
722+
| UNIQUE [<using_index>]
723+
| REFERENCES other_table [(colname)] [<using_index>]
724+
[ON DELETE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}]
725+
[ON UPDATE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}]
726+
| CHECK (<check_condition>)
727+
| NOT NULL }
728+
[<constraint characteristics>]
729+
730+
<tconstraint> ::=
731+
[CONSTRAINT constr_name]
732+
{ PRIMARY KEY (<col_list>) [<using_index>]
733+
| UNIQUE (<col_list>) [<using_index>]
734+
| FOREIGN KEY (<col_list>)
735+
REFERENCES other_table [(<col_list>)] [<using_index>]
736+
[ON DELETE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}]
737+
[ON UPDATE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}]
738+
| CHECK (<check_condition>) }
739+
[<constraint characteristics>]
740+
741+
<constraint characteristics> ::=
742+
<constraint enforcement>
743+
744+
<constraint enforcement> ::=
745+
[ NOT ] ENFORCED
746+
747+
Note: In contrast to ANSI SQL standard PRIMARY KEY and UNIQUE constraint
748+
are allowed to be not enforced.
749+
750+
Also ALTER CONSTRAINT clause is added to ALTER TABLE statement.
751+
752+
Syntax:
753+
754+
ALTER TABLE ALTER CONSTRAINT <constraint name> <constraint enforcement>
755+
756+
Primary and unique keys cannot be deactivated if they are referenced by any active foreign key.
757+
758+
The corresponding ALTER INDEX and ALTER TRIGGER statements are allowed as well.

src/common/ParserTokens.h

+1
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ PARSER_TOKEN(TOK_ENABLE, "ENABLE", true)
197197
PARSER_TOKEN(TOK_ENCRYPT, "ENCRYPT", true)
198198
PARSER_TOKEN(TOK_END, "END", false)
199199
PARSER_TOKEN(TOK_ENGINE, "ENGINE", true)
200+
PARSER_TOKEN(TOK_ENFORCED, "ENFORCED", true)
200201
PARSER_TOKEN(TOK_ENTRY_POINT, "ENTRY_POINT", true)
201202
PARSER_TOKEN(TOK_ESCAPE, "ESCAPE", false)
202203
PARSER_TOKEN(TOK_EXCEPTION, "EXCEPTION", true)

src/dsql/DdlNodes.epp

+169-2
Original file line numberDiff line numberDiff line change
@@ -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

116118
static 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;

src/dsql/DdlNodes.h

+15
Original file line numberDiff line numberDiff line change
@@ -1300,6 +1300,7 @@ class RelationNode : public DdlNode
13001300
const char* refDeleteAction;
13011301
Firebird::ObjectsArray<TriggerDefinition> triggers;
13021302
Firebird::ObjectsArray<BlrWriter> blrWritersHolder;
1303+
bool enforced = true;
13031304
};
13041305

13051306
struct CreateDropConstraint
@@ -1324,6 +1325,7 @@ class RelationNode : public DdlNode
13241325
TYPE_ALTER_COL_NULL,
13251326
TYPE_ALTER_COL_POS,
13261327
TYPE_ALTER_COL_TYPE,
1328+
TYPE_ALTER_CONSTRAINT,
13271329
TYPE_DROP_COLUMN,
13281330
TYPE_DROP_CONSTRAINT,
13291331
TYPE_ALTER_SQL_SECURITY,
@@ -1388,6 +1390,7 @@ class RelationNode : public DdlNode
13881390
NestConst<RefActionClause> refAction;
13891391
NestConst<BoolSourceClause> check;
13901392
bool createIfNotExistsOnly = false;
1393+
bool enforced = true;
13911394
};
13921395

13931396
struct IdentityOptions
@@ -1518,6 +1521,18 @@ class RelationNode : public DdlNode
15181521
bool silent = false;
15191522
};
15201523

1524+
struct AlterConstraintClause : public Clause
1525+
{
1526+
explicit AlterConstraintClause(MemoryPool& p)
1527+
: Clause(p, TYPE_ALTER_CONSTRAINT),
1528+
name(p)
1529+
{
1530+
}
1531+
1532+
MetaName name;
1533+
bool enforced = false;
1534+
};
1535+
15211536
RelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode);
15221537

15231538
static bool deleteLocalField(thread_db* tdbb, jrd_tra* transaction,

src/dsql/parse-conflicts.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
115 shift/reduce conflicts, 22 reduce/reduce conflicts.
1+
116 shift/reduce conflicts, 22 reduce/reduce conflicts.

0 commit comments

Comments
 (0)