Skip to content

Commit

Permalink
ALTER CONSTRAINT clause for constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
aafemt committed Aug 7, 2024
1 parent 0616d8b commit 9abe46e
Show file tree
Hide file tree
Showing 11 changed files with 344 additions and 221 deletions.
47 changes: 47 additions & 0 deletions doc/sql.extensions/README.ddl.txt
Original file line number Diff line number Diff line change
Expand Up @@ -709,3 +709,50 @@ CREATE PACKAGE BODY [IF NOT EXISTS] ...
CREATE [GLOBAL] MAPPING [IF NOT EXISTS] ...
ALTER TABLE <table> ADD [IF NOT EXISTS] <column name> ...
ALTER TABLE <table> ADD CONSTRAINT [IF NOT EXISTS] <constraint name> ...

3) Non-enforced constraints.

CREATE/ALTER TABLE supports creation of non-enforced constraints.

Syntax:

<col_constraint> ::=
[CONSTRAINT constr_name]
{ PRIMARY KEY [<using_index>]
| UNIQUE [<using_index>]
| REFERENCES other_table [(colname)] [<using_index>]
[ON DELETE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}]
[ON UPDATE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}]
| CHECK (<check_condition>)
| NOT NULL }
[<constraint characteristics>]

<tconstraint> ::=
[CONSTRAINT constr_name]
{ PRIMARY KEY (<col_list>) [<using_index>]
| UNIQUE (<col_list>) [<using_index>]
| FOREIGN KEY (<col_list>)
REFERENCES other_table [(<col_list>)] [<using_index>]
[ON DELETE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}]
[ON UPDATE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}]
| CHECK (<check_condition>) }
[<constraint characteristics>]

<constraint characteristics> ::=
<constraint enforcement>

<constraint enforcement> ::=
[ 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 <constraint name> <constraint enforcement>

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.
1 change: 1 addition & 0 deletions src/common/ParserTokens.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
171 changes: 169 additions & 2 deletions src/dsql/DdlNodes.epp
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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:
Expand All @@ -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;
}

Expand All @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -7979,6 +8032,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
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();
Expand Down Expand Up @@ -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;
Expand Down
15 changes: 15 additions & 0 deletions src/dsql/DdlNodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -1300,6 +1300,7 @@ class RelationNode : public DdlNode
const char* refDeleteAction;
Firebird::ObjectsArray<TriggerDefinition> triggers;
Firebird::ObjectsArray<BlrWriter> blrWritersHolder;
bool enforced = true;
};

struct CreateDropConstraint
Expand All @@ -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,
Expand Down Expand Up @@ -1388,6 +1390,7 @@ class RelationNode : public DdlNode
NestConst<RefActionClause> refAction;
NestConst<BoolSourceClause> check;
bool createIfNotExistsOnly = false;
bool enforced = true;
};

struct IdentityOptions
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/dsql/parse-conflicts.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
115 shift/reduce conflicts, 22 reduce/reduce conflicts.
116 shift/reduce conflicts, 22 reduce/reduce conflicts.
Loading

0 comments on commit 9abe46e

Please sign in to comment.