Skip to content
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

Non-enforced constraints #8076

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading