diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 424a426..958f7d3 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -6681,6 +6681,26 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint", cmdcon->conname, RelationGetRelationName(rel)))); + /* + * Check which deferrable attributes changed. But consider that if changed + * only initdeferred attribute and to true, force deferrable to be also + * true. On the other hand, if changed only deferrable attribute and to + * false, force initdeferred to be also false. + */ + if (!cmdcon->was_deferrable_set) + cmdcon->deferrable = cmdcon->initdeferred ? true : currcon->condeferrable; + + if (!cmdcon->was_initdeferred_set) + cmdcon->initdeferred = !cmdcon->deferrable ? false : currcon->condeferred; + + /* + * This is a safe check only, should never happen here. + */ + if (cmdcon->initdeferred && !cmdcon->deferrable) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"))); + if (currcon->condeferrable != cmdcon->deferrable || currcon->condeferred != cmdcon->initdeferred) { diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index db492a7..2e0d68b 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2592,6 +2592,8 @@ _copyConstraint(const Constraint *from) COPY_SCALAR_FIELD(deferrable); COPY_SCALAR_FIELD(initdeferred); COPY_LOCATION_FIELD(location); + COPY_SCALAR_FIELD(was_deferrable_set); + COPY_SCALAR_FIELD(was_initdeferred_set); COPY_SCALAR_FIELD(is_no_inherit); COPY_NODE_FIELD(raw_expr); COPY_STRING_FIELD(cooked_expr); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 5c03e9f..78fa6fa 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2843,6 +2843,8 @@ _outConstraint(StringInfo str, const Constraint *node) WRITE_BOOL_FIELD(deferrable); WRITE_BOOL_FIELD(initdeferred); WRITE_LOCATION_FIELD(location); + WRITE_BOOL_FIELD(was_deferrable_set); + WRITE_BOOL_FIELD(was_initdeferred_set); appendStringInfoString(str, " :contype "); switch (node->contype) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index e27d37c..a6bd48b 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -173,7 +173,8 @@ static void SplitColQualList(List *qualList, List **constraintList, CollateClause **collClause, core_yyscan_t yyscanner); static void processCASbits(int cas_bits, int location, const char *constrType, - bool *deferrable, bool *initdeferred, bool *not_valid, + bool *deferrable, bool *was_deferrable_set, + bool *initdeferred, bool *was_initdeferred_set, bool *not_valid, bool *no_inherit, core_yyscan_t yyscanner); static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); @@ -2084,8 +2085,8 @@ alter_table_cmd: c->contype = CONSTR_FOREIGN; /* others not supported, yet */ c->conname = $3; processCASbits($4, @4, "ALTER CONSTRAINT statement", - &c->deferrable, - &c->initdeferred, + &c->deferrable, &c->was_deferrable_set, + &c->initdeferred, &c->was_initdeferred_set, NULL, NULL, yyscanner); $$ = (Node *)n; } @@ -3200,7 +3201,7 @@ ConstraintElem: n->raw_expr = $3; n->cooked_expr = NULL; processCASbits($5, @5, "CHECK", - NULL, NULL, &n->skip_validation, + NULL, NULL, NULL, NULL, &n->skip_validation, &n->is_no_inherit, yyscanner); n->initially_valid = !n->skip_validation; $$ = (Node *)n; @@ -3216,8 +3217,8 @@ ConstraintElem: n->indexname = NULL; n->indexspace = $6; processCASbits($7, @7, "UNIQUE", - &n->deferrable, &n->initdeferred, NULL, - NULL, yyscanner); + &n->deferrable, NULL, &n->initdeferred, NULL, + NULL, NULL, yyscanner); $$ = (Node *)n; } | UNIQUE ExistingIndex ConstraintAttributeSpec @@ -3230,8 +3231,8 @@ ConstraintElem: n->indexname = $2; n->indexspace = NULL; processCASbits($3, @3, "UNIQUE", - &n->deferrable, &n->initdeferred, NULL, - NULL, yyscanner); + &n->deferrable, NULL, &n->initdeferred, NULL, + NULL, NULL, yyscanner); $$ = (Node *)n; } | PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace @@ -3245,8 +3246,8 @@ ConstraintElem: n->indexname = NULL; n->indexspace = $7; processCASbits($8, @8, "PRIMARY KEY", - &n->deferrable, &n->initdeferred, NULL, - NULL, yyscanner); + &n->deferrable, NULL, &n->initdeferred, NULL, + NULL, NULL, yyscanner); $$ = (Node *)n; } | PRIMARY KEY ExistingIndex ConstraintAttributeSpec @@ -3259,8 +3260,8 @@ ConstraintElem: n->indexname = $3; n->indexspace = NULL; processCASbits($4, @4, "PRIMARY KEY", - &n->deferrable, &n->initdeferred, NULL, - NULL, yyscanner); + &n->deferrable, NULL, &n->initdeferred, NULL, + NULL, NULL, yyscanner); $$ = (Node *)n; } | EXCLUDE access_method_clause '(' ExclusionConstraintList ')' @@ -3277,8 +3278,8 @@ ConstraintElem: n->indexspace = $7; n->where_clause = $8; processCASbits($9, @9, "EXCLUDE", - &n->deferrable, &n->initdeferred, NULL, - NULL, yyscanner); + &n->deferrable, NULL, &n->initdeferred, NULL, + NULL, NULL, yyscanner); $$ = (Node *)n; } | FOREIGN KEY '(' columnList ')' REFERENCES qualified_name @@ -3294,7 +3295,8 @@ ConstraintElem: n->fk_upd_action = (char) ($10 >> 8); n->fk_del_action = (char) ($10 & 0xFF); processCASbits($11, @11, "FOREIGN KEY", - &n->deferrable, &n->initdeferred, + &n->deferrable, NULL, + &n->initdeferred, NULL, &n->skip_validation, NULL, yyscanner); n->initially_valid = !n->skip_validation; @@ -4741,8 +4743,9 @@ CreateTrigStmt: n->whenClause = $14; n->isconstraint = TRUE; processCASbits($10, @10, "TRIGGER", - &n->deferrable, &n->initdeferred, NULL, - NULL, yyscanner); + &n->deferrable, NULL, + &n->initdeferred, NULL, + NULL, NULL, yyscanner); n->constrrel = $9; $$ = (Node *)n; } @@ -4992,8 +4995,9 @@ CreateAssertStmt: n->args = list_make1($6); n->isconstraint = TRUE; processCASbits($8, @8, "ASSERTION", - &n->deferrable, &n->initdeferred, NULL, - NULL, yyscanner); + &n->deferrable, NULL, + &n->initdeferred, NULL, + NULL, NULL, yyscanner); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -14759,7 +14763,8 @@ SplitColQualList(List *qualList, */ static void processCASbits(int cas_bits, int location, const char *constrType, - bool *deferrable, bool *initdeferred, bool *not_valid, + bool *deferrable, bool *was_deferrable_set, + bool *initdeferred, bool *was_initdeferred_set, bool *not_valid, bool *no_inherit, core_yyscan_t yyscanner) { /* defaults */ @@ -14770,6 +14775,14 @@ processCASbits(int cas_bits, int location, const char *constrType, if (not_valid) *not_valid = false; + if (was_deferrable_set) + *was_deferrable_set = cas_bits & (CAS_DEFERRABLE + | CAS_NOT_DEFERRABLE) ? true : false; + + if (was_initdeferred_set) + *was_initdeferred_set = cas_bits & (CAS_INITIALLY_DEFERRED + | CAS_INITIALLY_IMMEDIATE) ? true : false; + if (cas_bits & (CAS_DEFERRABLE | CAS_INITIALLY_DEFERRED)) { if (deferrable) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 4a19842..2fbce55 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1822,6 +1822,10 @@ typedef struct Constraint bool initdeferred; /* INITIALLY DEFERRED? */ int location; /* token location, or -1 if unknown */ + /* Fields used by ALTER CONSTRAINT to verify if a change was actually made */ + bool was_deferrable_set; /* Was DEFERRABLE informed? */ + bool was_initdeferred_set; /* Was INITIALLY DEFERRED informed? */ + /* Fields used for constraints with expressions (CHECK and DEFAULT): */ bool is_no_inherit; /* is constraint non-inheritable? */ Node *raw_expr; /* expr, as untransformed parse tree */ diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 6f459df..51b0521 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -654,6 +654,28 @@ ORDER BY 1,2,3; fknd2 | "RI_FKey_check_upd" | 17 | f | f (12 rows) +ALTER TABLE FKTABLE ALTER CONSTRAINT fkdi2 DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE FKTABLE ALTER CONSTRAINT fkdi2; +SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass AND conname = 'fkdi2'; + pg_get_constraintdef +------------------------------------------------------------------------------------------------- + FOREIGN KEY (ftest1) REFERENCES pktable(ptest1) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED +(1 row) + +ALTER TABLE FKTABLE ALTER CONSTRAINT fkdi2 INITIALLY IMMEDIATE; +SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass AND conname = 'fkdi2'; + pg_get_constraintdef +------------------------------------------------------------------------------ + FOREIGN KEY (ftest1) REFERENCES pktable(ptest1) ON DELETE CASCADE DEFERRABLE +(1 row) + +ALTER TABLE FKTABLE ALTER CONSTRAINT fkdi2 NOT DEFERRABLE; +SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass AND conname = 'fkdi2'; + pg_get_constraintdef +------------------------------------------------------------------- + FOREIGN KEY (ftest1) REFERENCES pktable(ptest1) ON DELETE CASCADE +(1 row) + -- temp tables should go away by themselves, need not drop them. -- test check constraint adding create table atacc1 ( test int ); diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 358081b..c6844ec 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -473,6 +473,16 @@ FROM pg_trigger JOIN pg_constraint con ON con.oid = tgconstraint WHERE tgrelid = 'fktable'::regclass ORDER BY 1,2,3; +ALTER TABLE FKTABLE ALTER CONSTRAINT fkdi2 DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE FKTABLE ALTER CONSTRAINT fkdi2; +SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass AND conname = 'fkdi2'; + +ALTER TABLE FKTABLE ALTER CONSTRAINT fkdi2 INITIALLY IMMEDIATE; +SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass AND conname = 'fkdi2'; + +ALTER TABLE FKTABLE ALTER CONSTRAINT fkdi2 NOT DEFERRABLE; +SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass AND conname = 'fkdi2'; + -- temp tables should go away by themselves, need not drop them. -- test check constraint adding