Skip to content

Commit

Permalink
Support INSERT ... ON CONFLICT IGNORE
Browse files Browse the repository at this point in the history
This non-standard INSERT clause allows DML statement authors to specify
that in the event of each of any of the tuples being inserted
duplicating an existing tuple in terms of a value or set of values
constrained by a unique index, an alternative IGNORE path may be taken
(the tuple slot proposed for insertion is skipped without raising an
error).  The implementation loops until either an insert occurs, or a
conclusively committed conflicting tuple is determined to exist.

This is implemented using a new infrastructure called "speculative
insertion", which is an optimistic variant of regular insertion that has
sessions attempt an insert following a pre-check, and handle uncommon
conflicts (would-be unique/exclusion violations) in a special manner
(this necessiates "super-deletion", and a new iteration).  Speculative
heap insertions are WAL-logged in two steps: One record relates to an
initial intent to insert, while a second minimal record simply confirms
that that attempt was ultimately successful (i.e.  no conflicts where
detected when inserting into constraint-related indexes).  Logical
decoding does not rely on the presence of this second record to affirm
that a speculative insertion succeeded, though; it relies on the
*absence* on an (internal) "super-deletion" record.

Optionally, INSERT ... ON CONFLICT IGNORE commands may restrict the
implementation from considering all would-be duplicate violations as
reason to take the IGNORE path.  A "unique index inference"
clause/specification can be provided, which must have a set of
user-supplied column names (or expressions), and optionally, a predicate
(for partial indexes).  This is used to infer some existing unique index
(or, in corner cases, multiple unique indexes).  Failure to infer at
least one unique index is an error.  Arbiter unique indexes are
displayed in EXPLAIN output.

Peter Geoghegan, with review and contributions from numerous others,
including Heikki Linnakangas, Andres Freund, and Robert Haas.  External
testing infrastructure used during review developed by Jeff Janes.
  • Loading branch information
petergeoghegan committed Apr 26, 2015
1 parent 3cf8686 commit c842c79
Show file tree
Hide file tree
Showing 77 changed files with 3,089 additions and 228 deletions.
13 changes: 13 additions & 0 deletions contrib/pg_stat_statements/pg_stat_statements.c
Expand Up @@ -2264,6 +2264,9 @@ JumbleQuery(pgssJumbleState *jstate, Query *query)
JumbleRangeTable(jstate, query->rtable);
JumbleExpr(jstate, (Node *) query->jointree);
JumbleExpr(jstate, (Node *) query->targetList);
APP_JUMB(query->specClause);
JumbleExpr(jstate, (Node *) query->arbiterElems);
JumbleExpr(jstate, query->arbiterWhere);
JumbleExpr(jstate, (Node *) query->returningList);
JumbleExpr(jstate, (Node *) query->groupClause);
JumbleExpr(jstate, query->havingQual);
Expand Down Expand Up @@ -2631,6 +2634,16 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
APP_JUMB(ce->cursor_param);
}
break;
case T_InferenceElem:
{
InferenceElem *ie = (InferenceElem *) node;

APP_JUMB(ie->infercollid);
APP_JUMB(ie->inferopfamily);
APP_JUMB(ie->inferopcinputtype);
JumbleExpr(jstate, ie->expr);
}
break;
case T_TargetEntry:
{
TargetEntry *tle = (TargetEntry *) node;
Expand Down
7 changes: 5 additions & 2 deletions contrib/postgres_fdw/deparse.c
Expand Up @@ -847,8 +847,8 @@ appendWhereClause(StringInfo buf,
void
deparseInsertSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel,
List *targetAttrs, List *returningList,
List **retrieved_attrs)
List *targetAttrs, bool ignore,
List *returningList, List **retrieved_attrs)
{
AttrNumber pindex;
bool first;
Expand Down Expand Up @@ -892,6 +892,9 @@ deparseInsertSql(StringInfo buf, PlannerInfo *root,
else
appendStringInfoString(buf, " DEFAULT VALUES");

if (ignore)
appendStringInfoString(buf, " ON CONFLICT IGNORE");

deparseReturningList(buf, root, rtindex, rel,
rel->trigdesc && rel->trigdesc->trig_insert_after_row,
returningList, retrieved_attrs);
Expand Down
3 changes: 3 additions & 0 deletions contrib/postgres_fdw/expected/postgres_fdw.out
Expand Up @@ -2327,6 +2327,9 @@ INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key
ERROR: duplicate key value violates unique constraint "t1_pkey"
DETAIL: Key ("C 1")=(11) already exists.
CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT IGNORE; -- works
INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) IGNORE; -- unsupported
ERROR: postgres_fdw does not support ON CONFLICT unique index inference
INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive
ERROR: new row for relation "T 1" violates check constraint "c2positive"
DETAIL: Failing row contains (1111, -2, null, null, null, null, ft1 , null).
Expand Down
12 changes: 11 additions & 1 deletion contrib/postgres_fdw/postgres_fdw.c
Expand Up @@ -1171,6 +1171,7 @@ postgresPlanForeignModify(PlannerInfo *root,
List *targetAttrs = NIL;
List *returningList = NIL;
List *retrieved_attrs = NIL;
bool ignore = false;

initStringInfo(&sql);

Expand Down Expand Up @@ -1222,14 +1223,23 @@ postgresPlanForeignModify(PlannerInfo *root,
if (plan->returningLists)
returningList = (List *) list_nth(plan->returningLists, subplan_index);

if (root->parse->arbiterElems)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("postgres_fdw does not support ON CONFLICT unique index inference")));
else if (plan->spec == SPEC_IGNORE)
ignore = true;
else if (plan->spec != SPEC_NONE)
elog(ERROR, "unexpected speculative specification: %d", (int) plan->spec);

/*
* Construct the SQL command string.
*/
switch (operation)
{
case CMD_INSERT:
deparseInsertSql(&sql, root, resultRelation, rel,
targetAttrs, returningList,
targetAttrs, ignore, returningList,
&retrieved_attrs);
break;
case CMD_UPDATE:
Expand Down
2 changes: 1 addition & 1 deletion contrib/postgres_fdw/postgres_fdw.h
Expand Up @@ -60,7 +60,7 @@ extern void appendWhereClause(StringInfo buf,
List **params);
extern void deparseInsertSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel,
List *targetAttrs, List *returningList,
List *targetAttrs, bool ignore, List *returningList,
List **retrieved_attrs);
extern void deparseUpdateSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel,
Expand Down
2 changes: 2 additions & 0 deletions contrib/postgres_fdw/sql/postgres_fdw.sql
Expand Up @@ -372,6 +372,8 @@ UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *;
ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0);

INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key
INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT IGNORE; -- works
INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) IGNORE; -- unsupported
INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive
UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive

Expand Down
4 changes: 4 additions & 0 deletions doc/src/sgml/ddl.sgml
Expand Up @@ -2442,6 +2442,10 @@ VALUES ('Albany', NULL, NULL, 'NY');
All check constraints and not-null constraints on a parent table are
automatically inherited by its children. Other types of constraints
(unique, primary key, and foreign key constraints) are not inherited.
Therefore, <command>INSERT</command> with <literal>ON CONFLICT</>
unique index inference considers only unique constraints/indexes
directly associated with the table inserted into (which can be an
inheritance parent or child).
</para>

<para>
Expand Down
7 changes: 7 additions & 0 deletions doc/src/sgml/fdwhandler.sgml
Expand Up @@ -1014,6 +1014,13 @@ GetForeignServerByName(const char *name, bool missing_ok);
source provides.
</para>

<para>
<command>INSERT</> with an <literal>ON CONFLICT</> clause is not
supported with a unique index inference specification, since a
conflict arbitrating unique index cannot meaningfully be inferred
on a foreign table.
</para>

</sect1>

</chapter>
7 changes: 7 additions & 0 deletions doc/src/sgml/keywords.sgml
Expand Up @@ -853,6 +853,13 @@
<entry></entry>
<entry></entry>
</row>
<row>
<entry><token>CONFLICT</token></entry>
<entry>non-reserved</entry>
<entry></entry>
<entry></entry>
<entry></entry>
</row>
<row>
<entry><token>CONNECT</token></entry>
<entry></entry>
Expand Down
6 changes: 6 additions & 0 deletions doc/src/sgml/postgres-fdw.sgml
Expand Up @@ -68,6 +68,12 @@
in your user mapping must have privileges to do these things.)
</para>

<para>
<filename>postgres_fdw</> supports <command>INSERT</command>
statements with an <literal>ON CONFLICT IGNORE</> clause, provided a
unique index inference specification is omitted.
</para>

<para>
It is generally recommended that the columns of a foreign table be declared
with exactly the same data types, and collations if applicable, as the
Expand Down
7 changes: 6 additions & 1 deletion doc/src/sgml/ref/create_rule.sgml
Expand Up @@ -136,7 +136,12 @@ CREATE [ OR REPLACE ] RULE <replaceable class="parameter">name</replaceable> AS
<para>
The event is one of <literal>SELECT</literal>,
<literal>INSERT</literal>, <literal>UPDATE</literal>, or
<literal>DELETE</literal>.
<literal>DELETE</literal>. Note that an
<command>INSERT</command> containing an <literal>ON CONFLICT
IGNORE</literal> clause cannot be used on tables that have
either <literal>INSERT</literal> or <literal>UPDATE</literal>
rules. Consider using an updatable view instead, which are
supported with <literal>ON CONFLICT IGNORE</literal>.
</para>
</listitem>
</varlistentry>
Expand Down
5 changes: 4 additions & 1 deletion doc/src/sgml/ref/create_table.sgml
Expand Up @@ -717,7 +717,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
<literal>EXCLUDE</>, and
<literal>REFERENCES</> (foreign key) constraints accept this
clause. <literal>NOT NULL</> and <literal>CHECK</> constraints are not
deferrable.
deferrable. Note that constraints that were created with this
clause cannot be used as arbiters of whether or not to take the
alternative path with an <command>INSERT</command> statement
that includes an <literal>ON CONFLICT</> clause.
</para>
</listitem>
</varlistentry>
Expand Down
11 changes: 9 additions & 2 deletions doc/src/sgml/ref/create_view.sgml
Expand Up @@ -286,8 +286,9 @@ CREATE VIEW vista AS SELECT text 'Hello World' AS hello;
<para>
Simple views are automatically updatable: the system will allow
<command>INSERT</>, <command>UPDATE</> and <command>DELETE</> statements
to be used on the view in the same way as on a regular table. A view is
automatically updatable if it satisfies all of the following conditions:
to be used on the view in the same way as on a regular table (aside from
the limitations on ON CONFLICT noted below). A view is automatically
updatable if it satisfies all of the following conditions:

<itemizedlist>
<listitem>
Expand Down Expand Up @@ -383,6 +384,12 @@ CREATE VIEW vista AS SELECT text 'Hello World' AS hello;
not need any permissions on the underlying base relations (see
<xref linkend="rules-privileges">).
</para>
<para>
<command>INSERT</command> with an <literal>ON CONFLICT IGNORE</>
clause is supported on updatable views (if an inference
specification is provided, it must infer a unique index on the
underlying base relation).
</para>
</refsect2>
</refsect1>

Expand Down

0 comments on commit c842c79

Please sign in to comment.