[PATCH] WIP Add ALWAYS DEFERRED option for constraints
Attached are patches to add an ALWAYS DEFERRED option to CONSTRAINTs and
CONSTRAINT TRIGGERs, meaning: SET CONSTRAINTS .. IMMEDIATE will not make
immediate any constraint/trigger that is declared as ALWAYS DEFERRED.
I.e., the opposite of NOT DEFERRED. Perhaps I should make this NOT
IMMEDIATE? Making it NOT IMMEDIATE has the benefit of not having to
change the precedence of ALWAYS to avoid a shift/reduce conflict... It
may also be more in keeping with NOT DEFERRED.
Motivation:
- I have trigger procedures that must run at the end of the transaction
(after the last statement prior to COMMIT sent by the client/user),
which I make DEFERRABLE, INITIALLY DEFERRED CONSTRAINT TRIGGERs out
of, but SET CONSTRAINTS can be used to foil my triggers. I have
written SQL code to detect that constraint triggers have fired too
soon, but I'd rather not need it.
- Symmetry. If we can have NOT DEFERRABLE constraints, why not also
NOT IMMEDIABLE? :) Naturally "immediable" is not a word, but you
get the point.
- To learn my way around PostgreSQL source code in preparation for
other contributions.
Anyways, this patch is NOT passing tests at the moment, and I'm not sure
why. I'm sure I can figure it out, but first I need to understand the
failures. E.g., I see this sort of difference:
\d testschema.test_index1
Index "testschema.test_index1"
Column | Type | Definition
--------+--------+------------
id | bigint | id
-btree, for table "testschema.test_default_tab"
+f, for table "testschema.btree", predicate (test_default_tab)
which means, I think, that I've screwed up in src/bin/psql/describe.c,
don't it's not obvious to me yet how.
Some questions for experienced PostgreSQL developers:
Q0: Is this sort of patch welcomed?
Q1: Should new columns for pg_catalog.pg_constraint go at the end, or may
they be added in the middle?
Q2: Can I add new columns to information_schema tables, or are there
standards-compliance issues with that?
Q3: Is the C-style for PG documented somewhere? (sorry if I missed this)
Q4: Any ideas what I'm doing wrong in this patch series?
Nico
--
Attachments:
0001-WIP-Add-ALWAYS-DEFERRED-option-for-CONSTRAINTs.patchtext/x-diff; charset=us-asciiDownload
From 02f2765bde7e7d4fd357853c33dac55e4bdc2732 Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@cryptonector.com>
Date: Tue, 3 Oct 2017 00:33:09 -0500
Subject: [PATCH 1/4] WIP: 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).
---
WIP | 25 +++++++++
doc/src/sgml/catalogs.sgml | 7 +++
doc/src/sgml/ref/alter_table.sgml | 4 +-
doc/src/sgml/ref/create_table.sgml | 10 ++--
doc/src/sgml/ref/create_trigger.sgml | 2 +-
src/backend/catalog/heap.c | 1 +
src/backend/catalog/index.c | 8 +++
src/backend/catalog/information_schema.sql | 8 +++
src/backend/catalog/pg_constraint.c | 2 +
src/backend/catalog/toasting.c | 2 +-
src/backend/commands/indexcmds.c | 2 +-
src/backend/commands/tablecmds.c | 20 +++++++-
src/backend/commands/trigger.c | 25 +++++++--
src/backend/commands/typecmds.c | 3 ++
src/backend/parser/gram.y | 81 ++++++++++++++++++++++++------
src/backend/parser/parse_utilcmd.c | 1 +
src/backend/utils/adt/ruleutils.c | 2 +
src/bin/pg_dump/pg_dump.c | 23 +++++++--
src/bin/pg_dump/pg_dump.h | 2 +
src/bin/psql/describe.c | 15 ++++--
src/include/catalog/index.h | 2 +
src/include/catalog/pg_constraint.h | 4 +-
src/include/catalog/pg_constraint_fn.h | 1 +
src/include/catalog/pg_trigger.h | 4 +-
src/include/commands/trigger.h | 1 +
src/include/nodes/parsenodes.h | 6 ++-
src/include/utils/reltrigger.h | 1 +
27 files changed, 221 insertions(+), 41 deletions(-)
create mode 100644 WIP
diff --git a/WIP b/WIP
new file mode 100644
index 0000000..806df83
--- /dev/null
+++ b/WIP
@@ -0,0 +1,25 @@
+WIP notes for adding ALWAYS DEFERRED option for CONSTRAINTs and CONSTRAINT TRIGGERs
+===================================================================================
+
+ - add ALWAYS DEFERRED syntax in src/backend/parser/gram.y (DONE)
+
+ (the existing NOT DEFERRABLE == ALWAYS IMMEDIATE, so we don't add that)
+
+ - add alwaysdeferred field to several structs -- wherever initdeferred
+ is defined, basically (DONE)
+
+ - add conalwaysdeferred column to pg_constraints
+
+ - in src/backend/commands/trigger.c modify places where all_isdeferred
+ and all_isset are used (DONE)
+
+ - add AFTER_TRIGGER_ALWAYSDEFERRED for AfterTriggerSharedData's
+ ats_event (struct) and fill it in in AfterTriggerSaveEvent(), that
+ way afterTriggerCheckState() can check this
+
+ (DONE)
+
+ - add docs (DONE)
+
+ - add tests (TBD)
+
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 77c7676..b6ab8ab 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2202,6 +2202,13 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</row>
<row>
+ <entry><structfield>conalwaysdeferred</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>Is the constraint always deferred?</entry>
+ </row>
+
+ <row>
<entry><structfield>convalidated</structfield></entry>
<entry><type>bool</type></entry>
<entry></entry>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index dae6307..e6a1144 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 ]
@@ -89,7 +89,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
[ 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 ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 9c63d92..665adae 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>
@@ -939,13 +939,17 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
<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>PRIMARY KEY</>,
<literal>EXCLUDE</>, and
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 2496250..2b1cb7c 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/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a376b99..3c86ec2 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2087,6 +2087,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
CONSTRAINT_CHECK, /* Constraint Type */
false, /* Is Deferrable */
false, /* Is Deferred */
+ false, /* Is Always Deferred */
is_validated,
RelationGetRelid(rel), /* relation */
attNos, /* attrs in the constraint */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 25c5bea..f3edb6a 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -681,6 +681,7 @@ UpdateIndexRelation(Oid indexoid,
* isconstraint: index is owned by PRIMARY KEY, UNIQUE, or EXCLUSION constraint
* deferrable: constraint is DEFERRABLE
* initdeferred: constraint is INITIALLY DEFERRED
+ * alwaysdeferred: constraint is ALWAYS DEFERRED
* allow_system_table_mods: allow table to be a system catalog
* skip_build: true to skip the index_build() step for the moment; caller
* must do it later (typically via reindex_index())
@@ -710,6 +711,7 @@ index_create(Relation heapRelation,
bool isconstraint,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool allow_system_table_mods,
bool skip_build,
bool concurrent,
@@ -963,6 +965,7 @@ index_create(Relation heapRelation,
constraintType,
deferrable,
initdeferred,
+ alwaysdeferred,
false, /* already marked primary */
false, /* pg_index entry is OK */
false, /* no old dependencies */
@@ -1006,6 +1009,7 @@ index_create(Relation heapRelation,
/* Non-constraint indexes can't be deferrable */
Assert(!deferrable);
Assert(!initdeferred);
+ Assert(!alwaysdeferred);
}
/* Store dependency on collations */
@@ -1059,6 +1063,7 @@ index_create(Relation heapRelation,
Assert(!isconstraint);
Assert(!deferrable);
Assert(!initdeferred);
+ Assert(!alwaysdeferred);
}
/* Post creation hook for new index */
@@ -1151,6 +1156,7 @@ index_constraint_create(Relation heapRelation,
char constraintType,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool mark_as_primary,
bool update_pgindex,
bool remove_old_dependencies,
@@ -1199,6 +1205,7 @@ index_constraint_create(Relation heapRelation,
constraintType,
deferrable,
initdeferred,
+ alwaysdeferred,
true,
RelationGetRelid(heapRelation),
indexInfo->ii_KeyAttrNumbers,
@@ -1263,6 +1270,7 @@ index_constraint_create(Relation heapRelation,
trigger->isconstraint = true;
trigger->deferrable = true;
trigger->initdeferred = initdeferred;
+ trigger->alwaysdeferred = alwaysdeferred;
trigger->constrrel = NULL;
(void) CreateTrigger(trigger, NULL, RelationGetRelid(heapRelation),
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 5398271..4fa230c 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -895,6 +895,10 @@ CREATE VIEW domain_constraints AS
AS yes_or_no) AS is_deferrable,
CAST(CASE WHEN condeferred 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
@@ -1779,6 +1783,10 @@ CREATE VIEW table_constraints AS
AS is_deferrable,
CAST(CASE WHEN c.condeferred 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 nc,
pg_namespace nr,
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 1336c46..81c0477 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -51,6 +51,7 @@ CreateConstraintEntry(const char *constraintName,
char constraintType,
bool isDeferrable,
bool isDeferred,
+ bool isAlwaysDeferred,
bool isValidated,
Oid relId,
const int16 *constraintKey,
@@ -166,6 +167,7 @@ CreateConstraintEntry(const char *constraintName,
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_conalwaysdeferred - 1] = BoolGetDatum(isAlwaysDeferred);
values[Anum_pg_constraint_convalidated - 1] = BoolGetDatum(isValidated);
values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(relId);
values[Anum_pg_constraint_contypid - 1] = ObjectIdGetDatum(domainId);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 29756eb..d388817 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -333,7 +333,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
BTREE_AM_OID,
rel->rd_rel->reltablespace,
collationObjectId, classObjectId, coloptions, (Datum) 0,
- true, false, false, false,
+ true, false, false, false, false,
true, false, false, true, false);
heap_close(toast_rel, NoLock);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 620704e..e6b7e34 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -672,7 +672,7 @@ DefineIndex(Oid relationId,
collationObjectId, classObjectId,
coloptions, reloptions, stmt->primary,
stmt->isconstraint, stmt->deferrable, stmt->initdeferred,
- allowSystemTableMods,
+ stmt->alwaysdeferred, allowSystemTableMods,
skip_build || stmt->concurrent,
stmt->concurrent, !check_rights,
stmt->if_not_exists);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 33c99b3..f48978f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6779,6 +6779,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
constraintType,
stmt->deferrable,
stmt->initdeferred,
+ stmt->alwaysdeferred,
stmt->primary,
true, /* update pg_index */
true, /* remove old dependencies */
@@ -7346,6 +7347,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
CONSTRAINT_FOREIGN,
fkconstraint->deferrable,
fkconstraint->initdeferred,
+ fkconstraint->alwaysdeferred,
fkconstraint->initially_valid,
RelationGetRelid(rel),
fkattnum,
@@ -7470,7 +7472,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
cmdcon->conname, RelationGetRelationName(rel))));
if (currcon->condeferrable != cmdcon->deferrable ||
- currcon->condeferred != cmdcon->initdeferred)
+ currcon->condeferred != cmdcon->initdeferred ||
+ currcon->conalwaysdeferred != cmdcon->alwaysdeferred)
{
HeapTuple copyTuple;
HeapTuple tgtuple;
@@ -7488,6 +7491,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
copy_con->condeferrable = cmdcon->deferrable;
copy_con->condeferred = cmdcon->initdeferred;
+ copy_con->conalwaysdeferred = cmdcon->alwaysdeferred;
CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
InvokeObjectPostAlterHook(ConstraintRelationId,
@@ -7541,6 +7545,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
copy_tg->tgdeferrable = cmdcon->deferrable;
copy_tg->tginitdeferred = cmdcon->initdeferred;
+ copy_tg->tgalwaysdeferred = cmdcon->alwaysdeferred;
CatalogTupleUpdate(tgrel, ©Tuple->t_self, copyTuple);
InvokeObjectPostAlterHook(TriggerRelationId,
@@ -8208,6 +8213,7 @@ validateForeignKeyConstraint(char *conname,
trig.tgconstraint = constraintOid;
trig.tgdeferrable = FALSE;
trig.tginitdeferred = FALSE;
+ trig.tgalwaysdeferred = FALSE;
/* we needn't fill in remaining fields */
/*
@@ -8297,6 +8303,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
fk_trigger->isconstraint = true;
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
+ fk_trigger->alwaysdeferred = fkconstraint->alwaysdeferred;
fk_trigger->constrrel = NULL;
fk_trigger->args = NIL;
@@ -8345,26 +8352,31 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
case FKCONSTR_ACTION_NOACTION:
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
+ fk_trigger->alwaysdeferred = fkconstraint->alwaysdeferred;
fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
break;
case FKCONSTR_ACTION_RESTRICT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
break;
case FKCONSTR_ACTION_CASCADE:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
break;
case FKCONSTR_ACTION_SETNULL:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
break;
case FKCONSTR_ACTION_SETDEFAULT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
break;
default:
@@ -8400,26 +8412,31 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
case FKCONSTR_ACTION_NOACTION:
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
+ fk_trigger->alwaysdeferred = fkconstraint->alwaysdeferred;
fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
break;
case FKCONSTR_ACTION_RESTRICT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
break;
case FKCONSTR_ACTION_CASCADE:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
break;
case FKCONSTR_ACTION_SETNULL:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
break;
case FKCONSTR_ACTION_SETDEFAULT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
break;
default:
@@ -11162,6 +11179,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
if (acon->condeferrable != bcon->condeferrable ||
acon->condeferred != bcon->condeferred ||
+ acon->conalwaysdeferred != bcon->conalwaysdeferred ||
strcmp(decompile_conbin(a, tupleDesc),
decompile_conbin(b, tupleDesc)) != 0)
return false;
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index e75a59d..8589473 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -650,6 +650,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
CONSTRAINT_TRIGGER,
stmt->deferrable,
stmt->initdeferred,
+ stmt->alwaysdeferred,
true,
RelationGetRelid(rel),
NULL, /* no conkey */
@@ -748,6 +749,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
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_tgalwaysdeferred - 1] = BoolGetDatum(stmt->alwaysdeferred);
if (stmt->args)
{
@@ -1245,6 +1247,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
}
fkcon->deferrable = stmt->deferrable;
fkcon->initdeferred = stmt->initdeferred;
+ fkcon->alwaysdeferred = stmt->alwaysdeferred;
fkcon->skip_validation = false;
fkcon->initially_valid = true;
@@ -1742,6 +1745,7 @@ RelationBuildTriggers(Relation relation)
build->tgconstraint = pg_trigger->tgconstraint;
build->tgdeferrable = pg_trigger->tgdeferrable;
build->tginitdeferred = pg_trigger->tginitdeferred;
+ build->tgalwaysdeferred = pg_trigger->tgalwaysdeferred;
build->tgnargs = pg_trigger->tgnargs;
/* tgattr is first var-width field, so OK to access directly */
build->tgnattr = pg_trigger->tgattr.dim1;
@@ -2050,6 +2054,8 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
return false;
if (trig1->tginitdeferred != trig2->tginitdeferred)
return false;
+ if (trig1->tgalwaysdeferred != trig2->tgalwaysdeferred)
+ return false;
if (trig1->tgnargs != trig2->tgnargs)
return false;
if (trig1->tgnattr != trig2->tgnattr)
@@ -3392,6 +3398,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;
@@ -3678,6 +3685,8 @@ afterTriggerCheckState(AfterTriggerShared evtshared)
*/
if ((evtshared->ats_event & AFTER_TRIGGER_DEFERRABLE) == 0)
return false;
+ if ((evtshared->ats_event & AFTER_TRIGGER_ALWAYSDEFERRED) == 0)
+ return true;
/*
* If constraint state exists, SET CONSTRAINTS might have been executed
@@ -5191,14 +5200,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->condeferrable)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("constraint \"%s\" is not deferrable",
constraint->relname)));
+ else if (!stmt->deferred && con->conalwaysdeferred)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("constraint \"%s\" is always deferred",
+ constraint->relname)));
+ else if (con->condeferrable && !con->conalwaysdeferred)
+ conoidlist = lappend_oid(conoidlist,
+ HeapTupleGetOid(tup));
found = true;
}
@@ -5695,7 +5709,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
(event & TRIGGER_EVENT_OPMASK) |
(row_trigger ? TRIGGER_EVENT_ROW : 0) |
(trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) |
- (trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0);
+ (trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0) |
+ (trigger->tgalwaysdeferred ? 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 8e5134b..199bedf 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1014,6 +1014,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),
@@ -2554,6 +2555,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),
@@ -3100,6 +3102,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
CONSTRAINT_CHECK, /* Constraint Type */
false, /* Is Deferrable */
false, /* Is Deferred */
+ false, /* Is Always Deferred */
!constr->skip_validation, /* Is Validated */
InvalidOid, /* not a relation constraint */
NULL,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99..654f38c 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)
@@ -185,7 +186,8 @@ static void SplitColQualList(List *qualList,
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);
+ bool *no_inherit, bool *alwaysdeferred,
+ core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -725,6 +727,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* RANGE, ROWS to support opt_existing_window_name; and for RANGE, ROWS
* 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.
*
@@ -743,7 +746,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 PRECEDING FOLLOWING CUBE ROLLUP
+%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP ALWAYS
%left Op OPERATOR /* multi-character ops and user-defined operators */
%left '+' '-'
%left '*' '/' '%'
@@ -2216,7 +2219,9 @@ alter_table_cmd:
processCASbits($4, @4, "ALTER CONSTRAINT statement",
&c->deferrable,
&c->initdeferred,
- NULL, NULL, yyscanner);
+ NULL, NULL,
+ &c->alwaysdeferred,
+ yyscanner);
$$ = (Node *)n;
}
/* ALTER TABLE <name> VALIDATE CONSTRAINT ... */
@@ -3469,6 +3474,13 @@ ConstraintAttr:
n->location = @1;
$$ = (Node *)n;
}
+ | ALWAYS DEFERRED
+ {
+ Constraint *n = makeNode(Constraint);
+ n->contype = CONSTR_ATTR_ALWAYS_DEFERRED;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
;
@@ -3524,7 +3536,8 @@ ConstraintElem:
n->cooked_expr = NULL;
processCASbits($5, @5, "CHECK",
NULL, NULL, &n->skip_validation,
- &n->is_no_inherit, yyscanner);
+ &n->is_no_inherit,
+ NULL, yyscanner);
n->initially_valid = !n->skip_validation;
$$ = (Node *)n;
}
@@ -3540,7 +3553,8 @@ ConstraintElem:
n->indexspace = $6;
processCASbits($7, @7, "UNIQUE",
&n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ NULL, &n->alwaysdeferred,
+ yyscanner);
$$ = (Node *)n;
}
| UNIQUE ExistingIndex ConstraintAttributeSpec
@@ -3554,7 +3568,8 @@ ConstraintElem:
n->indexspace = NULL;
processCASbits($3, @3, "UNIQUE",
&n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ NULL, &n->alwaysdeferred,
+ yyscanner);
$$ = (Node *)n;
}
| PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace
@@ -3569,7 +3584,8 @@ ConstraintElem:
n->indexspace = $7;
processCASbits($8, @8, "PRIMARY KEY",
&n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ NULL, &n->alwaysdeferred,
+ yyscanner);
$$ = (Node *)n;
}
| PRIMARY KEY ExistingIndex ConstraintAttributeSpec
@@ -3583,7 +3599,8 @@ ConstraintElem:
n->indexspace = NULL;
processCASbits($4, @4, "PRIMARY KEY",
&n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ NULL, &n->alwaysdeferred,
+ yyscanner);
$$ = (Node *)n;
}
| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
@@ -3601,7 +3618,8 @@ ConstraintElem:
n->where_clause = $8;
processCASbits($9, @9, "EXCLUDE",
&n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ NULL, &n->alwaysdeferred,
+ yyscanner);
$$ = (Node *)n;
}
| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
@@ -3619,6 +3637,7 @@ ConstraintElem:
processCASbits($11, @11, "FOREIGN KEY",
&n->deferrable, &n->initdeferred,
&n->skip_validation, NULL,
+ &n->alwaysdeferred,
yyscanner);
n->initially_valid = !n->skip_validation;
$$ = (Node *)n;
@@ -5179,6 +5198,7 @@ CreateTrigStmt:
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
+ n->alwaysdeferred = FALSE;
n->constrrel = NULL;
$$ = (Node *)n;
}
@@ -5201,7 +5221,8 @@ CreateTrigStmt:
n->isconstraint = TRUE;
processCASbits($10, @10, "TRIGGER",
&n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ NULL, &n->alwaysdeferred,
+ yyscanner);
n->constrrel = $9;
$$ = (Node *)n;
}
@@ -5357,14 +5378,22 @@ 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"),
+ errmsg("constraint declared INITIALLY or ALWAYS 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"),
+ 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))
+ (newspec & (CAS_INITIALLY_IMMEDIATE | CAS_INITIALLY_DEFERRED)) == (CAS_INITIALLY_IMMEDIATE | CAS_INITIALLY_DEFERRED) ||
+ (newspec & CAS_ALWAYS_DEFERRED))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting constraint properties"),
@@ -5378,6 +5407,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; }
;
@@ -5469,7 +5499,8 @@ CreateAssertStmt:
n->isconstraint = TRUE;
processCASbits($8, @8, "ASSERTION",
&n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ NULL, &n->alwaysdeferred,
+ yyscanner);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -7146,6 +7177,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
n->isconstraint = false;
n->deferrable = false;
n->initdeferred = false;
+ n->alwaysdeferred = false;
n->transformed = false;
n->if_not_exists = false;
$$ = (Node *)n;
@@ -7172,6 +7204,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
n->isconstraint = false;
n->deferrable = false;
n->initdeferred = false;
+ n->alwaysdeferred = false;
n->transformed = false;
n->if_not_exists = true;
$$ = (Node *)n;
@@ -15780,17 +15813,20 @@ 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)
+ bool *no_inherit, bool *alwaysdeferred,
+ core_yyscan_t yyscanner)
{
/* defaults */
if (deferrable)
*deferrable = false;
if (initdeferred)
*initdeferred = false;
+ if (alwaysdeferred)
+ *alwaysdeferred = false;
if (not_valid)
*not_valid = false;
- if (cas_bits & (CAS_DEFERRABLE | CAS_INITIALLY_DEFERRED))
+ if (cas_bits & (CAS_DEFERRABLE | CAS_INITIALLY_DEFERRED | CAS_ALWAYS_DEFERRED))
{
if (deferrable)
*deferrable = true;
@@ -15816,6 +15852,19 @@ processCASbits(int cas_bits, int location, const char *constrType,
parser_errposition(location)));
}
+ if (cas_bits & CAS_ALWAYS_DEFERRED)
+ {
+ if (alwaysdeferred)
+ *alwaysdeferred = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is CHECK, UNIQUE, or similar */
+ errmsg("%s constraints cannot be marked ALWAYS DEFERRED",
+ constrType),
+ parser_errposition(location)));
+ }
+
if (cas_bits & CAS_NOT_VALID)
{
if (not_valid)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ed9ed83..689b89c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1359,6 +1359,7 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
index->isconstraint = true;
index->deferrable = conrec->condeferrable;
index->initdeferred = conrec->condeferred;
+ index->alwaysdeferred = conrec->conalwaysdeferred;
/* If it's an exclusion constraint, we need the operator names */
if (idxrec->indisexclusion)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index eb01f35..d1f636d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2144,6 +2144,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
appendStringInfoString(&buf, " DEFERRABLE");
if (conForm->condeferred)
appendStringInfoString(&buf, " INITIALLY DEFERRED");
+ if (conForm->conalwaysdeferred)
+ 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 b93dd8b..60fcc77 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6404,6 +6404,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_conname,
i_condeferrable,
i_condeferred,
+ i_conalwaysdeferred,
i_contableoid,
i_conoid,
i_condef,
@@ -6466,7 +6467,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
"c.oid AS conoid, "
"pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
- "t.reloptions AS indreloptions "
+ "t.reloptions AS indreloptions, "
+ "c.conalwaysdeferred "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_constraint c "
@@ -6497,7 +6499,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
"c.oid AS conoid, "
"pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
- "t.reloptions AS indreloptions "
+ "t.reloptions AS indreloptions, "
+ "c.conalwaysdeferred "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_constraint c "
@@ -6524,7 +6527,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
"c.oid AS conoid, "
"null AS condef, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
- "t.reloptions AS indreloptions "
+ "t.reloptions AS indreloptions, "
+ "c.conalwaysdeferred "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
@@ -6554,7 +6558,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
"c.oid AS conoid, "
"null AS condef, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
- "null AS indreloptions "
+ "null AS indreloptions, "
+ "c.conalwaysdeferred "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
@@ -6586,6 +6591,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_conname = PQfnumber(res, "conname");
i_condeferrable = PQfnumber(res, "condeferrable");
i_condeferred = PQfnumber(res, "condeferred");
+ i_conalwaysdeferred = PQfnumber(res, "conalwaysdeferred");
i_contableoid = PQfnumber(res, "contableoid");
i_conoid = PQfnumber(res, "conoid");
i_condef = PQfnumber(res, "condef");
@@ -6641,6 +6647,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
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].conalwaysdeferred = *(PQgetvalue(res, j, i_conalwaysdeferred)) == 't';
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
@@ -6838,6 +6845,7 @@ getConstraints(Archive *fout, TableInfo tblinfo[], int numTables)
constrinfo[j].conindex = 0;
constrinfo[j].condeferrable = false;
constrinfo[j].condeferred = false;
+ constrinfo[j].conalwaysdeferred = false;
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
}
@@ -6924,6 +6932,7 @@ getDomainConstraints(Archive *fout, TypeInfo *tyinfo)
constrinfo[i].conindex = 0;
constrinfo[i].condeferrable = false;
constrinfo[i].condeferred = false;
+ constrinfo[i].conalwaysdeferred = false;
constrinfo[i].conislocal = true;
constrinfo[i].separate = !validated;
@@ -8178,6 +8187,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
constrs[j].conindex = 0;
constrs[j].condeferrable = false;
constrs[j].condeferred = false;
+ constrs[j].conalwaysdeferred = false;
constrs[j].conislocal = (PQgetvalue(res, j, 4)[0] == 't');
/*
@@ -16089,6 +16099,11 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
appendPQExpBufferStr(q, " INITIALLY DEFERRED");
}
+ if (coninfo->conalwaysdeferred)
+ {
+ appendPQExpBufferStr(q, " ALWAYS DEFERRED");
+ }
+
appendPQExpBufferStr(q, ";\n");
}
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e7593e6..93b098a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -399,6 +399,7 @@ typedef struct _triggerInfo
char tgenabled;
bool tgdeferrable;
bool tginitdeferred;
+ bool tgalwaysdeferred;
char *tgdef;
} TriggerInfo;
@@ -432,6 +433,7 @@ typedef struct _constraintInfo
DumpId conindex; /* identifies associated index if any */
bool condeferrable; /* TRUE if constraint is DEFERRABLE */
bool condeferred; /* TRUE if constraint is INITIALLY DEFERRED */
+ bool conalwaysdeferred; /* TRUE if constraint is 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 019aa84..07f21d0 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2037,10 +2037,17 @@ describeOneTableDetails(const char *schemaname,
"WHERE conrelid = i.indrelid AND "
"conindid = i.indexrelid AND "
"contype IN ('p','u','x') AND "
- "condeferred) AS condeferred,\n");
+ "condeferred) AS condeferred,\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 "
+ "conalwaysdeferred) AS conalwaysdeferred,\n"
+ );
else
appendPQExpBufferStr(&buf,
- " false AS condeferrable, false AS condeferred,\n");
+ " false AS condeferrable, false AS condeferred, false AS conalwaysdeferred\n");
if (pset.sversion >= 90400)
appendPQExpBuffer(&buf, "i.indisreplident,\n");
@@ -2134,11 +2141,11 @@ describeOneTableDetails(const char *schemaname,
if (pset.sversion >= 90000)
appendPQExpBufferStr(&buf,
"pg_catalog.pg_get_constraintdef(con.oid, true), "
- "contype, condeferrable, condeferred");
+ "contype, condeferrable, condeferred, conalwaysdeferred");
else
appendPQExpBufferStr(&buf,
"null AS constraintdef, null AS contype, "
- "false AS condeferrable, false AS condeferred");
+ "false AS condeferrable, false AS condeferred, false as conalwaysdeferred");
if (pset.sversion >= 90400)
appendPQExpBufferStr(&buf, ", i.indisreplident");
else
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 1d4ec09..0e6dea4 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -58,6 +58,7 @@ extern Oid index_create(Relation heapRelation,
bool isconstraint,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool allow_system_table_mods,
bool skip_build,
bool concurrent,
@@ -71,6 +72,7 @@ extern ObjectAddress index_constraint_create(Relation heapRelation,
char constraintType,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool mark_as_primary,
bool update_pgindex,
bool remove_old_dependencies,
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index ec035d8..ef2e519 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -44,6 +44,7 @@ CATALOG(pg_constraint,2606)
char contype; /* constraint type; see codes below */
bool condeferrable; /* deferrable constraint? */
bool condeferred; /* deferred by default? */
+ bool conalwaysdeferred; /* always deferred? */
bool convalidated; /* constraint has been validated? */
/*
@@ -150,7 +151,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
* compiler constants for pg_constraint
* ----------------
*/
-#define Natts_pg_constraint 24
+#define Natts_pg_constraint 25
#define Anum_pg_constraint_conname 1
#define Anum_pg_constraint_connamespace 2
#define Anum_pg_constraint_contype 3
@@ -175,6 +176,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
#define Anum_pg_constraint_conexclop 22
#define Anum_pg_constraint_conbin 23
#define Anum_pg_constraint_consrc 24
+#define Anum_pg_constraint_conalwaysdeferred 25
/* ----------------
* initial contents of pg_constraint
diff --git a/src/include/catalog/pg_constraint_fn.h b/src/include/catalog/pg_constraint_fn.h
index a4c4689..c9daf2c 100644
--- a/src/include/catalog/pg_constraint_fn.h
+++ b/src/include/catalog/pg_constraint_fn.h
@@ -32,6 +32,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
char constraintType,
bool isDeferrable,
bool isDeferred,
+ bool isAlwaysDeferred,
bool isValidated,
Oid relId,
const int16 *constraintKey,
diff --git a/src/include/catalog/pg_trigger.h b/src/include/catalog/pg_trigger.h
index f413caf..29b2c13 100644
--- a/src/include/catalog/pg_trigger.h
+++ b/src/include/catalog/pg_trigger.h
@@ -48,6 +48,7 @@ CATALOG(pg_trigger,2620)
Oid tgconstraint; /* associated pg_constraint entry, if any */
bool tgdeferrable; /* constraint trigger is deferrable */
bool tginitdeferred; /* constraint trigger is deferred initially */
+ bool tgalwaysdeferred; /* constraint trigger is always deferred */
int16 tgnargs; /* # of extra arguments in tgargs */
/*
@@ -75,7 +76,7 @@ typedef FormData_pg_trigger *Form_pg_trigger;
* compiler constants for pg_trigger
* ----------------
*/
-#define Natts_pg_trigger 17
+#define Natts_pg_trigger 18
#define Anum_pg_trigger_tgrelid 1
#define Anum_pg_trigger_tgname 2
#define Anum_pg_trigger_tgfoid 3
@@ -93,6 +94,7 @@ typedef FormData_pg_trigger *Form_pg_trigger;
#define Anum_pg_trigger_tgqual 15
#define Anum_pg_trigger_tgoldtable 16
#define Anum_pg_trigger_tgnewtable 17
+#define Anum_pg_trigger_tgalwaysdeferred 18
/* Bits within tgtype */
#define TRIGGER_TYPE_ROW (1 << 0)
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index adbcfa1..082affa 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 ef6753e..856321c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2057,7 +2057,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 */
@@ -2081,6 +2082,7 @@ typedef struct Constraint
char *conname; /* Constraint name, or NULL if unnamed */
bool deferrable; /* DEFERRABLE? */
bool initdeferred; /* INITIALLY DEFERRED? */
+ bool alwaysdeferred; /* ALWAYS DEFERRED? */
int location; /* token location, or -1 if unknown */
/* Fields used for constraints with expressions (CHECK and DEFAULT): */
@@ -2365,6 +2367,7 @@ typedef struct CreateTrigStmt
/* The remaining fields are only used for constraint triggers */
bool deferrable; /* [NOT] DEFERRABLE */
bool initdeferred; /* INITIALLY {DEFERRED|IMMEDIATE} */
+ bool alwaysdeferred; /* ALWAYS DEFERRED? */
RangeVar *constrrel; /* opposite relation, if RI trigger */
} CreateTrigStmt;
@@ -2709,6 +2712,7 @@ typedef struct IndexStmt
bool isconstraint; /* is it for a pkey/unique constraint? */
bool deferrable; /* is the constraint DEFERRABLE? */
bool initdeferred; /* is the constraint INITIALLY DEFERRED? */
+ bool alwaysdeferred; /* ALWAYS DEFERRED? */
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 2169b03..06e1f21 100644
--- a/src/include/utils/reltrigger.h
+++ b/src/include/utils/reltrigger.h
@@ -34,6 +34,7 @@ typedef struct Trigger
Oid tgconstraint;
bool tgdeferrable;
bool tginitdeferred;
+ bool tgalwaysdeferred;
int16 tgnargs;
int16 tgnattr;
int16 *tgattr;
--
2.7.4
0002-WIP-Insert-conalwaysdeferred-in-the-middle-pg_constr.patchtext/x-diff; charset=us-asciiDownload
From 4085e8526155b2a3b02bc3618dab42f895d4751c Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@cryptonector.com>
Date: Tue, 3 Oct 2017 13:54:47 -0500
Subject: [PATCH 2/4] WIP: Insert conalwaysdeferred in the middle?
(pg_constraint)
---
src/include/catalog/pg_constraint.h | 40 ++++++++++++++++++-------------------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index ef2e519..f423901 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -157,26 +157,26 @@ typedef FormData_pg_constraint *Form_pg_constraint;
#define Anum_pg_constraint_contype 3
#define Anum_pg_constraint_condeferrable 4
#define Anum_pg_constraint_condeferred 5
-#define Anum_pg_constraint_convalidated 6
-#define Anum_pg_constraint_conrelid 7
-#define Anum_pg_constraint_contypid 8
-#define Anum_pg_constraint_conindid 9
-#define Anum_pg_constraint_confrelid 10
-#define Anum_pg_constraint_confupdtype 11
-#define Anum_pg_constraint_confdeltype 12
-#define Anum_pg_constraint_confmatchtype 13
-#define Anum_pg_constraint_conislocal 14
-#define Anum_pg_constraint_coninhcount 15
-#define Anum_pg_constraint_connoinherit 16
-#define Anum_pg_constraint_conkey 17
-#define Anum_pg_constraint_confkey 18
-#define Anum_pg_constraint_conpfeqop 19
-#define Anum_pg_constraint_conppeqop 20
-#define Anum_pg_constraint_conffeqop 21
-#define Anum_pg_constraint_conexclop 22
-#define Anum_pg_constraint_conbin 23
-#define Anum_pg_constraint_consrc 24
-#define Anum_pg_constraint_conalwaysdeferred 25
+#define Anum_pg_constraint_conalwaysdeferred 6
+#define Anum_pg_constraint_convalidated 7
+#define Anum_pg_constraint_conrelid 8
+#define Anum_pg_constraint_contypid 9
+#define Anum_pg_constraint_conindid 10
+#define Anum_pg_constraint_confrelid 11
+#define Anum_pg_constraint_confupdtype 12
+#define Anum_pg_constraint_confdeltype 13
+#define Anum_pg_constraint_confmatchtype 14
+#define Anum_pg_constraint_conislocal 15
+#define Anum_pg_constraint_coninhcount 16
+#define Anum_pg_constraint_connoinherit 17
+#define Anum_pg_constraint_conkey 18
+#define Anum_pg_constraint_confkey 19
+#define Anum_pg_constraint_conpfeqop 20
+#define Anum_pg_constraint_conppeqop 21
+#define Anum_pg_constraint_conffeqop 22
+#define Anum_pg_constraint_conexclop 23
+#define Anum_pg_constraint_conbin 24
+#define Anum_pg_constraint_consrc 25
/* ----------------
* initial contents of pg_constraint
--
2.7.4
0003-WIP-Insert-conalwaysdeferred-in-the-middle-dump.patchtext/x-diff; charset=us-asciiDownload
From 25f1cc7ed25e7f3d06db60d1c940e88f8ee2606b Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@cryptonector.com>
Date: Tue, 3 Oct 2017 13:58:55 -0500
Subject: [PATCH 3/4] WIP: Insert conalwaysdeferred in the middle? (dump)
---
src/bin/pg_dump/pg_dump.c | 20 ++++++++------------
1 file changed, 8 insertions(+), 12 deletions(-)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 60fcc77..b4c5c35 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6462,13 +6462,12 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
"i.indkey, i.indisclustered, "
"i.indisreplident, t.relpages, "
"c.contype, c.conname, "
- "c.condeferrable, c.condeferred, "
+ "c.condeferrable, c.condeferred, c.conalwaysdeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
- "t.reloptions AS indreloptions, "
- "c.conalwaysdeferred "
+ "t.reloptions AS indreloptions "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_constraint c "
@@ -6494,13 +6493,12 @@ 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.condeferrable, c.condeferred, c.conalwaysdeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
- "t.reloptions AS indreloptions, "
- "c.conalwaysdeferred "
+ "t.reloptions AS indreloptions "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_constraint c "
@@ -6522,13 +6520,12 @@ 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.condeferrable, c.condeferred, c.conalwaysdeferred "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"null AS condef, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
- "t.reloptions AS indreloptions, "
- "c.conalwaysdeferred "
+ "t.reloptions AS indreloptions "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
@@ -6553,13 +6550,12 @@ 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.condeferrable, c.condeferred, c.conalwaysdeferred "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"null AS condef, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
- "null AS indreloptions, "
- "c.conalwaysdeferred "
+ "null AS indreloptions "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
--
2.7.4
0004-Add-always_deferred-to-information_schema.patchtext/x-diff; charset=us-asciiDownload
From 12cf8c372d80e38fd11c4c30b141bd51282053b7 Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@cryptonector.com>
Date: Tue, 3 Oct 2017 12:25:05 -0500
Subject: [PATCH 4/4] Add always_deferred to information_schema??
---
src/backend/catalog/information_schema.sql | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 4fa230c..5b05843 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -894,11 +894,13 @@ CREATE VIEW domain_constraints AS
CAST(CASE WHEN condeferrable THEN 'YES' ELSE 'NO' END
AS yes_or_no) AS is_deferrable,
CAST(CASE WHEN condeferred THEN 'YES' ELSE 'NO' END
- AS yes_or_no) AS initially_deferred
+ AS yes_or_no) AS initially_deferred,
/*
* XXX Can we add is_always_deferred here? Are there
* standards considerations?
*/
+ CAST(CASE WHEN conalwaysdeferred THEN 'YES' ELSE 'NO' END
+ AS yes_or_no) AS always_deferred
FROM pg_namespace rs, pg_namespace n, pg_constraint con, pg_type t
WHERE rs.oid = con.connamespace
AND n.oid = t.typnamespace
@@ -1782,11 +1784,13 @@ CREATE VIEW table_constraints AS
CAST(CASE WHEN c.condeferrable THEN 'YES' ELSE 'NO' END AS yes_or_no)
AS is_deferrable,
CAST(CASE WHEN c.condeferred THEN 'YES' ELSE 'NO' END AS yes_or_no)
- AS initially_deferred
+ AS initially_deferred,
/*
* XXX Can we add is_always_deferred here? Are there
* standards considerations?
*/
+ CAST(CASE WHEN c.conalwaysdeferred THEN 'YES' ELSE 'NO' END AS yes_or_no)
+ AS always_deferred
FROM pg_namespace nc,
pg_namespace nr,
@@ -1815,7 +1819,8 @@ CREATE VIEW table_constraints AS
CAST(r.relname AS sql_identifier) AS table_name,
CAST('CHECK' AS character_data) AS constraint_type,
CAST('NO' AS yes_or_no) AS is_deferrable,
- CAST('NO' AS yes_or_no) AS initially_deferred
+ CAST('NO' AS yes_or_no) AS initially_deferred,
+ CAST('NO' AS yes_or_no) AS always_deferred
FROM pg_namespace nr,
pg_class r,
--
2.7.4
På tirsdag 03. oktober 2017 kl. 21:51:30, skrev Nico Williams <
nico@cryptonector.com <mailto:nico@cryptonector.com>>:
Attached are patches to add an ALWAYS DEFERRED option to CONSTRAINTs and
CONSTRAINT TRIGGERs, meaning: SET CONSTRAINTS .. IMMEDIATE will not make
immediate any constraint/trigger that is declared as ALWAYS DEFERRED.
I.e., the opposite of NOT DEFERRED. Perhaps I should make this NOT
IMMEDIATE? Making it NOT IMMEDIATE has the benefit of not having to
change the precedence of ALWAYS to avoid a shift/reduce conflict... It
may also be more in keeping with NOT DEFERRED.
Motivation:
- I have trigger procedures that must run at the end of the transaction
(after the last statement prior to COMMIT sent by the client/user),
which I make DEFERRABLE, INITIALLY DEFERRED CONSTRAINT TRIGGERs out
of, but SET CONSTRAINTS can be used to foil my triggers. I have
written SQL code to detect that constraint triggers have fired too
soon, but I'd rather not need it.
- Symmetry. If we can have NOT DEFERRABLE constraints, why not also
NOT IMMEDIABLE? :) Naturally "immediable" is not a word, but you
get the point.
- To learn my way around PostgreSQL source code in preparation for
other contributions.
Anyways, this patch is NOT passing tests at the moment, and I'm not sure
why. I'm sure I can figure it out, but first I need to understand the
failures. E.g., I see this sort of difference:
\d testschema.test_index1
Index "testschema.test_index1"
Column | Type | Definition
--------+--------+------------
id | bigint | id
-btree, for table "testschema.test_default_tab"
+f, for table "testschema.btree", predicate (test_default_tab)
which means, I think, that I've screwed up in src/bin/psql/describe.c,
don't it's not obvious to me yet how.
Some questions for experienced PostgreSQL developers:
Q0: Is this sort of patch welcomed?
Q1: Should new columns for pg_catalog.pg_constraint go at the end, or may
they be added in the middle?
Q2: Can I add new columns to information_schema tables, or are there
standards-compliance issues with that?
Q3: Is the C-style for PG documented somewhere? (sorry if I missed this)
Q4: Any ideas what I'm doing wrong in this patch series?
Nico
+1.
While we're in deferrable constraints land...; I even more often need
deferrable conditional unique-indexes.
In PG you now may have:
ALTER TABLE email_folder ADD CONSTRAINT some_uk UNIQUE (owner_id, folder_type,
name) DEFERRABLE INITIALLY DEFERRED;
But this isn't supported:
CREATE UNIQUE INDEX some_uk ON email_folder(owner_id, folder_type, name) WHERE
parent_idIS NULL DEFERRABLE INITIALLY DEFERRED;
Are there any plans to support this?
Thanks.
--
Andreas Joseph Krogh
On Tue, Oct 03, 2017 at 10:10:59PM +0200, Andreas Joseph Krogh wrote:
+1.
While we're in deferrable constraints land...; I even more often need
deferrable conditional unique-indexes.
In PG you now may have:
ALTER TABLE email_folder ADD CONSTRAINT some_uk UNIQUE (owner_id, folder_type,
name) DEFERRABLE INITIALLY DEFERRED;But this isn't supported:
CREATE UNIQUE INDEX some_uk ON email_folder(owner_id, folder_type, name) WHERE
parent_id IS NULL DEFERRABLE INITIALLY DEFERRED;Are there any plans to support this?
Not by me, but I can take a look and, if it is trivial, do it. At a
quick glance it does look like it should be easy enough to do it, at
least to get started on a patch.
If I can get some help with my current patch, I'll take a look :)
But yes, I'd like to have full consistency between CREATE and ALTER.
Everything that one can do with CREATE should be possible to do with
ALTER, including IF NOT EXISTS.
Nico
--
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Oct 03, 2017 at 02:51:30PM -0500, Nico Williams wrote:
Anyways, this patch is NOT passing tests at the moment, and I'm not sure
why. I'm sure I can figure it out, but first I need to understand the
failures. E.g., I see this sort of difference:\d testschema.test_index1 Index "testschema.test_index1" Column | Type | Definition --------+--------+------------ id | bigint | id -btree, for table "testschema.test_default_tab" +f, for table "testschema.btree", predicate (test_default_tab)which means, I think, that I've screwed up in src/bin/psql/describe.c,
don't it's not obvious to me yet how.
Ah, I needed to adjust references to results columns. I suspect that
something similar relates to other remaining failures.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10/03/2017 10:10 PM, Andreas Joseph Krogh wrote:
While we're in deferrable constraints land...;
I even more often need deferrable /conditional /unique-indexes.
In PG you now may have:ALTER TABLE email_folder ADD CONSTRAINT some_uk UNIQUE (owner_id, folder_type, name) DEFERRABLE INITIALLY DEFERRED;
But this isn't supported:
CREATE UNIQUE INDEX some_uk ON email_folder(owner_id, folder_type, name) WHERE parent_id IS NULL DEFERRABLE INITIALLY DEFERRED;
Are there any plans to support this?
I don't want to hijack the thread, but you can do that with exclusion
constraints.
--
Vik Fearing +33 6 46 75 15 36
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
På onsdag 04. oktober 2017 kl. 00:24:19, skrev Vik Fearing <
vik.fearing@2ndquadrant.com <mailto:vik.fearing@2ndquadrant.com>>:
On 10/03/2017 10:10 PM, Andreas Joseph Krogh wrote:
While we're in deferrable constraints land...;
I even more often need deferrable /conditional /unique-indexes.
In PG you now may have:ALTER TABLE email_folder ADD CONSTRAINT some_uk UNIQUE (owner_id,
folder_type, name) DEFERRABLE INITIALLY DEFERRED;
But this isn't supported:CREATE UNIQUE INDEX some_uk ON email_folder(owner_id, folder_type, name)
WHERE parent_id IS NULL DEFERRABLE INITIALLY DEFERRED;
Are there any plans to support this?
I don't want to hijack the thread, but you can do that with exclusion
constraints.
True.
--
Andreas Joseph Krogh
[make check-world passes. Tests and docs included. Should be ready for
code review.]
Attached are patches to add an ALWAYS DEFERRED option to CONSTRAINTs and
CONSTRAINT TRIGGERs, meaning: SET CONSTRAINTS .. IMMEDIATE will not make
immediate any constraint/trigger that is declared as ALWAYS DEFERRED.
I.e., the opposite of NOT DEFERRED.
Motivation:
- Security.
One may have triggers they need to always be deferred and they
cannot give direct PG access because of SET CONSTRAINTS .. IMMEDIATE.
I have such triggers that must run at the end of the transaction
(after the last statement prior to COMMIT sent by the client/user),
which I make DEFERRABLE, INITIALLY DEFERRED CONSTRAINT TRIGGERs.
I have written SQL code to detect that constraint triggers have fired
too soon, but I'd rather not need it as it does slow things down (it
uses DDL event triggers and per-table triggers).
Making it easier to write secure code DEFERRED CONSTRAINT TRIGGERs
seems like a good idea to me.
- Symmetry.
Not using NOT DEFERRABLE is not the inverse of NOT DEFERRABLE. There
is no inverse at this time.
If we can have NOT DEFERRABLE constraints, why not also the inverse,
a constraint that cannot be made IMMEDIATE with SET CONSTRAINTs?
I've *not* cleaned up C style issues in surrounding -- I'm not sure
if that's desired. Not cleaning up makes it easier to see what I
changed.
Some questions for experienced PostgreSQL developers:
Q0: Is this sort of patch welcomed?
Q1: Should new columns for pg_catalog tables go at the end, or may they
be added in the middle?
FYI, I'm adding them in the middle, so they are next to related
columns.
Q2: Can I add new columns to information_schema tables, or are there
standards-compliance issues with that?
This is done in the second patch, and it can be dropped safely.
Q3: Perhaps I should make this NOT IMMEDIATE rather than ALWAYS DEFERRED?
Making it NOT IMMEDIATE has the benefit of not having to change the
precedence of ALWAYS to avoid a shift/reduce conflict... It may
also be more in keeping with NOT DEFERRED. Thoughts?
Nico
--
Attachments:
0001-Add-ALWAYS-DEFERRED-option-for-CONSTRAINTs.patchtext/x-diff; charset=us-asciiDownload
From 1d04483511f99cd3417df571ecc0498e928ace35 Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@cryptonector.com>
Date: Tue, 3 Oct 2017 00:33:09 -0500
Subject: [PATCH 1/2] 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 | 17 ++++-
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 | 1 +
src/backend/bootstrap/bootparse.y | 2 +
src/backend/catalog/heap.c | 1 +
src/backend/catalog/index.c | 8 +++
src/backend/catalog/information_schema.sql | 8 +++
src/backend/catalog/pg_constraint.c | 2 +
src/backend/catalog/toasting.c | 2 +-
src/backend/commands/indexcmds.c | 2 +-
src/backend/commands/tablecmds.c | 20 +++++-
src/backend/commands/trigger.c | 28 +++++++--
src/backend/commands/typecmds.c | 3 +
src/backend/nodes/copyfuncs.c | 3 +
src/backend/nodes/outfuncs.c | 4 ++
src/backend/parser/gram.y | 99 ++++++++++++++++++++++--------
src/backend/parser/parse_utilcmd.c | 46 +++++++++++++-
src/backend/utils/adt/ruleutils.c | 4 ++
src/bin/pg_dump/pg_dump.c | 31 ++++++++--
src/bin/pg_dump/pg_dump.h | 2 +
src/bin/psql/describe.c | 34 +++++++---
src/include/catalog/index.h | 2 +
src/include/catalog/pg_constraint.h | 42 +++++++------
src/include/catalog/pg_constraint_fn.h | 1 +
src/include/catalog/pg_trigger.h | 16 ++---
src/include/commands/trigger.h | 1 +
src/include/nodes/parsenodes.h | 6 +-
src/include/utils/reltrigger.h | 1 +
src/test/regress/input/constraints.source | 51 +++++++++++++++
src/test/regress/output/constraints.source | 54 +++++++++++++++-
32 files changed, 416 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9af77c1..2c3ed23 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2202,6 +2202,13 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</row>
<row>
+ <entry><structfield>conalwaysdeferred</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>Is the constraint always deferred?</entry>
+ </row>
+
+ <row>
<entry><structfield>convalidated</structfield></entry>
<entry><type>bool</type></entry>
<entry></entry>
@@ -6948,6 +6955,13 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</row>
<row>
+ <entry><structfield>tgalwaysdeferred</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if constraint trigger is always deferred</entry>
+ </row>
+
+ <row>
<entry><structfield>tgnargs</structfield></entry>
<entry><type>int2</type></entry>
<entry></entry>
@@ -7009,7 +7023,8 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
<para>
When <structfield>tgconstraint</> is nonzero,
<structfield>tgconstrrelid</>, <structfield>tgconstrindid</>,
- <structfield>tgdeferrable</>, and <structfield>tginitdeferred</> are
+ <structfield>tgdeferrable</>, <structfield>tginitdeferred</>, and
+ <structfield>tgalwaysdeferred</> are
largely redundant with the referenced <structname>pg_constraint</> 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 0fb385e..e81d1fa 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 ]
@@ -89,7 +89,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
[ 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 ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 1477288..38c88b8 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>
@@ -961,13 +961,17 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
<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>PRIMARY KEY</>,
<literal>EXCLUDE</>, and
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 2496250..2b1cb7c 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 f5f74af..342964e 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -646,6 +646,7 @@ typedef struct Trigger
Oid tgconstraint;
bool tgdeferrable;
bool tginitdeferred;
+ bool tgalwaysdeferred;
int16 tgnargs;
int16 tgnattr;
int16 *tgattr;
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 2e1fef0..c08ac60 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -310,6 +310,7 @@ Boot_DeclareIndexStmt:
stmt->isconstraint = false;
stmt->deferrable = false;
stmt->initdeferred = false;
+ stmt->alwaysdeferred = false;
stmt->transformed = false;
stmt->concurrent = false;
stmt->if_not_exists = false;
@@ -354,6 +355,7 @@ Boot_DeclareUniqueIndexStmt:
stmt->isconstraint = false;
stmt->deferrable = false;
stmt->initdeferred = false;
+ stmt->alwaysdeferred = false;
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 05e7081..a54c524 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2113,6 +2113,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
CONSTRAINT_CHECK, /* Constraint Type */
false, /* Is Deferrable */
false, /* Is Deferred */
+ false, /* Is Always Deferred */
is_validated,
RelationGetRelid(rel), /* relation */
attNos, /* attrs in the constraint */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index c7b2f03..65e995d 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -684,6 +684,7 @@ UpdateIndexRelation(Oid indexoid,
* isconstraint: index is owned by PRIMARY KEY, UNIQUE, or EXCLUSION constraint
* deferrable: constraint is DEFERRABLE
* initdeferred: constraint is INITIALLY DEFERRED
+ * alwaysdeferred: constraint is ALWAYS DEFERRED
* allow_system_table_mods: allow table to be a system catalog
* skip_build: true to skip the index_build() step for the moment; caller
* must do it later (typically via reindex_index())
@@ -713,6 +714,7 @@ index_create(Relation heapRelation,
bool isconstraint,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool allow_system_table_mods,
bool skip_build,
bool concurrent,
@@ -966,6 +968,7 @@ index_create(Relation heapRelation,
constraintType,
deferrable,
initdeferred,
+ alwaysdeferred,
false, /* already marked primary */
false, /* pg_index entry is OK */
false, /* no old dependencies */
@@ -1009,6 +1012,7 @@ index_create(Relation heapRelation,
/* Non-constraint indexes can't be deferrable */
Assert(!deferrable);
Assert(!initdeferred);
+ Assert(!alwaysdeferred);
}
/* Store dependency on collations */
@@ -1062,6 +1066,7 @@ index_create(Relation heapRelation,
Assert(!isconstraint);
Assert(!deferrable);
Assert(!initdeferred);
+ Assert(!alwaysdeferred);
}
/* Post creation hook for new index */
@@ -1154,6 +1159,7 @@ index_constraint_create(Relation heapRelation,
char constraintType,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool mark_as_primary,
bool update_pgindex,
bool remove_old_dependencies,
@@ -1202,6 +1208,7 @@ index_constraint_create(Relation heapRelation,
constraintType,
deferrable,
initdeferred,
+ alwaysdeferred,
true,
RelationGetRelid(heapRelation),
indexInfo->ii_KeyAttrNumbers,
@@ -1266,6 +1273,7 @@ index_constraint_create(Relation heapRelation,
trigger->isconstraint = true;
trigger->deferrable = true;
trigger->initdeferred = initdeferred;
+ trigger->alwaysdeferred = alwaysdeferred;
trigger->constrrel = NULL;
(void) CreateTrigger(trigger, NULL, RelationGetRelid(heapRelation),
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 236f6be..4f0d193 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -895,6 +895,10 @@ CREATE VIEW domain_constraints AS
AS yes_or_no) AS is_deferrable,
CAST(CASE WHEN condeferred 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
@@ -1779,6 +1783,10 @@ CREATE VIEW table_constraints AS
AS is_deferrable,
CAST(CASE WHEN c.condeferred 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 nc,
pg_namespace nr,
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 1336c46..81c0477 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -51,6 +51,7 @@ CreateConstraintEntry(const char *constraintName,
char constraintType,
bool isDeferrable,
bool isDeferred,
+ bool isAlwaysDeferred,
bool isValidated,
Oid relId,
const int16 *constraintKey,
@@ -166,6 +167,7 @@ CreateConstraintEntry(const char *constraintName,
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_conalwaysdeferred - 1] = BoolGetDatum(isAlwaysDeferred);
values[Anum_pg_constraint_convalidated - 1] = BoolGetDatum(isValidated);
values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(relId);
values[Anum_pg_constraint_contypid - 1] = ObjectIdGetDatum(domainId);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 6f517bb..f90ea32 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -333,7 +333,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
BTREE_AM_OID,
rel->rd_rel->reltablespace,
collationObjectId, classObjectId, coloptions, (Datum) 0,
- true, false, false, false,
+ true, false, false, false, false,
true, false, false, true, false);
heap_close(toast_rel, NoLock);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index b61aaac..791c1f8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -672,7 +672,7 @@ DefineIndex(Oid relationId,
collationObjectId, classObjectId,
coloptions, reloptions, stmt->primary,
stmt->isconstraint, stmt->deferrable, stmt->initdeferred,
- allowSystemTableMods,
+ stmt->alwaysdeferred, allowSystemTableMods,
skip_build || stmt->concurrent,
stmt->concurrent, !check_rights,
stmt->if_not_exists);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 563bcda..8177684 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6874,6 +6874,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
constraintType,
stmt->deferrable,
stmt->initdeferred,
+ stmt->alwaysdeferred,
stmt->primary,
true, /* update pg_index */
true, /* remove old dependencies */
@@ -7443,6 +7444,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
CONSTRAINT_FOREIGN,
fkconstraint->deferrable,
fkconstraint->initdeferred,
+ fkconstraint->alwaysdeferred,
fkconstraint->initially_valid,
RelationGetRelid(rel),
fkattnum,
@@ -7567,7 +7569,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
cmdcon->conname, RelationGetRelationName(rel))));
if (currcon->condeferrable != cmdcon->deferrable ||
- currcon->condeferred != cmdcon->initdeferred)
+ currcon->condeferred != cmdcon->initdeferred ||
+ currcon->conalwaysdeferred != cmdcon->alwaysdeferred)
{
HeapTuple copyTuple;
HeapTuple tgtuple;
@@ -7585,6 +7588,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
copy_con->condeferrable = cmdcon->deferrable;
copy_con->condeferred = cmdcon->initdeferred;
+ copy_con->conalwaysdeferred = cmdcon->alwaysdeferred;
CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
InvokeObjectPostAlterHook(ConstraintRelationId,
@@ -7638,6 +7642,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
copy_tg->tgdeferrable = cmdcon->deferrable;
copy_tg->tginitdeferred = cmdcon->initdeferred;
+ copy_tg->tgalwaysdeferred = cmdcon->alwaysdeferred;
CatalogTupleUpdate(tgrel, ©Tuple->t_self, copyTuple);
InvokeObjectPostAlterHook(TriggerRelationId,
@@ -8305,6 +8310,7 @@ validateForeignKeyConstraint(char *conname,
trig.tgconstraint = constraintOid;
trig.tgdeferrable = FALSE;
trig.tginitdeferred = FALSE;
+ trig.tgalwaysdeferred = FALSE;
/* we needn't fill in remaining fields */
/*
@@ -8394,6 +8400,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
fk_trigger->isconstraint = true;
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
+ fk_trigger->alwaysdeferred = fkconstraint->alwaysdeferred;
fk_trigger->constrrel = NULL;
fk_trigger->args = NIL;
@@ -8442,26 +8449,31 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
case FKCONSTR_ACTION_NOACTION:
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
+ fk_trigger->alwaysdeferred = fkconstraint->alwaysdeferred;
fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
break;
case FKCONSTR_ACTION_RESTRICT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
break;
case FKCONSTR_ACTION_CASCADE:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
break;
case FKCONSTR_ACTION_SETNULL:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
break;
case FKCONSTR_ACTION_SETDEFAULT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
break;
default:
@@ -8497,26 +8509,31 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
case FKCONSTR_ACTION_NOACTION:
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
+ fk_trigger->alwaysdeferred = fkconstraint->alwaysdeferred;
fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
break;
case FKCONSTR_ACTION_RESTRICT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
break;
case FKCONSTR_ACTION_CASCADE:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
break;
case FKCONSTR_ACTION_SETNULL:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
break;
case FKCONSTR_ACTION_SETDEFAULT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
break;
default:
@@ -11261,6 +11278,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
if (acon->condeferrable != bcon->condeferrable ||
acon->condeferred != bcon->condeferred ||
+ acon->conalwaysdeferred != bcon->conalwaysdeferred ||
strcmp(decompile_conbin(a, tupleDesc),
decompile_conbin(b, tupleDesc)) != 0)
return false;
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index e75a59d..ee41280 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -650,6 +650,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
CONSTRAINT_TRIGGER,
stmt->deferrable,
stmt->initdeferred,
+ stmt->alwaysdeferred,
true,
RelationGetRelid(rel),
NULL, /* no conkey */
@@ -748,6 +749,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
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_tgalwaysdeferred - 1] = BoolGetDatum(stmt->alwaysdeferred);
if (stmt->args)
{
@@ -1245,6 +1247,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
}
fkcon->deferrable = stmt->deferrable;
fkcon->initdeferred = stmt->initdeferred;
+ fkcon->alwaysdeferred = stmt->alwaysdeferred;
fkcon->skip_validation = false;
fkcon->initially_valid = true;
@@ -1742,6 +1745,7 @@ RelationBuildTriggers(Relation relation)
build->tgconstraint = pg_trigger->tgconstraint;
build->tgdeferrable = pg_trigger->tgdeferrable;
build->tginitdeferred = pg_trigger->tginitdeferred;
+ build->tgalwaysdeferred = pg_trigger->tgalwaysdeferred;
build->tgnargs = pg_trigger->tgnargs;
/* tgattr is first var-width field, so OK to access directly */
build->tgnattr = pg_trigger->tgattr.dim1;
@@ -2050,6 +2054,8 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
return false;
if (trig1->tginitdeferred != trig2->tginitdeferred)
return false;
+ if (trig1->tgalwaysdeferred != trig2->tgalwaysdeferred)
+ return false;
if (trig1->tgnargs != trig2->tgnargs)
return false;
if (trig1->tgnattr != trig2->tgnattr)
@@ -2143,7 +2149,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_ALEWAYSDEFERRED)) ||
(trigdata->tg_oldtable == NULL && trigdata->tg_newtable == NULL));
finfo += tgindx;
@@ -3392,6 +3399,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;
@@ -3678,6 +3686,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
@@ -5191,14 +5201,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->condeferrable)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("constraint \"%s\" is not deferrable",
constraint->relname)));
+ else if (!stmt->deferred && con->conalwaysdeferred)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("constraint \"%s\" is always deferred",
+ constraint->relname)));
+ else if (con->condeferrable && !con->conalwaysdeferred)
+ conoidlist = lappend_oid(conoidlist,
+ HeapTupleGetOid(tup));
found = true;
}
@@ -5695,7 +5710,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
(event & TRIGGER_EVENT_OPMASK) |
(row_trigger ? TRIGGER_EVENT_ROW : 0) |
(trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) |
- (trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0);
+ (trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0) |
+ (trigger->tgalwaysdeferred ? 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 c1b87e0..85b73ba 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),
@@ -2603,6 +2604,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),
@@ -3150,6 +3152,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
CONSTRAINT_CHECK, /* Constraint Type */
false, /* Is Deferrable */
false, /* Is Deferred */
+ false, /* Is Always Deferred */
!constr->skip_validation, /* Is Validated */
InvalidOid, /* not a relation constraint */
NULL,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c1a83ca..bbd1705 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2831,6 +2831,7 @@ _copyConstraint(const Constraint *from)
COPY_STRING_FIELD(conname);
COPY_SCALAR_FIELD(deferrable);
COPY_SCALAR_FIELD(initdeferred);
+ COPY_SCALAR_FIELD(alwaysdeferred);
COPY_LOCATION_FIELD(location);
COPY_SCALAR_FIELD(is_no_inherit);
COPY_NODE_FIELD(raw_expr);
@@ -3379,6 +3380,7 @@ _copyIndexStmt(const IndexStmt *from)
COPY_SCALAR_FIELD(isconstraint);
COPY_SCALAR_FIELD(deferrable);
COPY_SCALAR_FIELD(initdeferred);
+ COPY_SCALAR_FIELD(alwaysdeferred);
COPY_SCALAR_FIELD(transformed);
COPY_SCALAR_FIELD(concurrent);
COPY_SCALAR_FIELD(if_not_exists);
@@ -4148,6 +4150,7 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
COPY_NODE_FIELD(transitionRels);
COPY_SCALAR_FIELD(deferrable);
COPY_SCALAR_FIELD(initdeferred);
+ COPY_SCALAR_FIELD(alwaysdeferred);
COPY_NODE_FIELD(constrrel);
return newnode;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2532edc..3b2f8df 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3508,6 +3508,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 4c83a63..dd5fe68 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);
+ bool *deferrable, bool *initdeferred, bool *alwaysdeferred,
+ bool *not_valid, bool *no_inherit, core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -727,6 +728,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* RANGE, ROWS to support opt_existing_window_name; and for RANGE, ROWS
* 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.
*
@@ -745,7 +747,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 PRECEDING FOLLOWING CUBE ROLLUP
+%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP ALWAYS
%left Op OPERATOR /* multi-character ops and user-defined operators */
%left '+' '-'
%left '*' '/' '%'
@@ -2234,7 +2236,9 @@ alter_table_cmd:
processCASbits($4, @4, "ALTER CONSTRAINT statement",
&c->deferrable,
&c->initdeferred,
- NULL, NULL, yyscanner);
+ &c->alwaysdeferred,
+ NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
/* ALTER TABLE <name> VALIDATE CONSTRAINT ... */
@@ -3500,6 +3504,13 @@ ConstraintAttr:
n->location = @1;
$$ = (Node *)n;
}
+ | ALWAYS DEFERRED
+ {
+ Constraint *n = makeNode(Constraint);
+ n->contype = CONSTR_ATTR_ALWAYS_DEFERRED;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
;
@@ -3554,8 +3565,10 @@ ConstraintElem:
n->raw_expr = $3;
n->cooked_expr = NULL;
processCASbits($5, @5, "CHECK",
- NULL, NULL, &n->skip_validation,
- &n->is_no_inherit, yyscanner);
+ NULL, NULL, NULL,
+ &n->skip_validation,
+ &n->is_no_inherit,
+ yyscanner);
n->initially_valid = !n->skip_validation;
$$ = (Node *)n;
}
@@ -3570,8 +3583,9 @@ ConstraintElem:
n->indexname = NULL;
n->indexspace = $6;
processCASbits($7, @7, "UNIQUE",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| UNIQUE ExistingIndex ConstraintAttributeSpec
@@ -3584,8 +3598,9 @@ ConstraintElem:
n->indexname = $2;
n->indexspace = NULL;
processCASbits($3, @3, "UNIQUE",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace
@@ -3599,8 +3614,9 @@ ConstraintElem:
n->indexname = NULL;
n->indexspace = $7;
processCASbits($8, @8, "PRIMARY KEY",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| PRIMARY KEY ExistingIndex ConstraintAttributeSpec
@@ -3613,8 +3629,9 @@ ConstraintElem:
n->indexname = $3;
n->indexspace = NULL;
processCASbits($4, @4, "PRIMARY KEY",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
@@ -3631,8 +3648,9 @@ ConstraintElem:
n->indexspace = $7;
n->where_clause = $8;
processCASbits($9, @9, "EXCLUDE",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
@@ -3649,6 +3667,7 @@ ConstraintElem:
n->fk_del_action = (char) ($10 & 0xFF);
processCASbits($11, @11, "FOREIGN KEY",
&n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred,
&n->skip_validation, NULL,
yyscanner);
n->initially_valid = !n->skip_validation;
@@ -5210,6 +5229,7 @@ CreateTrigStmt:
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
+ n->alwaysdeferred = FALSE;
n->constrrel = NULL;
$$ = (Node *)n;
}
@@ -5231,8 +5251,9 @@ CreateTrigStmt:
n->transitionRels = NIL;
n->isconstraint = TRUE;
processCASbits($10, @10, "TRIGGER",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
n->constrrel = $9;
$$ = (Node *)n;
}
@@ -5388,17 +5409,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;
}
@@ -5409,6 +5437,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; }
;
@@ -5499,8 +5528,9 @@ CreateAssertStmt:
n->args = list_make1($6);
n->isconstraint = TRUE;
processCASbits($8, @8, "ASSERTION",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -7177,6 +7207,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
n->isconstraint = false;
n->deferrable = false;
n->initdeferred = false;
+ n->alwaysdeferred = false;
n->transformed = false;
n->if_not_exists = false;
$$ = (Node *)n;
@@ -7203,6 +7234,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
n->isconstraint = false;
n->deferrable = false;
n->initdeferred = false;
+ n->alwaysdeferred = false;
n->transformed = false;
n->if_not_exists = true;
$$ = (Node *)n;
@@ -15791,18 +15823,20 @@ 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)
+ bool *deferrable, bool *initdeferred, bool *alwaysdeferred,
+ bool *not_valid, bool *no_inherit, core_yyscan_t yyscanner)
{
/* defaults */
if (deferrable)
*deferrable = false;
if (initdeferred)
*initdeferred = false;
+ if (alwaysdeferred)
+ *alwaysdeferred = false;
if (not_valid)
*not_valid = false;
- if (cas_bits & (CAS_DEFERRABLE | CAS_INITIALLY_DEFERRED))
+ if (cas_bits & (CAS_DEFERRABLE | CAS_INITIALLY_DEFERRED | CAS_ALWAYS_DEFERRED))
{
if (deferrable)
*deferrable = true;
@@ -15815,7 +15849,7 @@ processCASbits(int cas_bits, int location, const char *constrType,
parser_errposition(location)));
}
- if (cas_bits & CAS_INITIALLY_DEFERRED)
+ if (cas_bits & (CAS_INITIALLY_DEFERRED | CAS_ALWAYS_DEFERRED))
{
if (initdeferred)
*initdeferred = true;
@@ -15828,6 +15862,19 @@ processCASbits(int cas_bits, int location, const char *constrType,
parser_errposition(location)));
}
+ if (cas_bits & CAS_ALWAYS_DEFERRED)
+ {
+ if (alwaysdeferred)
+ *alwaysdeferred = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is CHECK, UNIQUE, or similar */
+ errmsg("%s constraints cannot be marked DEFERRED",
+ constrType),
+ parser_errposition(location)));
+ }
+
if (cas_bits & CAS_NOT_VALID)
{
if (not_valid)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 27e568f..54d1c3f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -750,6 +750,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 */
@@ -878,6 +879,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",
@@ -1359,6 +1361,7 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
index->isconstraint = true;
index->deferrable = conrec->condeferrable;
index->initdeferred = conrec->condeferred;
+ index->alwaysdeferred = conrec->conalwaysdeferred;
/* If it's an exclusion constraint, we need the operator names */
if (idxrec->indisexclusion)
@@ -1715,7 +1718,8 @@ transformIndexConstraints(CreateStmtContext *cxt)
equal(index->excludeOpNames, priorindex->excludeOpNames) &&
strcmp(index->accessMethod, priorindex->accessMethod) == 0 &&
index->deferrable == priorindex->deferrable &&
- index->initdeferred == priorindex->initdeferred)
+ index->initdeferred == priorindex->initdeferred &&
+ index->alwaysdeferred == priorindex->alwaysdeferred)
{
priorindex->unique |= index->unique;
@@ -1770,6 +1774,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
index->isconstraint = true;
index->deferrable = constraint->deferrable;
index->initdeferred = constraint->initdeferred;
+ index->alwaysdeferred = constraint->alwaysdeferred;
if (constraint->conname != NULL)
index->idxname = pstrdup(constraint->conname);
@@ -2983,6 +2988,9 @@ 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;
@@ -3008,12 +3016,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;
+ saw_deferrable = true;
lastprimarycon->deferrable = true;
break;
@@ -3023,13 +3032,20 @@ 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)));
+ 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_deferrability = true;
+ saw_notdeferrable = true;
lastprimarycon->deferrable = false;
+ lastprimarycon->alwaysdeferred = false;
if (saw_initially &&
lastprimarycon->initdeferred)
ereport(ERROR,
@@ -3038,6 +3054,30 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
parser_errposition(cxt->pstate, con->location)));
break;
+ case CONSTR_ATTR_ALWAYS_DEFERRED:
+ if (!SUPPORTS_ATTRS(lastprimarycon))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("misplaced ALWAYS DEFERRED clause"),
+ parser_errposition(cxt->pstate, con->location)));
+ if (saw_alwaysdeferred || saw_notdeferrable)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("multiple ALWAYS DEFERRED/NOT DEFERRABLE clauses not allowed"),
+ parser_errposition(cxt->pstate, con->location)));
+ saw_initially = true;
+ saw_deferrability = true;
+ saw_alwaysdeferred = true;
+ lastprimarycon->deferrable = true;
+ lastprimarycon->alwaysdeferred = true;
+ if (saw_initially &&
+ !lastprimarycon->initdeferred)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("constraint declared INITIALLY IMMEDIATE must not be ALWAYS DEFERRED"),
+ parser_errposition(cxt->pstate, con->location)));
+ break;
+
case CONSTR_ATTR_DEFERRED:
if (!SUPPORTS_ATTRS(lastprimarycon))
ereport(ERROR,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 84759b6..61fd899 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -936,6 +936,8 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
appendStringInfoString(&buf, "DEFERRED ");
else
appendStringInfoString(&buf, "IMMEDIATE ");
+ if (trigrec->tgalwaysdeferred)
+ appendStringInfoString(&buf, "ALWAYS DEFERRED");
}
value = fastgetattr(ht_trig, Anum_pg_trigger_tgoldtable,
@@ -2144,6 +2146,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
appendStringInfoString(&buf, " DEFERRABLE");
if (conForm->condeferred)
appendStringInfoString(&buf, " INITIALLY DEFERRED");
+ if (conForm->conalwaysdeferred)
+ 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 e34c83a..a095a0a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6509,6 +6509,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_conname,
i_condeferrable,
i_condeferred,
+ i_conalwaysdeferred,
i_contableoid,
i_conoid,
i_condef,
@@ -6566,7 +6567,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.condeferrable, c.condeferred, c.conalwaysdeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
@@ -6597,7 +6598,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.condeferrable, c.condeferred, c.conalwaysdeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
@@ -6624,7 +6625,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.condeferrable, c.condeferred, c.conalwaysdeferred "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"null AS condef, "
@@ -6654,7 +6655,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.condeferrable, c.condeferred, c.conalwaysdeferred "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"null AS condef, "
@@ -6691,6 +6692,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_conname = PQfnumber(res, "conname");
i_condeferrable = PQfnumber(res, "condeferrable");
i_condeferred = PQfnumber(res, "condeferred");
+ i_conalwaysdeferred = PQfnumber(res, "conalwaysdeferred");
i_contableoid = PQfnumber(res, "contableoid");
i_conoid = PQfnumber(res, "conoid");
i_condef = PQfnumber(res, "condef");
@@ -6746,6 +6748,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
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].conalwaysdeferred = *(PQgetvalue(res, j, i_conalwaysdeferred)) == 't';
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
@@ -6943,6 +6946,7 @@ getConstraints(Archive *fout, TableInfo tblinfo[], int numTables)
constrinfo[j].conindex = 0;
constrinfo[j].condeferrable = false;
constrinfo[j].condeferred = false;
+ constrinfo[j].conalwaysdeferred = false;
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
}
@@ -7029,6 +7033,7 @@ getDomainConstraints(Archive *fout, TypeInfo *tyinfo)
constrinfo[i].conindex = 0;
constrinfo[i].condeferrable = false;
constrinfo[i].condeferred = false;
+ constrinfo[i].conalwaysdeferred = false;
constrinfo[i].conislocal = true;
constrinfo[i].separate = !validated;
@@ -7193,6 +7198,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
i_tgenabled,
i_tgdeferrable,
i_tginitdeferred,
+ i_tgalwaysdeferred,
i_tgdef;
int ntups;
@@ -7291,6 +7297,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
i_tgenabled = PQfnumber(res, "tgenabled");
i_tgdeferrable = PQfnumber(res, "tgdeferrable");
i_tginitdeferred = PQfnumber(res, "tginitdeferred");
+ i_tgalwaysdeferred = PQfnumber(res, "tgalwaysdeferred");
i_tgdef = PQfnumber(res, "tgdef");
tginfo = (TriggerInfo *) pg_malloc(ntups * sizeof(TriggerInfo));
@@ -7320,6 +7327,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
tginfo[j].tgisconstraint = false;
tginfo[j].tgdeferrable = false;
tginfo[j].tginitdeferred = false;
+ tginfo[j].tgalwaysdeferred = false;
tginfo[j].tgconstrname = NULL;
tginfo[j].tgconstrrelid = InvalidOid;
tginfo[j].tgconstrrelname = NULL;
@@ -7335,6 +7343,8 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
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';
+ if (i_tgalwaysdeferred != -1)
+ tginfo[j].tgalwaysdeferred = *(PQgetvalue(res, j, i_tgalwaysdeferred)) == 't';
if (tginfo[j].tgisconstraint)
{
@@ -8283,6 +8293,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
constrs[j].conindex = 0;
constrs[j].condeferrable = false;
constrs[j].condeferred = false;
+ constrs[j].conalwaysdeferred = false;
constrs[j].conislocal = (PQgetvalue(res, j, 4)[0] == 't');
/*
@@ -16204,6 +16215,11 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
appendPQExpBufferStr(q, " INITIALLY DEFERRED");
}
+ if (coninfo->conalwaysdeferred)
+ {
+ appendPQExpBufferStr(q, " ALWAYS DEFERRED");
+ }
+
appendPQExpBufferStr(q, ";\n");
}
@@ -16866,9 +16882,12 @@ dumpTrigger(Archive *fout, TriggerInfo *tginfo)
appendPQExpBufferStr(query, "NOT ");
appendPQExpBufferStr(query, "DEFERRABLE INITIALLY ");
if (tginfo->tginitdeferred)
- appendPQExpBufferStr(query, "DEFERRED\n");
+ appendPQExpBufferStr(query, "DEFERRED");
else
- appendPQExpBufferStr(query, "IMMEDIATE\n");
+ appendPQExpBufferStr(query, "IMMEDIATE");
+ if (tginfo->tgalwaysdeferred)
+ 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 e7593e6..93b098a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -399,6 +399,7 @@ typedef struct _triggerInfo
char tgenabled;
bool tgdeferrable;
bool tginitdeferred;
+ bool tgalwaysdeferred;
char *tgdef;
} TriggerInfo;
@@ -432,6 +433,7 @@ typedef struct _constraintInfo
DumpId conindex; /* identifies associated index if any */
bool condeferrable; /* TRUE if constraint is DEFERRABLE */
bool condeferred; /* TRUE if constraint is INITIALLY DEFERRED */
+ bool conalwaysdeferred; /* TRUE if constraint is 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 0688571..c387d19 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2043,10 +2043,17 @@ describeOneTableDetails(const char *schemaname,
"WHERE conrelid = i.indrelid AND "
"conindid = i.indexrelid AND "
"contype IN ('p','u','x') AND "
- "condeferred) AS condeferred,\n");
+ "condeferred) AS condeferred,\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 "
+ "conalwaysdeferred) AS conalwaysdeferred,\n"
+ );
else
appendPQExpBufferStr(&buf,
- " false AS condeferrable, false AS condeferred,\n");
+ " false AS condeferrable, false AS condeferred, false AS conalwaysdeferred\n");
if (pset.sversion >= 90400)
appendPQExpBuffer(&buf, "i.indisreplident,\n");
@@ -2076,10 +2083,11 @@ describeOneTableDetails(const char *schemaname,
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 *alwaysdeferred = PQgetvalue(result, 0, 6);
+ char *indisreplident = PQgetvalue(result, 0, 7);
+ char *indamname = PQgetvalue(result, 0, 8);
+ char *indtable = PQgetvalue(result, 0, 9);
+ char *indpred = PQgetvalue(result, 0, 10);
if (strcmp(indisprimary, "t") == 0)
printfPQExpBuffer(&tmpbuf, _("primary key, "));
@@ -2108,6 +2116,9 @@ describeOneTableDetails(const char *schemaname,
if (strcmp(deferred, "t") == 0)
appendPQExpBufferStr(&tmpbuf, _(", initially deferred"));
+ if (strcmp(alwaysdeferred, "t") == 0)
+ appendPQExpBufferStr(&tmpbuf, _(", always deferred"));
+
if (strcmp(indisreplident, "t") == 0)
appendPQExpBuffer(&tmpbuf, _(", replica identity"));
@@ -2140,11 +2151,11 @@ describeOneTableDetails(const char *schemaname,
if (pset.sversion >= 90000)
appendPQExpBufferStr(&buf,
"pg_catalog.pg_get_constraintdef(con.oid, true), "
- "contype, condeferrable, condeferred");
+ "contype, condeferrable, condeferred, conalwaysdeferred");
else
appendPQExpBufferStr(&buf,
"null AS constraintdef, null AS contype, "
- "false AS condeferrable, false AS condeferred");
+ "false AS condeferrable, false AS condeferred, false as conalwaysdeferred");
if (pset.sversion >= 90400)
appendPQExpBufferStr(&buf, ", i.indisreplident");
else
@@ -2210,6 +2221,9 @@ describeOneTableDetails(const char *schemaname,
if (strcmp(PQgetvalue(result, i, 9), "t") == 0)
appendPQExpBufferStr(&buf, " INITIALLY DEFERRED");
+
+ if (strcmp(PQgetvalue(result, i, 10), "t") == 0)
+ appendPQExpBufferStr(&buf, " ALWAYS DEFERRED");
}
/* Add these for all cases */
@@ -2219,7 +2233,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, 11), "t") == 0)
appendPQExpBuffer(&buf, " REPLICA IDENTITY");
printTableAddFooter(&cont, buf.data);
@@ -2227,7 +2241,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, 12)),
false);
}
}
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 1d4ec09..0e6dea4 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -58,6 +58,7 @@ extern Oid index_create(Relation heapRelation,
bool isconstraint,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool allow_system_table_mods,
bool skip_build,
bool concurrent,
@@ -71,6 +72,7 @@ extern ObjectAddress index_constraint_create(Relation heapRelation,
char constraintType,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool mark_as_primary,
bool update_pgindex,
bool remove_old_dependencies,
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index ec035d8..f423901 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -44,6 +44,7 @@ CATALOG(pg_constraint,2606)
char contype; /* constraint type; see codes below */
bool condeferrable; /* deferrable constraint? */
bool condeferred; /* deferred by default? */
+ bool conalwaysdeferred; /* always deferred? */
bool convalidated; /* constraint has been validated? */
/*
@@ -150,31 +151,32 @@ typedef FormData_pg_constraint *Form_pg_constraint;
* compiler constants for pg_constraint
* ----------------
*/
-#define Natts_pg_constraint 24
+#define Natts_pg_constraint 25
#define Anum_pg_constraint_conname 1
#define Anum_pg_constraint_connamespace 2
#define Anum_pg_constraint_contype 3
#define Anum_pg_constraint_condeferrable 4
#define Anum_pg_constraint_condeferred 5
-#define Anum_pg_constraint_convalidated 6
-#define Anum_pg_constraint_conrelid 7
-#define Anum_pg_constraint_contypid 8
-#define Anum_pg_constraint_conindid 9
-#define Anum_pg_constraint_confrelid 10
-#define Anum_pg_constraint_confupdtype 11
-#define Anum_pg_constraint_confdeltype 12
-#define Anum_pg_constraint_confmatchtype 13
-#define Anum_pg_constraint_conislocal 14
-#define Anum_pg_constraint_coninhcount 15
-#define Anum_pg_constraint_connoinherit 16
-#define Anum_pg_constraint_conkey 17
-#define Anum_pg_constraint_confkey 18
-#define Anum_pg_constraint_conpfeqop 19
-#define Anum_pg_constraint_conppeqop 20
-#define Anum_pg_constraint_conffeqop 21
-#define Anum_pg_constraint_conexclop 22
-#define Anum_pg_constraint_conbin 23
-#define Anum_pg_constraint_consrc 24
+#define Anum_pg_constraint_conalwaysdeferred 6
+#define Anum_pg_constraint_convalidated 7
+#define Anum_pg_constraint_conrelid 8
+#define Anum_pg_constraint_contypid 9
+#define Anum_pg_constraint_conindid 10
+#define Anum_pg_constraint_confrelid 11
+#define Anum_pg_constraint_confupdtype 12
+#define Anum_pg_constraint_confdeltype 13
+#define Anum_pg_constraint_confmatchtype 14
+#define Anum_pg_constraint_conislocal 15
+#define Anum_pg_constraint_coninhcount 16
+#define Anum_pg_constraint_connoinherit 17
+#define Anum_pg_constraint_conkey 18
+#define Anum_pg_constraint_confkey 19
+#define Anum_pg_constraint_conpfeqop 20
+#define Anum_pg_constraint_conppeqop 21
+#define Anum_pg_constraint_conffeqop 22
+#define Anum_pg_constraint_conexclop 23
+#define Anum_pg_constraint_conbin 24
+#define Anum_pg_constraint_consrc 25
/* ----------------
* initial contents of pg_constraint
diff --git a/src/include/catalog/pg_constraint_fn.h b/src/include/catalog/pg_constraint_fn.h
index a4c4689..c9daf2c 100644
--- a/src/include/catalog/pg_constraint_fn.h
+++ b/src/include/catalog/pg_constraint_fn.h
@@ -32,6 +32,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
char constraintType,
bool isDeferrable,
bool isDeferred,
+ bool isAlwaysDeferred,
bool isValidated,
Oid relId,
const int16 *constraintKey,
diff --git a/src/include/catalog/pg_trigger.h b/src/include/catalog/pg_trigger.h
index f413caf..acb9d17 100644
--- a/src/include/catalog/pg_trigger.h
+++ b/src/include/catalog/pg_trigger.h
@@ -48,6 +48,7 @@ CATALOG(pg_trigger,2620)
Oid tgconstraint; /* associated pg_constraint entry, if any */
bool tgdeferrable; /* constraint trigger is deferrable */
bool tginitdeferred; /* constraint trigger is deferred initially */
+ bool tgalwaysdeferred; /* constraint trigger is always deferred */
int16 tgnargs; /* # of extra arguments in tgargs */
/*
@@ -75,7 +76,7 @@ typedef FormData_pg_trigger *Form_pg_trigger;
* compiler constants for pg_trigger
* ----------------
*/
-#define Natts_pg_trigger 17
+#define Natts_pg_trigger 18
#define Anum_pg_trigger_tgrelid 1
#define Anum_pg_trigger_tgname 2
#define Anum_pg_trigger_tgfoid 3
@@ -87,12 +88,13 @@ typedef FormData_pg_trigger *Form_pg_trigger;
#define Anum_pg_trigger_tgconstraint 9
#define Anum_pg_trigger_tgdeferrable 10
#define Anum_pg_trigger_tginitdeferred 11
-#define Anum_pg_trigger_tgnargs 12
-#define Anum_pg_trigger_tgattr 13
-#define Anum_pg_trigger_tgargs 14
-#define Anum_pg_trigger_tgqual 15
-#define Anum_pg_trigger_tgoldtable 16
-#define Anum_pg_trigger_tgnewtable 17
+#define Anum_pg_trigger_tgalwaysdeferred 12
+#define Anum_pg_trigger_tgnargs 13
+#define Anum_pg_trigger_tgattr 14
+#define Anum_pg_trigger_tgargs 15
+#define Anum_pg_trigger_tgqual 16
+#define Anum_pg_trigger_tgoldtable 17
+#define Anum_pg_trigger_tgnewtable 18
/* Bits within tgtype */
#define TRIGGER_TYPE_ROW (1 << 0)
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index adbcfa1..082affa 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 50eec73..47486a3 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2060,7 +2060,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 */
@@ -2084,6 +2085,7 @@ typedef struct Constraint
char *conname; /* Constraint name, or NULL if unnamed */
bool deferrable; /* DEFERRABLE? */
bool initdeferred; /* INITIALLY DEFERRED? */
+ bool alwaysdeferred; /* ALWAYS DEFERRED? */
int location; /* token location, or -1 if unknown */
/* Fields used for constraints with expressions (CHECK and DEFAULT): */
@@ -2368,6 +2370,7 @@ typedef struct CreateTrigStmt
/* The remaining fields are only used for constraint triggers */
bool deferrable; /* [NOT] DEFERRABLE */
bool initdeferred; /* INITIALLY {DEFERRED|IMMEDIATE} */
+ bool alwaysdeferred; /* ALWAYS DEFERRED? */
RangeVar *constrrel; /* opposite relation, if RI trigger */
} CreateTrigStmt;
@@ -2712,6 +2715,7 @@ typedef struct IndexStmt
bool isconstraint; /* is it for a pkey/unique constraint? */
bool deferrable; /* is the constraint DEFERRABLE? */
bool initdeferred; /* is the constraint INITIALLY DEFERRED? */
+ bool alwaysdeferred; /* ALWAYS DEFERRED? */
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 2169b03..06e1f21 100644
--- a/src/include/utils/reltrigger.h
+++ b/src/include/utils/reltrigger.h
@@ -34,6 +34,7 @@ typedef struct Trigger
Oid tgconstraint;
bool tgdeferrable;
bool tginitdeferred;
+ bool tgalwaysdeferred;
int16 tgnargs;
int16 tgnattr;
int16 *tgattr;
diff --git a/src/test/regress/input/constraints.source b/src/test/regress/input/constraints.source
index dbab8f1..0885a39 100644
--- a/src/test/regress/input/constraints.source
+++ b/src/test/regress/input/constraints.source
@@ -421,8 +421,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 bb75165..d55803b 100644
--- a/src/test/regress/output/constraints.source
+++ b/src/test/regress/output/constraints.source
@@ -344,7 +344,7 @@ SELECT * FROM INSERT_TBL;
CREATE TABLE COPY_TBL (x INT, y TEXT, z INT,
CONSTRAINT COPY_CON
CHECK (x > 3 AND y <> 'check failed' AND x < 7 ));
-COPY COPY_TBL FROM '@abs_srcdir@/data/constro.data';
+COPY COPY_TBL FROM '/home/nico/ws/postgres/src/test/regress/data/constro.data';
SELECT '' AS two, * FROM COPY_TBL;
two | x | y | z
-----+---+---------------+---
@@ -352,7 +352,7 @@ SELECT '' AS two, * FROM COPY_TBL;
| 6 | OK | 4
(2 rows)
-COPY COPY_TBL FROM '@abs_srcdir@/data/constrf.data';
+COPY COPY_TBL FROM '/home/nico/ws/postgres/src/test/regress/data/constrf.data';
ERROR: new row for relation "copy_tbl" violates check constraint "copy_con"
DETAIL: Failing row contains (7, check failed, 6).
CONTEXT: COPY copy_tbl, line 2: "7 check failed 6"
@@ -592,7 +592,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
--
--
2.7.4
0002-Add-always_deferred-to-information_schema.patchtext/x-diff; charset=us-asciiDownload
From 1c87e6d66ed5e30ee3dc51ecc1d76334586a155f Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@cryptonector.com>
Date: Tue, 3 Oct 2017 12:25:05 -0500
Subject: [PATCH 2/2] Add always_deferred to information_schema??
---
src/backend/catalog/information_schema.sql | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 4f0d193..db113df 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -894,11 +894,13 @@ CREATE VIEW domain_constraints AS
CAST(CASE WHEN condeferrable THEN 'YES' ELSE 'NO' END
AS yes_or_no) AS is_deferrable,
CAST(CASE WHEN condeferred THEN 'YES' ELSE 'NO' END
- AS yes_or_no) AS initially_deferred
+ AS yes_or_no) AS initially_deferred,
/*
* XXX Can we add is_always_deferred here? Are there
* standards considerations?
*/
+ CAST(CASE WHEN conalwaysdeferred THEN 'YES' ELSE 'NO' END
+ AS yes_or_no) AS always_deferred
FROM pg_namespace rs, pg_namespace n, pg_constraint con, pg_type t
WHERE rs.oid = con.connamespace
AND n.oid = t.typnamespace
@@ -1782,11 +1784,13 @@ CREATE VIEW table_constraints AS
CAST(CASE WHEN c.condeferrable THEN 'YES' ELSE 'NO' END AS yes_or_no)
AS is_deferrable,
CAST(CASE WHEN c.condeferred THEN 'YES' ELSE 'NO' END AS yes_or_no)
- AS initially_deferred
+ AS initially_deferred,
/*
* XXX Can we add is_always_deferred here? Are there
* standards considerations?
*/
+ CAST(CASE WHEN c.conalwaysdeferred THEN 'YES' ELSE 'NO' END AS yes_or_no)
+ AS always_deferred
FROM pg_namespace nc,
pg_namespace nr,
@@ -1815,7 +1819,8 @@ CREATE VIEW table_constraints AS
CAST(r.relname AS sql_identifier) AS table_name,
CAST('CHECK' AS character_data) AS constraint_type,
CAST('NO' AS yes_or_no) AS is_deferrable,
- CAST('NO' AS yes_or_no) AS initially_deferred
+ CAST('NO' AS yes_or_no) AS initially_deferred,
+ CAST('NO' AS yes_or_no) AS always_deferred
FROM pg_namespace nr,
pg_class r,
--
2.7.4
Ay, NOT WIP -- I left that in the Subject: by accident.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Ah, David Fetter points out that I should also update tabe completion
for psql. I'll do that at some point. I notice there's no table
completion for column constraint attributes... If it's obvious enough
I'll try to fix that too.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
I accidentally typoed when saving a file. Here's the new patch with
that typo corrected, changes to information_schema dropped, and with the
addition of tab completion of ALWAYS DEFERRED in psql.
Nico
--
Attachments:
0001-Add-ALWAYS-DEFERRED-option-for-CONSTRAINTs.patchtext/x-diff; charset=us-asciiDownload
From 97d3db0be9307eff5919821db7fc437da52ef7e3 Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@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 | 17 ++++-
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 | 1 +
src/backend/bootstrap/bootparse.y | 2 +
src/backend/catalog/heap.c | 1 +
src/backend/catalog/index.c | 8 +++
src/backend/catalog/information_schema.sql | 8 +++
src/backend/catalog/pg_constraint.c | 2 +
src/backend/catalog/toasting.c | 2 +-
src/backend/commands/indexcmds.c | 2 +-
src/backend/commands/tablecmds.c | 20 +++++-
src/backend/commands/trigger.c | 28 +++++++--
src/backend/commands/typecmds.c | 3 +
src/backend/nodes/copyfuncs.c | 3 +
src/backend/nodes/outfuncs.c | 4 ++
src/backend/parser/gram.y | 99 ++++++++++++++++++++++--------
src/backend/parser/parse_utilcmd.c | 46 +++++++++++++-
src/backend/utils/adt/ruleutils.c | 4 ++
src/bin/pg_dump/pg_dump.c | 31 ++++++++--
src/bin/pg_dump/pg_dump.h | 2 +
src/bin/psql/describe.c | 34 +++++++---
src/bin/psql/tab-complete.c | 4 +-
src/include/catalog/index.h | 2 +
src/include/catalog/pg_constraint.h | 42 +++++++------
src/include/catalog/pg_constraint_fn.h | 1 +
src/include/catalog/pg_trigger.h | 16 ++---
src/include/commands/trigger.h | 1 +
src/include/nodes/parsenodes.h | 6 +-
src/include/utils/reltrigger.h | 1 +
src/test/regress/input/constraints.source | 51 +++++++++++++++
src/test/regress/output/constraints.source | 54 +++++++++++++++-
33 files changed, 418 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9af77c1..2c3ed23 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2202,6 +2202,13 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</row>
<row>
+ <entry><structfield>conalwaysdeferred</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>Is the constraint always deferred?</entry>
+ </row>
+
+ <row>
<entry><structfield>convalidated</structfield></entry>
<entry><type>bool</type></entry>
<entry></entry>
@@ -6948,6 +6955,13 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</row>
<row>
+ <entry><structfield>tgalwaysdeferred</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if constraint trigger is always deferred</entry>
+ </row>
+
+ <row>
<entry><structfield>tgnargs</structfield></entry>
<entry><type>int2</type></entry>
<entry></entry>
@@ -7009,7 +7023,8 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
<para>
When <structfield>tgconstraint</> is nonzero,
<structfield>tgconstrrelid</>, <structfield>tgconstrindid</>,
- <structfield>tgdeferrable</>, and <structfield>tginitdeferred</> are
+ <structfield>tgdeferrable</>, <structfield>tginitdeferred</>, and
+ <structfield>tgalwaysdeferred</> are
largely redundant with the referenced <structname>pg_constraint</> 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 0fb385e..e81d1fa 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 ]
@@ -89,7 +89,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
[ 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 ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 1477288..38c88b8 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>
@@ -961,13 +961,17 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
<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>PRIMARY KEY</>,
<literal>EXCLUDE</>, and
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 2496250..2b1cb7c 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 f5f74af..342964e 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -646,6 +646,7 @@ typedef struct Trigger
Oid tgconstraint;
bool tgdeferrable;
bool tginitdeferred;
+ bool tgalwaysdeferred;
int16 tgnargs;
int16 tgnattr;
int16 *tgattr;
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 2e1fef0..c08ac60 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -310,6 +310,7 @@ Boot_DeclareIndexStmt:
stmt->isconstraint = false;
stmt->deferrable = false;
stmt->initdeferred = false;
+ stmt->alwaysdeferred = false;
stmt->transformed = false;
stmt->concurrent = false;
stmt->if_not_exists = false;
@@ -354,6 +355,7 @@ Boot_DeclareUniqueIndexStmt:
stmt->isconstraint = false;
stmt->deferrable = false;
stmt->initdeferred = false;
+ stmt->alwaysdeferred = false;
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 05e7081..a54c524 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2113,6 +2113,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
CONSTRAINT_CHECK, /* Constraint Type */
false, /* Is Deferrable */
false, /* Is Deferred */
+ false, /* Is Always Deferred */
is_validated,
RelationGetRelid(rel), /* relation */
attNos, /* attrs in the constraint */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index c7b2f03..65e995d 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -684,6 +684,7 @@ UpdateIndexRelation(Oid indexoid,
* isconstraint: index is owned by PRIMARY KEY, UNIQUE, or EXCLUSION constraint
* deferrable: constraint is DEFERRABLE
* initdeferred: constraint is INITIALLY DEFERRED
+ * alwaysdeferred: constraint is ALWAYS DEFERRED
* allow_system_table_mods: allow table to be a system catalog
* skip_build: true to skip the index_build() step for the moment; caller
* must do it later (typically via reindex_index())
@@ -713,6 +714,7 @@ index_create(Relation heapRelation,
bool isconstraint,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool allow_system_table_mods,
bool skip_build,
bool concurrent,
@@ -966,6 +968,7 @@ index_create(Relation heapRelation,
constraintType,
deferrable,
initdeferred,
+ alwaysdeferred,
false, /* already marked primary */
false, /* pg_index entry is OK */
false, /* no old dependencies */
@@ -1009,6 +1012,7 @@ index_create(Relation heapRelation,
/* Non-constraint indexes can't be deferrable */
Assert(!deferrable);
Assert(!initdeferred);
+ Assert(!alwaysdeferred);
}
/* Store dependency on collations */
@@ -1062,6 +1066,7 @@ index_create(Relation heapRelation,
Assert(!isconstraint);
Assert(!deferrable);
Assert(!initdeferred);
+ Assert(!alwaysdeferred);
}
/* Post creation hook for new index */
@@ -1154,6 +1159,7 @@ index_constraint_create(Relation heapRelation,
char constraintType,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool mark_as_primary,
bool update_pgindex,
bool remove_old_dependencies,
@@ -1202,6 +1208,7 @@ index_constraint_create(Relation heapRelation,
constraintType,
deferrable,
initdeferred,
+ alwaysdeferred,
true,
RelationGetRelid(heapRelation),
indexInfo->ii_KeyAttrNumbers,
@@ -1266,6 +1273,7 @@ index_constraint_create(Relation heapRelation,
trigger->isconstraint = true;
trigger->deferrable = true;
trigger->initdeferred = initdeferred;
+ trigger->alwaysdeferred = alwaysdeferred;
trigger->constrrel = NULL;
(void) CreateTrigger(trigger, NULL, RelationGetRelid(heapRelation),
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 236f6be..4f0d193 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -895,6 +895,10 @@ CREATE VIEW domain_constraints AS
AS yes_or_no) AS is_deferrable,
CAST(CASE WHEN condeferred 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
@@ -1779,6 +1783,10 @@ CREATE VIEW table_constraints AS
AS is_deferrable,
CAST(CASE WHEN c.condeferred 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 nc,
pg_namespace nr,
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 1336c46..81c0477 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -51,6 +51,7 @@ CreateConstraintEntry(const char *constraintName,
char constraintType,
bool isDeferrable,
bool isDeferred,
+ bool isAlwaysDeferred,
bool isValidated,
Oid relId,
const int16 *constraintKey,
@@ -166,6 +167,7 @@ CreateConstraintEntry(const char *constraintName,
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_conalwaysdeferred - 1] = BoolGetDatum(isAlwaysDeferred);
values[Anum_pg_constraint_convalidated - 1] = BoolGetDatum(isValidated);
values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(relId);
values[Anum_pg_constraint_contypid - 1] = ObjectIdGetDatum(domainId);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 6f517bb..f90ea32 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -333,7 +333,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
BTREE_AM_OID,
rel->rd_rel->reltablespace,
collationObjectId, classObjectId, coloptions, (Datum) 0,
- true, false, false, false,
+ true, false, false, false, false,
true, false, false, true, false);
heap_close(toast_rel, NoLock);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index b61aaac..791c1f8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -672,7 +672,7 @@ DefineIndex(Oid relationId,
collationObjectId, classObjectId,
coloptions, reloptions, stmt->primary,
stmt->isconstraint, stmt->deferrable, stmt->initdeferred,
- allowSystemTableMods,
+ stmt->alwaysdeferred, allowSystemTableMods,
skip_build || stmt->concurrent,
stmt->concurrent, !check_rights,
stmt->if_not_exists);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 563bcda..8177684 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6874,6 +6874,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
constraintType,
stmt->deferrable,
stmt->initdeferred,
+ stmt->alwaysdeferred,
stmt->primary,
true, /* update pg_index */
true, /* remove old dependencies */
@@ -7443,6 +7444,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
CONSTRAINT_FOREIGN,
fkconstraint->deferrable,
fkconstraint->initdeferred,
+ fkconstraint->alwaysdeferred,
fkconstraint->initially_valid,
RelationGetRelid(rel),
fkattnum,
@@ -7567,7 +7569,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
cmdcon->conname, RelationGetRelationName(rel))));
if (currcon->condeferrable != cmdcon->deferrable ||
- currcon->condeferred != cmdcon->initdeferred)
+ currcon->condeferred != cmdcon->initdeferred ||
+ currcon->conalwaysdeferred != cmdcon->alwaysdeferred)
{
HeapTuple copyTuple;
HeapTuple tgtuple;
@@ -7585,6 +7588,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
copy_con->condeferrable = cmdcon->deferrable;
copy_con->condeferred = cmdcon->initdeferred;
+ copy_con->conalwaysdeferred = cmdcon->alwaysdeferred;
CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
InvokeObjectPostAlterHook(ConstraintRelationId,
@@ -7638,6 +7642,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
copy_tg->tgdeferrable = cmdcon->deferrable;
copy_tg->tginitdeferred = cmdcon->initdeferred;
+ copy_tg->tgalwaysdeferred = cmdcon->alwaysdeferred;
CatalogTupleUpdate(tgrel, ©Tuple->t_self, copyTuple);
InvokeObjectPostAlterHook(TriggerRelationId,
@@ -8305,6 +8310,7 @@ validateForeignKeyConstraint(char *conname,
trig.tgconstraint = constraintOid;
trig.tgdeferrable = FALSE;
trig.tginitdeferred = FALSE;
+ trig.tgalwaysdeferred = FALSE;
/* we needn't fill in remaining fields */
/*
@@ -8394,6 +8400,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
fk_trigger->isconstraint = true;
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
+ fk_trigger->alwaysdeferred = fkconstraint->alwaysdeferred;
fk_trigger->constrrel = NULL;
fk_trigger->args = NIL;
@@ -8442,26 +8449,31 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
case FKCONSTR_ACTION_NOACTION:
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
+ fk_trigger->alwaysdeferred = fkconstraint->alwaysdeferred;
fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
break;
case FKCONSTR_ACTION_RESTRICT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
break;
case FKCONSTR_ACTION_CASCADE:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
break;
case FKCONSTR_ACTION_SETNULL:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
break;
case FKCONSTR_ACTION_SETDEFAULT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
break;
default:
@@ -8497,26 +8509,31 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
case FKCONSTR_ACTION_NOACTION:
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
+ fk_trigger->alwaysdeferred = fkconstraint->alwaysdeferred;
fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
break;
case FKCONSTR_ACTION_RESTRICT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
break;
case FKCONSTR_ACTION_CASCADE:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
break;
case FKCONSTR_ACTION_SETNULL:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
break;
case FKCONSTR_ACTION_SETDEFAULT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
break;
default:
@@ -11261,6 +11278,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
if (acon->condeferrable != bcon->condeferrable ||
acon->condeferred != bcon->condeferred ||
+ acon->conalwaysdeferred != bcon->conalwaysdeferred ||
strcmp(decompile_conbin(a, tupleDesc),
decompile_conbin(b, tupleDesc)) != 0)
return false;
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index e75a59d..1770347 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -650,6 +650,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
CONSTRAINT_TRIGGER,
stmt->deferrable,
stmt->initdeferred,
+ stmt->alwaysdeferred,
true,
RelationGetRelid(rel),
NULL, /* no conkey */
@@ -748,6 +749,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
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_tgalwaysdeferred - 1] = BoolGetDatum(stmt->alwaysdeferred);
if (stmt->args)
{
@@ -1245,6 +1247,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
}
fkcon->deferrable = stmt->deferrable;
fkcon->initdeferred = stmt->initdeferred;
+ fkcon->alwaysdeferred = stmt->alwaysdeferred;
fkcon->skip_validation = false;
fkcon->initially_valid = true;
@@ -1742,6 +1745,7 @@ RelationBuildTriggers(Relation relation)
build->tgconstraint = pg_trigger->tgconstraint;
build->tgdeferrable = pg_trigger->tgdeferrable;
build->tginitdeferred = pg_trigger->tginitdeferred;
+ build->tgalwaysdeferred = pg_trigger->tgalwaysdeferred;
build->tgnargs = pg_trigger->tgnargs;
/* tgattr is first var-width field, so OK to access directly */
build->tgnattr = pg_trigger->tgattr.dim1;
@@ -2050,6 +2054,8 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
return false;
if (trig1->tginitdeferred != trig2->tginitdeferred)
return false;
+ if (trig1->tgalwaysdeferred != trig2->tgalwaysdeferred)
+ return false;
if (trig1->tgnargs != trig2->tgnargs)
return false;
if (trig1->tgnattr != trig2->tgnattr)
@@ -2143,7 +2149,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;
@@ -3392,6 +3399,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;
@@ -3678,6 +3686,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
@@ -5191,14 +5201,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->condeferrable)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("constraint \"%s\" is not deferrable",
constraint->relname)));
+ else if (!stmt->deferred && con->conalwaysdeferred)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("constraint \"%s\" is always deferred",
+ constraint->relname)));
+ else if (con->condeferrable && !con->conalwaysdeferred)
+ conoidlist = lappend_oid(conoidlist,
+ HeapTupleGetOid(tup));
found = true;
}
@@ -5695,7 +5710,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
(event & TRIGGER_EVENT_OPMASK) |
(row_trigger ? TRIGGER_EVENT_ROW : 0) |
(trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) |
- (trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0);
+ (trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0) |
+ (trigger->tgalwaysdeferred ? 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 c1b87e0..85b73ba 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),
@@ -2603,6 +2604,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),
@@ -3150,6 +3152,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
CONSTRAINT_CHECK, /* Constraint Type */
false, /* Is Deferrable */
false, /* Is Deferred */
+ false, /* Is Always Deferred */
!constr->skip_validation, /* Is Validated */
InvalidOid, /* not a relation constraint */
NULL,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c1a83ca..bbd1705 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2831,6 +2831,7 @@ _copyConstraint(const Constraint *from)
COPY_STRING_FIELD(conname);
COPY_SCALAR_FIELD(deferrable);
COPY_SCALAR_FIELD(initdeferred);
+ COPY_SCALAR_FIELD(alwaysdeferred);
COPY_LOCATION_FIELD(location);
COPY_SCALAR_FIELD(is_no_inherit);
COPY_NODE_FIELD(raw_expr);
@@ -3379,6 +3380,7 @@ _copyIndexStmt(const IndexStmt *from)
COPY_SCALAR_FIELD(isconstraint);
COPY_SCALAR_FIELD(deferrable);
COPY_SCALAR_FIELD(initdeferred);
+ COPY_SCALAR_FIELD(alwaysdeferred);
COPY_SCALAR_FIELD(transformed);
COPY_SCALAR_FIELD(concurrent);
COPY_SCALAR_FIELD(if_not_exists);
@@ -4148,6 +4150,7 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
COPY_NODE_FIELD(transitionRels);
COPY_SCALAR_FIELD(deferrable);
COPY_SCALAR_FIELD(initdeferred);
+ COPY_SCALAR_FIELD(alwaysdeferred);
COPY_NODE_FIELD(constrrel);
return newnode;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2532edc..3b2f8df 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3508,6 +3508,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 4c83a63..dd5fe68 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);
+ bool *deferrable, bool *initdeferred, bool *alwaysdeferred,
+ bool *not_valid, bool *no_inherit, core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -727,6 +728,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* RANGE, ROWS to support opt_existing_window_name; and for RANGE, ROWS
* 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.
*
@@ -745,7 +747,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 PRECEDING FOLLOWING CUBE ROLLUP
+%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP ALWAYS
%left Op OPERATOR /* multi-character ops and user-defined operators */
%left '+' '-'
%left '*' '/' '%'
@@ -2234,7 +2236,9 @@ alter_table_cmd:
processCASbits($4, @4, "ALTER CONSTRAINT statement",
&c->deferrable,
&c->initdeferred,
- NULL, NULL, yyscanner);
+ &c->alwaysdeferred,
+ NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
/* ALTER TABLE <name> VALIDATE CONSTRAINT ... */
@@ -3500,6 +3504,13 @@ ConstraintAttr:
n->location = @1;
$$ = (Node *)n;
}
+ | ALWAYS DEFERRED
+ {
+ Constraint *n = makeNode(Constraint);
+ n->contype = CONSTR_ATTR_ALWAYS_DEFERRED;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
;
@@ -3554,8 +3565,10 @@ ConstraintElem:
n->raw_expr = $3;
n->cooked_expr = NULL;
processCASbits($5, @5, "CHECK",
- NULL, NULL, &n->skip_validation,
- &n->is_no_inherit, yyscanner);
+ NULL, NULL, NULL,
+ &n->skip_validation,
+ &n->is_no_inherit,
+ yyscanner);
n->initially_valid = !n->skip_validation;
$$ = (Node *)n;
}
@@ -3570,8 +3583,9 @@ ConstraintElem:
n->indexname = NULL;
n->indexspace = $6;
processCASbits($7, @7, "UNIQUE",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| UNIQUE ExistingIndex ConstraintAttributeSpec
@@ -3584,8 +3598,9 @@ ConstraintElem:
n->indexname = $2;
n->indexspace = NULL;
processCASbits($3, @3, "UNIQUE",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace
@@ -3599,8 +3614,9 @@ ConstraintElem:
n->indexname = NULL;
n->indexspace = $7;
processCASbits($8, @8, "PRIMARY KEY",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| PRIMARY KEY ExistingIndex ConstraintAttributeSpec
@@ -3613,8 +3629,9 @@ ConstraintElem:
n->indexname = $3;
n->indexspace = NULL;
processCASbits($4, @4, "PRIMARY KEY",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
@@ -3631,8 +3648,9 @@ ConstraintElem:
n->indexspace = $7;
n->where_clause = $8;
processCASbits($9, @9, "EXCLUDE",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
@@ -3649,6 +3667,7 @@ ConstraintElem:
n->fk_del_action = (char) ($10 & 0xFF);
processCASbits($11, @11, "FOREIGN KEY",
&n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred,
&n->skip_validation, NULL,
yyscanner);
n->initially_valid = !n->skip_validation;
@@ -5210,6 +5229,7 @@ CreateTrigStmt:
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
+ n->alwaysdeferred = FALSE;
n->constrrel = NULL;
$$ = (Node *)n;
}
@@ -5231,8 +5251,9 @@ CreateTrigStmt:
n->transitionRels = NIL;
n->isconstraint = TRUE;
processCASbits($10, @10, "TRIGGER",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
n->constrrel = $9;
$$ = (Node *)n;
}
@@ -5388,17 +5409,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;
}
@@ -5409,6 +5437,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; }
;
@@ -5499,8 +5528,9 @@ CreateAssertStmt:
n->args = list_make1($6);
n->isconstraint = TRUE;
processCASbits($8, @8, "ASSERTION",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -7177,6 +7207,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
n->isconstraint = false;
n->deferrable = false;
n->initdeferred = false;
+ n->alwaysdeferred = false;
n->transformed = false;
n->if_not_exists = false;
$$ = (Node *)n;
@@ -7203,6 +7234,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
n->isconstraint = false;
n->deferrable = false;
n->initdeferred = false;
+ n->alwaysdeferred = false;
n->transformed = false;
n->if_not_exists = true;
$$ = (Node *)n;
@@ -15791,18 +15823,20 @@ 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)
+ bool *deferrable, bool *initdeferred, bool *alwaysdeferred,
+ bool *not_valid, bool *no_inherit, core_yyscan_t yyscanner)
{
/* defaults */
if (deferrable)
*deferrable = false;
if (initdeferred)
*initdeferred = false;
+ if (alwaysdeferred)
+ *alwaysdeferred = false;
if (not_valid)
*not_valid = false;
- if (cas_bits & (CAS_DEFERRABLE | CAS_INITIALLY_DEFERRED))
+ if (cas_bits & (CAS_DEFERRABLE | CAS_INITIALLY_DEFERRED | CAS_ALWAYS_DEFERRED))
{
if (deferrable)
*deferrable = true;
@@ -15815,7 +15849,7 @@ processCASbits(int cas_bits, int location, const char *constrType,
parser_errposition(location)));
}
- if (cas_bits & CAS_INITIALLY_DEFERRED)
+ if (cas_bits & (CAS_INITIALLY_DEFERRED | CAS_ALWAYS_DEFERRED))
{
if (initdeferred)
*initdeferred = true;
@@ -15828,6 +15862,19 @@ processCASbits(int cas_bits, int location, const char *constrType,
parser_errposition(location)));
}
+ if (cas_bits & CAS_ALWAYS_DEFERRED)
+ {
+ if (alwaysdeferred)
+ *alwaysdeferred = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is CHECK, UNIQUE, or similar */
+ errmsg("%s constraints cannot be marked DEFERRED",
+ constrType),
+ parser_errposition(location)));
+ }
+
if (cas_bits & CAS_NOT_VALID)
{
if (not_valid)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 27e568f..54d1c3f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -750,6 +750,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 */
@@ -878,6 +879,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",
@@ -1359,6 +1361,7 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
index->isconstraint = true;
index->deferrable = conrec->condeferrable;
index->initdeferred = conrec->condeferred;
+ index->alwaysdeferred = conrec->conalwaysdeferred;
/* If it's an exclusion constraint, we need the operator names */
if (idxrec->indisexclusion)
@@ -1715,7 +1718,8 @@ transformIndexConstraints(CreateStmtContext *cxt)
equal(index->excludeOpNames, priorindex->excludeOpNames) &&
strcmp(index->accessMethod, priorindex->accessMethod) == 0 &&
index->deferrable == priorindex->deferrable &&
- index->initdeferred == priorindex->initdeferred)
+ index->initdeferred == priorindex->initdeferred &&
+ index->alwaysdeferred == priorindex->alwaysdeferred)
{
priorindex->unique |= index->unique;
@@ -1770,6 +1774,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
index->isconstraint = true;
index->deferrable = constraint->deferrable;
index->initdeferred = constraint->initdeferred;
+ index->alwaysdeferred = constraint->alwaysdeferred;
if (constraint->conname != NULL)
index->idxname = pstrdup(constraint->conname);
@@ -2983,6 +2988,9 @@ 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;
@@ -3008,12 +3016,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;
+ saw_deferrable = true;
lastprimarycon->deferrable = true;
break;
@@ -3023,13 +3032,20 @@ 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)));
+ 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_deferrability = true;
+ saw_notdeferrable = true;
lastprimarycon->deferrable = false;
+ lastprimarycon->alwaysdeferred = false;
if (saw_initially &&
lastprimarycon->initdeferred)
ereport(ERROR,
@@ -3038,6 +3054,30 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
parser_errposition(cxt->pstate, con->location)));
break;
+ case CONSTR_ATTR_ALWAYS_DEFERRED:
+ if (!SUPPORTS_ATTRS(lastprimarycon))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("misplaced ALWAYS DEFERRED clause"),
+ parser_errposition(cxt->pstate, con->location)));
+ if (saw_alwaysdeferred || saw_notdeferrable)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("multiple ALWAYS DEFERRED/NOT DEFERRABLE clauses not allowed"),
+ parser_errposition(cxt->pstate, con->location)));
+ saw_initially = true;
+ saw_deferrability = true;
+ saw_alwaysdeferred = true;
+ lastprimarycon->deferrable = true;
+ lastprimarycon->alwaysdeferred = true;
+ if (saw_initially &&
+ !lastprimarycon->initdeferred)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("constraint declared INITIALLY IMMEDIATE must not be ALWAYS DEFERRED"),
+ parser_errposition(cxt->pstate, con->location)));
+ break;
+
case CONSTR_ATTR_DEFERRED:
if (!SUPPORTS_ATTRS(lastprimarycon))
ereport(ERROR,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 84759b6..61fd899 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -936,6 +936,8 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
appendStringInfoString(&buf, "DEFERRED ");
else
appendStringInfoString(&buf, "IMMEDIATE ");
+ if (trigrec->tgalwaysdeferred)
+ appendStringInfoString(&buf, "ALWAYS DEFERRED");
}
value = fastgetattr(ht_trig, Anum_pg_trigger_tgoldtable,
@@ -2144,6 +2146,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
appendStringInfoString(&buf, " DEFERRABLE");
if (conForm->condeferred)
appendStringInfoString(&buf, " INITIALLY DEFERRED");
+ if (conForm->conalwaysdeferred)
+ 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 e34c83a..a095a0a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6509,6 +6509,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_conname,
i_condeferrable,
i_condeferred,
+ i_conalwaysdeferred,
i_contableoid,
i_conoid,
i_condef,
@@ -6566,7 +6567,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.condeferrable, c.condeferred, c.conalwaysdeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
@@ -6597,7 +6598,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.condeferrable, c.condeferred, c.conalwaysdeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
@@ -6624,7 +6625,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.condeferrable, c.condeferred, c.conalwaysdeferred "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"null AS condef, "
@@ -6654,7 +6655,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.condeferrable, c.condeferred, c.conalwaysdeferred "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"null AS condef, "
@@ -6691,6 +6692,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_conname = PQfnumber(res, "conname");
i_condeferrable = PQfnumber(res, "condeferrable");
i_condeferred = PQfnumber(res, "condeferred");
+ i_conalwaysdeferred = PQfnumber(res, "conalwaysdeferred");
i_contableoid = PQfnumber(res, "contableoid");
i_conoid = PQfnumber(res, "conoid");
i_condef = PQfnumber(res, "condef");
@@ -6746,6 +6748,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
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].conalwaysdeferred = *(PQgetvalue(res, j, i_conalwaysdeferred)) == 't';
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
@@ -6943,6 +6946,7 @@ getConstraints(Archive *fout, TableInfo tblinfo[], int numTables)
constrinfo[j].conindex = 0;
constrinfo[j].condeferrable = false;
constrinfo[j].condeferred = false;
+ constrinfo[j].conalwaysdeferred = false;
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
}
@@ -7029,6 +7033,7 @@ getDomainConstraints(Archive *fout, TypeInfo *tyinfo)
constrinfo[i].conindex = 0;
constrinfo[i].condeferrable = false;
constrinfo[i].condeferred = false;
+ constrinfo[i].conalwaysdeferred = false;
constrinfo[i].conislocal = true;
constrinfo[i].separate = !validated;
@@ -7193,6 +7198,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
i_tgenabled,
i_tgdeferrable,
i_tginitdeferred,
+ i_tgalwaysdeferred,
i_tgdef;
int ntups;
@@ -7291,6 +7297,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
i_tgenabled = PQfnumber(res, "tgenabled");
i_tgdeferrable = PQfnumber(res, "tgdeferrable");
i_tginitdeferred = PQfnumber(res, "tginitdeferred");
+ i_tgalwaysdeferred = PQfnumber(res, "tgalwaysdeferred");
i_tgdef = PQfnumber(res, "tgdef");
tginfo = (TriggerInfo *) pg_malloc(ntups * sizeof(TriggerInfo));
@@ -7320,6 +7327,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
tginfo[j].tgisconstraint = false;
tginfo[j].tgdeferrable = false;
tginfo[j].tginitdeferred = false;
+ tginfo[j].tgalwaysdeferred = false;
tginfo[j].tgconstrname = NULL;
tginfo[j].tgconstrrelid = InvalidOid;
tginfo[j].tgconstrrelname = NULL;
@@ -7335,6 +7343,8 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
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';
+ if (i_tgalwaysdeferred != -1)
+ tginfo[j].tgalwaysdeferred = *(PQgetvalue(res, j, i_tgalwaysdeferred)) == 't';
if (tginfo[j].tgisconstraint)
{
@@ -8283,6 +8293,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
constrs[j].conindex = 0;
constrs[j].condeferrable = false;
constrs[j].condeferred = false;
+ constrs[j].conalwaysdeferred = false;
constrs[j].conislocal = (PQgetvalue(res, j, 4)[0] == 't');
/*
@@ -16204,6 +16215,11 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
appendPQExpBufferStr(q, " INITIALLY DEFERRED");
}
+ if (coninfo->conalwaysdeferred)
+ {
+ appendPQExpBufferStr(q, " ALWAYS DEFERRED");
+ }
+
appendPQExpBufferStr(q, ";\n");
}
@@ -16866,9 +16882,12 @@ dumpTrigger(Archive *fout, TriggerInfo *tginfo)
appendPQExpBufferStr(query, "NOT ");
appendPQExpBufferStr(query, "DEFERRABLE INITIALLY ");
if (tginfo->tginitdeferred)
- appendPQExpBufferStr(query, "DEFERRED\n");
+ appendPQExpBufferStr(query, "DEFERRED");
else
- appendPQExpBufferStr(query, "IMMEDIATE\n");
+ appendPQExpBufferStr(query, "IMMEDIATE");
+ if (tginfo->tgalwaysdeferred)
+ 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 e7593e6..93b098a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -399,6 +399,7 @@ typedef struct _triggerInfo
char tgenabled;
bool tgdeferrable;
bool tginitdeferred;
+ bool tgalwaysdeferred;
char *tgdef;
} TriggerInfo;
@@ -432,6 +433,7 @@ typedef struct _constraintInfo
DumpId conindex; /* identifies associated index if any */
bool condeferrable; /* TRUE if constraint is DEFERRABLE */
bool condeferred; /* TRUE if constraint is INITIALLY DEFERRED */
+ bool conalwaysdeferred; /* TRUE if constraint is 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 0688571..c387d19 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2043,10 +2043,17 @@ describeOneTableDetails(const char *schemaname,
"WHERE conrelid = i.indrelid AND "
"conindid = i.indexrelid AND "
"contype IN ('p','u','x') AND "
- "condeferred) AS condeferred,\n");
+ "condeferred) AS condeferred,\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 "
+ "conalwaysdeferred) AS conalwaysdeferred,\n"
+ );
else
appendPQExpBufferStr(&buf,
- " false AS condeferrable, false AS condeferred,\n");
+ " false AS condeferrable, false AS condeferred, false AS conalwaysdeferred\n");
if (pset.sversion >= 90400)
appendPQExpBuffer(&buf, "i.indisreplident,\n");
@@ -2076,10 +2083,11 @@ describeOneTableDetails(const char *schemaname,
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 *alwaysdeferred = PQgetvalue(result, 0, 6);
+ char *indisreplident = PQgetvalue(result, 0, 7);
+ char *indamname = PQgetvalue(result, 0, 8);
+ char *indtable = PQgetvalue(result, 0, 9);
+ char *indpred = PQgetvalue(result, 0, 10);
if (strcmp(indisprimary, "t") == 0)
printfPQExpBuffer(&tmpbuf, _("primary key, "));
@@ -2108,6 +2116,9 @@ describeOneTableDetails(const char *schemaname,
if (strcmp(deferred, "t") == 0)
appendPQExpBufferStr(&tmpbuf, _(", initially deferred"));
+ if (strcmp(alwaysdeferred, "t") == 0)
+ appendPQExpBufferStr(&tmpbuf, _(", always deferred"));
+
if (strcmp(indisreplident, "t") == 0)
appendPQExpBuffer(&tmpbuf, _(", replica identity"));
@@ -2140,11 +2151,11 @@ describeOneTableDetails(const char *schemaname,
if (pset.sversion >= 90000)
appendPQExpBufferStr(&buf,
"pg_catalog.pg_get_constraintdef(con.oid, true), "
- "contype, condeferrable, condeferred");
+ "contype, condeferrable, condeferred, conalwaysdeferred");
else
appendPQExpBufferStr(&buf,
"null AS constraintdef, null AS contype, "
- "false AS condeferrable, false AS condeferred");
+ "false AS condeferrable, false AS condeferred, false as conalwaysdeferred");
if (pset.sversion >= 90400)
appendPQExpBufferStr(&buf, ", i.indisreplident");
else
@@ -2210,6 +2221,9 @@ describeOneTableDetails(const char *schemaname,
if (strcmp(PQgetvalue(result, i, 9), "t") == 0)
appendPQExpBufferStr(&buf, " INITIALLY DEFERRED");
+
+ if (strcmp(PQgetvalue(result, i, 10), "t") == 0)
+ appendPQExpBufferStr(&buf, " ALWAYS DEFERRED");
}
/* Add these for all cases */
@@ -2219,7 +2233,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, 11), "t") == 0)
appendPQExpBuffer(&buf, " REPLICA IDENTITY");
printTableAddFooter(&cont, buf.data);
@@ -2227,7 +2241,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, 12)),
false);
}
}
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a09c49d..47cb478 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2549,10 +2549,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 1d4ec09..0e6dea4 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -58,6 +58,7 @@ extern Oid index_create(Relation heapRelation,
bool isconstraint,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool allow_system_table_mods,
bool skip_build,
bool concurrent,
@@ -71,6 +72,7 @@ extern ObjectAddress index_constraint_create(Relation heapRelation,
char constraintType,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool mark_as_primary,
bool update_pgindex,
bool remove_old_dependencies,
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index ec035d8..f423901 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -44,6 +44,7 @@ CATALOG(pg_constraint,2606)
char contype; /* constraint type; see codes below */
bool condeferrable; /* deferrable constraint? */
bool condeferred; /* deferred by default? */
+ bool conalwaysdeferred; /* always deferred? */
bool convalidated; /* constraint has been validated? */
/*
@@ -150,31 +151,32 @@ typedef FormData_pg_constraint *Form_pg_constraint;
* compiler constants for pg_constraint
* ----------------
*/
-#define Natts_pg_constraint 24
+#define Natts_pg_constraint 25
#define Anum_pg_constraint_conname 1
#define Anum_pg_constraint_connamespace 2
#define Anum_pg_constraint_contype 3
#define Anum_pg_constraint_condeferrable 4
#define Anum_pg_constraint_condeferred 5
-#define Anum_pg_constraint_convalidated 6
-#define Anum_pg_constraint_conrelid 7
-#define Anum_pg_constraint_contypid 8
-#define Anum_pg_constraint_conindid 9
-#define Anum_pg_constraint_confrelid 10
-#define Anum_pg_constraint_confupdtype 11
-#define Anum_pg_constraint_confdeltype 12
-#define Anum_pg_constraint_confmatchtype 13
-#define Anum_pg_constraint_conislocal 14
-#define Anum_pg_constraint_coninhcount 15
-#define Anum_pg_constraint_connoinherit 16
-#define Anum_pg_constraint_conkey 17
-#define Anum_pg_constraint_confkey 18
-#define Anum_pg_constraint_conpfeqop 19
-#define Anum_pg_constraint_conppeqop 20
-#define Anum_pg_constraint_conffeqop 21
-#define Anum_pg_constraint_conexclop 22
-#define Anum_pg_constraint_conbin 23
-#define Anum_pg_constraint_consrc 24
+#define Anum_pg_constraint_conalwaysdeferred 6
+#define Anum_pg_constraint_convalidated 7
+#define Anum_pg_constraint_conrelid 8
+#define Anum_pg_constraint_contypid 9
+#define Anum_pg_constraint_conindid 10
+#define Anum_pg_constraint_confrelid 11
+#define Anum_pg_constraint_confupdtype 12
+#define Anum_pg_constraint_confdeltype 13
+#define Anum_pg_constraint_confmatchtype 14
+#define Anum_pg_constraint_conislocal 15
+#define Anum_pg_constraint_coninhcount 16
+#define Anum_pg_constraint_connoinherit 17
+#define Anum_pg_constraint_conkey 18
+#define Anum_pg_constraint_confkey 19
+#define Anum_pg_constraint_conpfeqop 20
+#define Anum_pg_constraint_conppeqop 21
+#define Anum_pg_constraint_conffeqop 22
+#define Anum_pg_constraint_conexclop 23
+#define Anum_pg_constraint_conbin 24
+#define Anum_pg_constraint_consrc 25
/* ----------------
* initial contents of pg_constraint
diff --git a/src/include/catalog/pg_constraint_fn.h b/src/include/catalog/pg_constraint_fn.h
index a4c4689..c9daf2c 100644
--- a/src/include/catalog/pg_constraint_fn.h
+++ b/src/include/catalog/pg_constraint_fn.h
@@ -32,6 +32,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
char constraintType,
bool isDeferrable,
bool isDeferred,
+ bool isAlwaysDeferred,
bool isValidated,
Oid relId,
const int16 *constraintKey,
diff --git a/src/include/catalog/pg_trigger.h b/src/include/catalog/pg_trigger.h
index f413caf..acb9d17 100644
--- a/src/include/catalog/pg_trigger.h
+++ b/src/include/catalog/pg_trigger.h
@@ -48,6 +48,7 @@ CATALOG(pg_trigger,2620)
Oid tgconstraint; /* associated pg_constraint entry, if any */
bool tgdeferrable; /* constraint trigger is deferrable */
bool tginitdeferred; /* constraint trigger is deferred initially */
+ bool tgalwaysdeferred; /* constraint trigger is always deferred */
int16 tgnargs; /* # of extra arguments in tgargs */
/*
@@ -75,7 +76,7 @@ typedef FormData_pg_trigger *Form_pg_trigger;
* compiler constants for pg_trigger
* ----------------
*/
-#define Natts_pg_trigger 17
+#define Natts_pg_trigger 18
#define Anum_pg_trigger_tgrelid 1
#define Anum_pg_trigger_tgname 2
#define Anum_pg_trigger_tgfoid 3
@@ -87,12 +88,13 @@ typedef FormData_pg_trigger *Form_pg_trigger;
#define Anum_pg_trigger_tgconstraint 9
#define Anum_pg_trigger_tgdeferrable 10
#define Anum_pg_trigger_tginitdeferred 11
-#define Anum_pg_trigger_tgnargs 12
-#define Anum_pg_trigger_tgattr 13
-#define Anum_pg_trigger_tgargs 14
-#define Anum_pg_trigger_tgqual 15
-#define Anum_pg_trigger_tgoldtable 16
-#define Anum_pg_trigger_tgnewtable 17
+#define Anum_pg_trigger_tgalwaysdeferred 12
+#define Anum_pg_trigger_tgnargs 13
+#define Anum_pg_trigger_tgattr 14
+#define Anum_pg_trigger_tgargs 15
+#define Anum_pg_trigger_tgqual 16
+#define Anum_pg_trigger_tgoldtable 17
+#define Anum_pg_trigger_tgnewtable 18
/* Bits within tgtype */
#define TRIGGER_TYPE_ROW (1 << 0)
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index adbcfa1..082affa 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 50eec73..47486a3 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2060,7 +2060,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 */
@@ -2084,6 +2085,7 @@ typedef struct Constraint
char *conname; /* Constraint name, or NULL if unnamed */
bool deferrable; /* DEFERRABLE? */
bool initdeferred; /* INITIALLY DEFERRED? */
+ bool alwaysdeferred; /* ALWAYS DEFERRED? */
int location; /* token location, or -1 if unknown */
/* Fields used for constraints with expressions (CHECK and DEFAULT): */
@@ -2368,6 +2370,7 @@ typedef struct CreateTrigStmt
/* The remaining fields are only used for constraint triggers */
bool deferrable; /* [NOT] DEFERRABLE */
bool initdeferred; /* INITIALLY {DEFERRED|IMMEDIATE} */
+ bool alwaysdeferred; /* ALWAYS DEFERRED? */
RangeVar *constrrel; /* opposite relation, if RI trigger */
} CreateTrigStmt;
@@ -2712,6 +2715,7 @@ typedef struct IndexStmt
bool isconstraint; /* is it for a pkey/unique constraint? */
bool deferrable; /* is the constraint DEFERRABLE? */
bool initdeferred; /* is the constraint INITIALLY DEFERRED? */
+ bool alwaysdeferred; /* ALWAYS DEFERRED? */
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 2169b03..06e1f21 100644
--- a/src/include/utils/reltrigger.h
+++ b/src/include/utils/reltrigger.h
@@ -34,6 +34,7 @@ typedef struct Trigger
Oid tgconstraint;
bool tgdeferrable;
bool tginitdeferred;
+ bool tgalwaysdeferred;
int16 tgnargs;
int16 tgnattr;
int16 *tgattr;
diff --git a/src/test/regress/input/constraints.source b/src/test/regress/input/constraints.source
index dbab8f1..0885a39 100644
--- a/src/test/regress/input/constraints.source
+++ b/src/test/regress/input/constraints.source
@@ -421,8 +421,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 bb75165..d55803b 100644
--- a/src/test/regress/output/constraints.source
+++ b/src/test/regress/output/constraints.source
@@ -344,7 +344,7 @@ SELECT * FROM INSERT_TBL;
CREATE TABLE COPY_TBL (x INT, y TEXT, z INT,
CONSTRAINT COPY_CON
CHECK (x > 3 AND y <> 'check failed' AND x < 7 ));
-COPY COPY_TBL FROM '@abs_srcdir@/data/constro.data';
+COPY COPY_TBL FROM '/home/nico/ws/postgres/src/test/regress/data/constro.data';
SELECT '' AS two, * FROM COPY_TBL;
two | x | y | z
-----+---+---------------+---
@@ -352,7 +352,7 @@ SELECT '' AS two, * FROM COPY_TBL;
| 6 | OK | 4
(2 rows)
-COPY COPY_TBL FROM '@abs_srcdir@/data/constrf.data';
+COPY COPY_TBL FROM '/home/nico/ws/postgres/src/test/regress/data/constrf.data';
ERROR: new row for relation "copy_tbl" violates check constraint "copy_con"
DETAIL: Failing row contains (7, check failed, 6).
CONTEXT: COPY copy_tbl, line 2: "7 check failed 6"
@@ -592,7 +592,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
--
--
2.7.4
FYI, I've added my patch to the commitfest.
https://commitfest.postgresql.org/15/1319/
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Rebased (there were conflicts in the SGML files).
Nico
--
Attachments:
0001-Add-ALWAYS-DEFERRED-option-for-CONSTRAINTs.patchtext/x-diff; charset=us-asciiDownload
From 80d284ecefa22945d507d2822f1f1a195e2af751 Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@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 | 17 ++++-
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 | 1 +
src/backend/bootstrap/bootparse.y | 2 +
src/backend/catalog/heap.c | 1 +
src/backend/catalog/index.c | 8 +++
src/backend/catalog/information_schema.sql | 8 +++
src/backend/catalog/pg_constraint.c | 2 +
src/backend/catalog/toasting.c | 2 +-
src/backend/commands/indexcmds.c | 2 +-
src/backend/commands/tablecmds.c | 20 +++++-
src/backend/commands/trigger.c | 28 +++++++--
src/backend/commands/typecmds.c | 3 +
src/backend/nodes/copyfuncs.c | 3 +
src/backend/nodes/outfuncs.c | 4 ++
src/backend/parser/gram.y | 99 ++++++++++++++++++++++--------
src/backend/parser/parse_utilcmd.c | 46 +++++++++++++-
src/backend/utils/adt/ruleutils.c | 4 ++
src/bin/pg_dump/pg_dump.c | 31 ++++++++--
src/bin/pg_dump/pg_dump.h | 2 +
src/bin/psql/describe.c | 34 +++++++---
src/bin/psql/tab-complete.c | 4 +-
src/include/catalog/index.h | 2 +
src/include/catalog/pg_constraint.h | 42 +++++++------
src/include/catalog/pg_constraint_fn.h | 1 +
src/include/catalog/pg_trigger.h | 16 ++---
src/include/commands/trigger.h | 1 +
src/include/nodes/parsenodes.h | 6 +-
src/include/utils/reltrigger.h | 1 +
src/test/regress/input/constraints.source | 51 +++++++++++++++
src/test/regress/output/constraints.source | 54 +++++++++++++++-
33 files changed, 418 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ef60a58..1bc35dc 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2222,6 +2222,13 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</row>
<row>
+ <entry><structfield>conalwaysdeferred</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>Is the constraint always deferred?</entry>
+ </row>
+
+ <row>
<entry><structfield>convalidated</structfield></entry>
<entry><type>bool</type></entry>
<entry></entry>
@@ -6968,6 +6975,13 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</row>
<row>
+ <entry><structfield>tgalwaysdeferred</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True if constraint trigger is always deferred</entry>
+ </row>
+
+ <row>
<entry><structfield>tgnargs</structfield></entry>
<entry><type>int2</type></entry>
<entry></entry>
@@ -7029,7 +7043,8 @@ 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
+ <structfield>tgdeferrable</structfield>, <structfield>tginitdeferred</structfield>, and
+ <structfield>tgalwaysdeferred</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 b4b8dab..fe24521 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 ]
@@ -89,7 +89,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
[ 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 ]
</synopsis>
</refsynopsisdiv>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 2db2e9f..cf1ba1c 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>
@@ -961,13 +961,17 @@ FROM ( { <replaceable class="parameter">numeric_literal</replaceable> | <replace
<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 6726e3c..4e55799 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 b0e160a..00e8cf7 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -646,6 +646,7 @@ typedef struct Trigger
Oid tgconstraint;
bool tgdeferrable;
bool tginitdeferred;
+ bool tgalwaysdeferred;
int16 tgnargs;
int16 tgnattr;
int16 *tgattr;
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 2e1fef0..c08ac60 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -310,6 +310,7 @@ Boot_DeclareIndexStmt:
stmt->isconstraint = false;
stmt->deferrable = false;
stmt->initdeferred = false;
+ stmt->alwaysdeferred = false;
stmt->transformed = false;
stmt->concurrent = false;
stmt->if_not_exists = false;
@@ -354,6 +355,7 @@ Boot_DeclareUniqueIndexStmt:
stmt->isconstraint = false;
stmt->deferrable = false;
stmt->initdeferred = false;
+ stmt->alwaysdeferred = false;
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 05e7081..a54c524 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2113,6 +2113,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
CONSTRAINT_CHECK, /* Constraint Type */
false, /* Is Deferrable */
false, /* Is Deferred */
+ false, /* Is Always Deferred */
is_validated,
RelationGetRelid(rel), /* relation */
attNos, /* attrs in the constraint */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index c7b2f03..65e995d 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -684,6 +684,7 @@ UpdateIndexRelation(Oid indexoid,
* isconstraint: index is owned by PRIMARY KEY, UNIQUE, or EXCLUSION constraint
* deferrable: constraint is DEFERRABLE
* initdeferred: constraint is INITIALLY DEFERRED
+ * alwaysdeferred: constraint is ALWAYS DEFERRED
* allow_system_table_mods: allow table to be a system catalog
* skip_build: true to skip the index_build() step for the moment; caller
* must do it later (typically via reindex_index())
@@ -713,6 +714,7 @@ index_create(Relation heapRelation,
bool isconstraint,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool allow_system_table_mods,
bool skip_build,
bool concurrent,
@@ -966,6 +968,7 @@ index_create(Relation heapRelation,
constraintType,
deferrable,
initdeferred,
+ alwaysdeferred,
false, /* already marked primary */
false, /* pg_index entry is OK */
false, /* no old dependencies */
@@ -1009,6 +1012,7 @@ index_create(Relation heapRelation,
/* Non-constraint indexes can't be deferrable */
Assert(!deferrable);
Assert(!initdeferred);
+ Assert(!alwaysdeferred);
}
/* Store dependency on collations */
@@ -1062,6 +1066,7 @@ index_create(Relation heapRelation,
Assert(!isconstraint);
Assert(!deferrable);
Assert(!initdeferred);
+ Assert(!alwaysdeferred);
}
/* Post creation hook for new index */
@@ -1154,6 +1159,7 @@ index_constraint_create(Relation heapRelation,
char constraintType,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool mark_as_primary,
bool update_pgindex,
bool remove_old_dependencies,
@@ -1202,6 +1208,7 @@ index_constraint_create(Relation heapRelation,
constraintType,
deferrable,
initdeferred,
+ alwaysdeferred,
true,
RelationGetRelid(heapRelation),
indexInfo->ii_KeyAttrNumbers,
@@ -1266,6 +1273,7 @@ index_constraint_create(Relation heapRelation,
trigger->isconstraint = true;
trigger->deferrable = true;
trigger->initdeferred = initdeferred;
+ trigger->alwaysdeferred = alwaysdeferred;
trigger->constrrel = NULL;
(void) CreateTrigger(trigger, NULL, RelationGetRelid(heapRelation),
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 236f6be..4f0d193 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -895,6 +895,10 @@ CREATE VIEW domain_constraints AS
AS yes_or_no) AS is_deferrable,
CAST(CASE WHEN condeferred 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
@@ -1779,6 +1783,10 @@ CREATE VIEW table_constraints AS
AS is_deferrable,
CAST(CASE WHEN c.condeferred 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 nc,
pg_namespace nr,
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 1336c46..81c0477 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -51,6 +51,7 @@ CreateConstraintEntry(const char *constraintName,
char constraintType,
bool isDeferrable,
bool isDeferred,
+ bool isAlwaysDeferred,
bool isValidated,
Oid relId,
const int16 *constraintKey,
@@ -166,6 +167,7 @@ CreateConstraintEntry(const char *constraintName,
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_conalwaysdeferred - 1] = BoolGetDatum(isAlwaysDeferred);
values[Anum_pg_constraint_convalidated - 1] = BoolGetDatum(isValidated);
values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(relId);
values[Anum_pg_constraint_contypid - 1] = ObjectIdGetDatum(domainId);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 6f517bb..f90ea32 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -333,7 +333,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
BTREE_AM_OID,
rel->rd_rel->reltablespace,
collationObjectId, classObjectId, coloptions, (Datum) 0,
- true, false, false, false,
+ true, false, false, false, false,
true, false, false, true, false);
heap_close(toast_rel, NoLock);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 3f615b6..d2e966b 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -671,7 +671,7 @@ DefineIndex(Oid relationId,
collationObjectId, classObjectId,
coloptions, reloptions, stmt->primary,
stmt->isconstraint, stmt->deferrable, stmt->initdeferred,
- allowSystemTableMods,
+ stmt->alwaysdeferred, allowSystemTableMods,
skip_build || stmt->concurrent,
stmt->concurrent, !check_rights,
stmt->if_not_exists);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2d4dcd7..4609300 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6874,6 +6874,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
constraintType,
stmt->deferrable,
stmt->initdeferred,
+ stmt->alwaysdeferred,
stmt->primary,
true, /* update pg_index */
true, /* remove old dependencies */
@@ -7443,6 +7444,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
CONSTRAINT_FOREIGN,
fkconstraint->deferrable,
fkconstraint->initdeferred,
+ fkconstraint->alwaysdeferred,
fkconstraint->initially_valid,
RelationGetRelid(rel),
fkattnum,
@@ -7567,7 +7569,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
cmdcon->conname, RelationGetRelationName(rel))));
if (currcon->condeferrable != cmdcon->deferrable ||
- currcon->condeferred != cmdcon->initdeferred)
+ currcon->condeferred != cmdcon->initdeferred ||
+ currcon->conalwaysdeferred != cmdcon->alwaysdeferred)
{
HeapTuple copyTuple;
HeapTuple tgtuple;
@@ -7585,6 +7588,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
copy_con->condeferrable = cmdcon->deferrable;
copy_con->condeferred = cmdcon->initdeferred;
+ copy_con->conalwaysdeferred = cmdcon->alwaysdeferred;
CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
InvokeObjectPostAlterHook(ConstraintRelationId,
@@ -7638,6 +7642,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
copy_tg->tgdeferrable = cmdcon->deferrable;
copy_tg->tginitdeferred = cmdcon->initdeferred;
+ copy_tg->tgalwaysdeferred = cmdcon->alwaysdeferred;
CatalogTupleUpdate(tgrel, ©Tuple->t_self, copyTuple);
InvokeObjectPostAlterHook(TriggerRelationId,
@@ -8305,6 +8310,7 @@ validateForeignKeyConstraint(char *conname,
trig.tgconstraint = constraintOid;
trig.tgdeferrable = FALSE;
trig.tginitdeferred = FALSE;
+ trig.tgalwaysdeferred = FALSE;
/* we needn't fill in remaining fields */
/*
@@ -8394,6 +8400,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
fk_trigger->isconstraint = true;
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
+ fk_trigger->alwaysdeferred = fkconstraint->alwaysdeferred;
fk_trigger->constrrel = NULL;
fk_trigger->args = NIL;
@@ -8442,26 +8449,31 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
case FKCONSTR_ACTION_NOACTION:
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
+ fk_trigger->alwaysdeferred = fkconstraint->alwaysdeferred;
fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
break;
case FKCONSTR_ACTION_RESTRICT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
break;
case FKCONSTR_ACTION_CASCADE:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
break;
case FKCONSTR_ACTION_SETNULL:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
break;
case FKCONSTR_ACTION_SETDEFAULT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
break;
default:
@@ -8497,26 +8509,31 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
case FKCONSTR_ACTION_NOACTION:
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
+ fk_trigger->alwaysdeferred = fkconstraint->alwaysdeferred;
fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
break;
case FKCONSTR_ACTION_RESTRICT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
break;
case FKCONSTR_ACTION_CASCADE:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
break;
case FKCONSTR_ACTION_SETNULL:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
break;
case FKCONSTR_ACTION_SETDEFAULT:
fk_trigger->deferrable = false;
fk_trigger->initdeferred = false;
+ fk_trigger->alwaysdeferred = false;
fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
break;
default:
@@ -11261,6 +11278,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
if (acon->condeferrable != bcon->condeferrable ||
acon->condeferred != bcon->condeferred ||
+ acon->conalwaysdeferred != bcon->conalwaysdeferred ||
strcmp(decompile_conbin(a, tupleDesc),
decompile_conbin(b, tupleDesc)) != 0)
return false;
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 8d0345c..61bbbdc 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -650,6 +650,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
CONSTRAINT_TRIGGER,
stmt->deferrable,
stmt->initdeferred,
+ stmt->alwaysdeferred,
true,
RelationGetRelid(rel),
NULL, /* no conkey */
@@ -748,6 +749,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
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_tgalwaysdeferred - 1] = BoolGetDatum(stmt->alwaysdeferred);
if (stmt->args)
{
@@ -1245,6 +1247,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
}
fkcon->deferrable = stmt->deferrable;
fkcon->initdeferred = stmt->initdeferred;
+ fkcon->alwaysdeferred = stmt->alwaysdeferred;
fkcon->skip_validation = false;
fkcon->initially_valid = true;
@@ -1742,6 +1745,7 @@ RelationBuildTriggers(Relation relation)
build->tgconstraint = pg_trigger->tgconstraint;
build->tgdeferrable = pg_trigger->tgdeferrable;
build->tginitdeferred = pg_trigger->tginitdeferred;
+ build->tgalwaysdeferred = pg_trigger->tgalwaysdeferred;
build->tgnargs = pg_trigger->tgnargs;
/* tgattr is first var-width field, so OK to access directly */
build->tgnattr = pg_trigger->tgattr.dim1;
@@ -2050,6 +2054,8 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
return false;
if (trig1->tginitdeferred != trig2->tginitdeferred)
return false;
+ if (trig1->tgalwaysdeferred != trig2->tgalwaysdeferred)
+ return false;
if (trig1->tgnargs != trig2->tgnargs)
return false;
if (trig1->tgnattr != trig2->tgnattr)
@@ -2143,7 +2149,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;
@@ -3392,6 +3399,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;
@@ -3671,6 +3679,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
@@ -5177,14 +5187,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->condeferrable)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("constraint \"%s\" is not deferrable",
constraint->relname)));
+ else if (!stmt->deferred && con->conalwaysdeferred)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("constraint \"%s\" is always deferred",
+ constraint->relname)));
+ else if (con->condeferrable && !con->conalwaysdeferred)
+ conoidlist = lappend_oid(conoidlist,
+ HeapTupleGetOid(tup));
found = true;
}
@@ -5681,7 +5696,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
(event & TRIGGER_EVENT_OPMASK) |
(row_trigger ? TRIGGER_EVENT_ROW : 0) |
(trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) |
- (trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0);
+ (trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0) |
+ (trigger->tgalwaysdeferred ? 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 c1b87e0..85b73ba 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),
@@ -2603,6 +2604,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),
@@ -3150,6 +3152,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
CONSTRAINT_CHECK, /* Constraint Type */
false, /* Is Deferrable */
false, /* Is Deferred */
+ false, /* Is Always Deferred */
!constr->skip_validation, /* Is Validated */
InvalidOid, /* not a relation constraint */
NULL,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c1a83ca..bbd1705 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2831,6 +2831,7 @@ _copyConstraint(const Constraint *from)
COPY_STRING_FIELD(conname);
COPY_SCALAR_FIELD(deferrable);
COPY_SCALAR_FIELD(initdeferred);
+ COPY_SCALAR_FIELD(alwaysdeferred);
COPY_LOCATION_FIELD(location);
COPY_SCALAR_FIELD(is_no_inherit);
COPY_NODE_FIELD(raw_expr);
@@ -3379,6 +3380,7 @@ _copyIndexStmt(const IndexStmt *from)
COPY_SCALAR_FIELD(isconstraint);
COPY_SCALAR_FIELD(deferrable);
COPY_SCALAR_FIELD(initdeferred);
+ COPY_SCALAR_FIELD(alwaysdeferred);
COPY_SCALAR_FIELD(transformed);
COPY_SCALAR_FIELD(concurrent);
COPY_SCALAR_FIELD(if_not_exists);
@@ -4148,6 +4150,7 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
COPY_NODE_FIELD(transitionRels);
COPY_SCALAR_FIELD(deferrable);
COPY_SCALAR_FIELD(initdeferred);
+ COPY_SCALAR_FIELD(alwaysdeferred);
COPY_NODE_FIELD(constrrel);
return newnode;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 43d6206..4399ef3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3513,6 +3513,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 4c83a63..dd5fe68 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);
+ bool *deferrable, bool *initdeferred, bool *alwaysdeferred,
+ bool *not_valid, bool *no_inherit, core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -727,6 +728,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* RANGE, ROWS to support opt_existing_window_name; and for RANGE, ROWS
* 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.
*
@@ -745,7 +747,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 PRECEDING FOLLOWING CUBE ROLLUP
+%nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP ALWAYS
%left Op OPERATOR /* multi-character ops and user-defined operators */
%left '+' '-'
%left '*' '/' '%'
@@ -2234,7 +2236,9 @@ alter_table_cmd:
processCASbits($4, @4, "ALTER CONSTRAINT statement",
&c->deferrable,
&c->initdeferred,
- NULL, NULL, yyscanner);
+ &c->alwaysdeferred,
+ NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
/* ALTER TABLE <name> VALIDATE CONSTRAINT ... */
@@ -3500,6 +3504,13 @@ ConstraintAttr:
n->location = @1;
$$ = (Node *)n;
}
+ | ALWAYS DEFERRED
+ {
+ Constraint *n = makeNode(Constraint);
+ n->contype = CONSTR_ATTR_ALWAYS_DEFERRED;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
;
@@ -3554,8 +3565,10 @@ ConstraintElem:
n->raw_expr = $3;
n->cooked_expr = NULL;
processCASbits($5, @5, "CHECK",
- NULL, NULL, &n->skip_validation,
- &n->is_no_inherit, yyscanner);
+ NULL, NULL, NULL,
+ &n->skip_validation,
+ &n->is_no_inherit,
+ yyscanner);
n->initially_valid = !n->skip_validation;
$$ = (Node *)n;
}
@@ -3570,8 +3583,9 @@ ConstraintElem:
n->indexname = NULL;
n->indexspace = $6;
processCASbits($7, @7, "UNIQUE",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| UNIQUE ExistingIndex ConstraintAttributeSpec
@@ -3584,8 +3598,9 @@ ConstraintElem:
n->indexname = $2;
n->indexspace = NULL;
processCASbits($3, @3, "UNIQUE",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace
@@ -3599,8 +3614,9 @@ ConstraintElem:
n->indexname = NULL;
n->indexspace = $7;
processCASbits($8, @8, "PRIMARY KEY",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| PRIMARY KEY ExistingIndex ConstraintAttributeSpec
@@ -3613,8 +3629,9 @@ ConstraintElem:
n->indexname = $3;
n->indexspace = NULL;
processCASbits($4, @4, "PRIMARY KEY",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| EXCLUDE access_method_clause '(' ExclusionConstraintList ')'
@@ -3631,8 +3648,9 @@ ConstraintElem:
n->indexspace = $7;
n->where_clause = $8;
processCASbits($9, @9, "EXCLUDE",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
$$ = (Node *)n;
}
| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
@@ -3649,6 +3667,7 @@ ConstraintElem:
n->fk_del_action = (char) ($10 & 0xFF);
processCASbits($11, @11, "FOREIGN KEY",
&n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred,
&n->skip_validation, NULL,
yyscanner);
n->initially_valid = !n->skip_validation;
@@ -5210,6 +5229,7 @@ CreateTrigStmt:
n->isconstraint = FALSE;
n->deferrable = FALSE;
n->initdeferred = FALSE;
+ n->alwaysdeferred = FALSE;
n->constrrel = NULL;
$$ = (Node *)n;
}
@@ -5231,8 +5251,9 @@ CreateTrigStmt:
n->transitionRels = NIL;
n->isconstraint = TRUE;
processCASbits($10, @10, "TRIGGER",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
n->constrrel = $9;
$$ = (Node *)n;
}
@@ -5388,17 +5409,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;
}
@@ -5409,6 +5437,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; }
;
@@ -5499,8 +5528,9 @@ CreateAssertStmt:
n->args = list_make1($6);
n->isconstraint = TRUE;
processCASbits($8, @8, "ASSERTION",
- &n->deferrable, &n->initdeferred, NULL,
- NULL, yyscanner);
+ &n->deferrable, &n->initdeferred,
+ &n->alwaysdeferred, NULL, NULL,
+ yyscanner);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -7177,6 +7207,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
n->isconstraint = false;
n->deferrable = false;
n->initdeferred = false;
+ n->alwaysdeferred = false;
n->transformed = false;
n->if_not_exists = false;
$$ = (Node *)n;
@@ -7203,6 +7234,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
n->isconstraint = false;
n->deferrable = false;
n->initdeferred = false;
+ n->alwaysdeferred = false;
n->transformed = false;
n->if_not_exists = true;
$$ = (Node *)n;
@@ -15791,18 +15823,20 @@ 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)
+ bool *deferrable, bool *initdeferred, bool *alwaysdeferred,
+ bool *not_valid, bool *no_inherit, core_yyscan_t yyscanner)
{
/* defaults */
if (deferrable)
*deferrable = false;
if (initdeferred)
*initdeferred = false;
+ if (alwaysdeferred)
+ *alwaysdeferred = false;
if (not_valid)
*not_valid = false;
- if (cas_bits & (CAS_DEFERRABLE | CAS_INITIALLY_DEFERRED))
+ if (cas_bits & (CAS_DEFERRABLE | CAS_INITIALLY_DEFERRED | CAS_ALWAYS_DEFERRED))
{
if (deferrable)
*deferrable = true;
@@ -15815,7 +15849,7 @@ processCASbits(int cas_bits, int location, const char *constrType,
parser_errposition(location)));
}
- if (cas_bits & CAS_INITIALLY_DEFERRED)
+ if (cas_bits & (CAS_INITIALLY_DEFERRED | CAS_ALWAYS_DEFERRED))
{
if (initdeferred)
*initdeferred = true;
@@ -15828,6 +15862,19 @@ processCASbits(int cas_bits, int location, const char *constrType,
parser_errposition(location)));
}
+ if (cas_bits & CAS_ALWAYS_DEFERRED)
+ {
+ if (alwaysdeferred)
+ *alwaysdeferred = true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is CHECK, UNIQUE, or similar */
+ errmsg("%s constraints cannot be marked DEFERRED",
+ constrType),
+ parser_errposition(location)));
+ }
+
if (cas_bits & CAS_NOT_VALID)
{
if (not_valid)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 27e568f..54d1c3f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -750,6 +750,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 */
@@ -878,6 +879,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",
@@ -1359,6 +1361,7 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
index->isconstraint = true;
index->deferrable = conrec->condeferrable;
index->initdeferred = conrec->condeferred;
+ index->alwaysdeferred = conrec->conalwaysdeferred;
/* If it's an exclusion constraint, we need the operator names */
if (idxrec->indisexclusion)
@@ -1715,7 +1718,8 @@ transformIndexConstraints(CreateStmtContext *cxt)
equal(index->excludeOpNames, priorindex->excludeOpNames) &&
strcmp(index->accessMethod, priorindex->accessMethod) == 0 &&
index->deferrable == priorindex->deferrable &&
- index->initdeferred == priorindex->initdeferred)
+ index->initdeferred == priorindex->initdeferred &&
+ index->alwaysdeferred == priorindex->alwaysdeferred)
{
priorindex->unique |= index->unique;
@@ -1770,6 +1774,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
index->isconstraint = true;
index->deferrable = constraint->deferrable;
index->initdeferred = constraint->initdeferred;
+ index->alwaysdeferred = constraint->alwaysdeferred;
if (constraint->conname != NULL)
index->idxname = pstrdup(constraint->conname);
@@ -2983,6 +2988,9 @@ 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;
@@ -3008,12 +3016,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;
+ saw_deferrable = true;
lastprimarycon->deferrable = true;
break;
@@ -3023,13 +3032,20 @@ 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)));
+ 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_deferrability = true;
+ saw_notdeferrable = true;
lastprimarycon->deferrable = false;
+ lastprimarycon->alwaysdeferred = false;
if (saw_initially &&
lastprimarycon->initdeferred)
ereport(ERROR,
@@ -3038,6 +3054,30 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
parser_errposition(cxt->pstate, con->location)));
break;
+ case CONSTR_ATTR_ALWAYS_DEFERRED:
+ if (!SUPPORTS_ATTRS(lastprimarycon))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("misplaced ALWAYS DEFERRED clause"),
+ parser_errposition(cxt->pstate, con->location)));
+ if (saw_alwaysdeferred || saw_notdeferrable)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("multiple ALWAYS DEFERRED/NOT DEFERRABLE clauses not allowed"),
+ parser_errposition(cxt->pstate, con->location)));
+ saw_initially = true;
+ saw_deferrability = true;
+ saw_alwaysdeferred = true;
+ lastprimarycon->deferrable = true;
+ lastprimarycon->alwaysdeferred = true;
+ if (saw_initially &&
+ !lastprimarycon->initdeferred)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("constraint declared INITIALLY IMMEDIATE must not be ALWAYS DEFERRED"),
+ parser_errposition(cxt->pstate, con->location)));
+ break;
+
case CONSTR_ATTR_DEFERRED:
if (!SUPPORTS_ATTRS(lastprimarycon))
ereport(ERROR,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 84759b6..61fd899 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -936,6 +936,8 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
appendStringInfoString(&buf, "DEFERRED ");
else
appendStringInfoString(&buf, "IMMEDIATE ");
+ if (trigrec->tgalwaysdeferred)
+ appendStringInfoString(&buf, "ALWAYS DEFERRED");
}
value = fastgetattr(ht_trig, Anum_pg_trigger_tgoldtable,
@@ -2144,6 +2146,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
appendStringInfoString(&buf, " DEFERRABLE");
if (conForm->condeferred)
appendStringInfoString(&buf, " INITIALLY DEFERRED");
+ if (conForm->conalwaysdeferred)
+ 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 8733426..0a6c158 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6510,6 +6510,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_conname,
i_condeferrable,
i_condeferred,
+ i_conalwaysdeferred,
i_contableoid,
i_conoid,
i_condef,
@@ -6567,7 +6568,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.condeferrable, c.condeferred, c.conalwaysdeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
@@ -6598,7 +6599,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.condeferrable, c.condeferred, c.conalwaysdeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
@@ -6625,7 +6626,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.condeferrable, c.condeferred, c.conalwaysdeferred "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"null AS condef, "
@@ -6655,7 +6656,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.condeferrable, c.condeferred, c.conalwaysdeferred "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"null AS condef, "
@@ -6692,6 +6693,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_conname = PQfnumber(res, "conname");
i_condeferrable = PQfnumber(res, "condeferrable");
i_condeferred = PQfnumber(res, "condeferred");
+ i_conalwaysdeferred = PQfnumber(res, "conalwaysdeferred");
i_contableoid = PQfnumber(res, "contableoid");
i_conoid = PQfnumber(res, "conoid");
i_condef = PQfnumber(res, "condef");
@@ -6747,6 +6749,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
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].conalwaysdeferred = *(PQgetvalue(res, j, i_conalwaysdeferred)) == 't';
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
@@ -6944,6 +6947,7 @@ getConstraints(Archive *fout, TableInfo tblinfo[], int numTables)
constrinfo[j].conindex = 0;
constrinfo[j].condeferrable = false;
constrinfo[j].condeferred = false;
+ constrinfo[j].conalwaysdeferred = false;
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
}
@@ -7030,6 +7034,7 @@ getDomainConstraints(Archive *fout, TypeInfo *tyinfo)
constrinfo[i].conindex = 0;
constrinfo[i].condeferrable = false;
constrinfo[i].condeferred = false;
+ constrinfo[i].conalwaysdeferred = false;
constrinfo[i].conislocal = true;
constrinfo[i].separate = !validated;
@@ -7194,6 +7199,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
i_tgenabled,
i_tgdeferrable,
i_tginitdeferred,
+ i_tgalwaysdeferred,
i_tgdef;
int ntups;
@@ -7292,6 +7298,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
i_tgenabled = PQfnumber(res, "tgenabled");
i_tgdeferrable = PQfnumber(res, "tgdeferrable");
i_tginitdeferred = PQfnumber(res, "tginitdeferred");
+ i_tgalwaysdeferred = PQfnumber(res, "tgalwaysdeferred");
i_tgdef = PQfnumber(res, "tgdef");
tginfo = (TriggerInfo *) pg_malloc(ntups * sizeof(TriggerInfo));
@@ -7321,6 +7328,7 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
tginfo[j].tgisconstraint = false;
tginfo[j].tgdeferrable = false;
tginfo[j].tginitdeferred = false;
+ tginfo[j].tgalwaysdeferred = false;
tginfo[j].tgconstrname = NULL;
tginfo[j].tgconstrrelid = InvalidOid;
tginfo[j].tgconstrrelname = NULL;
@@ -7336,6 +7344,8 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
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';
+ if (i_tgalwaysdeferred != -1)
+ tginfo[j].tgalwaysdeferred = *(PQgetvalue(res, j, i_tgalwaysdeferred)) == 't';
if (tginfo[j].tgisconstraint)
{
@@ -8284,6 +8294,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
constrs[j].conindex = 0;
constrs[j].condeferrable = false;
constrs[j].condeferred = false;
+ constrs[j].conalwaysdeferred = false;
constrs[j].conislocal = (PQgetvalue(res, j, 4)[0] == 't');
/*
@@ -16287,6 +16298,11 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
appendPQExpBufferStr(q, " INITIALLY DEFERRED");
}
+ if (coninfo->conalwaysdeferred)
+ {
+ appendPQExpBufferStr(q, " ALWAYS DEFERRED");
+ }
+
appendPQExpBufferStr(q, ";\n");
}
@@ -16949,9 +16965,12 @@ dumpTrigger(Archive *fout, TriggerInfo *tginfo)
appendPQExpBufferStr(query, "NOT ");
appendPQExpBufferStr(query, "DEFERRABLE INITIALLY ");
if (tginfo->tginitdeferred)
- appendPQExpBufferStr(query, "DEFERRED\n");
+ appendPQExpBufferStr(query, "DEFERRED");
else
- appendPQExpBufferStr(query, "IMMEDIATE\n");
+ appendPQExpBufferStr(query, "IMMEDIATE");
+ if (tginfo->tgalwaysdeferred)
+ 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 e7593e6..93b098a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -399,6 +399,7 @@ typedef struct _triggerInfo
char tgenabled;
bool tgdeferrable;
bool tginitdeferred;
+ bool tgalwaysdeferred;
char *tgdef;
} TriggerInfo;
@@ -432,6 +433,7 @@ typedef struct _constraintInfo
DumpId conindex; /* identifies associated index if any */
bool condeferrable; /* TRUE if constraint is DEFERRABLE */
bool condeferred; /* TRUE if constraint is INITIALLY DEFERRED */
+ bool conalwaysdeferred; /* TRUE if constraint is 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 0688571..c387d19 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2043,10 +2043,17 @@ describeOneTableDetails(const char *schemaname,
"WHERE conrelid = i.indrelid AND "
"conindid = i.indexrelid AND "
"contype IN ('p','u','x') AND "
- "condeferred) AS condeferred,\n");
+ "condeferred) AS condeferred,\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 "
+ "conalwaysdeferred) AS conalwaysdeferred,\n"
+ );
else
appendPQExpBufferStr(&buf,
- " false AS condeferrable, false AS condeferred,\n");
+ " false AS condeferrable, false AS condeferred, false AS conalwaysdeferred\n");
if (pset.sversion >= 90400)
appendPQExpBuffer(&buf, "i.indisreplident,\n");
@@ -2076,10 +2083,11 @@ describeOneTableDetails(const char *schemaname,
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 *alwaysdeferred = PQgetvalue(result, 0, 6);
+ char *indisreplident = PQgetvalue(result, 0, 7);
+ char *indamname = PQgetvalue(result, 0, 8);
+ char *indtable = PQgetvalue(result, 0, 9);
+ char *indpred = PQgetvalue(result, 0, 10);
if (strcmp(indisprimary, "t") == 0)
printfPQExpBuffer(&tmpbuf, _("primary key, "));
@@ -2108,6 +2116,9 @@ describeOneTableDetails(const char *schemaname,
if (strcmp(deferred, "t") == 0)
appendPQExpBufferStr(&tmpbuf, _(", initially deferred"));
+ if (strcmp(alwaysdeferred, "t") == 0)
+ appendPQExpBufferStr(&tmpbuf, _(", always deferred"));
+
if (strcmp(indisreplident, "t") == 0)
appendPQExpBuffer(&tmpbuf, _(", replica identity"));
@@ -2140,11 +2151,11 @@ describeOneTableDetails(const char *schemaname,
if (pset.sversion >= 90000)
appendPQExpBufferStr(&buf,
"pg_catalog.pg_get_constraintdef(con.oid, true), "
- "contype, condeferrable, condeferred");
+ "contype, condeferrable, condeferred, conalwaysdeferred");
else
appendPQExpBufferStr(&buf,
"null AS constraintdef, null AS contype, "
- "false AS condeferrable, false AS condeferred");
+ "false AS condeferrable, false AS condeferred, false as conalwaysdeferred");
if (pset.sversion >= 90400)
appendPQExpBufferStr(&buf, ", i.indisreplident");
else
@@ -2210,6 +2221,9 @@ describeOneTableDetails(const char *schemaname,
if (strcmp(PQgetvalue(result, i, 9), "t") == 0)
appendPQExpBufferStr(&buf, " INITIALLY DEFERRED");
+
+ if (strcmp(PQgetvalue(result, i, 10), "t") == 0)
+ appendPQExpBufferStr(&buf, " ALWAYS DEFERRED");
}
/* Add these for all cases */
@@ -2219,7 +2233,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, 11), "t") == 0)
appendPQExpBuffer(&buf, " REPLICA IDENTITY");
printTableAddFooter(&cont, buf.data);
@@ -2227,7 +2241,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, 12)),
false);
}
}
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a09c49d..47cb478 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2549,10 +2549,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 1d4ec09..0e6dea4 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -58,6 +58,7 @@ extern Oid index_create(Relation heapRelation,
bool isconstraint,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool allow_system_table_mods,
bool skip_build,
bool concurrent,
@@ -71,6 +72,7 @@ extern ObjectAddress index_constraint_create(Relation heapRelation,
char constraintType,
bool deferrable,
bool initdeferred,
+ bool alwaysdeferred,
bool mark_as_primary,
bool update_pgindex,
bool remove_old_dependencies,
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index ec035d8..f423901 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -44,6 +44,7 @@ CATALOG(pg_constraint,2606)
char contype; /* constraint type; see codes below */
bool condeferrable; /* deferrable constraint? */
bool condeferred; /* deferred by default? */
+ bool conalwaysdeferred; /* always deferred? */
bool convalidated; /* constraint has been validated? */
/*
@@ -150,31 +151,32 @@ typedef FormData_pg_constraint *Form_pg_constraint;
* compiler constants for pg_constraint
* ----------------
*/
-#define Natts_pg_constraint 24
+#define Natts_pg_constraint 25
#define Anum_pg_constraint_conname 1
#define Anum_pg_constraint_connamespace 2
#define Anum_pg_constraint_contype 3
#define Anum_pg_constraint_condeferrable 4
#define Anum_pg_constraint_condeferred 5
-#define Anum_pg_constraint_convalidated 6
-#define Anum_pg_constraint_conrelid 7
-#define Anum_pg_constraint_contypid 8
-#define Anum_pg_constraint_conindid 9
-#define Anum_pg_constraint_confrelid 10
-#define Anum_pg_constraint_confupdtype 11
-#define Anum_pg_constraint_confdeltype 12
-#define Anum_pg_constraint_confmatchtype 13
-#define Anum_pg_constraint_conislocal 14
-#define Anum_pg_constraint_coninhcount 15
-#define Anum_pg_constraint_connoinherit 16
-#define Anum_pg_constraint_conkey 17
-#define Anum_pg_constraint_confkey 18
-#define Anum_pg_constraint_conpfeqop 19
-#define Anum_pg_constraint_conppeqop 20
-#define Anum_pg_constraint_conffeqop 21
-#define Anum_pg_constraint_conexclop 22
-#define Anum_pg_constraint_conbin 23
-#define Anum_pg_constraint_consrc 24
+#define Anum_pg_constraint_conalwaysdeferred 6
+#define Anum_pg_constraint_convalidated 7
+#define Anum_pg_constraint_conrelid 8
+#define Anum_pg_constraint_contypid 9
+#define Anum_pg_constraint_conindid 10
+#define Anum_pg_constraint_confrelid 11
+#define Anum_pg_constraint_confupdtype 12
+#define Anum_pg_constraint_confdeltype 13
+#define Anum_pg_constraint_confmatchtype 14
+#define Anum_pg_constraint_conislocal 15
+#define Anum_pg_constraint_coninhcount 16
+#define Anum_pg_constraint_connoinherit 17
+#define Anum_pg_constraint_conkey 18
+#define Anum_pg_constraint_confkey 19
+#define Anum_pg_constraint_conpfeqop 20
+#define Anum_pg_constraint_conppeqop 21
+#define Anum_pg_constraint_conffeqop 22
+#define Anum_pg_constraint_conexclop 23
+#define Anum_pg_constraint_conbin 24
+#define Anum_pg_constraint_consrc 25
/* ----------------
* initial contents of pg_constraint
diff --git a/src/include/catalog/pg_constraint_fn.h b/src/include/catalog/pg_constraint_fn.h
index a4c4689..c9daf2c 100644
--- a/src/include/catalog/pg_constraint_fn.h
+++ b/src/include/catalog/pg_constraint_fn.h
@@ -32,6 +32,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
char constraintType,
bool isDeferrable,
bool isDeferred,
+ bool isAlwaysDeferred,
bool isValidated,
Oid relId,
const int16 *constraintKey,
diff --git a/src/include/catalog/pg_trigger.h b/src/include/catalog/pg_trigger.h
index f413caf..acb9d17 100644
--- a/src/include/catalog/pg_trigger.h
+++ b/src/include/catalog/pg_trigger.h
@@ -48,6 +48,7 @@ CATALOG(pg_trigger,2620)
Oid tgconstraint; /* associated pg_constraint entry, if any */
bool tgdeferrable; /* constraint trigger is deferrable */
bool tginitdeferred; /* constraint trigger is deferred initially */
+ bool tgalwaysdeferred; /* constraint trigger is always deferred */
int16 tgnargs; /* # of extra arguments in tgargs */
/*
@@ -75,7 +76,7 @@ typedef FormData_pg_trigger *Form_pg_trigger;
* compiler constants for pg_trigger
* ----------------
*/
-#define Natts_pg_trigger 17
+#define Natts_pg_trigger 18
#define Anum_pg_trigger_tgrelid 1
#define Anum_pg_trigger_tgname 2
#define Anum_pg_trigger_tgfoid 3
@@ -87,12 +88,13 @@ typedef FormData_pg_trigger *Form_pg_trigger;
#define Anum_pg_trigger_tgconstraint 9
#define Anum_pg_trigger_tgdeferrable 10
#define Anum_pg_trigger_tginitdeferred 11
-#define Anum_pg_trigger_tgnargs 12
-#define Anum_pg_trigger_tgattr 13
-#define Anum_pg_trigger_tgargs 14
-#define Anum_pg_trigger_tgqual 15
-#define Anum_pg_trigger_tgoldtable 16
-#define Anum_pg_trigger_tgnewtable 17
+#define Anum_pg_trigger_tgalwaysdeferred 12
+#define Anum_pg_trigger_tgnargs 13
+#define Anum_pg_trigger_tgattr 14
+#define Anum_pg_trigger_tgargs 15
+#define Anum_pg_trigger_tgqual 16
+#define Anum_pg_trigger_tgoldtable 17
+#define Anum_pg_trigger_tgnewtable 18
/* Bits within tgtype */
#define TRIGGER_TYPE_ROW (1 << 0)
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index adbcfa1..082affa 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 732e5d6..94bd5bf 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2060,7 +2060,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 */
@@ -2084,6 +2085,7 @@ typedef struct Constraint
char *conname; /* Constraint name, or NULL if unnamed */
bool deferrable; /* DEFERRABLE? */
bool initdeferred; /* INITIALLY DEFERRED? */
+ bool alwaysdeferred; /* ALWAYS DEFERRED? */
int location; /* token location, or -1 if unknown */
/* Fields used for constraints with expressions (CHECK and DEFAULT): */
@@ -2368,6 +2370,7 @@ typedef struct CreateTrigStmt
/* The remaining fields are only used for constraint triggers */
bool deferrable; /* [NOT] DEFERRABLE */
bool initdeferred; /* INITIALLY {DEFERRED|IMMEDIATE} */
+ bool alwaysdeferred; /* ALWAYS DEFERRED? */
RangeVar *constrrel; /* opposite relation, if RI trigger */
} CreateTrigStmt;
@@ -2712,6 +2715,7 @@ typedef struct IndexStmt
bool isconstraint; /* is it for a pkey/unique constraint? */
bool deferrable; /* is the constraint DEFERRABLE? */
bool initdeferred; /* is the constraint INITIALLY DEFERRED? */
+ bool alwaysdeferred; /* ALWAYS DEFERRED? */
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 2169b03..06e1f21 100644
--- a/src/include/utils/reltrigger.h
+++ b/src/include/utils/reltrigger.h
@@ -34,6 +34,7 @@ typedef struct Trigger
Oid tgconstraint;
bool tgdeferrable;
bool tginitdeferred;
+ bool tgalwaysdeferred;
int16 tgnargs;
int16 tgnattr;
int16 *tgattr;
diff --git a/src/test/regress/input/constraints.source b/src/test/regress/input/constraints.source
index dbab8f1..0885a39 100644
--- a/src/test/regress/input/constraints.source
+++ b/src/test/regress/input/constraints.source
@@ -421,8 +421,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 bb75165..d55803b 100644
--- a/src/test/regress/output/constraints.source
+++ b/src/test/regress/output/constraints.source
@@ -344,7 +344,7 @@ SELECT * FROM INSERT_TBL;
CREATE TABLE COPY_TBL (x INT, y TEXT, z INT,
CONSTRAINT COPY_CON
CHECK (x > 3 AND y <> 'check failed' AND x < 7 ));
-COPY COPY_TBL FROM '@abs_srcdir@/data/constro.data';
+COPY COPY_TBL FROM '/home/nico/ws/postgres/src/test/regress/data/constro.data';
SELECT '' AS two, * FROM COPY_TBL;
two | x | y | z
-----+---+---------------+---
@@ -352,7 +352,7 @@ SELECT '' AS two, * FROM COPY_TBL;
| 6 | OK | 4
(2 rows)
-COPY COPY_TBL FROM '@abs_srcdir@/data/constrf.data';
+COPY COPY_TBL FROM '/home/nico/ws/postgres/src/test/regress/data/constrf.data';
ERROR: new row for relation "copy_tbl" violates check constraint "copy_con"
DETAIL: Failing row contains (7, check failed, 6).
CONTEXT: COPY copy_tbl, line 2: "7 check failed 6"
@@ -592,7 +592,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
--
--
2.7.4
I haven't really thought about this feature too hard; I just want to
give you a couple of code comments.
I think the catalog structure, and relatedly also the parser structures,
could be made more compact. We currently have condeferrable and
condeferred to represent three valid states (NOT DEFERRABLE, DEFERRABLE
INITIALLY IMMEDIATE, DEFERRABLE INITIALLY DEFERRED). You are adding
conalwaysdeferred, but you are adding only additional state (ALWAYS
DEFERRED). So we end up with three bool fields to represent four
states. I think this should all be rolled into one char field with four
states.
In psql and pg_dump, if you are query new catalog fields, you need to
have a version check to have a different query for >=PG11. (This would
likely apply whether you adopt my suggestion above or not.)
Maybe a test case in pg_dump would be useful.
Other than that, this looks like a pretty complete patch.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Nov 02, 2017 at 04:20:19PM -0400, Peter Eisentraut wrote:
I haven't really thought about this feature too hard; I just want to
give you a couple of code comments.
Thanks!
I think the catalog structure, and relatedly also the parser structures,
could be made more compact. We currently have condeferrable and
condeferred to represent three valid states (NOT DEFERRABLE, DEFERRABLE
INITIALLY IMMEDIATE, DEFERRABLE INITIALLY DEFERRED). You are adding
conalwaysdeferred, but you are adding only additional state (ALWAYS
DEFERRED). So we end up with three bool fields to represent four
states. I think this should all be rolled into one char field with four
states.
I thought about this. I couldn't see a way to make the two existing
boolean columns have a combination meaning "ALWAYS DEFERRED" that might
not break something else.
Since (condeferred AND NOT condeferrable) is an impossible combination
today, I could use it to mean ALWAYS DEFERRED. I'm not sure how safe
that would be. And it does seem like a weird way to express ALWAYS
DEFERRED, though it would work.
Replacing condeferred and condeferrable with a char columns also
occurred to me, and though I assume backwards-incompatible changes to
pg_catalog tables are fair game, I assumed everyone would prefer
avoiding such changes where possible.
Also, a backwards-incompatible change to the table would significantly
enlarge the patch, as more version checks would be needed, particularly
regarding upgrades (which are otherwise trivial).
I felt adding a new column was probably safest. I'll make a backwards-
incompatible change if requested, naturally, but I guess I'd want to
get wider consensus on that, as I fear others may not agree. That fear
may just be due to my ignorance of the community's preference as to
pg_catalog backwards-compatibility vs. cleanliness.
Hmmm, must I do anything special about _downgrades_? Does PostgreSQL
support downgrades?
In psql and pg_dump, if you are query new catalog fields, you need to
have a version check to have a different query for >=PG11. (This would
likely apply whether you adopt my suggestion above or not.)
Ah, yes, of course. I will add such a check.
Maybe a test case in pg_dump would be useful.
Will do.
Other than that, this looks like a pretty complete patch.
Thanks for the review! It's a small-ish patch, and my very first for
PG. It was fun writing it. I greatly appreciate that PG source is easy
to read.
Nico
--
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 11/2/17 16:54, Nico Williams wrote:
Replacing condeferred and condeferrable with a char columns also
occurred to me, and though I assume backwards-incompatible changes to
pg_catalog tables are fair game, I assumed everyone would prefer
avoiding such changes where possible.
I don't think there is an overriding mandate to avoid such catalog
changes. Consider old clients that don't know about your new column.
They might look at the catalog entries and derive information about a
constraint, not being aware that there is additional information in
another column that overrides that. So in such cases it's arguably
better to make a break.
(In any case, it might be worth waiting for a review of the rest of the
patch before taking on a significant rewrite of the catalog structures.)
Hmmm, must I do anything special about _downgrades_? Does PostgreSQL
support downgrades?
no
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Nov 03, 2017 at 01:41:45PM -0400, Peter Eisentraut wrote:
On 11/2/17 16:54, Nico Williams wrote:
Replacing condeferred and condeferrable with a char columns also
occurred to me, and though I assume backwards-incompatible changes to
pg_catalog tables are fair game, I assumed everyone would prefer
avoiding such changes where possible.I don't think there is an overriding mandate to avoid such catalog
changes. Consider old clients that don't know about your new column.
They might look at the catalog entries and derive information about a
constraint, not being aware that there is additional information in
another column that overrides that. So in such cases it's arguably
better to make a break.
Makes sense.
(In any case, it might be worth waiting for a review of the rest of the
patch before taking on a significant rewrite of the catalog structures.)
I'll wait then :)
When you're done with that I'll make this change (replacing those three
bool columns with a single char column).
Hmmm, must I do anything special about _downgrades_? Does PostgreSQL
support downgrades?no
Oh good. Thanks for clarifying that.
Nico
--
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Oct 20, 2017 at 9:05 AM, Nico Williams <nico@cryptonector.com> wrote:
Rebased (there were conflicts in the SGML files).
Hi Nico
FYI that version has some stray absolute paths in constraints.source:
-COPY COPY_TBL FROM '@abs_srcdir@/data/constro.data';
+COPY COPY_TBL FROM '/home/nico/ws/postgres/src/test/regress/data/constro.data';
-COPY COPY_TBL FROM '@abs_srcdir@/data/constrf.data';
+COPY COPY_TBL FROM '/home/nico/ws/postgres/src/test/regress/data/constrf.data';
--
Thomas Munro
http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Nov 06, 2017 at 05:50:21PM +1300, Thomas Munro wrote:
On Fri, Oct 20, 2017 at 9:05 AM, Nico Williams <nico@cryptonector.com> wrote:
Rebased (there were conflicts in the SGML files).
Hi Nico
FYI that version has some stray absolute paths in constraints.source:
-COPY COPY_TBL FROM '@abs_srcdir@/data/constro.data'; +COPY COPY_TBL FROM '/home/nico/ws/postgres/src/test/regress/data/constro.data';-COPY COPY_TBL FROM '@abs_srcdir@/data/constrf.data'; +COPY COPY_TBL FROM '/home/nico/ws/postgres/src/test/regress/data/constrf.data';
Oops! Thanks for catching that!
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Nov 8, 2017 at 8:30 AM, Nico Williams <nico@cryptonector.com> wrote:
On Mon, Nov 06, 2017 at 05:50:21PM +1300, Thomas Munro wrote:
On Fri, Oct 20, 2017 at 9:05 AM, Nico Williams <nico@cryptonector.com> wrote:
Rebased (there were conflicts in the SGML files).
Hi Nico
FYI that version has some stray absolute paths in constraints.source:
-COPY COPY_TBL FROM '@abs_srcdir@/data/constro.data'; +COPY COPY_TBL FROM '/home/nico/ws/postgres/src/test/regress/data/constro.data';-COPY COPY_TBL FROM '@abs_srcdir@/data/constrf.data'; +COPY COPY_TBL FROM '/home/nico/ws/postgres/src/test/regress/data/constrf.data';Oops! Thanks for catching that!
Those are not fixed yet, and it has been three weeks since this
report, so I am marking this patch as returned with feedback. Of
course feel free to send an updated version if you can get into it.
--
Michael
[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
--
Attachments:
0001-Add-ALWAYS-DEFERRED-option-for-CONSTRAINTs.patchtext/x-diff; charset=us-asciiDownload
From 20323d6f19601f5471eb4db7570af8d3342b627d Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@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
Nico Williams <nico@cryptonector.com> writes:
[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).
This design seems correct to me. I have a couple questions inline, and
some nits to go with them.
All tests (make check) pass.
Thank you for adding tests!
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,
From the rest of the code, I think this is supposed to be 'i', not 'd'.
+ <literal>n</literal> = not deferrable + </entry> </row><row>
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);
What does this ensure, and why is it in this part of the code? We're in
the `else` branch here - isn't this always the case (i.e., Assert can
never fire), since `flags` isn't manipulated in this block?
}
/* Store dependency on parent index, if any */
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? + */
I'm not familiar enough to speak to this. Hopefully someone else can.
Absent other input, I think we should add is_always_deferred. (And if
we were building this today, probably just expose a single character
instead of three booleans.)
FROM pg_namespace rs, pg_namespace n, pg_constraint con, pg_type t
WHERE rs.oid = con.connamespace
AND n.oid = t.typnamespace
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 @@ -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 */
"Whether this must be deferred"? Also, I'm not sure what this is for -
it doesn't seem to be used anywhere.
CommandId ats_firing_id; /* ID for firing cycle */
struct AfterTriggersTableData *ats_table; /* transition table access */
} AfterTriggerSharedData;
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 @@ -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);
Can you fix the wrapping on this?
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -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"),
I'd prefer you just repeat the message (or make them more situationally
descriptive), rather than appending a number. (Repeating error messages
is in keeping with the style here.)
parser_errposition(@2))); $$ = newspec; } @@ -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)
Line wrapping?
{ /* 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)
Style on this file doesn't cuddle `else`. (i.e., `else if (cond)` gets its own
line without any braces on it.)
+ { + 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),
Thanks,
--Robbie
On Tue, Jun 26, 2018 at 04:54:13PM -0400, Robbie Harwood wrote:
Nico Williams <nico@cryptonector.com> writes:
[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).This design seems correct to me. I have a couple questions inline, and
some nits to go with them.
Thanks for the review. I'm traveling (on vacation). I'll try to get to
your comments within a week. Thanks!
On Tue, Jun 26, 2018 at 04:54:13PM -0400, Robbie Harwood wrote:
Nico Williams <nico@cryptonector.com> writes:
[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).This design seems correct to me. I have a couple questions inline, and
some nits to go with them.
Thanks. Replies below.
All tests (make check) pass.
Thank you for adding tests!
Well, yeah :)
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,From the rest of the code, I think this is supposed to be 'i', not 'd'.
Oh yes, good catch.
+ <literal>n</literal> = not deferrable + </entry> </row><row>
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);What does this ensure, and why is it in this part of the code? We're in
the `else` branch here - isn't this always the case (i.e., Assert can
never fire), since `flags` isn't manipulated in this block?
Oy, nothing. I think that's a cut-n-paste error.
I'll remove it.
}
/* Store dependency on parent index, if any */
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? + */I'm not familiar enough to speak to this. Hopefully someone else can.
Absent other input, I think we should add is_always_deferred. (And if
we were building this today, probably just expose a single character
instead of three booleans.)
I had found the answer ("yes") in the history of this file but forgot to
remove the comment while rebasing. I'll remove the comment.
FROM pg_namespace rs, pg_namespace n, pg_constraint con, pg_type t
WHERE rs.oid = con.connamespace
AND n.oid = t.typnamespacediff --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 @@ -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 */"Whether this must be deferred"? Also, I'm not sure what this is for -
it doesn't seem to be used anywhere.
Ah, this became unused during the rebase. I'll remove it.
CommandId ats_firing_id; /* ID for firing cycle */
struct AfterTriggersTableData *ats_table; /* transition table access */
} AfterTriggerSharedData;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 @@ -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);Can you fix the wrapping on this?
Maybe! :) I couldn't quite figure out what it should be. There
doesn't seem to be a consistent style of wrapping.
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%}
@@ -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"),I'd prefer you just repeat the message (or make them more situationally
descriptive), rather than appending a number. (Repeating error messages
is in keeping with the style here.)
Oy, I forgot about these. The number was a bread crumb I forgot to
cleanup :( So sorry about that.
parser_errposition(@2))); $$ = newspec; } @@ -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)Line wrapping?
{ /* 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)Style on this file doesn't cuddle `else`. (i.e., `else if (cond)` gets its own
line without any braces on it.)
OK.
+ { + 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),Thanks,
--Robbie
Thank you. I'll rebase, update, and post a new patch soon.
Nico
--
On 2018-Jun-06, Nico Williams wrote:
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).
Nice stuff.
Please add #defines for the chars in pg_constraint.h instead of using
the values directly in the source. Also, catalogs.sgml has a typo in
one of the values.
What happens if you do SET CONSTRAINTS ALL IMMEDIATE and you have one of
these constraints? I expect that it silently does not alter that
constraint, but you didn't change that manpage.
I suggest not to include psql/tab-complete changes with your main patch.
If you want to update it, split it out to its own patch. Committer can
choose to include it in one commit or not (I'm mildly inclined not to,
but I'm probably inconsistent about it), but for review IMO it's better
not to mix things -- It's way too easy to get entangled in silly details
while editing that code, and it's not critical anyway.
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.
I've done this manually a few times. Please share, I'm curious.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Wed, Jul 11, 2018 at 03:13:30PM -0400, Alvaro Herrera wrote:
On 2018-Jun-06, Nico Williams wrote:
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).Nice stuff.
Please add #defines for the chars in pg_constraint.h instead of using
the values directly in the source. Also, catalogs.sgml has a typo in
one of the values.What happens if you do SET CONSTRAINTS ALL IMMEDIATE and you have one of
these constraints? I expect that it silently does not alter that
constraint, but you didn't change that manpage.
Correct, that's the idea, that it should be possible to write deferred
constraints/triggers which users cannot make immediate. For example, an
audit extension that logs changes via FOR EACH ROW ALWAYS DEFERRED, or
my PoC COMMIT TRIGGER implementation (which depends on deferred
triggers).
I missed that there is a page for SET CONSTRAINTS! I'll update it.
I suggest not to include psql/tab-complete changes with your main patch.
If you want to update it, split it out to its own patch. Committer can
choose to include it in one commit or not (I'm mildly inclined not to,
but I'm probably inconsistent about it), but for review IMO it's better
not to mix things -- It's way too easy to get entangled in silly details
while editing that code, and it's not critical anyway.
OK, sure, though, of course, the committer could always leave that out
themselves, no?
To me though it seems that the change should be complete.
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.I've done this manually a few times. Please share, I'm curious.
OK, attached is my latest version of that script, though this one is a
bit changed from the one I used. This version tries to be faster / more
efficient by first doing 1 commit, then 2, then 3, and so on, and on
conflict aborts and halves N to try again. The idea is to only have to
merge conflicts at each commit where conflicts arise, never resolving
conflicts across more than one commit -- this makes is much easier to
reason about conflicts!
Note that the script is actually a shell function, and that it keeps
state in shel variables. A better implementation would do the sort of
thing that git(1) itself does to keep rebase state.
Nico
--
Attachments:
On Wed, Jul 11, 2018 at 01:41:12PM -0500, Nico Williams wrote:
@@ -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"),I'd prefer you just repeat the message (or make them more situationally
descriptive), rather than appending a number. (Repeating error messages
is in keeping with the style here.)Oy, I forgot about these. The number was a bread crumb I forgot to
cleanup :( So sorry about that.
So, I'm tempted not to add new messages that will require translation,
leaving these as "conflicting constraint properties".
But I'm perfectly happy to make these more informative too if that's
desired. I just don't know what the expectations are regarding message
catalog translation. Any advice?
Nico
--
Attached is an additional patch, as well as a new, rebased patch.
This includes changes responsive to Álvaro Herrera's commentary about
the SET CONSTRAINTS manual page.
Nico
--
Attachments:
0001-Add-ALWAYS-DEFERRED-option-for-CONSTRAINTs-CR.patchtext/x-diff; charset=us-asciiDownload
From e7838b60dbf0a8cd7f35591db2f9aab78d8903cb Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@cryptonector.com>
Date: Wed, 11 Jul 2018 19:53:01 -0500
Subject: [PATCH] Add ALWAYS DEFERRED option for CONSTRAINTs (CR)
---
doc/src/sgml/catalogs.sgml | 2 +-
doc/src/sgml/ref/set_constraints.sgml | 10 ++++++----
src/backend/catalog/index.c | 1 -
src/backend/catalog/information_schema.sql | 8 +++-----
src/backend/commands/trigger.c | 1 -
src/backend/parser/gram.y | 18 ++++++++++--------
6 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 291e6a9..4d42594 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2245,7 +2245,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<entry>Constraint deferral option:
<literal>a</literal> = always deferred,
<literal>d</literal> = deferrable,
- <literal>d</literal> = deferrable initially deferred,
+ <literal>i</literal> = deferrable initially deferred,
<literal>n</literal> = not deferrable
</entry>
</row>
diff --git a/doc/src/sgml/ref/set_constraints.sgml b/doc/src/sgml/ref/set_constraints.sgml
index 671332a..390015e 100644
--- a/doc/src/sgml/ref/set_constraints.sgml
+++ b/doc/src/sgml/ref/set_constraints.sgml
@@ -34,11 +34,13 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ...
</para>
<para>
- Upon creation, a constraint is given one of three
+ Upon creation, a constraint is given one of four
characteristics: <literal>DEFERRABLE INITIALLY DEFERRED</literal>,
- <literal>DEFERRABLE INITIALLY IMMEDIATE</literal>, or
- <literal>NOT DEFERRABLE</literal>. The third
- class is always <literal>IMMEDIATE</literal> and is not affected by the
+ <literal>DEFERRABLE INITIALLY IMMEDIATE</literal>,
+ <literal>NOT DEFERRABLE</literal>, or <literal>ALWAYS DEFERRED</literal>.
+ The third
+ class is always <literal>IMMEDIATE</literal>, while the fourth class is
+ always <literal>DEFERRED</literal>, and neither affected by the
<command>SET CONSTRAINTS</command> command. The first two classes start
every transaction in the indicated mode, but their behavior can be changed
within a transaction by <command>SET CONSTRAINTS</command>.
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 795a7a9..45b52b4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1070,7 +1070,6 @@ index_create(Relation heapRelation,
recordDependencyOn(&myself, &referenced, deptype);
}
- Assert((flags & INDEX_CREATE_ADD_CONSTRAINT) == 0);
}
/* Store dependency on parent index, if any */
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index bde6199..dd4792a 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -894,11 +894,9 @@ CREATE VIEW domain_constraints AS
CAST(CASE WHEN condeferral = 'n' THEN 'NO' ELSE 'YES' END
AS yes_or_no) AS is_deferrable,
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?
- */
+ AS yes_or_no) AS initially_deferred,
+ CAST(CASE WHEN condeferral = 'a' THEN 'YES' ELSE 'NO' END
+ AS yes_or_no) AS always_deferred
FROM pg_namespace rs, pg_namespace n, pg_constraint con, pg_type t
WHERE rs.oid = con.connamespace
AND n.oid = t.typnamespace
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 41dc6a4..33b1095 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3627,7 +3627,6 @@ 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;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dab721a..9aaa2af 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -185,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,
- char *deferral,
- 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);
%}
@@ -5579,13 +5579,13 @@ ConstraintAttributeSpec:
(newspec & (CAS_INITIALLY_IMMEDIATE)))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("conflicting constraint properties 1"),
+ errmsg("conflicting constraint properties"),
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 2"),
+ errmsg("conflicting constraint properties"),
parser_errposition(@2)));
$$ = newspec;
}
@@ -16234,8 +16234,8 @@ SplitColQualList(List *qualList,
*/
static void
processCASbits(int cas_bits, int location, const char *constrType,
- char *deferral,
- bool *not_valid, bool *no_inherit, core_yyscan_t yyscanner)
+ char *deferral, bool *not_valid, bool *no_inherit,
+ core_yyscan_t yyscanner)
{
/* defaults */
if (deferral)
@@ -16254,7 +16254,8 @@ processCASbits(int cas_bits, int location, const char *constrType,
errmsg("%s constraints cannot be marked ALWAYS DEFERRED",
constrType),
parser_errposition(location)));
- } else if (cas_bits & CAS_INITIALLY_DEFERRED)
+ }
+ else if (cas_bits & CAS_INITIALLY_DEFERRED)
{
if (deferral)
*deferral = 'i';
@@ -16265,7 +16266,8 @@ processCASbits(int cas_bits, int location, const char *constrType,
errmsg("%s constraints cannot be marked INITIALLY DEFERRED",
constrType),
parser_errposition(location)));
- } else if (cas_bits & CAS_DEFERRABLE)
+ }
+ else if (cas_bits & CAS_DEFERRABLE)
{
if (deferral)
*deferral = 'd';
--
2.7.4
0001-Add-ALWAYS-DEFERRED-option-for-CONSTRAINTs.patchtext/x-diff; charset=us-asciiDownload
From 0c4b527e45bfaed3c128dac825bd7365ec8fc3cc Mon Sep 17 00:00:00 2001
From: Nicolas Williams <nico@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/ref/set_constraints.sgml | 10 ++-
doc/src/sgml/trigger.sgml | 3 +-
src/backend/bootstrap/bootparse.y | 6 +-
src/backend/catalog/heap.c | 3 +-
src/backend/catalog/index.c | 26 +++---
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 | 41 +++++----
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 | 134 +++++++++++++++++++----------
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 +-
35 files changed, 490 insertions(+), 336 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4851bc2..4d42594 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>i</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 b449520..6f0ca8a 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>
@@ -1098,13 +1098,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/ref/set_constraints.sgml b/doc/src/sgml/ref/set_constraints.sgml
index 671332a..390015e 100644
--- a/doc/src/sgml/ref/set_constraints.sgml
+++ b/doc/src/sgml/ref/set_constraints.sgml
@@ -34,11 +34,13 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ...
</para>
<para>
- Upon creation, a constraint is given one of three
+ Upon creation, a constraint is given one of four
characteristics: <literal>DEFERRABLE INITIALLY DEFERRED</literal>,
- <literal>DEFERRABLE INITIALLY IMMEDIATE</literal>, or
- <literal>NOT DEFERRABLE</literal>. The third
- class is always <literal>IMMEDIATE</literal> and is not affected by the
+ <literal>DEFERRABLE INITIALLY IMMEDIATE</literal>,
+ <literal>NOT DEFERRABLE</literal>, or <literal>ALWAYS DEFERRED</literal>.
+ The third
+ class is always <literal>IMMEDIATE</literal>, while the fourth class is
+ always <literal>DEFERRED</literal>, and neither affected by the
<command>SET CONSTRAINTS</command> command. The first two classes start
every transaction in the indicated mode, but their behavior can be changed
within a transaction by <command>SET CONSTRAINTS</command>.
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 d223ba8..cf7b3e66 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2341,8 +2341,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..45b52b4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1215,6 +1215,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 +1237,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 +1301,7 @@ index_constraint_create(Relation heapRelation,
conOid = CreateConstraintEntry(constraintName,
namespaceId,
constraintType,
- deferrable,
- initdeferred,
+ deferral,
true,
parentConstraintId,
RelationGetRelid(heapRelation),
@@ -1356,7 +1361,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 +1378,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 +1395,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 +1416,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..dd4792a 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -891,10 +891,12 @@ 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
- AS yes_or_no) AS initially_deferred
+ CAST(CASE WHEN condeferral = 'i' OR condeferral = 'a' THEN 'YES' ELSE 'NO' END
+ AS yes_or_no) AS initially_deferred,
+ CAST(CASE WHEN condeferral = 'a' THEN 'YES' ELSE 'NO' END
+ AS yes_or_no) AS always_deferred
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 +1782,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 0053832..b0a70fa 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 7c0cf0d..b77f62f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -7055,8 +7055,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,
@@ -7648,8 +7649,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),
@@ -7811,8 +7811,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;
@@ -7828,8 +7827,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,
@@ -7881,8 +7879,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,
@@ -8548,8 +8545,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 */
/*
@@ -8637,8 +8633,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;
@@ -8678,28 +8673,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:
@@ -8734,28 +8724,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:
@@ -11606,8 +11591,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;
@@ -14608,8 +14592,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..33b1095 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;
@@ -3911,6 +3906,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 +5417,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 +5517,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 +5977,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 1c12075..074f9dd 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2885,8 +2885,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);
@@ -3447,8 +3446,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);
@@ -4219,8 +4217,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 a88c0ae..6249d1a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2718,8 +2718,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);
@@ -3496,8 +3495,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 ");
@@ -3584,6 +3582,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..9aaa2af 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,12 +5568,19 @@ 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"),
+ 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,
@@ -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,43 @@ 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 4636392..f2897bb 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)
{
@@ -8528,8 +8519,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');
/*
@@ -16445,13 +16435,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");
}
@@ -17085,13 +17080,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 1448005..f717b36 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -410,8 +410,7 @@ typedef struct _triggerInfo
Oid tgconstrrelid;
char *tgconstrrelname;
char tgenabled;
- bool tgdeferrable;
- bool tginitdeferred;
+ char tgdeferral;
char *tgdef;
} TriggerInfo;
@@ -431,7 +430,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
@@ -443,8 +442,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 6e08515..624248d 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 bb696f8..a172a0d 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 df604a3..88945fd 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -680,44 +680,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 22cf4ef..f1d3ce5 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -517,11 +517,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
Nico Williams <nico@cryptonector.com> writes:
Attached is an additional patch, as well as a new, rebased patch.
This includes changes responsive to Álvaro Herrera's commentary about
the SET CONSTRAINTS manual page.
This patch looks good to me. +1; Álvaro, please update the CF entry
when you're also satisfied.
Thanks,
--Robbie
On Thu, Jul 12, 2018 at 03:07:33PM -0400, Robbie Harwood wrote:
This patch looks good to me. +1; Álvaro, please update the CF entry
when you're also satisfied.
The patch set does not apply anymore, so I have moved it to next CF,
waiting on author.
--
Michael
On Tue, Oct 2, 2018 at 7:43 AM Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Jul 12, 2018 at 03:07:33PM -0400, Robbie Harwood wrote:
This patch looks good to me. +1; Álvaro, please update the CF entry
when you're also satisfied.The patch set does not apply anymore, so I have moved it to next CF,
waiting on author.
Nothing was changed since then. Since the patch got some interest I'm moving it
to the next CF, but please post the rebased version.
Hi,
On 2018-11-29 19:04:24 +0100, Dmitry Dolgov wrote:
On Tue, Oct 2, 2018 at 7:43 AM Michael Paquier <michael@paquier.xyz> wrote:
On Thu, Jul 12, 2018 at 03:07:33PM -0400, Robbie Harwood wrote:
This patch looks good to me. +1; �lvaro, please update the CF entry
when you're also satisfied.The patch set does not apply anymore, so I have moved it to next CF,
waiting on author.Nothing was changed since then. Since the patch got some interest I'm moving it
to the next CF, but please post the rebased version.
Since this hasn't happened yet, I'm going to mark it as returned with
feedback.
Greetings,
Andres Freund