[Re-send; first attempt appears to have hit /dev/null somewhere. My apologies if you get two copies.]
I've finally gotten around to rebasing this patch and making the change that was requested, which was: merge the now-would-be-three deferral- related bool columns in various pg_catalog tables into one char column. Instead of (deferrable, initdeferred, alwaysdeferred), now there is just (deferral). All tests (make check) pass. Sorry for the delay in doing this! Incidentally, I had to do commit-by-commit rebasing to make the rebase easier. I have a shell function I use for this, if anyone wants a copy of it -- sometimes it's much easier to do this than to do one huge jump. Nico --
>From 20323d6f19601f5471eb4db7570af8d3342b627d Mon Sep 17 00:00:00 2001 From: Nicolas Williams <n...@cryptonector.com> Date: Tue, 3 Oct 2017 00:33:09 -0500 Subject: [PATCH] Add ALWAYS DEFERRED option for CONSTRAINTs and CONSTRAINT TRIGGERs. This is important so that one can have triggers and constraints that must run after all of the user/client's statements in a transaction (i.e., at COMMIT time), so that the user/client may make no further changes (triggers, of course, still can). --- doc/src/sgml/catalogs.sgml | 39 ++++----- doc/src/sgml/ref/alter_table.sgml | 4 +- doc/src/sgml/ref/create_table.sgml | 10 ++- doc/src/sgml/ref/create_trigger.sgml | 2 +- doc/src/sgml/trigger.sgml | 3 +- src/backend/bootstrap/bootparse.y | 6 +- src/backend/catalog/heap.c | 3 +- src/backend/catalog/index.c | 27 +++--- src/backend/catalog/information_schema.sql | 12 ++- src/backend/catalog/pg_constraint.c | 14 ++- src/backend/commands/indexcmds.c | 6 +- src/backend/commands/tablecmds.c | 59 +++++-------- src/backend/commands/trigger.c | 42 +++++---- src/backend/commands/typecmds.c | 5 +- src/backend/nodes/copyfuncs.c | 9 +- src/backend/nodes/equalfuncs.c | 9 +- src/backend/nodes/outfuncs.c | 10 ++- src/backend/parser/gram.y | 136 +++++++++++++++++++---------- src/backend/parser/parse_utilcmd.c | 82 +++++++++++------ src/backend/utils/adt/ruleutils.c | 14 +-- src/bin/pg_dump/pg_dump.c | 66 +++++++------- src/bin/pg_dump/pg_dump.h | 8 +- src/bin/psql/describe.c | 51 +++++------ src/bin/psql/tab-complete.c | 4 +- src/include/catalog/index.h | 5 +- src/include/catalog/pg_constraint.h | 6 +- src/include/catalog/pg_trigger.h | 7 +- src/include/commands/trigger.h | 1 + src/include/nodes/parsenodes.h | 12 ++- src/include/utils/reltrigger.h | 3 +- src/test/regress/expected/alter_table.out | 60 ++++++------- src/test/regress/input/constraints.source | 51 +++++++++++ src/test/regress/output/constraints.source | 50 +++++++++++ src/test/regress/sql/alter_table.sql | 4 +- 34 files changed, 487 insertions(+), 333 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 3ed9021..e82e39b 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -2239,17 +2239,15 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </row> <row> - <entry><structfield>condeferrable</structfield></entry> - <entry><type>bool</type></entry> - <entry></entry> - <entry>Is the constraint deferrable?</entry> - </row> - - <row> - <entry><structfield>condeferred</structfield></entry> - <entry><type>bool</type></entry> + <entry><structfield>condeferral</structfield></entry> + <entry><type>char</type></entry> <entry></entry> - <entry>Is the constraint deferred by default?</entry> + <entry>Constraint deferral option: + <literal>a</literal> = always deferred, + <literal>d</literal> = deferrable, + <literal>d</literal> = deferrable initially deferred, + <literal>n</literal> = not deferrable + </entry> </row> <row> @@ -7044,17 +7042,16 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l </row> <row> - <entry><structfield>tgdeferrable</structfield></entry> - <entry><type>bool</type></entry> - <entry></entry> - <entry>True if constraint trigger is deferrable</entry> - </row> - - <row> - <entry><structfield>tginitdeferred</structfield></entry> - <entry><type>bool</type></entry> + <entry><structfield>tgdeferrral</structfield></entry> + <entry><type>char</type></entry> <entry></entry> - <entry>True if constraint trigger is initially deferred</entry> + <entry> + <structfield>tgdeferral</structfield> is + <literal>a</literal>always deferred, + <literal>d</literal>deferrable, + <literal>i</literal>deferrable initially deferred, + <literal>n</literal>not deferrable. + </entry> </row> <row> @@ -7119,7 +7116,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l <para> When <structfield>tgconstraint</structfield> is nonzero, <structfield>tgconstrrelid</structfield>, <structfield>tgconstrindid</structfield>, - <structfield>tgdeferrable</structfield>, and <structfield>tginitdeferred</structfield> are + and <structfield>tgdeferral</structfield> are largely redundant with the referenced <structname>pg_constraint</structname> entry. However, it is possible for a non-deferrable trigger to be associated with a deferrable constraint: foreign key constraints can have some diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 1cce00e..fd28a0d 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -55,7 +55,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN } ADD <replaceable class="parameter">table_constraint</replaceable> [ NOT VALID ] ADD <replaceable class="parameter">table_constraint_using_index</replaceable> - ALTER CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] + ALTER CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> [ DEFERRABLE | NOT DEFERRABLE | ALWAYS DEFERRED ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] VALIDATE CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> DROP CONSTRAINT [ IF EXISTS ] <replaceable class="parameter">constraint_name</replaceable> [ RESTRICT | CASCADE ] DISABLE TRIGGER [ <replaceable class="parameter">trigger_name</replaceable> | ALL | USER ] @@ -121,7 +121,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM [ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ] { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="parameter">index_name</replaceable> - [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] + [ DEFERRABLE | NOT DEFERRABLE | ALWAYS DEFERRED ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] <phrase><replaceable class="parameter">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase> diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 2a1eac9..780b981 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -67,7 +67,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> | REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ] } -[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] +[ DEFERRABLE | NOT DEFERRABLE | ALWAYS DEFERRED ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] <phrase>and <replaceable class="parameter">table_constraint</replaceable> is:</phrase> @@ -78,7 +78,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] | FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ] } -[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] +[ DEFERRABLE | NOT DEFERRABLE | ALWAYS DEFERRED ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] <phrase>and <replaceable class="parameter">like_option</replaceable> is:</phrase> @@ -1040,13 +1040,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM <varlistentry> <term><literal>DEFERRABLE</literal></term> <term><literal>NOT DEFERRABLE</literal></term> + <term><literal>ALWAYS DEFERRED</literal></term> <listitem> <para> - This controls whether the constraint can be deferred. A + This controls whether the constraint can be deferred or made immediate. A constraint that is not deferrable will be checked immediately after every command. Checking of constraints that are deferrable can be postponed until the end of the transaction (using the <xref linkend="sql-set-constraints"/> command). + Checking of constraints that are always deferred is always + postponed until the end of the transaction, and this may not be + altered with the <xref linkend="sql-set-constraints"/> command. <literal>NOT DEFERRABLE</literal> is the default. Currently, only <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, and diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml index 7b971ee..d823091 100644 --- a/doc/src/sgml/ref/create_trigger.sgml +++ b/doc/src/sgml/ref/create_trigger.sgml @@ -29,7 +29,7 @@ PostgreSQL documentation CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] } ON <replaceable class="parameter">table_name</replaceable> [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ] - [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ] + [ [ NOT DEFERRABLE | DEFERRABLE | ALWAYS DEFERRED ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ] [ REFERENCING { { OLD | NEW } TABLE [ AS ] <replaceable class="parameter">transition_relation_name</replaceable> } [ ... ] ] [ FOR [ EACH ] { ROW | STATEMENT } ] [ WHEN ( <replaceable class="parameter">condition</replaceable> ) ] diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml index c43dbc9..4f1234e30 100644 --- a/doc/src/sgml/trigger.sgml +++ b/doc/src/sgml/trigger.sgml @@ -673,8 +673,7 @@ typedef struct Trigger Oid tgconstrrelid; Oid tgconstrindid; Oid tgconstraint; - bool tgdeferrable; - bool tginitdeferred; + char tgdeferral; int16 tgnargs; int16 tgnattr; int16 *tgattr; diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index 4c72989..587d035 100644 --- a/src/backend/bootstrap/bootparse.y +++ b/src/backend/bootstrap/bootparse.y @@ -307,8 +307,7 @@ Boot_DeclareIndexStmt: stmt->unique = false; stmt->primary = false; stmt->isconstraint = false; - stmt->deferrable = false; - stmt->initdeferred = false; + stmt->deferral = 'n'; stmt->transformed = false; stmt->concurrent = false; stmt->if_not_exists = false; @@ -356,8 +355,7 @@ Boot_DeclareUniqueIndexStmt: stmt->unique = true; stmt->primary = false; stmt->isconstraint = false; - stmt->deferrable = false; - stmt->initdeferred = false; + stmt->deferral = 'n'; stmt->transformed = false; stmt->concurrent = false; stmt->if_not_exists = false; diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index d59bd5b..5f93595 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -2261,8 +2261,7 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr, CreateConstraintEntry(ccname, /* Constraint Name */ RelationGetNamespace(rel), /* namespace */ CONSTRAINT_CHECK, /* Constraint Type */ - false, /* Is Deferrable */ - false, /* Is Deferred */ + 'n', /* not deferrable */ is_validated, InvalidOid, /* no parent constraint */ RelationGetRelid(rel), /* relation */ diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 8b276bc..795a7a9 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -1070,6 +1070,7 @@ index_create(Relation heapRelation, recordDependencyOn(&myself, &referenced, deptype); } + Assert((flags & INDEX_CREATE_ADD_CONSTRAINT) == 0); } /* Store dependency on parent index, if any */ @@ -1215,6 +1216,7 @@ index_create(Relation heapRelation, * INDEX_CONSTR_CREATE_MARK_AS_PRIMARY: index is a PRIMARY KEY * INDEX_CONSTR_CREATE_DEFERRABLE: constraint is DEFERRABLE * INDEX_CONSTR_CREATE_INIT_DEFERRED: constraint is INITIALLY DEFERRED + * INDEX_CONSTR_CREATE_ALWAYS_DEFERRED: constraint is ALWAYS DEFERRED * INDEX_CONSTR_CREATE_UPDATE_INDEX: update the pg_index row * INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS: remove existing dependencies * of index on table's columns @@ -1236,15 +1238,20 @@ index_constraint_create(Relation heapRelation, ObjectAddress myself, referenced; Oid conOid; - bool deferrable; - bool initdeferred; + char deferral; bool mark_as_primary; bool islocal; bool noinherit; int inhcount; - deferrable = (constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) != 0; - initdeferred = (constr_flags & INDEX_CONSTR_CREATE_INIT_DEFERRED) != 0; + if (constr_flags & INDEX_CONSTR_CREATE_ALWAYS_DEFERRED) + deferral = 'a'; + else if (constr_flags & INDEX_CONSTR_CREATE_INIT_DEFERRED) + deferral = 'i'; + else if (constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) + deferral = 'd'; + else + deferral = 'n'; mark_as_primary = (constr_flags & INDEX_CONSTR_CREATE_MARK_AS_PRIMARY) != 0; /* constraint creation support doesn't work while bootstrapping */ @@ -1295,8 +1302,7 @@ index_constraint_create(Relation heapRelation, conOid = CreateConstraintEntry(constraintName, namespaceId, constraintType, - deferrable, - initdeferred, + deferral, true, parentConstraintId, RelationGetRelid(heapRelation), @@ -1356,7 +1362,7 @@ index_constraint_create(Relation heapRelation, * checking trigger. (The trigger will be given an internal dependency on * the constraint by CreateTrigger.) */ - if (deferrable) + if (deferral != 'n') { CreateTrigStmt *trigger; @@ -1373,8 +1379,7 @@ index_constraint_create(Relation heapRelation, trigger->columns = NIL; trigger->whenClause = NULL; trigger->isconstraint = true; - trigger->deferrable = true; - trigger->initdeferred = initdeferred; + trigger->deferral = deferral; trigger->constrrel = NULL; (void) CreateTrigger(trigger, NULL, RelationGetRelid(heapRelation), @@ -1391,7 +1396,7 @@ index_constraint_create(Relation heapRelation, * index at all. */ if ((constr_flags & INDEX_CONSTR_CREATE_UPDATE_INDEX) && - (mark_as_primary || deferrable)) + (mark_as_primary || deferral != 'n')) { Relation pg_index; HeapTuple indexTuple; @@ -1412,7 +1417,7 @@ index_constraint_create(Relation heapRelation, dirty = true; } - if (deferrable && indexForm->indimmediate) + if (deferral != 'n' && indexForm->indimmediate) { indexForm->indimmediate = false; dirty = true; diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql index f4e69f4..bde6199 100644 --- a/src/backend/catalog/information_schema.sql +++ b/src/backend/catalog/information_schema.sql @@ -891,10 +891,14 @@ CREATE VIEW domain_constraints AS CAST(current_database() AS sql_identifier) AS domain_catalog, CAST(n.nspname AS sql_identifier) AS domain_schema, CAST(t.typname AS sql_identifier) AS domain_name, - CAST(CASE WHEN condeferrable THEN 'YES' ELSE 'NO' END + CAST(CASE WHEN condeferral = 'n' THEN 'NO' ELSE 'YES' END AS yes_or_no) AS is_deferrable, - CAST(CASE WHEN condeferred THEN 'YES' ELSE 'NO' END + CAST(CASE WHEN condeferral = 'i' OR condeferral = 'a' THEN 'YES' ELSE 'NO' END AS yes_or_no) AS initially_deferred + /* + * XXX Can we add is_always_deferred here? Are there + * standards considerations? + */ FROM pg_namespace rs, pg_namespace n, pg_constraint con, pg_type t WHERE rs.oid = con.connamespace AND n.oid = t.typnamespace @@ -1780,9 +1784,9 @@ CREATE VIEW table_constraints AS WHEN 'p' THEN 'PRIMARY KEY' WHEN 'u' THEN 'UNIQUE' END AS character_data) AS constraint_type, - CAST(CASE WHEN c.condeferrable THEN 'YES' ELSE 'NO' END AS yes_or_no) + CAST(CASE WHEN c.condeferral = 'n' THEN 'NO' ELSE 'YES' END AS yes_or_no) AS is_deferrable, - CAST(CASE WHEN c.condeferred THEN 'YES' ELSE 'NO' END AS yes_or_no) + CAST(CASE WHEN c.condeferral = 'i' OR c.condeferral = 'a' THEN 'YES' ELSE 'NO' END AS yes_or_no) AS initially_deferred, CAST('YES' AS yes_or_no) AS enforced diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 7a6d158..321a83e 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -51,8 +51,7 @@ Oid CreateConstraintEntry(const char *constraintName, Oid constraintNamespace, char constraintType, - bool isDeferrable, - bool isDeferred, + char deferralOption, bool isValidated, Oid parentConstrId, Oid relId, @@ -184,8 +183,7 @@ CreateConstraintEntry(const char *constraintName, values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname); values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace); values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType); - values[Anum_pg_constraint_condeferrable - 1] = BoolGetDatum(isDeferrable); - values[Anum_pg_constraint_condeferred - 1] = BoolGetDatum(isDeferred); + values[Anum_pg_constraint_condeferral - 1] = CharGetDatum(deferralOption); values[Anum_pg_constraint_convalidated - 1] = BoolGetDatum(isValidated); values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(relId); values[Anum_pg_constraint_contypid - 1] = ObjectIdGetDatum(domainId); @@ -564,8 +562,7 @@ CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned) CreateConstraintEntry(NameStr(constrForm->conname), constrForm->connamespace, CONSTRAINT_FOREIGN, - constrForm->condeferrable, - constrForm->condeferred, + constrForm->condeferral, constrForm->convalidated, HeapTupleGetOid(tuple), relationId, @@ -597,8 +594,7 @@ CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned) /* for now this is all we need */ fkconstraint->fk_upd_action = constrForm->confupdtype; fkconstraint->fk_del_action = constrForm->confdeltype; - fkconstraint->deferrable = constrForm->condeferrable; - fkconstraint->initdeferred = constrForm->condeferred; + fkconstraint->deferral = constrForm->condeferral; createForeignKeyTriggers(rel, constrForm->confrelid, fkconstraint, constrOid, constrForm->conindid, false); @@ -1357,7 +1353,7 @@ get_primary_key_attnos(Oid relid, bool deferrableOk, Oid *constraintOid) * ignore deferrable constraints, then we might as well give up * searching, since there can only be a single primary key on a table. */ - if (con->condeferrable && !deferrableOk) + if (con->condeferral != 'n' && !deferrableOk) break; /* Extract the conkey array, ie, attnums of PK's columns */ diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 3a3223b..77ff99b 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -841,10 +841,12 @@ DefineIndex(Oid relationId, if (partitioned && stmt->relation && !stmt->relation->inh) flags |= INDEX_CREATE_INVALID; - if (stmt->deferrable) + if (stmt->deferral != 'n') constr_flags |= INDEX_CONSTR_CREATE_DEFERRABLE; - if (stmt->initdeferred) + if (stmt->deferral == 'i') constr_flags |= INDEX_CONSTR_CREATE_INIT_DEFERRED; + if (stmt->deferral == 'a') + constr_flags |= INDEX_CONSTR_CREATE_ALWAYS_DEFERRED; indexRelationId = index_create(rel, indexRelationName, indexRelationId, parentIndexId, diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 0e95037..52e2de1 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -7042,8 +7042,9 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel, /* Create the catalog entries for the constraint */ flags = INDEX_CONSTR_CREATE_UPDATE_INDEX | INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS | - (stmt->initdeferred ? INDEX_CONSTR_CREATE_INIT_DEFERRED : 0) | - (stmt->deferrable ? INDEX_CONSTR_CREATE_DEFERRABLE : 0) | + (stmt->deferral == 'a' ? INDEX_CONSTR_CREATE_ALWAYS_DEFERRED : 0) | + (stmt->deferral == 'i' ? INDEX_CONSTR_CREATE_INIT_DEFERRED : 0) | + (stmt->deferral != 'n' ? INDEX_CONSTR_CREATE_DEFERRABLE : 0) | (stmt->primary ? INDEX_CONSTR_CREATE_MARK_AS_PRIMARY : 0); address = index_constraint_create(rel, @@ -7635,8 +7636,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, constrOid = CreateConstraintEntry(fkconstraint->conname, RelationGetNamespace(rel), CONSTRAINT_FOREIGN, - fkconstraint->deferrable, - fkconstraint->initdeferred, + fkconstraint->deferral, fkconstraint->initially_valid, parentConstr, RelationGetRelid(rel), @@ -7798,8 +7798,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint", cmdcon->conname, RelationGetRelationName(rel)))); - if (currcon->condeferrable != cmdcon->deferrable || - currcon->condeferred != cmdcon->initdeferred) + if (currcon->condeferral != cmdcon->deferral) { HeapTuple copyTuple; HeapTuple tgtuple; @@ -7815,8 +7814,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, */ copyTuple = heap_copytuple(contuple); copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple); - copy_con->condeferrable = cmdcon->deferrable; - copy_con->condeferred = cmdcon->initdeferred; + copy_con->condeferral = cmdcon->deferral; CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple); InvokeObjectPostAlterHook(ConstraintRelationId, @@ -7868,8 +7866,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, copyTuple = heap_copytuple(tgtuple); copy_tg = (Form_pg_trigger) GETSTRUCT(copyTuple); - copy_tg->tgdeferrable = cmdcon->deferrable; - copy_tg->tginitdeferred = cmdcon->initdeferred; + copy_tg->tgdeferral = cmdcon->deferral; CatalogTupleUpdate(tgrel, ©Tuple->t_self, copyTuple); InvokeObjectPostAlterHook(TriggerRelationId, @@ -8535,8 +8532,7 @@ validateForeignKeyConstraint(char *conname, trig.tgconstrrelid = RelationGetRelid(pkrel); trig.tgconstrindid = pkindOid; trig.tgconstraint = constraintOid; - trig.tgdeferrable = false; - trig.tginitdeferred = false; + trig.tgdeferral = 'n'; /* we needn't fill in remaining fields */ /* @@ -8624,8 +8620,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint, fk_trigger->transitionRels = NIL; fk_trigger->whenClause = NULL; fk_trigger->isconstraint = true; - fk_trigger->deferrable = fkconstraint->deferrable; - fk_trigger->initdeferred = fkconstraint->initdeferred; + fk_trigger->deferral = fkconstraint->deferral; fk_trigger->constrrel = NULL; fk_trigger->args = NIL; @@ -8665,28 +8660,23 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr switch (fkconstraint->fk_del_action) { case FKCONSTR_ACTION_NOACTION: - fk_trigger->deferrable = fkconstraint->deferrable; - fk_trigger->initdeferred = fkconstraint->initdeferred; + fk_trigger->deferral = fkconstraint->deferral; fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del"); break; case FKCONSTR_ACTION_RESTRICT: - fk_trigger->deferrable = false; - fk_trigger->initdeferred = false; + fk_trigger->deferral = 'n'; fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del"); break; case FKCONSTR_ACTION_CASCADE: - fk_trigger->deferrable = false; - fk_trigger->initdeferred = false; + fk_trigger->deferral = 'n'; fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del"); break; case FKCONSTR_ACTION_SETNULL: - fk_trigger->deferrable = false; - fk_trigger->initdeferred = false; + fk_trigger->deferral = 'n'; fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del"); break; case FKCONSTR_ACTION_SETDEFAULT: - fk_trigger->deferrable = false; - fk_trigger->initdeferred = false; + fk_trigger->deferral = 'n'; fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del"); break; default: @@ -8721,28 +8711,23 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr switch (fkconstraint->fk_upd_action) { case FKCONSTR_ACTION_NOACTION: - fk_trigger->deferrable = fkconstraint->deferrable; - fk_trigger->initdeferred = fkconstraint->initdeferred; + fk_trigger->deferral = fkconstraint->deferral; fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd"); break; case FKCONSTR_ACTION_RESTRICT: - fk_trigger->deferrable = false; - fk_trigger->initdeferred = false; + fk_trigger->deferral = 'n'; fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd"); break; case FKCONSTR_ACTION_CASCADE: - fk_trigger->deferrable = false; - fk_trigger->initdeferred = false; + fk_trigger->deferral = 'n'; fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd"); break; case FKCONSTR_ACTION_SETNULL: - fk_trigger->deferrable = false; - fk_trigger->initdeferred = false; + fk_trigger->deferral = 'n'; fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd"); break; case FKCONSTR_ACTION_SETDEFAULT: - fk_trigger->deferrable = false; - fk_trigger->initdeferred = false; + fk_trigger->deferral = 'n'; fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd"); break; default: @@ -11593,8 +11578,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc) Form_pg_constraint acon = (Form_pg_constraint) GETSTRUCT(a); Form_pg_constraint bcon = (Form_pg_constraint) GETSTRUCT(b); - if (acon->condeferrable != bcon->condeferrable || - acon->condeferred != bcon->condeferred || + if (acon->condeferral != bcon->condeferral || strcmp(decompile_conbin(a, tupleDesc), decompile_conbin(b, tupleDesc)) != 0) return false; @@ -14587,8 +14571,7 @@ CloneRowTriggersToPartition(Relation parent, Relation partition) trigStmt->whenClause = NULL; /* passed separately */ trigStmt->isconstraint = OidIsValid(trigForm->tgconstraint); trigStmt->transitionRels = NIL; /* not supported at present */ - trigStmt->deferrable = trigForm->tgdeferrable; - trigStmt->initdeferred = trigForm->tginitdeferred; + trigStmt->deferral = trigForm->tgdeferral; trigStmt->constrrel = NULL; /* passed separately */ CreateTrigger(trigStmt, NULL, RelationGetRelid(partition), diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 57519fe..41dc6a4 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -728,8 +728,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, constraintOid = CreateConstraintEntry(stmt->trigname, RelationGetNamespace(rel), CONSTRAINT_TRIGGER, - stmt->deferrable, - stmt->initdeferred, + stmt->deferral, true, InvalidOid, /* no parent */ RelationGetRelid(rel), @@ -833,8 +832,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(constrrelid); values[Anum_pg_trigger_tgconstrindid - 1] = ObjectIdGetDatum(indexOid); values[Anum_pg_trigger_tgconstraint - 1] = ObjectIdGetDatum(constraintOid); - values[Anum_pg_trigger_tgdeferrable - 1] = BoolGetDatum(stmt->deferrable); - values[Anum_pg_trigger_tginitdeferred - 1] = BoolGetDatum(stmt->initdeferred); + values[Anum_pg_trigger_tgdeferral - 1] = CharGetDatum(stmt->deferral); if (stmt->args) { @@ -1449,8 +1447,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid) /* can't get here because of earlier checks */ elog(ERROR, "confused about RI delete function"); } - fkcon->deferrable = stmt->deferrable; - fkcon->initdeferred = stmt->initdeferred; + fkcon->deferral = stmt->deferral; fkcon->skip_validation = false; fkcon->initially_valid = true; @@ -1967,8 +1964,7 @@ RelationBuildTriggers(Relation relation) build->tgconstrrelid = pg_trigger->tgconstrrelid; build->tgconstrindid = pg_trigger->tgconstrindid; build->tgconstraint = pg_trigger->tgconstraint; - build->tgdeferrable = pg_trigger->tgdeferrable; - build->tginitdeferred = pg_trigger->tginitdeferred; + build->tgdeferral = pg_trigger->tgdeferral; build->tgnargs = pg_trigger->tgnargs; /* tgattr is first var-width field, so OK to access directly */ build->tgnattr = pg_trigger->tgattr.dim1; @@ -2273,9 +2269,7 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2) return false; if (trig1->tgconstraint != trig2->tgconstraint) return false; - if (trig1->tgdeferrable != trig2->tgdeferrable) - return false; - if (trig1->tginitdeferred != trig2->tginitdeferred) + if (trig1->tgdeferral != trig2->tgdeferral) return false; if (trig1->tgnargs != trig2->tgnargs) return false; @@ -2370,7 +2364,8 @@ ExecCallTriggerFunc(TriggerData *trigdata, TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) && TRIGGER_FIRED_AFTER(trigdata->tg_event) && !(trigdata->tg_event & AFTER_TRIGGER_DEFERRABLE) && - !(trigdata->tg_event & AFTER_TRIGGER_INITDEFERRED)) || + !(trigdata->tg_event & AFTER_TRIGGER_INITDEFERRED) && + !(trigdata->tg_event & AFTER_TRIGGER_ALWAYSDEFERRED)) || (trigdata->tg_oldtable == NULL && trigdata->tg_newtable == NULL)); finfo += tgindx; @@ -3632,6 +3627,7 @@ typedef struct AfterTriggerSharedData TriggerEvent ats_event; /* event type indicator, see trigger.h */ Oid ats_tgoid; /* the trigger's ID */ Oid ats_relid; /* the relation it's on */ + bool ats_alwaysdeferred; /* whether this can be deferred */ CommandId ats_firing_id; /* ID for firing cycle */ struct AfterTriggersTableData *ats_table; /* transition table access */ } AfterTriggerSharedData; @@ -3911,6 +3907,8 @@ afterTriggerCheckState(AfterTriggerShared evtshared) */ if ((evtshared->ats_event & AFTER_TRIGGER_DEFERRABLE) == 0) return false; + if ((evtshared->ats_event & AFTER_TRIGGER_ALWAYSDEFERRED)) + return true; /* * If constraint state exists, SET CONSTRAINTS might have been executed @@ -5420,14 +5418,19 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt) { Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup); - if (con->condeferrable) - conoidlist = lappend_oid(conoidlist, - HeapTupleGetOid(tup)); - else if (stmt->deferred) + if (stmt->deferred && con->condeferral == 'n') ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("constraint \"%s\" is not deferrable", constraint->relname))); + else if (!stmt->deferred && con->condeferral == 'a') + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("constraint \"%s\" is always deferred", + constraint->relname))); + else if (con->condeferral != 'n' && con->condeferral != 'a') + conoidlist = lappend_oid(conoidlist, + HeapTupleGetOid(tup)); found = true; } @@ -5515,7 +5518,7 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt) * deferrable RI constraint may have some non-deferrable * actions. */ - if (pg_trigger->tgdeferrable) + if (pg_trigger->tgdeferral != 'n') tgoidlist = lappend_oid(tgoidlist, HeapTupleGetOid(htup)); @@ -5975,8 +5978,9 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, new_shared.ats_event = (event & TRIGGER_EVENT_OPMASK) | (row_trigger ? TRIGGER_EVENT_ROW : 0) | - (trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) | - (trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0); + (trigger->tgdeferral != 'n' ? AFTER_TRIGGER_DEFERRABLE : 0) | + (trigger->tgdeferral == 'i' ? AFTER_TRIGGER_INITDEFERRED : 0) | + (trigger->tgdeferral == 'a' ? AFTER_TRIGGER_ALWAYSDEFERRED : 0); new_shared.ats_tgoid = trigger->tgoid; new_shared.ats_relid = RelationGetRelid(rel); new_shared.ats_firing_id = 0; diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 175ecc8..156659a 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -1016,6 +1016,7 @@ DefineDomain(CreateDomainStmt *stmt) case CONSTR_ATTR_DEFERRABLE: case CONSTR_ATTR_NOT_DEFERRABLE: case CONSTR_ATTR_DEFERRED: + case CONSTR_ATTR_ALWAYS_DEFERRED: case CONSTR_ATTR_IMMEDIATE: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -2602,6 +2603,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint, case CONSTR_ATTR_DEFERRABLE: case CONSTR_ATTR_NOT_DEFERRABLE: case CONSTR_ATTR_DEFERRED: + case CONSTR_ATTR_ALWAYS_DEFERRED: case CONSTR_ATTR_IMMEDIATE: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -3147,8 +3149,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid, CreateConstraintEntry(constr->conname, /* Constraint Name */ domainNamespace, /* namespace */ CONSTRAINT_CHECK, /* Constraint Type */ - false, /* Is Deferrable */ - false, /* Is Deferred */ + 'n', /* Deferral option */ !constr->skip_validation, /* Is Validated */ InvalidOid, /* no parent constraint */ InvalidOid, /* not a relation constraint */ diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 7c045a7..758ddb0 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2882,8 +2882,7 @@ _copyConstraint(const Constraint *from) COPY_SCALAR_FIELD(contype); COPY_STRING_FIELD(conname); - COPY_SCALAR_FIELD(deferrable); - COPY_SCALAR_FIELD(initdeferred); + COPY_SCALAR_FIELD(deferral); COPY_LOCATION_FIELD(location); COPY_SCALAR_FIELD(is_no_inherit); COPY_NODE_FIELD(raw_expr); @@ -3444,8 +3443,7 @@ _copyIndexStmt(const IndexStmt *from) COPY_SCALAR_FIELD(unique); COPY_SCALAR_FIELD(primary); COPY_SCALAR_FIELD(isconstraint); - COPY_SCALAR_FIELD(deferrable); - COPY_SCALAR_FIELD(initdeferred); + COPY_SCALAR_FIELD(deferral); COPY_SCALAR_FIELD(transformed); COPY_SCALAR_FIELD(concurrent); COPY_SCALAR_FIELD(if_not_exists); @@ -4216,8 +4214,7 @@ _copyCreateTrigStmt(const CreateTrigStmt *from) COPY_NODE_FIELD(whenClause); COPY_SCALAR_FIELD(isconstraint); COPY_NODE_FIELD(transitionRels); - COPY_SCALAR_FIELD(deferrable); - COPY_SCALAR_FIELD(initdeferred); + COPY_SCALAR_FIELD(deferral); COPY_NODE_FIELD(constrrel); return newnode; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 6a971d0..4ac1c23 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1338,8 +1338,7 @@ _equalIndexStmt(const IndexStmt *a, const IndexStmt *b) COMPARE_SCALAR_FIELD(unique); COMPARE_SCALAR_FIELD(primary); COMPARE_SCALAR_FIELD(isconstraint); - COMPARE_SCALAR_FIELD(deferrable); - COMPARE_SCALAR_FIELD(initdeferred); + COMPARE_SCALAR_FIELD(deferral); COMPARE_SCALAR_FIELD(transformed); COMPARE_SCALAR_FIELD(concurrent); COMPARE_SCALAR_FIELD(if_not_exists); @@ -1992,8 +1991,7 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b) COMPARE_NODE_FIELD(whenClause); COMPARE_SCALAR_FIELD(isconstraint); COMPARE_NODE_FIELD(transitionRels); - COMPARE_SCALAR_FIELD(deferrable); - COMPARE_SCALAR_FIELD(initdeferred); + COMPARE_SCALAR_FIELD(deferral); COMPARE_NODE_FIELD(constrrel); return true; @@ -2573,8 +2571,7 @@ _equalConstraint(const Constraint *a, const Constraint *b) { COMPARE_SCALAR_FIELD(contype); COMPARE_STRING_FIELD(conname); - COMPARE_SCALAR_FIELD(deferrable); - COMPARE_SCALAR_FIELD(initdeferred); + COMPARE_SCALAR_FIELD(deferral); COMPARE_LOCATION_FIELD(location); COMPARE_SCALAR_FIELD(is_no_inherit); COMPARE_NODE_FIELD(raw_expr); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 610f9ed..8490410 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2713,8 +2713,7 @@ _outIndexStmt(StringInfo str, const IndexStmt *node) WRITE_BOOL_FIELD(unique); WRITE_BOOL_FIELD(primary); WRITE_BOOL_FIELD(isconstraint); - WRITE_BOOL_FIELD(deferrable); - WRITE_BOOL_FIELD(initdeferred); + WRITE_BOOL_FIELD(deferral); WRITE_BOOL_FIELD(transformed); WRITE_BOOL_FIELD(concurrent); WRITE_BOOL_FIELD(if_not_exists); @@ -3491,8 +3490,7 @@ _outConstraint(StringInfo str, const Constraint *node) WRITE_NODE_TYPE("CONSTRAINT"); WRITE_STRING_FIELD(conname); - WRITE_BOOL_FIELD(deferrable); - WRITE_BOOL_FIELD(initdeferred); + WRITE_BOOL_FIELD(deferral); WRITE_LOCATION_FIELD(location); appendStringInfoString(str, " :contype "); @@ -3579,6 +3577,10 @@ _outConstraint(StringInfo str, const Constraint *node) appendStringInfoString(str, "ATTR_NOT_DEFERRABLE"); break; + case CONSTR_ATTR_ALWAYS_DEFERRED: + appendStringInfoString(str, "ATTR_ALWAYS_DEFERRED"); + break; + case CONSTR_ATTR_DEFERRED: appendStringInfoString(str, "ATTR_DEFERRED"); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 90dfac2..dab721a 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -133,6 +133,7 @@ typedef struct ImportQual #define CAS_INITIALLY_DEFERRED 0x08 #define CAS_NOT_VALID 0x10 #define CAS_NO_INHERIT 0x20 +#define CAS_ALWAYS_DEFERRED 0x40 #define parser_yyerror(msg) scanner_yyerror(msg, yyscanner) @@ -184,8 +185,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 *no_inherit, core_yyscan_t yyscanner); + char *deferral, + bool *not_valid, bool *no_inherit, core_yyscan_t yyscanner); static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %} @@ -734,6 +735,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * for RANGE, ROWS, GROUPS so that they can follow a_expr without creating * postfix-operator problems; * for GENERATED so that it can follow b_expr; + * for ALWAYS for column constraints ALWAYS DEFERRED; * and for NULL so that it can follow b_expr in ColQualList without creating * postfix-operator problems. * @@ -752,7 +754,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * blame any funny behavior of UNBOUNDED on the SQL standard, though. */ %nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */ -%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP +%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP ALWAYS %left Op OPERATOR /* multi-character ops and user-defined operators */ %left '+' '-' %left '*' '/' '%' @@ -2180,6 +2182,7 @@ alter_table_cmd: c->generated_when = $6; c->options = $9; c->location = @5; + c->deferral = 'n'; n->subtype = AT_AddIdentity; n->name = $3; @@ -2279,9 +2282,9 @@ alter_table_cmd: c->contype = CONSTR_FOREIGN; /* others not supported, yet */ c->conname = $3; processCASbits($4, @4, "ALTER CONSTRAINT statement", - &c->deferrable, - &c->initdeferred, - NULL, NULL, yyscanner); + &c->deferral, + NULL, NULL, + yyscanner); $$ = (Node *)n; } /* ALTER TABLE <name> VALIDATE CONSTRAINT ... */ @@ -3493,6 +3496,7 @@ ColConstraintElem: Constraint *n = makeNode(Constraint); n->contype = CONSTR_NOTNULL; n->location = @1; + n->deferral = 'n'; $$ = (Node *)n; } | NULL_P @@ -3500,6 +3504,7 @@ ColConstraintElem: Constraint *n = makeNode(Constraint); n->contype = CONSTR_NULL; n->location = @1; + n->deferral = 'n'; $$ = (Node *)n; } | UNIQUE opt_definition OptConsTableSpace @@ -3511,6 +3516,7 @@ ColConstraintElem: n->options = $2; n->indexname = NULL; n->indexspace = $3; + n->deferral = 'n'; $$ = (Node *)n; } | PRIMARY KEY opt_definition OptConsTableSpace @@ -3522,6 +3528,7 @@ ColConstraintElem: n->options = $3; n->indexname = NULL; n->indexspace = $4; + n->deferral = 'n'; $$ = (Node *)n; } | CHECK '(' a_expr ')' opt_no_inherit @@ -3534,6 +3541,7 @@ ColConstraintElem: n->cooked_expr = NULL; n->skip_validation = false; n->initially_valid = true; + n->deferral = 'n'; $$ = (Node *)n; } | DEFAULT b_expr @@ -3543,6 +3551,7 @@ ColConstraintElem: n->location = @1; n->raw_expr = $2; n->cooked_expr = NULL; + n->deferral = 'n'; $$ = (Node *)n; } | GENERATED generated_when AS IDENTITY_P OptParenthesizedSeqOptList @@ -3552,6 +3561,7 @@ ColConstraintElem: n->generated_when = $2; n->options = $5; n->location = @1; + n->deferral = 'n'; $$ = (Node *)n; } | REFERENCES qualified_name opt_column_list key_match key_actions @@ -3567,6 +3577,7 @@ ColConstraintElem: n->fk_del_action = (char) ($5 & 0xFF); n->skip_validation = false; n->initially_valid = true; + n->deferral = 'n'; $$ = (Node *)n; } ; @@ -3597,6 +3608,7 @@ ConstraintAttr: Constraint *n = makeNode(Constraint); n->contype = CONSTR_ATTR_DEFERRABLE; n->location = @1; + n->deferral = 'd'; $$ = (Node *)n; } | NOT DEFERRABLE @@ -3604,6 +3616,7 @@ ConstraintAttr: Constraint *n = makeNode(Constraint); n->contype = CONSTR_ATTR_NOT_DEFERRABLE; n->location = @1; + n->deferral = 'n'; $$ = (Node *)n; } | INITIALLY DEFERRED @@ -3611,6 +3624,7 @@ ConstraintAttr: Constraint *n = makeNode(Constraint); n->contype = CONSTR_ATTR_DEFERRED; n->location = @1; + n->deferral = 'i'; $$ = (Node *)n; } | INITIALLY IMMEDIATE @@ -3618,6 +3632,15 @@ ConstraintAttr: Constraint *n = makeNode(Constraint); n->contype = CONSTR_ATTR_IMMEDIATE; n->location = @1; + n->deferral = 'd'; + $$ = (Node *)n; + } + | ALWAYS DEFERRED + { + Constraint *n = makeNode(Constraint); + n->contype = CONSTR_ATTR_ALWAYS_DEFERRED; + n->location = @1; + n->deferral = 'a'; $$ = (Node *)n; } ; @@ -3675,8 +3698,10 @@ ConstraintElem: n->raw_expr = $3; n->cooked_expr = NULL; processCASbits($5, @5, "CHECK", - NULL, NULL, &n->skip_validation, - &n->is_no_inherit, yyscanner); + &n->deferral, + &n->skip_validation, + &n->is_no_inherit, + yyscanner); n->initially_valid = !n->skip_validation; $$ = (Node *)n; } @@ -3692,8 +3717,9 @@ ConstraintElem: n->indexname = NULL; n->indexspace = $7; processCASbits($8, @8, "UNIQUE", - &n->deferrable, &n->initdeferred, NULL, - NULL, yyscanner); + &n->deferral, + NULL, NULL, + yyscanner); $$ = (Node *)n; } | UNIQUE ExistingIndex ConstraintAttributeSpec @@ -3707,8 +3733,9 @@ ConstraintElem: n->indexname = $2; n->indexspace = NULL; processCASbits($3, @3, "UNIQUE", - &n->deferrable, &n->initdeferred, NULL, - NULL, yyscanner); + &n->deferral, + NULL, NULL, + yyscanner); $$ = (Node *)n; } | PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace @@ -3723,8 +3750,9 @@ ConstraintElem: n->indexname = NULL; n->indexspace = $8; processCASbits($9, @9, "PRIMARY KEY", - &n->deferrable, &n->initdeferred, NULL, - NULL, yyscanner); + &n->deferral, + NULL, NULL, + yyscanner); $$ = (Node *)n; } | PRIMARY KEY ExistingIndex ConstraintAttributeSpec @@ -3738,8 +3766,9 @@ ConstraintElem: n->indexname = $3; n->indexspace = NULL; processCASbits($4, @4, "PRIMARY KEY", - &n->deferrable, &n->initdeferred, NULL, - NULL, yyscanner); + &n->deferral, + NULL, NULL, + yyscanner); $$ = (Node *)n; } | EXCLUDE access_method_clause '(' ExclusionConstraintList ')' @@ -3757,8 +3786,9 @@ ConstraintElem: n->indexspace = $8; n->where_clause = $9; processCASbits($10, @10, "EXCLUDE", - &n->deferrable, &n->initdeferred, NULL, - NULL, yyscanner); + &n->deferral, + NULL, NULL, + yyscanner); $$ = (Node *)n; } | FOREIGN KEY '(' columnList ')' REFERENCES qualified_name @@ -3774,7 +3804,7 @@ 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->deferral, &n->skip_validation, NULL, yyscanner); n->initially_valid = !n->skip_validation; @@ -5358,8 +5388,7 @@ CreateTrigStmt: n->whenClause = $10; n->transitionRels = $8; n->isconstraint = false; - n->deferrable = false; - n->initdeferred = false; + n->deferral = 'n'; n->constrrel = NULL; $$ = (Node *)n; } @@ -5381,8 +5410,9 @@ CreateTrigStmt: n->transitionRels = NIL; n->isconstraint = true; processCASbits($10, @10, "TRIGGER", - &n->deferrable, &n->initdeferred, NULL, - NULL, yyscanner); + &n->deferral, + NULL, NULL, + yyscanner); n->constrrel = $9; $$ = (Node *)n; } @@ -5538,17 +5568,24 @@ ConstraintAttributeSpec: int newspec = $1 | $2; /* special message for this case */ - if ((newspec & (CAS_NOT_DEFERRABLE | CAS_INITIALLY_DEFERRED)) == (CAS_NOT_DEFERRABLE | CAS_INITIALLY_DEFERRED)) + if ((newspec & CAS_NOT_DEFERRABLE) && + (newspec & (CAS_INITIALLY_DEFERRED | CAS_ALWAYS_DEFERRED))) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"), parser_errposition(@2))); /* generic message for other conflicts */ + if ((newspec & CAS_ALWAYS_DEFERRED) && + (newspec & (CAS_INITIALLY_IMMEDIATE))) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting constraint properties 1"), + parser_errposition(@2))); if ((newspec & (CAS_NOT_DEFERRABLE | CAS_DEFERRABLE)) == (CAS_NOT_DEFERRABLE | CAS_DEFERRABLE) || (newspec & (CAS_INITIALLY_IMMEDIATE | CAS_INITIALLY_DEFERRED)) == (CAS_INITIALLY_IMMEDIATE | CAS_INITIALLY_DEFERRED)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("conflicting constraint properties"), + errmsg("conflicting constraint properties 2"), parser_errposition(@2))); $$ = newspec; } @@ -5559,6 +5596,7 @@ ConstraintAttributeElem: | DEFERRABLE { $$ = CAS_DEFERRABLE; } | INITIALLY IMMEDIATE { $$ = CAS_INITIALLY_IMMEDIATE; } | INITIALLY DEFERRED { $$ = CAS_INITIALLY_DEFERRED; } + | ALWAYS DEFERRED { $$ = CAS_ALWAYS_DEFERRED; } | NOT VALID { $$ = CAS_NOT_VALID; } | NO INHERIT { $$ = CAS_NO_INHERIT; } ; @@ -5649,8 +5687,9 @@ CreateAssertStmt: n->args = list_make1($6); n->isconstraint = true; processCASbits($8, @8, "ASSERTION", - &n->deferrable, &n->initdeferred, NULL, - NULL, yyscanner); + &n->deferral, + NULL, NULL, + yyscanner); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -7396,8 +7435,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name n->oldNode = InvalidOid; n->primary = false; n->isconstraint = false; - n->deferrable = false; - n->initdeferred = false; + n->deferral = 'n'; n->transformed = false; n->if_not_exists = false; $$ = (Node *)n; @@ -7424,8 +7462,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name n->oldNode = InvalidOid; n->primary = false; n->isconstraint = false; - n->deferrable = false; - n->initdeferred = false; + n->deferral = 'n'; n->transformed = false; n->if_not_exists = true; $$ = (Node *)n; @@ -16197,34 +16234,41 @@ SplitColQualList(List *qualList, */ static void processCASbits(int cas_bits, int location, const char *constrType, - bool *deferrable, bool *initdeferred, bool *not_valid, - bool *no_inherit, core_yyscan_t yyscanner) + char *deferral, + bool *not_valid, bool *no_inherit, core_yyscan_t yyscanner) { /* defaults */ - if (deferrable) - *deferrable = false; - if (initdeferred) - *initdeferred = false; + if (deferral) + *deferral = 'n'; if (not_valid) *not_valid = false; - if (cas_bits & (CAS_DEFERRABLE | CAS_INITIALLY_DEFERRED)) + if (cas_bits & CAS_ALWAYS_DEFERRED) { - if (deferrable) - *deferrable = true; + if (deferral) + *deferral = 'a'; else ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s is CHECK, UNIQUE, or similar */ - errmsg("%s constraints cannot be marked DEFERRABLE", + errmsg("%s constraints cannot be marked ALWAYS DEFERRED", constrType), parser_errposition(location))); - } - - if (cas_bits & CAS_INITIALLY_DEFERRED) + } else if (cas_bits & CAS_INITIALLY_DEFERRED) + { + if (deferral) + *deferral = 'i'; + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + /* translator: %s is CHECK, UNIQUE, or similar */ + errmsg("%s constraints cannot be marked INITIALLY DEFERRED", + constrType), + parser_errposition(location))); + } else if (cas_bits & CAS_DEFERRABLE) { - if (initdeferred) - *initdeferred = true; + if (deferral) + *deferral = 'd'; else ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 17b54b2..6efa303 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -616,6 +616,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) constraint = makeNode(Constraint); constraint->contype = CONSTR_DEFAULT; constraint->location = -1; + constraint->deferral = 'n'; constraint->raw_expr = (Node *) funccallnode; constraint->cooked_expr = NULL; column->constraints = lappend(column->constraints, constraint); @@ -623,6 +624,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) constraint = makeNode(Constraint); constraint->contype = CONSTR_NOTNULL; constraint->location = -1; + constraint->deferral = 'n'; column->constraints = lappend(column->constraints, constraint); } @@ -760,6 +762,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) case CONSTR_ATTR_DEFERRABLE: case CONSTR_ATTR_NOT_DEFERRABLE: + case CONSTR_ATTR_ALWAYS_DEFERRED: case CONSTR_ATTR_DEFERRED: case CONSTR_ATTR_IMMEDIATE: /* transformConstraintAttrs took care of these */ @@ -870,6 +873,7 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) case CONSTR_DEFAULT: case CONSTR_ATTR_DEFERRABLE: case CONSTR_ATTR_NOT_DEFERRABLE: + case CONSTR_ATTR_ALWAYS_DEFERRED: case CONSTR_ATTR_DEFERRED: case CONSTR_ATTR_IMMEDIATE: elog(ERROR, "invalid context for constraint type %d", @@ -1120,6 +1124,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla n->contype = CONSTR_CHECK; n->location = -1; + n->deferral = 'n'; n->conname = pstrdup(ccname); n->raw_expr = NULL; n->cooked_expr = nodeToString(ccbin_node); @@ -1348,6 +1353,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx, index->relation = heapRel; index->relationId = heapRelid; index->accessMethod = pstrdup(NameStr(amrec->amname)); + index->deferral = 'n'; if (OidIsValid(idxrelrec->reltablespace)) index->tableSpace = get_tablespace_name(idxrelrec->reltablespace); else @@ -1396,8 +1402,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx, conrec = (Form_pg_constraint) GETSTRUCT(ht_constr); index->isconstraint = true; - index->deferrable = conrec->condeferrable; - index->initdeferred = conrec->condeferred; + index->deferral = conrec->condeferral; /* If it's an exclusion constraint, we need the operator names */ if (idxrec->indisexclusion) @@ -1865,8 +1870,7 @@ transformIndexConstraints(CreateStmtContext *cxt) equal(index->whereClause, priorindex->whereClause) && equal(index->excludeOpNames, priorindex->excludeOpNames) && strcmp(index->accessMethod, priorindex->accessMethod) == 0 && - index->deferrable == priorindex->deferrable && - index->initdeferred == priorindex->initdeferred) + index->deferral == priorindex->deferral) { priorindex->unique |= index->unique; @@ -1919,8 +1923,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt) */ } index->isconstraint = true; - index->deferrable = constraint->deferrable; - index->initdeferred = constraint->initdeferred; + index->deferral = constraint->deferral; if (constraint->conname != NULL) index->idxname = pstrdup(constraint->conname); @@ -2035,7 +2038,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt) * non-constraint index couldn't be deferred anyway, so this case * should never occur; no need to sweat, but let's check it.) */ - if (!index_form->indimmediate && !constraint->deferrable) + if (!index_form->indimmediate && constraint->deferral == 'n') ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is a deferrable index", index_name), @@ -3259,7 +3262,9 @@ static void transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList) { Constraint *lastprimarycon = NULL; - bool saw_deferrability = false; + bool saw_deferrable = false; + bool saw_notdeferrable = false; + bool saw_alwaysdeferred = false; bool saw_initially = false; ListCell *clist; @@ -3285,13 +3290,13 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList) (errcode(ERRCODE_SYNTAX_ERROR), errmsg("misplaced DEFERRABLE clause"), parser_errposition(cxt->pstate, con->location))); - if (saw_deferrability) + if (saw_deferrable || saw_notdeferrable) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"), parser_errposition(cxt->pstate, con->location))); - saw_deferrability = true; - lastprimarycon->deferrable = true; + saw_deferrable = true; + lastprimarycon->deferral = 'd'; break; case CONSTR_ATTR_NOT_DEFERRABLE: @@ -3300,45 +3305,61 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList) (errcode(ERRCODE_SYNTAX_ERROR), errmsg("misplaced NOT DEFERRABLE clause"), parser_errposition(cxt->pstate, con->location))); - if (saw_deferrability) + if (saw_deferrable || saw_notdeferrable) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed"), parser_errposition(cxt->pstate, con->location))); - saw_deferrability = true; - lastprimarycon->deferrable = false; + if (saw_alwaysdeferred) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple ALWAYS DEFERRED/NOT DEFERRABLE clauses not allowed"), + parser_errposition(cxt->pstate, con->location))); + saw_notdeferrable = true; + lastprimarycon->deferral = 'n'; if (saw_initially && - lastprimarycon->initdeferred) + lastprimarycon->deferral == 'i') ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"), parser_errposition(cxt->pstate, con->location))); break; - case CONSTR_ATTR_DEFERRED: + case CONSTR_ATTR_ALWAYS_DEFERRED: if (!SUPPORTS_ATTRS(lastprimarycon)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("misplaced INITIALLY DEFERRED clause"), + errmsg("misplaced ALWAYS DEFERRED clause"), parser_errposition(cxt->pstate, con->location))); - if (saw_initially) + if (saw_alwaysdeferred || saw_notdeferrable) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"), + errmsg("multiple ALWAYS DEFERRED/NOT DEFERRABLE clauses not allowed"), parser_errposition(cxt->pstate, con->location))); saw_initially = true; - lastprimarycon->initdeferred = true; + saw_alwaysdeferred = true; + lastprimarycon->deferral = 'a'; + if (saw_initially && + lastprimarycon->deferral != 'a') + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("constraint declared INITIALLY IMMEDIATE must not be ALWAYS DEFERRED"), + parser_errposition(cxt->pstate, con->location))); + break; - /* - * If only INITIALLY DEFERRED appears, assume DEFERRABLE - */ - if (!saw_deferrability) - lastprimarycon->deferrable = true; - else if (!lastprimarycon->deferrable) + case CONSTR_ATTR_DEFERRED: + if (!SUPPORTS_ATTRS(lastprimarycon)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE"), + errmsg("misplaced INITIALLY DEFERRED clause"), parser_errposition(cxt->pstate, con->location))); + if (saw_initially) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"), + parser_errposition(cxt->pstate, con->location))); + saw_initially = true; + lastprimarycon->deferral = 'i'; break; case CONSTR_ATTR_IMMEDIATE: @@ -3353,14 +3374,17 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList) errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed"), parser_errposition(cxt->pstate, con->location))); saw_initially = true; - lastprimarycon->initdeferred = false; + lastprimarycon->deferral = 'd'; break; default: /* Otherwise it's not an attribute */ lastprimarycon = con; + lastprimarycon->deferral = 'n'; /* reset flags for new primary node */ - saw_deferrability = false; + saw_deferrable = false; + saw_notdeferrable = false; + saw_alwaysdeferred = false; saw_initially = false; break; } diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 065238b..3eafdf5 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -941,13 +941,15 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty) if (OidIsValid(trigrec->tgconstrrelid)) appendStringInfo(&buf, "FROM %s ", generate_relation_name(trigrec->tgconstrrelid, NIL)); - if (!trigrec->tgdeferrable) + if (trigrec->tgdeferral == 'n') appendStringInfoString(&buf, "NOT "); appendStringInfoString(&buf, "DEFERRABLE INITIALLY "); - if (trigrec->tginitdeferred) + if (trigrec->tgdeferral == 'i' || trigrec->tgdeferral == 'a') appendStringInfoString(&buf, "DEFERRED "); - else + else if (trigrec->tgdeferral == 'd') appendStringInfoString(&buf, "IMMEDIATE "); + if (trigrec->tgdeferral == 'a') + appendStringInfoString(&buf, "ALWAYS DEFERRED"); } value = fastgetattr(ht_trig, Anum_pg_trigger_tgoldtable, @@ -2206,10 +2208,12 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, break; } - if (conForm->condeferrable) + if (conForm->condeferral != 'n') appendStringInfoString(&buf, " DEFERRABLE"); - if (conForm->condeferred) + if (conForm->condeferral == 'i') appendStringInfoString(&buf, " INITIALLY DEFERRED"); + if (conForm->condeferral == 'a') + appendStringInfoString(&buf, " ALWAYS DEFERRED"); if (!conForm->convalidated) appendStringInfoString(&buf, " NOT VALID"); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index ea2f022..6505580 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -6729,8 +6729,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) i_indisreplident, i_contype, i_conname, - i_condeferrable, - i_condeferred, + i_condeferral, i_contableoid, i_conoid, i_condef, @@ -6785,7 +6784,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) "i.indkey, i.indisclustered, " "i.indisreplident, t.relpages, " "c.contype, c.conname, " - "c.condeferrable, c.condeferred, " + "c.condeferral, " "c.tableoid AS contableoid, " "c.oid AS conoid, " "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, " @@ -6855,7 +6854,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) "i.indkey, i.indisclustered, " "false AS indisreplident, t.relpages, " "c.contype, c.conname, " - "c.condeferrable, c.condeferred, " + "c.condeferral, " "c.tableoid AS contableoid, " "c.oid AS conoid, " "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, " @@ -6884,7 +6883,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) "i.indkey, i.indisclustered, " "false AS indisreplident, t.relpages, " "c.contype, c.conname, " - "c.condeferrable, c.condeferred, " + "c.condeferral, " "c.tableoid AS contableoid, " "c.oid AS conoid, " "null AS condef, " @@ -6916,7 +6915,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) "i.indkey, i.indisclustered, " "false AS indisreplident, t.relpages, " "c.contype, c.conname, " - "c.condeferrable, c.condeferred, " + "c.condeferral, " "c.tableoid AS contableoid, " "c.oid AS conoid, " "null AS condef, " @@ -6953,8 +6952,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) i_relpages = PQfnumber(res, "relpages"); i_contype = PQfnumber(res, "contype"); i_conname = PQfnumber(res, "conname"); - i_condeferrable = PQfnumber(res, "condeferrable"); - i_condeferred = PQfnumber(res, "condeferred"); + i_condeferral = PQfnumber(res, "condeferral"); i_contableoid = PQfnumber(res, "contableoid"); i_conoid = PQfnumber(res, "conoid"); i_condef = PQfnumber(res, "condef"); @@ -7014,8 +7012,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) constrinfo[j].condef = NULL; constrinfo[j].confrelid = InvalidOid; constrinfo[j].conindex = indxinfo[j].dobj.dumpId; - constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't'; - constrinfo[j].condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't'; + constrinfo[j].condeferral = *(PQgetvalue(res, j, i_condeferral)); constrinfo[j].conislocal = true; constrinfo[j].separate = true; @@ -7184,8 +7181,7 @@ getConstraints(Archive *fout, TableInfo tblinfo[], int numTables) constrinfo[j].condef = pg_strdup(PQgetvalue(res, j, i_condef)); constrinfo[j].confrelid = atooid(PQgetvalue(res, j, i_confrelid)); constrinfo[j].conindex = 0; - constrinfo[j].condeferrable = false; - constrinfo[j].condeferred = false; + constrinfo[j].condeferral = 'n'; constrinfo[j].conislocal = true; constrinfo[j].separate = true; } @@ -7264,8 +7260,7 @@ getDomainConstraints(Archive *fout, TypeInfo *tyinfo) constrinfo[i].condef = pg_strdup(PQgetvalue(res, i, i_consrc)); constrinfo[i].confrelid = InvalidOid; constrinfo[i].conindex = 0; - constrinfo[i].condeferrable = false; - constrinfo[i].condeferred = false; + constrinfo[i].condeferral = 'n'; constrinfo[i].conislocal = true; constrinfo[i].separate = !validated; @@ -7425,8 +7420,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) i_tgconstrrelid, i_tgconstrrelname, i_tgenabled, - i_tgdeferrable, - i_tginitdeferred, + i_tgdeferral, i_tgdef; int ntups; @@ -7470,8 +7464,8 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) "SELECT tgname, " "tgfoid::pg_catalog.regproc AS tgfname, " "tgtype, tgnargs, tgargs, tgenabled, " - "tgisconstraint, tgconstrname, tgdeferrable, " - "tgconstrrelid, tginitdeferred, tableoid, oid, " + "tgisconstraint, tgconstrname, tgdeferral, " + "tgconstrrelid, tableoid, oid, " "tgconstrrelid::pg_catalog.regclass AS tgconstrrelname " "FROM pg_catalog.pg_trigger t " "WHERE tgrelid = '%u'::pg_catalog.oid " @@ -7489,8 +7483,8 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) "SELECT tgname, " "tgfoid::pg_catalog.regproc AS tgfname, " "tgtype, tgnargs, tgargs, tgenabled, " - "tgisconstraint, tgconstrname, tgdeferrable, " - "tgconstrrelid, tginitdeferred, tableoid, oid, " + "tgisconstraint, tgconstrname, tgdeferral, " + "tgconstrrelid, tableoid, oid, " "tgconstrrelid::pg_catalog.regclass AS tgconstrrelname " "FROM pg_catalog.pg_trigger t " "WHERE tgrelid = '%u'::pg_catalog.oid " @@ -7518,8 +7512,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) i_tgconstrrelid = PQfnumber(res, "tgconstrrelid"); i_tgconstrrelname = PQfnumber(res, "tgconstrrelname"); i_tgenabled = PQfnumber(res, "tgenabled"); - i_tgdeferrable = PQfnumber(res, "tgdeferrable"); - i_tginitdeferred = PQfnumber(res, "tginitdeferred"); + i_tgdeferral = PQfnumber(res, "tgdeferral"); i_tgdef = PQfnumber(res, "tgdef"); tginfo = (TriggerInfo *) pg_malloc(ntups * sizeof(TriggerInfo)); @@ -7547,8 +7540,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) tginfo[j].tgnargs = 0; tginfo[j].tgargs = NULL; tginfo[j].tgisconstraint = false; - tginfo[j].tgdeferrable = false; - tginfo[j].tginitdeferred = false; + tginfo[j].tgdeferral = 'n'; tginfo[j].tgconstrname = NULL; tginfo[j].tgconstrrelid = InvalidOid; tginfo[j].tgconstrrelname = NULL; @@ -7562,8 +7554,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) tginfo[j].tgnargs = atoi(PQgetvalue(res, j, i_tgnargs)); tginfo[j].tgargs = pg_strdup(PQgetvalue(res, j, i_tgargs)); tginfo[j].tgisconstraint = *(PQgetvalue(res, j, i_tgisconstraint)) == 't'; - tginfo[j].tgdeferrable = *(PQgetvalue(res, j, i_tgdeferrable)) == 't'; - tginfo[j].tginitdeferred = *(PQgetvalue(res, j, i_tginitdeferred)) == 't'; + tginfo[j].tgdeferral = *(PQgetvalue(res, j, i_tgdeferral)); if (tginfo[j].tgisconstraint) { @@ -8492,8 +8483,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) constrs[j].condef = pg_strdup(PQgetvalue(res, j, 3)); constrs[j].confrelid = InvalidOid; constrs[j].conindex = 0; - constrs[j].condeferrable = false; - constrs[j].condeferred = false; + constrs[j].condeferral = 'n'; constrs[j].conislocal = (PQgetvalue(res, j, 4)[0] == 't'); /* @@ -16386,13 +16376,18 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo) appendPQExpBufferChar(q, ')'); } - if (coninfo->condeferrable) + if (coninfo->condeferral != 'n') { appendPQExpBufferStr(q, " DEFERRABLE"); - if (coninfo->condeferred) + if (coninfo->condeferral == 'i' || coninfo->condeferral == 'a') appendPQExpBufferStr(q, " INITIALLY DEFERRED"); } + if (coninfo->condeferral == 'a') + { + appendPQExpBufferStr(q, " ALWAYS DEFERRED"); + } + appendPQExpBufferStr(q, ";\n"); } @@ -17026,13 +17021,16 @@ dumpTrigger(Archive *fout, TriggerInfo *tginfo) appendPQExpBuffer(query, " FROM %s\n ", tginfo->tgconstrrelname); } - if (!tginfo->tgdeferrable) + if (tginfo->tgdeferral == 'n') appendPQExpBufferStr(query, "NOT "); appendPQExpBufferStr(query, "DEFERRABLE INITIALLY "); - if (tginfo->tginitdeferred) - appendPQExpBufferStr(query, "DEFERRED\n"); + if (tginfo->tgdeferral == 'i' || tginfo->tgdeferral == 'a') + appendPQExpBufferStr(query, "DEFERRED"); else - appendPQExpBufferStr(query, "IMMEDIATE\n"); + appendPQExpBufferStr(query, "IMMEDIATE"); + if (tginfo->tgdeferral == 'a') + appendPQExpBufferStr(query, " ALWAYS DEFERRED"); + appendPQExpBufferStr(query, "\n"); } if (TRIGGER_FOR_ROW(tginfo->tgtype)) diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index e96c662..dca62c7 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -409,8 +409,7 @@ typedef struct _triggerInfo Oid tgconstrrelid; char *tgconstrrelname; char tgenabled; - bool tgdeferrable; - bool tginitdeferred; + char tgdeferral; char *tgdef; } TriggerInfo; @@ -430,7 +429,7 @@ typedef struct _evttriggerInfo * use a different objType for foreign key constraints, to make it easier * to sort them the way we want. * - * Note: condeferrable and condeferred are currently only valid for + * Note: condeferral is currently only valid for * unique/primary-key constraints. Otherwise that info is in condef. */ typedef struct _constraintInfo @@ -442,8 +441,7 @@ typedef struct _constraintInfo char *condef; /* definition, if CHECK or FOREIGN KEY */ Oid confrelid; /* referenced table, if FOREIGN KEY */ DumpId conindex; /* identifies associated index if any */ - bool condeferrable; /* true if constraint is DEFERRABLE */ - bool condeferred; /* true if constraint is INITIALLY DEFERRED */ + char condeferral; /* 'n' for not deferrable, 'd' for deferrable, 'i' for initially deferred, 'a' for always deferred*/ bool conislocal; /* true if constraint has local definition */ bool separate; /* true if must dump as separate item */ } ConstraintInfo; diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index e5b3c1e..6ebc7b4 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -2077,21 +2077,15 @@ describeOneTableDetails(const char *schemaname, appendPQExpBufferStr(&buf, "true AS indisvalid,\n"); if (pset.sversion >= 90000) appendPQExpBufferStr(&buf, - " (NOT i.indimmediate) AND " - "EXISTS (SELECT 1 FROM pg_catalog.pg_constraint " + "(SELECT CASE WHEN i.indimmediate THEN 'n' ELSE condeferral END FROM pg_catalog.pg_constraint " "WHERE conrelid = i.indrelid AND " "conindid = i.indexrelid AND " - "contype IN ('p','u','x') AND " - "condeferrable) AS condeferrable,\n" - " (NOT i.indimmediate) AND " - "EXISTS (SELECT 1 FROM pg_catalog.pg_constraint " - "WHERE conrelid = i.indrelid AND " - "conindid = i.indexrelid AND " - "contype IN ('p','u','x') AND " - "condeferred) AS condeferred,\n"); + "contype IN ('p','u','x') " + "UNION SELECT 'n' ORDER BY 1 ASC) AS condeferral,\n" + ); else appendPQExpBufferStr(&buf, - " false AS condeferrable, false AS condeferred,\n"); + " 'n' AS condeferral,\n"); if (pset.sversion >= 90400) appendPQExpBuffer(&buf, "i.indisreplident,\n"); @@ -2119,12 +2113,11 @@ describeOneTableDetails(const char *schemaname, char *indisprimary = PQgetvalue(result, 0, 1); char *indisclustered = PQgetvalue(result, 0, 2); char *indisvalid = PQgetvalue(result, 0, 3); - char *deferrable = PQgetvalue(result, 0, 4); - char *deferred = PQgetvalue(result, 0, 5); - char *indisreplident = PQgetvalue(result, 0, 6); - char *indamname = PQgetvalue(result, 0, 7); - char *indtable = PQgetvalue(result, 0, 8); - char *indpred = PQgetvalue(result, 0, 9); + char *deferral = PQgetvalue(result, 0, 4); + char *indisreplident = PQgetvalue(result, 0, 5); + char *indamname = PQgetvalue(result, 0, 6); + char *indtable = PQgetvalue(result, 0, 7); + char *indpred = PQgetvalue(result, 0, 8); if (strcmp(indisprimary, "t") == 0) printfPQExpBuffer(&tmpbuf, _("primary key, ")); @@ -2147,12 +2140,15 @@ describeOneTableDetails(const char *schemaname, if (strcmp(indisvalid, "t") != 0) appendPQExpBufferStr(&tmpbuf, _(", invalid")); - if (strcmp(deferrable, "t") == 0) + if (*deferral != 'n') appendPQExpBufferStr(&tmpbuf, _(", deferrable")); - if (strcmp(deferred, "t") == 0) + if (*deferral == 'i' || *deferral == 'a') appendPQExpBufferStr(&tmpbuf, _(", initially deferred")); + if (*deferral == 'a') + appendPQExpBufferStr(&tmpbuf, _(", always deferred")); + if (strcmp(indisreplident, "t") == 0) appendPQExpBuffer(&tmpbuf, _(", replica identity")); @@ -2185,11 +2181,11 @@ describeOneTableDetails(const char *schemaname, if (pset.sversion >= 90000) appendPQExpBufferStr(&buf, "pg_catalog.pg_get_constraintdef(con.oid, true), " - "contype, condeferrable, condeferred"); + "contype, coalesce(condeferral, 'n')"); else appendPQExpBufferStr(&buf, "null AS constraintdef, null AS contype, " - "false AS condeferrable, false AS condeferred"); + "'n' AS condeferral"); if (pset.sversion >= 90400) appendPQExpBufferStr(&buf, ", i.indisreplident"); else @@ -2230,6 +2226,7 @@ describeOneTableDetails(const char *schemaname, { const char *indexdef; const char *usingpos; + char deferral; /* Label as primary key or unique (but not both) */ if (strcmp(PQgetvalue(result, i, 1), "t") == 0) @@ -2250,11 +2247,15 @@ describeOneTableDetails(const char *schemaname, appendPQExpBuffer(&buf, " %s", indexdef); /* Need these for deferrable PK/UNIQUE indexes */ - if (strcmp(PQgetvalue(result, i, 8), "t") == 0) + deferral = *PQgetvalue(result, i, 8); + if (deferral != 'n') appendPQExpBufferStr(&buf, " DEFERRABLE"); - if (strcmp(PQgetvalue(result, i, 9), "t") == 0) + if (deferral == 'i') appendPQExpBufferStr(&buf, " INITIALLY DEFERRED"); + + if (deferral == 'a') + appendPQExpBufferStr(&buf, " ALWAYS DEFERRED"); } /* Add these for all cases */ @@ -2264,7 +2265,7 @@ describeOneTableDetails(const char *schemaname, if (strcmp(PQgetvalue(result, i, 4), "t") != 0) appendPQExpBufferStr(&buf, " INVALID"); - if (strcmp(PQgetvalue(result, i, 10), "t") == 0) + if (strcmp(PQgetvalue(result, i, 9), "t") == 0) appendPQExpBuffer(&buf, " REPLICA IDENTITY"); printTableAddFooter(&cont, buf.data); @@ -2272,7 +2273,7 @@ describeOneTableDetails(const char *schemaname, /* Print tablespace of the index on the same line */ if (pset.sversion >= 80000) add_tablespace_footer(&cont, RELKIND_INDEX, - atooid(PQgetvalue(result, i, 11)), + atooid(PQgetvalue(result, i, 10)), false); } } diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 7bb47ea..acb7a7a 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -2766,10 +2766,10 @@ psql_completion(const char *text, int start, int end) else if (TailMatches7("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL); else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("ON", MatchAny)) - COMPLETE_WITH_LIST7("NOT DEFERRABLE", "DEFERRABLE", "INITIALLY", + COMPLETE_WITH_LIST8("ALWAYS DEFERRABLE", "NOT DEFERRABLE", "DEFERRABLE", "INITIALLY", "REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE"); else if (HeadMatches2("CREATE", "TRIGGER") && - (TailMatches1("DEFERRABLE") || TailMatches2("INITIALLY", "IMMEDIATE|DEFERRED"))) + (TailMatches2("ALLWAYS", "DEFERRABLE") || TailMatches1("DEFERRABLE") || TailMatches2("INITIALLY", "IMMEDIATE|DEFERRED"))) COMPLETE_WITH_LIST4("REFERENCING", "FOR", "WHEN (", "EXECUTE PROCEDURE"); else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("REFERENCING")) COMPLETE_WITH_LIST2("OLD TABLE", "NEW TABLE"); diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index f20c5f7..cb1614e 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -73,8 +73,9 @@ extern Oid index_create(Relation heapRelation, #define INDEX_CONSTR_CREATE_MARK_AS_PRIMARY (1 << 0) #define INDEX_CONSTR_CREATE_DEFERRABLE (1 << 1) #define INDEX_CONSTR_CREATE_INIT_DEFERRED (1 << 2) -#define INDEX_CONSTR_CREATE_UPDATE_INDEX (1 << 3) -#define INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS (1 << 4) +#define INDEX_CONSTR_CREATE_ALWAYS_DEFERRED (1 << 3) +#define INDEX_CONSTR_CREATE_UPDATE_INDEX (1 << 4) +#define INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS (1 << 5) extern ObjectAddress index_constraint_create(Relation heapRelation, Oid indexRelationId, diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h index 7c1c0e1..4190e59 100644 --- a/src/include/catalog/pg_constraint.h +++ b/src/include/catalog/pg_constraint.h @@ -43,8 +43,7 @@ CATALOG(pg_constraint,2606,ConstraintRelationId) NameData conname; /* name of this constraint */ Oid connamespace; /* OID of namespace containing constraint */ char contype; /* constraint type; see codes below */ - bool condeferrable; /* deferrable constraint? */ - bool condeferred; /* deferred by default? */ + char condeferral; /* constraint deferral option */ bool convalidated; /* constraint has been validated? */ /* @@ -204,8 +203,7 @@ typedef struct ClonedConstraint extern Oid CreateConstraintEntry(const char *constraintName, Oid constraintNamespace, char constraintType, - bool isDeferrable, - bool isDeferred, + char deferralOption, bool isValidated, Oid parentConstrId, Oid relId, diff --git a/src/include/catalog/pg_trigger.h b/src/include/catalog/pg_trigger.h index 951d7d8..82eb8f3 100644 --- a/src/include/catalog/pg_trigger.h +++ b/src/include/catalog/pg_trigger.h @@ -25,8 +25,8 @@ * pg_trigger definition. cpp turns this into * typedef struct FormData_pg_trigger * - * Note: when tgconstraint is nonzero, tgconstrrelid, tgconstrindid, - * tgdeferrable, and tginitdeferred are largely redundant with the referenced + * Note: when tgconstraint is nonzero, tgconstrrelid, tgconstrindid, and + * tgdeferral are largely redundant with the referenced * pg_constraint entry. However, it is possible for a non-deferrable trigger * to be associated with a deferrable constraint. * ---------------- @@ -44,8 +44,7 @@ CATALOG(pg_trigger,2620,TriggerRelationId) Oid tgconstrrelid; /* constraint's FROM table, if any */ Oid tgconstrindid; /* constraint's supporting index, if any */ Oid tgconstraint; /* associated pg_constraint entry, if any */ - bool tgdeferrable; /* constraint trigger is deferrable */ - bool tginitdeferred; /* constraint trigger is deferred initially */ + char tgdeferral; /* constraint trigger deferral option */ int16 tgnargs; /* # of extra arguments in tgargs */ /* diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index a5b8610..4cd93ef 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -112,6 +112,7 @@ typedef struct TransitionCaptureState #define AFTER_TRIGGER_DEFERRABLE 0x00000020 #define AFTER_TRIGGER_INITDEFERRED 0x00000040 +#define AFTER_TRIGGER_ALWAYSDEFERRED 0x00000080 #define TRIGGER_FIRED_BY_INSERT(event) \ (((event) & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_INSERT) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 6390f7e..e4b4736 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2071,7 +2071,8 @@ typedef enum ConstrType /* types of constraints */ CONSTR_ATTR_DEFERRABLE, /* attributes for previous constraint node */ CONSTR_ATTR_NOT_DEFERRABLE, CONSTR_ATTR_DEFERRED, - CONSTR_ATTR_IMMEDIATE + CONSTR_ATTR_IMMEDIATE, + CONSTR_ATTR_ALWAYS_DEFERRED } ConstrType; /* Foreign key action codes */ @@ -2093,8 +2094,7 @@ typedef struct Constraint /* Fields used for most/all constraint types: */ char *conname; /* Constraint name, or NULL if unnamed */ - bool deferrable; /* DEFERRABLE? */ - bool initdeferred; /* INITIALLY DEFERRED? */ + char deferral; /* deferral option */ int location; /* token location, or -1 if unknown */ /* Fields used for constraints with expressions (CHECK and DEFAULT): */ @@ -2380,8 +2380,7 @@ typedef struct CreateTrigStmt /* explicitly named transition data */ List *transitionRels; /* TriggerTransition nodes, or NIL if none */ /* The remaining fields are only used for constraint triggers */ - bool deferrable; /* [NOT] DEFERRABLE */ - bool initdeferred; /* INITIALLY {DEFERRED|IMMEDIATE} */ + char deferral; /* deferral option */ RangeVar *constrrel; /* opposite relation, if RI trigger */ } CreateTrigStmt; @@ -2731,8 +2730,7 @@ typedef struct IndexStmt bool unique; /* is index unique? */ bool primary; /* is index a primary key? */ bool isconstraint; /* is it for a pkey/unique constraint? */ - bool deferrable; /* is the constraint DEFERRABLE? */ - bool initdeferred; /* is the constraint INITIALLY DEFERRED? */ + char deferral; /* constraint deferral option */ bool transformed; /* true when transformIndexStmt is finished */ bool concurrent; /* should this be a concurrent index build? */ bool if_not_exists; /* just do nothing if index already exists? */ diff --git a/src/include/utils/reltrigger.h b/src/include/utils/reltrigger.h index 9b4dc7f..655ab8a 100644 --- a/src/include/utils/reltrigger.h +++ b/src/include/utils/reltrigger.h @@ -32,8 +32,7 @@ typedef struct Trigger Oid tgconstrrelid; Oid tgconstrindid; Oid tgconstraint; - bool tgdeferrable; - bool tginitdeferred; + char tgdeferral; int16 tgnargs; int16 tgnattr; int16 *tgattr; diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 702bf9f..6732997 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -662,44 +662,44 @@ ALTER TABLE FKTABLE ALTER CONSTRAINT fkdd2 DEFERRABLE INITIALLY DEFERRED; ALTER TABLE FKTABLE ADD CONSTRAINT fkdi2 FOREIGN KEY(ftest1) REFERENCES pktable ON DELETE CASCADE ON UPDATE NO ACTION NOT DEFERRABLE; ALTER TABLE FKTABLE ALTER CONSTRAINT fkdi2 DEFERRABLE INITIALLY IMMEDIATE; -SELECT conname, tgfoid::regproc, tgtype, tgdeferrable, tginitdeferred +SELECT conname, tgfoid::regproc, tgtype, tgdeferral FROM pg_trigger JOIN pg_constraint con ON con.oid = tgconstraint WHERE tgrelid = 'pktable'::regclass ORDER BY 1,2,3; - conname | tgfoid | tgtype | tgdeferrable | tginitdeferred ----------+------------------------+--------+--------------+---------------- - fkdd | "RI_FKey_cascade_del" | 9 | f | f - fkdd | "RI_FKey_noaction_upd" | 17 | t | t - fkdd2 | "RI_FKey_cascade_del" | 9 | f | f - fkdd2 | "RI_FKey_noaction_upd" | 17 | t | t - fkdi | "RI_FKey_cascade_del" | 9 | f | f - fkdi | "RI_FKey_noaction_upd" | 17 | t | f - fkdi2 | "RI_FKey_cascade_del" | 9 | f | f - fkdi2 | "RI_FKey_noaction_upd" | 17 | t | f - fknd | "RI_FKey_cascade_del" | 9 | f | f - fknd | "RI_FKey_noaction_upd" | 17 | f | f - fknd2 | "RI_FKey_cascade_del" | 9 | f | f - fknd2 | "RI_FKey_noaction_upd" | 17 | f | f + conname | tgfoid | tgtype | tgdeferral +---------+------------------------+--------+------------ + fkdd | "RI_FKey_cascade_del" | 9 | n + fkdd | "RI_FKey_noaction_upd" | 17 | i + fkdd2 | "RI_FKey_cascade_del" | 9 | n + fkdd2 | "RI_FKey_noaction_upd" | 17 | i + fkdi | "RI_FKey_cascade_del" | 9 | n + fkdi | "RI_FKey_noaction_upd" | 17 | d + fkdi2 | "RI_FKey_cascade_del" | 9 | n + fkdi2 | "RI_FKey_noaction_upd" | 17 | d + fknd | "RI_FKey_cascade_del" | 9 | n + fknd | "RI_FKey_noaction_upd" | 17 | n + fknd2 | "RI_FKey_cascade_del" | 9 | n + fknd2 | "RI_FKey_noaction_upd" | 17 | n (12 rows) -SELECT conname, tgfoid::regproc, tgtype, tgdeferrable, tginitdeferred +SELECT conname, tgfoid::regproc, tgtype, tgdeferral FROM pg_trigger JOIN pg_constraint con ON con.oid = tgconstraint WHERE tgrelid = 'fktable'::regclass ORDER BY 1,2,3; - conname | tgfoid | tgtype | tgdeferrable | tginitdeferred ----------+---------------------+--------+--------------+---------------- - fkdd | "RI_FKey_check_ins" | 5 | t | t - fkdd | "RI_FKey_check_upd" | 17 | t | t - fkdd2 | "RI_FKey_check_ins" | 5 | t | t - fkdd2 | "RI_FKey_check_upd" | 17 | t | t - fkdi | "RI_FKey_check_ins" | 5 | t | f - fkdi | "RI_FKey_check_upd" | 17 | t | f - fkdi2 | "RI_FKey_check_ins" | 5 | t | f - fkdi2 | "RI_FKey_check_upd" | 17 | t | f - fknd | "RI_FKey_check_ins" | 5 | f | f - fknd | "RI_FKey_check_upd" | 17 | f | f - fknd2 | "RI_FKey_check_ins" | 5 | f | f - fknd2 | "RI_FKey_check_upd" | 17 | f | f + conname | tgfoid | tgtype | tgdeferral +---------+---------------------+--------+------------ + fkdd | "RI_FKey_check_ins" | 5 | i + fkdd | "RI_FKey_check_upd" | 17 | i + fkdd2 | "RI_FKey_check_ins" | 5 | i + fkdd2 | "RI_FKey_check_upd" | 17 | i + fkdi | "RI_FKey_check_ins" | 5 | d + fkdi | "RI_FKey_check_upd" | 17 | d + fkdi2 | "RI_FKey_check_ins" | 5 | d + fkdi2 | "RI_FKey_check_upd" | 17 | d + fknd | "RI_FKey_check_ins" | 5 | n + fknd | "RI_FKey_check_upd" | 17 | n + fknd2 | "RI_FKey_check_ins" | 5 | n + fknd2 | "RI_FKey_check_upd" | 17 | n (12 rows) -- temp tables should go away by themselves, need not drop them. diff --git a/src/test/regress/input/constraints.source b/src/test/regress/input/constraints.source index 98dd421..68530fb 100644 --- a/src/test/regress/input/constraints.source +++ b/src/test/regress/input/constraints.source @@ -437,8 +437,59 @@ COMMIT; SELECT * FROM unique_tbl; +-- test ALWAYS DEFERRED +ALTER TABLE unique_tbl DROP CONSTRAINT unique_tbl_i_key; +ALTER TABLE unique_tbl ADD CONSTRAINT unique_tbl_i_key + UNIQUE (i) ALWAYS DEFERRED; + +BEGIN; +-- should fail at commit time +UPDATE unique_tbl SET i = 2 WHERE i = 1; +COMMIT; -- should fail + +BEGIN; +-- should fail at commit time +SET CONSTRAINTS ALL IMMEDIATE; +UPDATE unique_tbl SET i = 2 WHERE i = 1; +COMMIT; -- should fail + +BEGIN; +SET CONSTRAINTS unique_tbl_i_key IMMEDIATE; -- should fail +ROLLBACK; + DROP TABLE unique_tbl; +CREATE FUNCTION deferred_trigger_test() RETURNS TRIGGER AS $$ + BEGIN + RAISE EXCEPTION 'deferred_trigger_test() ran'; + END +$$ language plpgsql; + +CREATE TABLE deferred_trigger_test_tbl (a TEXT); + +CREATE CONSTRAINT TRIGGER deferred_trigger_test_constraint +AFTER INSERT OR UPDATE OR DELETE +ON deferred_trigger_test_tbl +ALWAYS DEFERRED +FOR EACH ROW +EXECUTE PROCEDURE deferred_trigger_test(); + +BEGIN; +INSERT INTO deferred_trigger_test_tbl (a) SELECT 'foo'; +COMMIT; -- should fail + +BEGIN; +SET CONSTRAINTS ALL IMMEDIATE; +INSERT INTO deferred_trigger_test_tbl (a) SELECT 'foo'; +COMMIT; -- should fail + +BEGIN; +SET CONSTRAINTS deferred_trigger_test_constraint IMMEDIATE; -- should fail +ROLLBACK; + +DROP TABLE deferred_trigger_test_tbl CASCADE; +DROP FUNCTION deferred_trigger_test(); + -- -- EXCLUDE constraints -- diff --git a/src/test/regress/output/constraints.source b/src/test/regress/output/constraints.source index a6a1df1..eefe8c0 100644 --- a/src/test/regress/output/constraints.source +++ b/src/test/regress/output/constraints.source @@ -618,7 +618,57 @@ SELECT * FROM unique_tbl; 3 | threex (5 rows) +-- test ALWAYS DEFERRED +ALTER TABLE unique_tbl DROP CONSTRAINT unique_tbl_i_key; +ALTER TABLE unique_tbl ADD CONSTRAINT unique_tbl_i_key + UNIQUE (i) ALWAYS DEFERRED; +BEGIN; +-- should fail at commit time +UPDATE unique_tbl SET i = 2 WHERE i = 1; +COMMIT; -- should fail +ERROR: duplicate key value violates unique constraint "unique_tbl_i_key" +DETAIL: Key (i)=(2) already exists. +BEGIN; +-- should fail at commit time +SET CONSTRAINTS ALL IMMEDIATE; +UPDATE unique_tbl SET i = 2 WHERE i = 1; +COMMIT; -- should fail +ERROR: duplicate key value violates unique constraint "unique_tbl_i_key" +DETAIL: Key (i)=(2) already exists. +BEGIN; +SET CONSTRAINTS unique_tbl_i_key IMMEDIATE; -- should fail +ERROR: constraint "unique_tbl_i_key" is always deferred +ROLLBACK; DROP TABLE unique_tbl; +CREATE FUNCTION deferred_trigger_test() RETURNS TRIGGER AS $$ + BEGIN + RAISE EXCEPTION 'deferred_trigger_test() ran'; + END +$$ language plpgsql; +CREATE TABLE deferred_trigger_test_tbl (a TEXT); +CREATE CONSTRAINT TRIGGER deferred_trigger_test_constraint +AFTER INSERT OR UPDATE OR DELETE +ON deferred_trigger_test_tbl +ALWAYS DEFERRED +FOR EACH ROW +EXECUTE PROCEDURE deferred_trigger_test(); +BEGIN; +INSERT INTO deferred_trigger_test_tbl (a) SELECT 'foo'; +COMMIT; -- should fail +ERROR: deferred_trigger_test() ran +CONTEXT: PL/pgSQL function deferred_trigger_test() line 3 at RAISE +BEGIN; +SET CONSTRAINTS ALL IMMEDIATE; +INSERT INTO deferred_trigger_test_tbl (a) SELECT 'foo'; +COMMIT; -- should fail +ERROR: deferred_trigger_test() ran +CONTEXT: PL/pgSQL function deferred_trigger_test() line 3 at RAISE +BEGIN; +SET CONSTRAINTS deferred_trigger_test_constraint IMMEDIATE; -- should fail +ERROR: constraint "deferred_trigger_test_constraint" is always deferred +ROLLBACK; +DROP TABLE deferred_trigger_test_tbl CASCADE; +DROP FUNCTION deferred_trigger_test(); -- -- EXCLUDE constraints -- diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index d508a69..c41249f 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -502,11 +502,11 @@ ALTER TABLE FKTABLE ADD CONSTRAINT fkdi2 FOREIGN KEY(ftest1) REFERENCES pktable ON DELETE CASCADE ON UPDATE NO ACTION NOT DEFERRABLE; ALTER TABLE FKTABLE ALTER CONSTRAINT fkdi2 DEFERRABLE INITIALLY IMMEDIATE; -SELECT conname, tgfoid::regproc, tgtype, tgdeferrable, tginitdeferred +SELECT conname, tgfoid::regproc, tgtype, tgdeferral FROM pg_trigger JOIN pg_constraint con ON con.oid = tgconstraint WHERE tgrelid = 'pktable'::regclass ORDER BY 1,2,3; -SELECT conname, tgfoid::regproc, tgtype, tgdeferrable, tginitdeferred +SELECT conname, tgfoid::regproc, tgtype, tgdeferral FROM pg_trigger JOIN pg_constraint con ON con.oid = tgconstraint WHERE tgrelid = 'fktable'::regclass ORDER BY 1,2,3; -- 2.7.4