Support INSERT ... ON CONFLICT IGNORE
authorPeter Geoghegan <[email protected]>
Wed, 4 Mar 2015 03:44:42 +0000 (19:44 -0800)
committerPeter Geoghegan <[email protected]>
Mon, 20 Apr 2015 04:27:54 +0000 (21:27 -0700)
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".  (The approach to "Value locking" presenting here follows
design #2, as described on the value locking Postgres Wiki page).

Users optionally specify a single unique index to take the alternative
path on, which is inferred from a set of user-supplied column names (or
expressions).

Speculative (heap) insertions are WAL-logged in two steps:  One record
relates to an initial intent to insert, while a second minimal record
simply affirms that that attempt was ultimately successful (i.e. no
conflicts where detected when inserting into constraint-related
indexes).  In this revision, 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.

85 files changed:
contrib/pageinspect/heapfuncs.c
contrib/pg_stat_statements/pg_stat_statements.c
contrib/postgres_fdw/deparse.c
contrib/postgres_fdw/expected/postgres_fdw.out
contrib/postgres_fdw/postgres_fdw.c
contrib/postgres_fdw/postgres_fdw.h
contrib/postgres_fdw/sql/postgres_fdw.sql
doc/src/sgml/ddl.sgml
doc/src/sgml/fdwhandler.sgml
doc/src/sgml/keywords.sgml
doc/src/sgml/postgres-fdw.sgml
doc/src/sgml/ref/create_rule.sgml
doc/src/sgml/ref/create_table.sgml
doc/src/sgml/ref/create_view.sgml
doc/src/sgml/ref/insert.sgml
doc/src/sgml/ref/set_constraints.sgml
src/backend/access/common/heaptuple.c
src/backend/access/heap/heapam.c
src/backend/access/heap/hio.c
src/backend/access/heap/pruneheap.c
src/backend/access/heap/rewriteheap.c
src/backend/access/heap/tuptoaster.c
src/backend/access/nbtree/nbtinsert.c
src/backend/access/rmgrdesc/heapdesc.c
src/backend/catalog/index.c
src/backend/catalog/indexing.c
src/backend/commands/constraint.c
src/backend/commands/copy.c
src/backend/commands/explain.c
src/backend/commands/sequence.c
src/backend/executor/README
src/backend/executor/execMain.c
src/backend/executor/execUtils.c
src/backend/executor/nodeModifyTable.c
src/backend/executor/spi.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/nodeFuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/util/plancat.c
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/parse_clause.c
src/backend/parser/parse_collate.c
src/backend/replication/logical/decode.c
src/backend/replication/logical/reorderbuffer.c
src/backend/rewrite/rewriteHandler.c
src/backend/storage/lmgr/lmgr.c
src/backend/utils/adt/lockfuncs.c
src/backend/utils/time/tqual.c
src/include/access/heapam.h
src/include/access/heapam_xlog.h
src/include/access/hio.h
src/include/access/htup_details.h
src/include/catalog/index.h
src/include/executor/executor.h
src/include/nodes/execnodes.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/nodes/plannodes.h
src/include/nodes/primnodes.h
src/include/optimizer/plancat.h
src/include/optimizer/planmain.h
src/include/parser/kwlist.h
src/include/parser/parse_clause.h
src/include/replication/reorderbuffer.h
src/include/storage/block.h
src/include/storage/lmgr.h
src/include/storage/lock.h
src/include/storage/off.h
src/include/utils/snapshot.h
src/test/isolation/expected/insert-conflict-ignore.out [new file with mode: 0644]
src/test/isolation/isolation_schedule
src/test/isolation/specs/insert-conflict-ignore.spec [new file with mode: 0644]
src/test/regress/expected/insert_conflict.out [new file with mode: 0644]
src/test/regress/expected/rules.out
src/test/regress/expected/updatable_views.out
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/insert_conflict.sql [new file with mode: 0644]
src/test/regress/sql/rules.sql
src/test/regress/sql/updatable_views.sql

index 8d1666c8bda3a10e77bc7a9f33dbfd5741599e2e..1390b43f094de23f1e730b2d2dcb35b93e40fde7 100644 (file)
@@ -163,7 +163,10 @@ heap_page_items(PG_FUNCTION_ARGS)
            values[4] = UInt32GetDatum(HeapTupleHeaderGetRawXmin(tuphdr));
            values[5] = UInt32GetDatum(HeapTupleHeaderGetRawXmax(tuphdr));
            values[6] = UInt32GetDatum(HeapTupleHeaderGetRawCommandId(tuphdr)); /* shared with xvac */
-           values[7] = PointerGetDatum(&tuphdr->t_ctid);
+           if (!HeapTupleHeaderIsSpeculative(tuphdr))
+               values[7] = PointerGetDatum(&tuphdr->t_tidstate.t_ctid);
+           else
+               values[7] = PointerGetDatum(&tuphdr->t_tidstate.t_token);
            values[8] = UInt32GetDatum(tuphdr->t_infomask2);
            values[9] = UInt32GetDatum(tuphdr->t_infomask);
            values[10] = UInt8GetDatum(tuphdr->t_hoff);
index 76d9e0a5ec6132277041e6918a199c2f53e5a01e..609b4d433b823170f4f963fcef1f3617d2de7de8 100644 (file)
@@ -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);
@@ -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;
index 94fab18c42504d0a1900d9a0c412eccb52e800b9..cb2b0989326358c1afc75b21f031e4fda3ef2608 100644 (file)
@@ -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;
@@ -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);
index 783cb41571d0a0fe5bb521bca372dac4b0637255..88c40c91dd23ea1334b48995fb365b737cdc53bb 100644 (file)
@@ -2327,6 +2327,10 @@ 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:  relation "ft1" is not an ordinary table
+HINT:  Only ordinary tables are accepted as targets when a unique index is inferred for ON CONFLICT.
 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).
index 478e12484b940ad9474b3604bc1615883018cde4..b4a58d5cfcdbaac89b5a157e07a3cc238c688b65 100644 (file)
@@ -1171,6 +1171,7 @@ postgresPlanForeignModify(PlannerInfo *root,
    List       *targetAttrs = NIL;
    List       *returningList = NIL;
    List       *retrieved_attrs = NIL;
+   bool        ignore = false;
 
    initStringInfo(&sql);
 
@@ -1222,6 +1223,15 @@ 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.
     */
@@ -1229,7 +1239,7 @@ postgresPlanForeignModify(PlannerInfo *root,
    {
        case CMD_INSERT:
            deparseInsertSql(&sql, root, resultRelation, rel,
-                            targetAttrs, returningList,
+                            targetAttrs, ignore, returningList,
                             &retrieved_attrs);
            break;
        case CMD_UPDATE:
index 950c6f79a22a75acdba6d8338d66fd8ffcb67ce1..3763a5797e356b1e23f469c78b2d3c7aca625d5a 100644 (file)
@@ -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,
index 4a23457e796536abce8b3c35cec59cc7ebd5962a..aba373e817eed80f2916aafe9e69435d0c6e3163 100644 (file)
@@ -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
 
index 1c56f162de3f9c338d5fc2b151e400a450db02ef..c7d46076a7ba66b81eba9dd55abd465b53c8814b 100644 (file)
@@ -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>
index c1daa4be5a25412b2e04b8fc600b64b5b6a93e54..4ed06e69d39e6c5cb0e5885c52aafcd79c241c10 100644 (file)
@@ -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>
index b0dfd5ff75bce4cb477c754a208d05e54a21f349..ea582116ab9dc8f24fa48d09f71d28eff353a275 100644 (file)
     <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>
index 43adb61455d919eae10c3debae954e4111b95296..81d4441d49a9c3fc3cb3d627ce3bd5e0c3410739 100644 (file)
   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
index 677766a2d5e3659b15b122a02503f16f63c6c5b9..2aabf62f67ba7e5256bbff0dfe622a3cf18e5ea1 100644 (file)
@@ -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>
index be7ebd5f54f74db650b428eaf2ae3bbeb2b94d50..05396e6c8fe1ca78e5dcd45acb88f1f0cc4f345b 100644 (file)
@@ -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>
index 5dadab1dee9d7b59581a8f61c1e8a508540cabc8..d8efd30448a24b6c3ce531afe72b9750d4408d43 100644 (file)
@@ -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>
@@ -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>
 
index a3cccb9f7c79a5bac71ed35a96d171e9b9587041..ac7ffa45ac1bdcfc694f172a1863603ae89090e3 100644 (file)
@@ -24,6 +24,7 @@ PostgreSQL documentation
 [ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
 INSERT INTO <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
     { DEFAULT VALUES | VALUES ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) [, ...] | <replaceable class="PARAMETER">query</replaceable> }
+    [ ON CONFLICT [ ( { <replaceable class="parameter">column_name_index</replaceable> | ( <replaceable class="parameter">expression_index</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ...] [ WHERE <replaceable class="PARAMETER">index_predicate</replaceable> ] ) ] IGNORE]
     [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
 </synopsis>
  </refsynopsisdiv>
@@ -58,6 +59,108 @@ INSERT INTO <replaceable class="PARAMETER">table_name</replaceable> [ ( <replace
    automatic type conversion will be attempted.
   </para>
 
+  <para>
+   The optional <literal>ON CONFLICT</> clause specifies a path to
+   take as an alternative to raising a conflict related error.  The
+   alternative path is considered individually for each row proposed
+   for insertion;  it is taken (or not taken) once per row.
+   <literal>ON CONFLICT IGNORE</> simply avoids inserting any
+   individual row when it is determined that a conflict related error
+   would otherwise need to be raised.
+  </para>
+
+  <para>
+   <literal>ON CONFLICT IGNORE</> optionally accepts a
+   <emphasis>unique index inference</emphasis> specification, which
+   consists of one or more <replaceable
+   class="PARAMETER">column_name_index</replaceable> columns and/or
+   <replaceable class="PARAMETER">expression_index</replaceable>
+   expressions on columns, appearing between parenthesis.  These are
+   used to infer a unique index to limit pre-checking for conflicts to
+   (if no appropriate index is available, an error is raised).  A
+   subset of the table to limit the check for conflicts to can
+   optionally also be specified using <replaceable
+   class="PARAMETER">index_predicate</replaceable> (this allows the
+   implementation to use an expression index only covering at least
+   the subset).  Note that omitting a unique index inference
+   specification indicates a total indifference to where any conflict
+   could occur, which isn't always appropriate.  At times, it may be
+   desirable for <literal>ON CONFLICT IGNORE</> to
+   <emphasis>not</emphasis> suppress a conflict related error
+   associated with an index where that isn't explicitly anticipated.
+  </para>
+
+  <para>
+   Columns and/or expressions appearing in a unique index inference
+   specification must match all the columns/expressions of some
+   existing unique index on <replaceable
+   class="PARAMETER">table_name</replaceable> - there can be no
+   columns/expressions from the unique index that do not appear in the
+   inference specification, nor can there be any columns/expressions
+   appearing in the inference specification that do not appear in the
+   unique index definition.  However, the order of the
+   columns/expressions in the index definition, or whether or not the
+   index definition specified <literal>NULLS FIRST</> or
+   <literal>NULLS LAST</>, or the internal sort order of each column
+   (whether <literal>DESC</> or <literal>ASC</> were specified) are
+   all irrelevant.  Deferred unique constraints are not supported as
+   arbiters of whether an alternative <literal>ON CONFLICT</> path
+   should be taken.
+  </para>
+
+  <para>
+   The definition of a conflict for the purposes of <literal>ON
+   CONFLICT</> is somewhat subtle, although the exact definition is
+   seldom of great interest.  A conflict is a condition that
+   ordinarily necessitates raising either a unique violation from a
+   unique constraint (or unique index), or an exclusion violation from
+   an exclusion constraint, occurring in an index/constraint that
+   arbitrates the <literal>ON CONFLICT</> path.  Only unique indexes
+   (or unique constraints) can be inferred with a unique index
+   inference specification.  In contrast to the rules around certain
+   other SQL clauses, like the <literal>DISTINCT</literal> clause, the
+   definition of a duplicate (a conflict) is based on whatever unique
+   indexes happen to be defined on columns on the table.  In
+   particular, the default operator class for the type of each indexed
+   column is not considered.  The inference clause can require a
+   particular named operator class be used per column/expression
+   indexed if that's a concern.  Similarly, the inference
+   specification can limit its consideration of arbiter unique indexes
+   on the basis of collations on column/expression covered by
+   available indexes.
+  </para>
+
+  <para>
+   The optional <replaceable
+   class="PARAMETER">index_predicate</replaceable> can be used to
+   allow the inference specification to infer that a partial unique
+   index can be used.  Any unique index that otherwise satisfies the
+   inference specification, while also covering at least all the rows
+   in the table covered by <replaceable
+   class="PARAMETER">index_predicate</replaceable> may be used.  It is
+   recommended that the partial index predicate of the unique index
+   intended to be used as the arbiter of taking the alternative path
+   be matched exactly, but this is not required.  Note that an error
+   will be raised if an arbiter unique index is chosen that does not
+   cover the tuple or tuples proposed for insertion.  However, an
+   overly specific <replaceable
+   class="PARAMETER">index_predicate</replaceable> does not imply that
+   arbitrating conflicts will be limited to the subset of rows covered
+   by the inferred unique index corresponding to <replaceable
+   class="PARAMETER">index_predicate</replaceable>.
+  </para>
+
+  <para>
+    Multiple unique indexes/constraints may be inferred where multiple
+    indexes exist that satisfy the inference specification, although
+    typically this does not occur (this behavior only exists to
+    smoothly cover certain corner cases).  Note that the ordering of
+    multiple <replaceable
+    class="PARAMETER">column_name_index</replaceable> columns and/or
+    <replaceable class="PARAMETER">expression_index</replaceable>
+    within the inference specification is not significant.
+  </para>
+
   <para>
    The optional <literal>RETURNING</> clause causes <command>INSERT</>
    to compute and return value(s) based on each row actually inserted.
@@ -126,6 +229,85 @@ INSERT INTO <replaceable class="PARAMETER">table_name</replaceable> [ ( <replace
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="PARAMETER">column_name_index</replaceable></term>
+    <listitem>
+     <para>
+      The name of a <replaceable
+      class="PARAMETER">table_name</replaceable> column.  Part of a
+      unique inference specification.  Follows <command>CREATE
+      INDEX</command> format.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="PARAMETER">expression_index</replaceable></term>
+    <listitem>
+     <para>
+      Similar to <replaceable
+      class="PARAMETER">column_name_index</replaceable>, but used to
+      infer expressions on <replaceable
+      class="PARAMETER">table_name</replaceable> columns appearing
+      within index definitions (not simple columns).  Part of unique
+      index inference clause.  Follows <command>CREATE INDEX</command>
+      format.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="PARAMETER">collation</replaceable></term>
+    <listitem>
+     <para>
+      When specified, mandates that corresponding <replaceable
+      class="PARAMETER">column_name_index</replaceable> or
+      <replaceable class="PARAMETER">expression_index</replaceable>
+      use particular collation in order to be inferred as arbitrating
+      the <literal>ON CONFLICT</> path taken.  Typically this is
+      omitted even when it is intended that <literal>ON CONFLICT</>
+      infer a particular unique index or unique constraint with a
+      non-default collation, since the use of a non-default collation
+      does not usually change the semantics of arbitration (because
+      the <emphasis>equality</emphasis> semantics are often equivalent
+      anyway).  Follows <command>CREATE INDEX</command> format.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="PARAMETER">opclass</replaceable></term>
+    <listitem>
+     <para>
+      When specified, mandates that corresponding <replaceable
+      class="PARAMETER">column_name_index</replaceable> or
+      <replaceable class="PARAMETER">expression_index</replaceable>
+      use particular operator class in order to be inferred as
+      arbitrating the <literal>ON CONFLICT</> path taken.  Sometimes
+      this is omitted even when it is intended that <literal>ON
+      CONFLICT</> infer a particular unique index or unique constraint
+      with a non-default operator class (because the
+      <emphasis>equality</emphasis> semantics are often equivalent
+      across a type's operator classes anyway, or because it's
+      sufficient to trust that the defined unique index has the
+      pertinent definition of equality).  Follows <command>CREATE
+      INDEX</command> format.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="PARAMETER">index_predicate</replaceable></term>
+    <listitem>
+     <para>
+      Used to allow inference of partial unique indexes.  Any indexes
+      that satisfy the predicate (which need not actually be partial
+      indexes) will be used in conflict arbitration.  Follows
+      <command>CREATE INDEX</command> format.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DEFAULT VALUES</literal></term>
     <listitem>
@@ -171,8 +353,9 @@ INSERT INTO <replaceable class="PARAMETER">table_name</replaceable> [ ( <replace
     <listitem>
      <para>
       An expression to be computed and returned by the <command>INSERT</>
-      command after each row is inserted.  The expression can use any
-      column names of the table named by <replaceable class="PARAMETER">table_name</replaceable>.
+      command after each row is inserted (not updated). The
+      expression can use any column names of the table named by
+      <replaceable class="PARAMETER">table_name</replaceable>.
       Write <literal>*</> to return all columns of the inserted row(s).
      </para>
     </listitem>
@@ -311,7 +494,35 @@ WITH upd AS (
     RETURNING *
 )
 INSERT INTO employees_log SELECT *, current_timestamp FROM upd;
-</programlisting></para>
+</programlisting>
+  </para>
+  <para>
+   Insert a distributor, or do nothing for rows proposed for insertion
+   when an existing, excluded row (a row with a matching constrained
+   column or columns after before row insert triggers fire) exists.
+   Example assumes a unique index has been defined that constrains
+   values appearing in the <literal>did</literal> column:
+<programlisting>
+  INSERT INTO distributors (did, dname) VALUES (7, 'Redline GmbH')
+  ON CONFLICT (did) IGNORE
+</programlisting>
+  </para>
+  <para>
+   Insert new distributor if possible;  otherwise
+   <literal>IGNORE</literal>.  Example assumes a unique index has been
+   defined that constrains values appearing in the
+   <literal>did</literal> column on a subset of rows where the
+   <literal>is_active</literal> boolean column evaluates to
+   <literal>true</literal>:
+<programlisting>
+  -- This statement could infer a partial unique index on did
+  -- with a predicate of WHERE is_active, but it could also
+  -- just use a regular unique constraint on did if that was
+  -- all that was available.
+  INSERT INTO distributors (did, dname) VALUES (9, 'Antwerp Design')
+  ON CONFLICT (did WHERE is_active) IGNORE
+</programlisting>
+  </para>
  </refsect1>
 
  <refsect1>
@@ -321,7 +532,8 @@ INSERT INTO employees_log SELECT *, current_timestamp FROM upd;
    <command>INSERT</command> conforms to the SQL standard, except that
    the <literal>RETURNING</> clause is a
    <productname>PostgreSQL</productname> extension, as is the ability
-   to use <literal>WITH</> with <command>INSERT</>.
+   to use <literal>WITH</> with <command>INSERT</>, and the ability to
+   specify an alternative path with <literal>ON CONFLICT</>.
    Also, the case in
    which a column name list is omitted, but not all the columns are
    filled from the <literal>VALUES</> clause or <replaceable>query</>,
index 7c31871b0bd2b7acbbfe466eeb29aae68c0b7f0d..ba2b5badbd26f6762f41ba0171ce902474faae54 100644 (file)
@@ -69,7 +69,10 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ...
   <para>
    Currently, only <literal>UNIQUE</>, <literal>PRIMARY KEY</>,
    <literal>REFERENCES</> (foreign key), and <literal>EXCLUDE</>
-   constraints are affected by this setting.
+   constraints are affected by this setting.  Note that constraints
+   that are <literal>DEFERRED</literal> cannot be used as arbiters by
+   the <literal>ON CONFLICT</> clause that <command>INSERT</>
+   supports.
    <literal>NOT NULL</> and <literal>CHECK</> constraints are
    always checked immediately when a row is inserted or modified
    (<emphasis>not</> at the end of the statement).
index 6cd4e8e11ae97a855972c02b085367deadd93e69..f21da7d79701e938a876b250db2ca9cf1d2dcbdc 100644 (file)
@@ -839,7 +839,7 @@ heap_modify_tuple(HeapTuple tuple,
     * copy the identification info of the old tuple: t_ctid, t_self, and OID
     * (if any)
     */
-   newTuple->t_data->t_ctid = tuple->t_data->t_ctid;
+   newTuple->t_data->t_tidstate = tuple->t_data->t_tidstate;
    newTuple->t_self = tuple->t_self;
    newTuple->t_tableOid = tuple->t_tableOid;
    if (tupleDesc->tdhasoid)
index 457cd708fd3b5ca889938c3425622363deddc946..d96a26c3e106bf700dcb9d54e6d25d41b47710c0 100644 (file)
@@ -104,6 +104,7 @@ static void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask,
 static HTSU_Result heap_lock_updated_tuple(Relation rel, HeapTuple tuple,
                        ItemPointer ctid, TransactionId xid,
                        LockTupleMode mode);
+static void heap_affirm_insert(Relation relation, HeapTuple tuple);
 static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask,
                       uint16 *new_infomask2);
 static TransactionId MultiXactIdGetUpdateXid(TransactionId xmax,
@@ -1804,9 +1805,9 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
         */
        if (HeapTupleIsHotUpdated(heapTuple))
        {
-           Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_ctid) ==
+           Assert(ItemPointerGetBlockNumber(&heapTuple->t_data->t_tidstate.t_ctid) ==
                   ItemPointerGetBlockNumber(tid));
-           offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_ctid);
+           offnum = ItemPointerGetOffsetNumber(&heapTuple->t_data->t_tidstate.t_ctid);
            at_chain_start = false;
            prev_xmax = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
        }
@@ -1947,18 +1948,26 @@ heap_get_latest_tid(Relation relation,
        if (valid)
            *tid = ctid;
 
+       /*
+        * Should not have followed a t_ctid chain and found a speculative
+        * tuple that way
+        */
+       Assert(!HeapTupleHeaderIsSpeculative(tp.t_data) ||
+              !TransactionIdIsValid(priorXmax));
+
        /*
         * If there's a valid t_ctid link, follow it, else we're done.
         */
        if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) ||
+           HeapTupleHeaderIsSpeculative(tp.t_data) ||
            HeapTupleHeaderIsOnlyLocked(tp.t_data) ||
-           ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid))
+           ItemPointerEquals(&tp.t_self, &tp.t_data->t_tidstate.t_ctid))
        {
            UnlockReleaseBuffer(buffer);
            break;
        }
 
-       ctid = tp.t_data->t_ctid;
+       ctid = tp.t_data->t_tidstate.t_ctid;
        priorXmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
        UnlockReleaseBuffer(buffer);
    }                           /* end of loop */
@@ -2102,7 +2111,9 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
    /* NO EREPORT(ERROR) from here till changes are logged */
    START_CRIT_SECTION();
 
-   RelationPutHeapTuple(relation, buffer, heaptup);
+   RelationPutHeapTuple(relation, buffer, heaptup,
+                        (options & HEAP_INSERT_SPECULATIVE) != 0);
+
 
    if (PageIsAllVisible(BufferGetPage(buffer)))
    {
@@ -2156,7 +2167,11 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
        }
 
        xlrec.offnum = ItemPointerGetOffsetNumber(&heaptup->t_self);
-       xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
+       xlrec.flags = 0;
+       if (all_visible_cleared)
+           xlrec.flags |= XLOG_HEAP_ALL_VISIBLE_CLEARED;
+       if (options & HEAP_INSERT_SPECULATIVE)
+           xlrec.flags |= XLOG_HEAP_SPECULATIVE_TUPLE;
        Assert(ItemPointerGetBlockNumber(&heaptup->t_self) == BufferGetBlockNumber(buffer));
 
        /*
@@ -2368,7 +2383,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
         * RelationGetBufferForTuple has ensured that the first tuple fits.
         * Put that on the page, and then as many other tuples as fit.
         */
-       RelationPutHeapTuple(relation, buffer, heaptuples[ndone]);
+       RelationPutHeapTuple(relation, buffer, heaptuples[ndone], false);
        for (nthispage = 1; ndone + nthispage < ntuples; nthispage++)
        {
            HeapTuple   heaptup = heaptuples[ndone + nthispage];
@@ -2376,7 +2391,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
            if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + saveFreeSpace)
                break;
 
-           RelationPutHeapTuple(relation, buffer, heaptup);
+           RelationPutHeapTuple(relation, buffer, heaptup, false);
 
            /*
             * We don't use heap_multi_insert for catalog tuples yet, but
@@ -2616,11 +2631,17 @@ xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask)
  * (the last only for HeapTupleSelfUpdated, since we
  * cannot obtain cmax from a combocid generated by another transaction).
  * See comments for struct HeapUpdateFailureData for additional info.
+ *
+ * If 'speculative' is true, caller requires that we "super-delete" a tuple we
+ * just inserted in the same command.  Instead of the normal visibility checks,
+ * we check that the tuple was inserted by the current transaction and given
+ * command id.  Also, instead of setting its xmax, we set xmin to invalid,
+ * making it immediately appear as dead to everyone.
  */
 HTSU_Result
 heap_delete(Relation relation, ItemPointer tid,
            CommandId cid, Snapshot crosscheck, bool wait,
-           HeapUpdateFailureData *hufd)
+           HeapUpdateFailureData *hufd, bool speculative)
 {
    HTSU_Result result;
    TransactionId xid = GetCurrentTransactionId();
@@ -2678,7 +2699,17 @@ heap_delete(Relation relation, ItemPointer tid,
    tp.t_self = *tid;
 
 l1:
-   result = HeapTupleSatisfiesUpdate(&tp, cid, buffer);
+   if (!speculative)
+   {
+       result = HeapTupleSatisfiesUpdate(&tp, cid, buffer);
+   }
+   else
+   {
+       if (tp.t_data->t_choice.t_heap.t_xmin != xid ||
+           tp.t_data->t_choice.t_heap.t_field3.t_cid != cid)
+           elog(ERROR, "attempted to super-delete a tuple from other CID");
+       result = HeapTupleMayBeUpdated;
+   }
 
    if (result == HeapTupleInvisible)
    {
@@ -2797,7 +2828,7 @@ l1:
               result == HeapTupleUpdated ||
               result == HeapTupleBeingUpdated);
        Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID));
-       hufd->ctid = tp.t_data->t_ctid;
+       hufd->ctid = tp.t_data->t_tidstate.t_ctid;
        hufd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data);
        if (result == HeapTupleSelfUpdated)
            hufd->cmax = HeapTupleHeaderGetCmax(tp.t_data);
@@ -2834,12 +2865,19 @@ l1:
     * using our own TransactionId below, since some other backend could
     * incorporate our XID into a MultiXact immediately afterwards.)
     */
-   MultiXactIdSetOldestMember();
+   if (!speculative)
+   {
+       MultiXactIdSetOldestMember();
 
-   compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(tp.t_data),
-                             tp.t_data->t_infomask, tp.t_data->t_infomask2,
-                             xid, LockTupleExclusive, true,
-                             &new_xmax, &new_infomask, &new_infomask2);
+       compute_new_xmax_infomask(HeapTupleHeaderGetRawXmax(tp.t_data),
+                                 tp.t_data->t_infomask, tp.t_data->t_infomask2,
+                                 xid, LockTupleExclusive, true,
+                                 &new_xmax, &new_infomask, &new_infomask2);
+   }
+   else
+   {
+       new_xmax = new_infomask = new_infomask2 = 0;
+   }
 
    START_CRIT_SECTION();
 
@@ -2866,10 +2904,25 @@ l1:
    tp.t_data->t_infomask |= new_infomask;
    tp.t_data->t_infomask2 |= new_infomask2;
    HeapTupleHeaderClearHotUpdated(tp.t_data);
-   HeapTupleHeaderSetXmax(tp.t_data, new_xmax);
-   HeapTupleHeaderSetCmax(tp.t_data, cid, iscombo);
+   /*
+    * When killing a speculatively-inserted tuple, we set xmin to invalid
+    * instead of setting xmax, to make the tuple clearly invisible to
+    * everyone. In particular, we want HeapTupleSatisfiesDirty() to regard
+    * the tuple as dead, so that another backend inserting a duplicate key
+    * value won't unnecessarily wait for our transaction to finish.
+    */
+   if (!speculative)
+   {
+       HeapTupleHeaderSetXmax(tp.t_data, new_xmax);
+       HeapTupleHeaderSetCmax(tp.t_data, cid, iscombo);
+   }
+   else
+   {
+       HeapTupleHeaderSetXmin(tp.t_data, InvalidTransactionId);
+   }
+
    /* Make sure there is no forward chain link in t_ctid */
-   tp.t_data->t_ctid = tp.t_self;
+   tp.t_data->t_tidstate.t_ctid = tp.t_self;
 
    MarkBufferDirty(buffer);
 
@@ -2883,7 +2936,11 @@ l1:
        if (RelationIsAccessibleInLogicalDecoding(relation))
            log_heap_new_cid(relation, &tp);
 
-       xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
+       xlrec.flags = 0;
+       if (all_visible_cleared)
+           xlrec.flags |= XLOG_HEAP_ALL_VISIBLE_CLEARED;
+       if (speculative)
+           xlrec.flags |= XLOG_HEAP_SPECULATIVE_TUPLE;
        xlrec.infobits_set = compute_infobits(tp.t_data->t_infomask,
                                              tp.t_data->t_infomask2);
        xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self);
@@ -2971,6 +3028,52 @@ l1:
    return HeapTupleMayBeUpdated;
 }
 
+/*
+ * heap_finish_speculative - set speculative tuple as permanent (or
+ * unsuccessful).
+ *
+ * This routine may be used to mark a tuple originally only inserted
+ * speculatively as a bonafide, permanent tuple.  The t_ctid field (which will
+ * contain a speculative token value) is modified in place to point to the
+ * tuple itself, which is characteristic of a newly inserted ordinary tuple.
+ * Alternatively, the tuple is "super deleted" when a speculative insertion was
+ * unsuccessful.
+ */
+void
+heap_finish_speculative(Relation relation, HeapTuple tuple, bool conflict)
+{
+   if (!conflict)
+   {
+       /*
+        * Affirm successful speculative insertion, making it physically
+        * indistinguishable from a tuple that was inserted in the conventional
+        * manner.
+        */
+       heap_affirm_insert(relation, tuple);
+   }
+   else
+   {
+       HeapUpdateFailureData hufd;
+
+       /*
+        * "Super delete" tuple due to conflict from concurrent insert.
+        *
+        * This is occasionally necessary so that "unprincipled deadlocks" are
+        * avoided;  now that a conflict was found, other sessions should not
+        * wait on our speculative token, and they certainly shouldn't treat
+        * our speculatively-inserted heap tuple as an ordinary tuple that it
+        * must wait on the outcome of our xact to UPDATE/DELETE.  This makes
+        * heap tuples behave as conceptual "value locks" of short duration,
+        * distinct from ordinary tuples that other xacts must wait on
+        * xmin-xact-end of in the event of a possible unique/exclusion
+        * violation (the violation that arbitrates taking the alternative
+        * path).
+        */
+       heap_delete(relation, &(tuple->t_self), FirstCommandId,
+                   InvalidSnapshot, false, &hufd, true);
+   }
+}
+
 /*
  * simple_heap_delete - delete a tuple
  *
@@ -2988,7 +3091,7 @@ simple_heap_delete(Relation relation, ItemPointer tid)
    result = heap_delete(relation, tid,
                         GetCurrentCommandId(true), InvalidSnapshot,
                         true /* wait for commit */ ,
-                        &hufd);
+                        &hufd, false);
    switch (result)
    {
        case HeapTupleSelfUpdated:
@@ -3388,7 +3491,7 @@ l2:
               result == HeapTupleUpdated ||
               result == HeapTupleBeingUpdated);
        Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
-       hufd->ctid = oldtup.t_data->t_ctid;
+       hufd->ctid = oldtup.t_data->t_tidstate.t_ctid;
        hufd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data);
        if (result == HeapTupleSelfUpdated)
            hufd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data);
@@ -3537,7 +3640,7 @@ l2:
        oldtup.t_data->t_infomask2 |= infomask2_old_tuple;
        HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
        /* temporarily make it look not-updated */
-       oldtup.t_data->t_ctid = oldtup.t_self;
+       oldtup.t_data->t_tidstate.t_ctid = oldtup.t_self;
        already_marked = true;
        LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
@@ -3688,7 +3791,7 @@ l2:
        HeapTupleClearHeapOnly(newtup);
    }
 
-   RelationPutHeapTuple(relation, newbuf, heaptup);    /* insert new tuple */
+   RelationPutHeapTuple(relation, newbuf, heaptup, false); /* insert new tuple */
 
    if (!already_marked)
    {
@@ -3704,7 +3807,7 @@ l2:
    }
 
    /* record address of new tuple in t_ctid of old one */
-   oldtup.t_data->t_ctid = heaptup->t_self;
+   oldtup.t_data->t_tidstate.t_ctid = heaptup->t_self;
 
    /* clear PD_ALL_VISIBLE flags */
    if (PageIsAllVisible(BufferGetPage(buffer)))
@@ -4140,7 +4243,7 @@ l3:
        xwait = HeapTupleHeaderGetRawXmax(tuple->t_data);
        infomask = tuple->t_data->t_infomask;
        infomask2 = tuple->t_data->t_infomask2;
-       ItemPointerCopy(&tuple->t_data->t_ctid, &t_ctid);
+       ItemPointerCopy(&tuple->t_data->t_tidstate.t_ctid, &t_ctid);
 
        LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
 
@@ -4558,7 +4661,7 @@ failed:
        Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated ||
               result == HeapTupleWouldBlock);
        Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID));
-       hufd->ctid = tuple->t_data->t_ctid;
+       hufd->ctid = tuple->t_data->t_tidstate.t_ctid;
        hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data);
        if (result == HeapTupleSelfUpdated)
            hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data);
@@ -4620,7 +4723,7 @@ failed:
     * the tuple as well.
     */
    if (HEAP_XMAX_IS_LOCKED_ONLY(new_infomask))
-       tuple->t_data->t_ctid = *tid;
+       tuple->t_data->t_tidstate.t_ctid = *tid;
 
    MarkBufferDirty(*buffer);
 
@@ -5301,7 +5404,7 @@ l4:
 
        /* if we find the end of update chain, we're done. */
        if (mytup.t_data->t_infomask & HEAP_XMAX_INVALID ||
-           ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) ||
+           ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_tidstate.t_ctid) ||
            HeapTupleHeaderIsOnlyLocked(mytup.t_data))
        {
            UnlockReleaseBuffer(buf);
@@ -5310,7 +5413,7 @@ l4:
 
        /* tail recursion */
        priorXmax = HeapTupleHeaderGetUpdateXid(mytup.t_data);
-       ItemPointerCopy(&(mytup.t_data->t_ctid), &tupid);
+       ItemPointerCopy(&(mytup.t_data->t_tidstate.t_ctid), &tupid);
        UnlockReleaseBuffer(buf);
    }
 }
@@ -5361,6 +5464,77 @@ heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
    return HeapTupleMayBeUpdated;
 }
 
+/*
+ * heap_affirm_insert - clear speculative token from tuple
+ *
+ * It would not be okay for a transaction to commit without confirming outcome
+ * of speculative insertion. Occasionally, super deletion is necessary (if an
+ * attempt at speculative insertion failed).  More often, this routine affirms
+ * a speculative insertion was successful.
+ *
+ * The need to WAL-log this action may not be obvious (logical decoding does
+ * not require it).  Doing so allows the implementation to not have to consider
+ * speculative tuples with an in-doubt status.  Either a transaction is in
+ * progress and its tuples may be speculative, or it committed and they cannot
+ * be, or it aborted and it doesn't matter either way.
+ */
+static void
+heap_affirm_insert(Relation relation, HeapTuple tuple)
+{
+   Buffer      buffer;
+   Page        page;
+   OffsetNumber offnum;
+   ItemId      lp = NULL;
+   HeapTupleHeader htup;
+
+   buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
+   LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+   page = (Page) BufferGetPage(buffer);
+
+   offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
+   if (PageGetMaxOffsetNumber(page) >= offnum)
+       lp = PageGetItemId(page, offnum);
+
+   if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsNormal(lp))
+       elog(ERROR, "heap_inplace_update: invalid lp");
+
+   htup = (HeapTupleHeader) PageGetItem(page, lp);
+
+   /* NO EREPORT(ERROR) from here till changes are logged */
+   START_CRIT_SECTION();
+
+   Assert(HeapTupleHeaderIsSpeculative(tuple->t_data));
+
+   MarkBufferDirty(buffer);
+
+   /*
+    * Make sure there is no apparent forward chain link in t_ctid.
+    * Speculative inserters rely on this (in fact, the forward link is a
+    * speculative token value).
+    */
+   htup->t_tidstate.t_ctid = tuple->t_self;
+
+   /* XLOG stuff */
+   if (RelationNeedsWAL(relation))
+   {
+       xl_heap_affirm xlrec;
+       XLogRecPtr  recptr;
+
+       xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
+
+       XLogBeginInsert();
+       XLogRegisterData((char *) &xlrec, SizeOfHeapAffirm);
+       XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
+
+       recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_AFFIRM);
+
+       PageSetLSN(page, recptr);
+   }
+
+   END_CRIT_SECTION();
+
+   UnlockReleaseBuffer(buffer);
+}
 
 /*
  * heap_inplace_update - update a tuple "in place" (ie, overwrite it)
@@ -7332,7 +7506,10 @@ heap_xlog_delete(XLogReaderState *record)
        HeapTupleHeaderClearHotUpdated(htup);
        fix_infomask_from_infobits(xlrec->infobits_set,
                                   &htup->t_infomask, &htup->t_infomask2);
-       HeapTupleHeaderSetXmax(htup, xlrec->xmax);
+       if (!(xlrec->flags & XLOG_HEAP_SPECULATIVE_TUPLE))
+           HeapTupleHeaderSetXmax(htup, xlrec->xmax);
+       else
+           HeapTupleHeaderSetXmin(htup, InvalidTransactionId);
        HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
 
        /* Mark the page as a candidate for pruning */
@@ -7342,7 +7519,7 @@ heap_xlog_delete(XLogReaderState *record)
            PageClearAllVisible(page);
 
        /* Make sure there is no forward chain link in t_ctid */
-       htup->t_ctid = target_tid;
+       htup->t_tidstate.t_ctid = target_tid;
        PageSetLSN(page, lsn);
        MarkBufferDirty(buffer);
    }
@@ -7432,7 +7609,7 @@ heap_xlog_insert(XLogReaderState *record)
        htup->t_hoff = xlhdr.t_hoff;
        HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
        HeapTupleHeaderSetCmin(htup, FirstCommandId);
-       htup->t_ctid = target_tid;
+       htup->t_tidstate.t_ctid = target_tid;
 
        if (PageAddItem(page, (Item) htup, newlen, xlrec->offnum,
                        true, true) == InvalidOffsetNumber)
@@ -7567,8 +7744,8 @@ heap_xlog_multi_insert(XLogReaderState *record)
            htup->t_hoff = xlhdr->t_hoff;
            HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
            HeapTupleHeaderSetCmin(htup, FirstCommandId);
-           ItemPointerSetBlockNumber(&htup->t_ctid, blkno);
-           ItemPointerSetOffsetNumber(&htup->t_ctid, offnum);
+           ItemPointerSetBlockNumber(&htup->t_tidstate.t_ctid, blkno);
+           ItemPointerSetOffsetNumber(&htup->t_tidstate.t_ctid, offnum);
 
            offnum = PageAddItem(page, (Item) htup, newlen, offnum, true, true);
            if (offnum == InvalidOffsetNumber)
@@ -7704,7 +7881,7 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
        HeapTupleHeaderSetXmax(htup, xlrec->old_xmax);
        HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
        /* Set forward chain link in t_ctid */
-       htup->t_ctid = newtid;
+       htup->t_tidstate.t_ctid = newtid;
 
        /* Mark the page as a candidate for pruning */
        PageSetPrunable(page, XLogRecGetXid(record));
@@ -7838,7 +8015,7 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
        HeapTupleHeaderSetCmin(htup, FirstCommandId);
        HeapTupleHeaderSetXmax(htup, xlrec->new_xmax);
        /* Make sure there is no forward chain link in t_ctid */
-       htup->t_ctid = newtid;
+       htup->t_tidstate.t_ctid = newtid;
 
        offnum = PageAddItem(page, (Item) htup, newlen, offnum, true, true);
        if (offnum == InvalidOffsetNumber)
@@ -7877,6 +8054,44 @@ heap_xlog_update(XLogReaderState *record, bool hot_update)
        XLogRecordPageWithFreeSpace(rnode, newblk, freespace);
 }
 
+static void
+heap_xlog_affirm(XLogReaderState *record)
+{
+   XLogRecPtr  lsn = record->EndRecPtr;
+   xl_heap_affirm *xlrec = (xl_heap_affirm *) XLogRecGetData(record);
+   Buffer      buffer;
+   Page        page;
+   OffsetNumber offnum;
+   ItemId      lp = NULL;
+   HeapTupleHeader htup;
+
+   if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO)
+   {
+       page = BufferGetPage(buffer);
+
+       offnum = xlrec->offnum;
+       if (PageGetMaxOffsetNumber(page) >= offnum)
+           lp = PageGetItemId(page, offnum);
+
+       if (PageGetMaxOffsetNumber(page) < offnum || !ItemIdIsNormal(lp))
+           elog(PANIC, "heap_affirm_redo: invalid lp");
+
+       htup = (HeapTupleHeader) PageGetItem(page, lp);
+
+       /*
+        * Affirm tuple as actually inserted
+        */
+       ItemPointerSet(&htup->t_tidstate.t_ctid,
+                      BufferGetBlockNumber(buffer),
+                      offnum);
+
+       PageSetLSN(page, lsn);
+       MarkBufferDirty(buffer);
+   }
+   if (BufferIsValid(buffer))
+       UnlockReleaseBuffer(buffer);
+}
+
 static void
 heap_xlog_lock(XLogReaderState *record)
 {
@@ -7912,7 +8127,7 @@ heap_xlog_lock(XLogReaderState *record)
        {
            HeapTupleHeaderClearHotUpdated(htup);
            /* Make sure there is no forward chain link in t_ctid */
-           ItemPointerSet(&htup->t_ctid,
+           ItemPointerSet(&htup->t_tidstate.t_ctid,
                           BufferGetBlockNumber(buffer),
                           offnum);
        }
@@ -8027,6 +8242,9 @@ heap_redo(XLogReaderState *record)
        case XLOG_HEAP_HOT_UPDATE:
            heap_xlog_update(record, true);
            break;
+       case XLOG_HEAP_AFFIRM:
+           heap_xlog_affirm(record);
+           break;
        case XLOG_HEAP_LOCK:
            heap_xlog_lock(record);
            break;
index 6d091f63af0dfda8c24607517fdad08631df11ef..e8b11a663b72a6b3f41fe2db200af33510d2e3e0 100644 (file)
@@ -35,7 +35,8 @@
 void
 RelationPutHeapTuple(Relation relation,
                     Buffer buffer,
-                    HeapTuple tuple)
+                    HeapTuple tuple,
+                    bool token)
 {
    Page        pageHeader;
    OffsetNumber offnum;
@@ -54,10 +55,19 @@ RelationPutHeapTuple(Relation relation,
    /* Update tuple->t_self to the actual position where it was stored */
    ItemPointerSet(&(tuple->t_self), BufferGetBlockNumber(buffer), offnum);
 
-   /* Insert the correct position into CTID of the stored tuple, too */
    itemId = PageGetItemId(pageHeader, offnum);
    item = PageGetItem(pageHeader, itemId);
-   ((HeapTupleHeader) item)->t_ctid = tuple->t_self;
+
+   if (!token)
+   {
+       /* Insert the correct position into CTID of the stored tuple, too */
+       ((HeapTupleHeader) item)->t_tidstate.t_ctid = tuple->t_self;
+   }
+   else
+   {
+       /* Speculatively inserted tuples should retain token value instead */
+       Assert(HeapTupleHeaderIsSpeculative(((HeapTupleHeader) item)));
+   }
 }
 
 /*
index 563e5c353cda36784da1b6f4b609e677a70e5e86..f5cf5f7f10a188612fc18f303418b613f8ca20e7 100644 (file)
@@ -547,9 +547,9 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
        /*
         * Advance to next chain member.
         */
-       Assert(ItemPointerGetBlockNumber(&htup->t_ctid) ==
+       Assert(ItemPointerGetBlockNumber(&htup->t_tidstate.t_ctid) ==
               BufferGetBlockNumber(buffer));
-       offnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
+       offnum = ItemPointerGetOffsetNumber(&htup->t_tidstate.t_ctid);
        priorXmax = HeapTupleHeaderGetUpdateXid(htup);
    }
 
@@ -774,7 +774,7 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
                continue;
 
            /* Set up to scan the HOT-chain */
-           nextoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
+           nextoffnum = ItemPointerGetOffsetNumber(&htup->t_tidstate.t_ctid);
            priorXmax = HeapTupleHeaderGetUpdateXid(htup);
        }
        else
@@ -815,7 +815,7 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
            if (!HeapTupleHeaderIsHotUpdated(htup))
                break;
 
-           nextoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
+           nextoffnum = ItemPointerGetOffsetNumber(&htup->t_tidstate.t_ctid);
            priorXmax = HeapTupleHeaderGetUpdateXid(htup);
        }
    }
index af5c158fc68984a776065be05b46a7d4ccf07ea4..43740ba820a218df7282bba5c6fba930ffbfadb2 100644 (file)
@@ -324,7 +324,7 @@ end_heap_rewrite(RewriteState state)
 
    while ((unresolved = hash_seq_search(&seq_status)) != NULL)
    {
-       ItemPointerSetInvalid(&unresolved->tuple->t_data->t_ctid);
+       ItemPointerSetInvalid(&unresolved->tuple->t_data->t_tidstate.t_ctid);
        raw_heap_insert(state, unresolved->tuple);
    }
 
@@ -414,7 +414,7 @@ rewrite_heap_tuple(RewriteState state,
     * Invalid ctid means that ctid should point to the tuple itself. We'll
     * override it later if the tuple is part of an update chain.
     */
-   ItemPointerSetInvalid(&new_tuple->t_data->t_ctid);
+   ItemPointerSetInvalid(&new_tuple->t_data->t_tidstate.t_ctid);
 
    /*
     * If the tuple has been updated, check the old-to-new mapping hash table.
@@ -422,13 +422,13 @@ rewrite_heap_tuple(RewriteState state,
    if (!((old_tuple->t_data->t_infomask & HEAP_XMAX_INVALID) ||
          HeapTupleHeaderIsOnlyLocked(old_tuple->t_data)) &&
        !(ItemPointerEquals(&(old_tuple->t_self),
-                           &(old_tuple->t_data->t_ctid))))
+                           &(old_tuple->t_data->t_tidstate.t_ctid))))
    {
        OldToNewMapping mapping;
 
        memset(&hashkey, 0, sizeof(hashkey));
        hashkey.xmin = HeapTupleHeaderGetUpdateXid(old_tuple->t_data);
-       hashkey.tid = old_tuple->t_data->t_ctid;
+       hashkey.tid = old_tuple->t_data->t_tidstate.t_ctid;
 
        mapping = (OldToNewMapping)
            hash_search(state->rs_old_new_tid_map, &hashkey,
@@ -441,7 +441,7 @@ rewrite_heap_tuple(RewriteState state,
             * set the ctid of this tuple to point to the new location, and
             * insert it right away.
             */
-           new_tuple->t_data->t_ctid = mapping->new_tid;
+           new_tuple->t_data->t_tidstate.t_ctid = mapping->new_tid;
 
            /* We don't need the mapping entry anymore */
            hash_search(state->rs_old_new_tid_map, &hashkey,
@@ -527,7 +527,7 @@ rewrite_heap_tuple(RewriteState state,
                new_tuple = unresolved->tuple;
                free_new = true;
                old_tid = unresolved->old_tid;
-               new_tuple->t_data->t_ctid = new_tid;
+               new_tuple->t_data->t_tidstate.t_ctid = new_tid;
 
                /*
                 * We don't need the hash entry anymore, but don't free its
@@ -725,7 +725,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
     * Insert the correct position into CTID of the stored tuple, too, if the
     * caller didn't supply a valid CTID.
     */
-   if (!ItemPointerIsValid(&tup->t_data->t_ctid))
+   if (!ItemPointerIsValid(&tup->t_data->t_tidstate.t_ctid))
    {
        ItemId      newitemid;
        HeapTupleHeader onpage_tup;
@@ -733,7 +733,7 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
        newitemid = PageGetItemId(page, newoff);
        onpage_tup = (HeapTupleHeader) PageGetItem(page, newitemid);
 
-       onpage_tup->t_ctid = tup->t_self;
+       onpage_tup->t_tidstate.t_ctid = tup->t_self;
    }
 
    /* If heaptup is a private copy, release it. */
index 8464e8794f65e299b5eb44dc0f1d589d081f0f89..181a79bb2c0a78e9336908c7f25c0a95e40cbdc7 100644 (file)
@@ -1089,7 +1089,7 @@ toast_flatten_tuple(HeapTuple tup, TupleDesc tupleDesc)
    new_tuple->t_tableOid = tup->t_tableOid;
 
    new_tuple->t_data->t_choice = tup->t_data->t_choice;
-   new_tuple->t_data->t_ctid = tup->t_data->t_ctid;
+   new_tuple->t_data->t_tidstate.t_ctid = tup->t_data->t_tidstate.t_ctid;
    new_tuple->t_data->t_infomask &= ~HEAP_XACT_MASK;
    new_tuple->t_data->t_infomask |=
        tup->t_data->t_infomask & HEAP_XACT_MASK;
index ef68a7145fce5f5352196a4b1c751aee26e10859..c7a774a7f035a6f175f54c640d012721e0c7c1a8 100644 (file)
@@ -51,7 +51,8 @@ static Buffer _bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf);
 static TransactionId _bt_check_unique(Relation rel, IndexTuple itup,
                 Relation heapRel, Buffer buf, OffsetNumber offset,
                 ScanKey itup_scankey,
-                IndexUniqueCheck checkUnique, bool *is_unique);
+                IndexUniqueCheck checkUnique, bool *is_unique,
+                uint32 *speculativeToken);
 static void _bt_findinsertloc(Relation rel,
                  Buffer *bufptr,
                  OffsetNumber *offsetptr,
@@ -159,17 +160,27 @@ top:
     */
    if (checkUnique != UNIQUE_CHECK_NO)
    {
-       TransactionId xwait;
+       TransactionId   xwait;
+       uint32          speculativeToken;
 
        offset = _bt_binsrch(rel, buf, natts, itup_scankey, false);
        xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey,
-                                checkUnique, &is_unique);
+                                checkUnique, &is_unique, &speculativeToken);
 
        if (TransactionIdIsValid(xwait))
        {
            /* Have to wait for the other guy ... */
            _bt_relbuf(rel, buf);
-           XactLockTableWait(xwait, rel, &itup->t_tid, XLTW_InsertIndex);
+           /*
+            * If it's a speculative insertion, wait for it to finish (ie.
+            * to go ahead with the insertion, or kill the tuple). Otherwise
+            * wait for the transaction to finish as usual.
+            */
+           if (speculativeToken)
+               SpeculativeInsertionWait(xwait, speculativeToken);
+           else
+               XactLockTableWait(xwait, rel, &itup->t_tid, XLTW_InsertIndex);
+
            /* start over... */
            _bt_freestack(stack);
            goto top;
@@ -211,9 +222,12 @@ top:
  * also point to end-of-page, which means that the first tuple to check
  * is the first tuple on the next page.
  *
- * Returns InvalidTransactionId if there is no conflict, else an xact ID
- * we must wait for to see if it commits a conflicting tuple.   If an actual
- * conflict is detected, no return --- just ereport().
+ * Returns InvalidTransactionId if there is no conflict, else an xact ID we
+ * must wait for to see if it commits a conflicting tuple. If an actual
+ * conflict is detected, no return --- just ereport(). If an xact ID is
+ * returned, and the conflicting tuple still has a speculative insertion in
+ * progress, *speculativeToken is set to non-zero, and the caller can wait for
+ * the verdict on the insertion using SpeculativeInsertionWait().
  *
  * However, if checkUnique == UNIQUE_CHECK_PARTIAL, we always return
  * InvalidTransactionId because we don't want to wait.  In this case we
@@ -223,7 +237,8 @@ top:
 static TransactionId
 _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
                 Buffer buf, OffsetNumber offset, ScanKey itup_scankey,
-                IndexUniqueCheck checkUnique, bool *is_unique)
+                IndexUniqueCheck checkUnique, bool *is_unique,
+                uint32 *speculativeToken)
 {
    TupleDesc   itupdesc = RelationGetDescr(rel);
    int         natts = rel->rd_rel->relnatts;
@@ -340,6 +355,7 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
                        if (nbuf != InvalidBuffer)
                            _bt_relbuf(rel, nbuf);
                        /* Tell _bt_doinsert to wait... */
+                       *speculativeToken = SnapshotDirty.speculativeToken;
                        return xwait;
                    }
 
index 4f06a2637aec4025e665734e4d8c7f87b5c57995..b8846fa3e0a1a0ac127c4e3319baaef6cb11ea91 100644 (file)
@@ -75,6 +75,12 @@ heap_desc(StringInfo buf, XLogReaderState *record)
                         xlrec->new_offnum,
                         xlrec->new_xmax);
    }
+   else if (info == XLOG_HEAP_AFFIRM)
+   {
+       xl_heap_affirm *xlrec = (xl_heap_affirm *) rec;
+
+       appendStringInfo(buf, "off %u", xlrec->offnum);
+   }
    else if (info == XLOG_HEAP_LOCK)
    {
        xl_heap_lock *xlrec = (xl_heap_lock *) rec;
@@ -177,6 +183,9 @@ heap_identify(uint8 info)
        case XLOG_HEAP_HOT_UPDATE | XLOG_HEAP_INIT_PAGE:
            id = "HOT_UPDATE+INIT";
            break;
+       case XLOG_HEAP_AFFIRM:
+           id = "HEAP_AFFIRM";
+           break;
        case XLOG_HEAP_LOCK:
            id = "LOCK";
            break;
index ac3b785b5a7230fdfba83a0c5fb7620123ca4c0a..35127b1f27519bb6dcc238cd72ee241d31487c4e 100644 (file)
@@ -1665,6 +1665,10 @@ BuildIndexInfo(Relation index)
    /* other info */
    ii->ii_Unique = indexStruct->indisunique;
    ii->ii_ReadyForInserts = IndexIsReady(indexStruct);
+   /* assume not doing speculative insertion for now */
+   ii->ii_UniqueOps = NULL;
+   ii->ii_UniqueProcs = NULL;
+   ii->ii_UniqueStrats = NULL;
 
    /* initialize index-build state to default */
    ii->ii_Concurrent = false;
@@ -1673,6 +1677,53 @@ BuildIndexInfo(Relation index)
    return ii;
 }
 
+/* ----------------
+ *     IndexInfoSpeculative
+ *         Append extra state to IndexInfo record
+ *
+ * For unique indexes, we usually don't want to add info to the IndexInfo for
+ * checking uniqueness, since the B-Tree AM handles that directly.  However, in
+ * the case of speculative insertion, external support is required.
+ *
+ * Do this processing here rather than in BuildIndexInfo() to save the common
+ * non-speculative cases the overhead they'd otherwise incur.
+ * ----------------
+ */
+void
+IndexInfoSpeculative(Relation index, IndexInfo *ii)
+{
+   int         ncols = index->rd_rel->relnatts;
+   int         i;
+
+   /*
+    * fetch info for checking unique indexes
+    */
+   Assert(ii->ii_Unique);
+
+   if (index->rd_rel->relam != BTREE_AM_OID)
+       elog(ERROR, "unexpected non-btree speculative unique index");
+
+   ii->ii_UniqueOps = (Oid *) palloc(sizeof(Oid) * ncols);
+   ii->ii_UniqueProcs = (Oid *) palloc(sizeof(Oid) * ncols);
+   ii->ii_UniqueStrats = (uint16 *) palloc(sizeof(uint16) * ncols);
+
+   /*
+    * We have to look up the operator's strategy number.  This
+    * provides a cross-check that the operator does match the index.
+    */
+   /* We need the func OIDs and strategy numbers too */
+   for (i = 0; i < ncols; i++)
+   {
+       ii->ii_UniqueStrats[i] = BTEqualStrategyNumber;
+       ii->ii_UniqueOps[i] =
+           get_opfamily_member(index->rd_opfamily[i],
+                               index->rd_opcintype[i],
+                               index->rd_opcintype[i],
+                               ii->ii_UniqueStrats[i]);
+       ii->ii_UniqueProcs[i] = get_opcode(ii->ii_UniqueOps[i]);
+   }
+}
+
 /* ----------------
  *     FormIndexDatum
  *         Construct values[] and isnull[] arrays for a new index tuple.
@@ -2609,10 +2660,10 @@ IndexCheckExclusion(Relation heapRelation,
        /*
         * Check that this tuple has no conflicts.
         */
-       check_exclusion_constraint(heapRelation,
-                                  indexRelation, indexInfo,
-                                  &(heapTuple->t_self), values, isnull,
-                                  estate, true, false);
+       check_exclusion_or_unique_constraint(heapRelation, indexRelation,
+                                            indexInfo, &(heapTuple->t_self),
+                                            values, isnull, estate, true,
+                                            false, true, NULL);
    }
 
    heap_endscan(scan);
index fe123addac0ce79ba9d1e6ce20fb040c3dc81291..0231084c7c9223405582ddd7affa7409057128d7 100644 (file)
@@ -46,7 +46,7 @@ CatalogOpenIndexes(Relation heapRel)
    resultRelInfo->ri_RelationDesc = heapRel;
    resultRelInfo->ri_TrigDesc = NULL;  /* we don't fire triggers */
 
-   ExecOpenIndices(resultRelInfo);
+   ExecOpenIndices(resultRelInfo, false);
 
    return resultRelInfo;
 }
index 561d8fae574e626d09b26a33767b60a072bc4858..d5ab12fd31b82ce8adf4873102744b7db04f9043 100644 (file)
@@ -170,9 +170,10 @@ unique_key_recheck(PG_FUNCTION_ARGS)
         * For exclusion constraints we just do the normal check, but now it's
         * okay to throw error.
         */
-       check_exclusion_constraint(trigdata->tg_relation, indexRel, indexInfo,
-                                  &(new_row->t_self), values, isnull,
-                                  estate, false, false);
+       check_exclusion_or_unique_constraint(trigdata->tg_relation, indexRel,
+                                            indexInfo, &(new_row->t_self),
+                                            values, isnull, estate, false,
+                                            false, true, NULL);
    }
 
    /*
index 92ff632e124e679cf5e29470321b29813c6d90bf..669bca406e5a322ed6f2e92a13fd003a2f1d3754 100644 (file)
@@ -2283,7 +2283,7 @@ CopyFrom(CopyState cstate)
                      1,        /* dummy rangetable index */
                      0);
 
-   ExecOpenIndices(resultRelInfo);
+   ExecOpenIndices(resultRelInfo, false);
 
    estate->es_result_relations = resultRelInfo;
    estate->es_num_result_relations = 1;
@@ -2438,7 +2438,8 @@ CopyFrom(CopyState cstate)
 
                if (resultRelInfo->ri_NumIndices > 0)
                    recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-                                                          estate);
+                                                          estate, false,
+                                                          NIL);
 
                /* AFTER ROW INSERT Triggers */
                ExecARInsertTriggers(estate, resultRelInfo, tuple,
@@ -2552,7 +2553,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
            ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
            recheckIndexes =
                ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
-                                     estate);
+                                     estate, false, NIL);
            ExecARInsertTriggers(estate, resultRelInfo,
                                 bufferedTuples[i],
                                 recheckIndexes);
index 315a52849c9494afd3d554639e85b7f765a4d28a..aec1e9fb80a98d77af3ea013bfe65d06d9b316f1 100644 (file)
@@ -2320,6 +2320,8 @@ show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
    const char *foperation;
    bool        labeltargets;
    int         j;
+   List       *idxNames = NIL;
+   ListCell   *lst;
 
    switch (node->operation)
    {
@@ -2405,6 +2407,26 @@ show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
        }
    }
 
+   foreach(lst, node->arbiterIndexes)
+   {
+       char       *indexname = get_rel_name(lfirst_oid(lst));
+
+       idxNames = lappend(idxNames, indexname);
+   }
+
+   /*
+    * Make sure that there is still an arbiter property list when ON CONFLICT
+    * IGNORE is used, and an inference specification is omitted (Non-text
+    * format explains will show an empty array, which seems appropriate
+    * there).
+    */
+   if (node->spec == SPEC_IGNORE && idxNames == NIL &&
+       es->format == EXPLAIN_FORMAT_TEXT)
+       idxNames = lappend(idxNames, "(All)");
+
+   if (node->spec == SPEC_IGNORE)
+       ExplainPropertyList("Conflict Arbiter Indexes", idxNames, es);
+
    if (labeltargets)
        ExplainCloseGroup("Target Tables", "Target Tables", false, es);
 }
index 6d316d62b6c58ca0c49ad3b6b925e9db23f5432b..38b99f24965d407f801443fba7f61d00da1442ff 100644 (file)
@@ -359,7 +359,7 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
    HeapTupleHeaderSetCmin(tuple->t_data, FirstCommandId);
    HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId);
    tuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
-   ItemPointerSet(&tuple->t_data->t_ctid, 0, FirstOffsetNumber);
+   ItemPointerSet(&tuple->t_data->t_tidstate.t_ctid, 0, FirstOffsetNumber);
 
    /* check the comment above nextval_internal()'s equivalent call. */
    if (RelationNeedsWAL(rel))
index 8afa1e3e4a7596475cbf19a76c88d48a04aeef02..892f9fe035bfbadf10de3efbc0636e148202ca2b 100644 (file)
@@ -200,3 +200,75 @@ is no explicit prohibition on SRFs in UPDATE, but the net effect will be
 that only the first result row of an SRF counts, because all subsequent
 rows will result in attempts to re-update an already updated target row.
 This is historical behavior and seems not worth changing.)
+
+Speculative insertion
+---------------------
+
+Speculative insertion is a process that the executor manages for the benefit of
+INSERT...ON CONFLICT IGNORE.  Supported indexes include nbtree unique
+indexes (nbtree is currently the only amcanunique index access method), or
+exclusion constraint indexes (exclusion constraints are considered a
+generalization of unique constraints).
+
+The primary user-visible goal for INSERT ... ON CONFLICT is to guarantee either
+an insert, or a conclusive determination that an insert cannot go ahead (due to
+a conclusively committed/visible conflict).  A would-be conflict (and the
+associated index) are the arbiters of whether or not the alternative (IGNORE)
+path is taken.  The implementation more or less tries to insert until one or
+the other of those two outcomes is reached.  There are some non-obvious hazards
+involved that are carefully avoided.  These hazards relate to concurrent
+activity causing conflicts for the implementation, which must be handled.
+
+The index is the authoritative source of truth for whether there is or is not a
+conflict, for unique index enforcement in general, and for speculative
+insertion in particular.  The heap must still be considered, though, not least
+since it alone has authoritative visibility information.  Through looping, we
+hope to overcome the disconnect between the heap and the arbiter index.
+Theoretically, some individual session could loop forever, although under high
+concurrency one session always proceeds.
+
+The first step in the loop is to perform a pre-check.  The indexes are scanned
+for existing conflicting values.  At this point, we may have to wait until the
+end of another xact (or xact's promise token -- more on that later), iff it
+isn't immediately conclusive that there is or is not a conflict (when we finish
+the pre-check, there is a conclusion about there either being or
+not being a conflict).
+
+The second step (skipped when a conflict is found) is to insert a heap tuple
+and related index tuples opportunistically.  This uses the same mechanism as
+deferred unique indexes, and so we never wait for a possibly conflicting xact
+to commit or abort (unlike with conventional unique index insertion) -- we
+simply detect a possible conflict.
+
+When opportunistically inserting during the second step, we are not logically
+inserting a tuple as such.  Rather, the process is somewhat similar to the
+conventional unique index insertion steps taken within the nbtree AM, where we
+must briefly lock the *value* being inserted:  in that codepath, the value
+proposed for insertion is for an instant locked *in the abstract*, by way of a
+buffer lock on "the first leaf page the value could be on".  Then, having
+established the right to physically insert, do so (or throw an error).  For
+speculative insertion, if no conflict occurs during the insertion (which is
+usually the case, since it was just determined in the first step that there was
+no conflict), then we're done.  Otherwise, we must restart (and likely find the
+same conflict tuple during the first step of the new iteration). But a
+counter-intuitive step must be taken first (which is what makes this whole
+dance similar to conventional nbtree "value locking").
+
+We must "super delete" the tuple when the opportunistic insertion finds a
+conflict.  This means that it immediately becomes invisible to all snapshot
+types, and immediately becomes reclaimable by VACUUM.  Other backends
+(speculative inserters or ordinary inserters) know to not wait on our
+transaction end when they encounter an optimistically inserted "promise tuple".
+Rather, they wait on a corresponding promise token lock, which we hold only for
+as long as opportunistically inserting.  We release the lock when done
+opportunistically inserting (and after "super deleting", if that proved
+necessary), releasing our waiters (who will ordinarily re-find our promise
+tuple as a bona fide tuple, or occasionally will find that they can insert
+after all).  It's important that other xacts not wait on the end of our xact
+until we've established that we've successfully and conclusively inserted
+logically (or established that there was an insertion conflict, and cleaned up
+after it by "super deleting").  Otherwise, concurrent speculative inserters
+could be involved in "unprincipled deadlocks":  deadlocks where there is no
+user-visible mutual dependency, and yet an implementation related mutual
+dependency is unexpectedly introduced.  The user might be left with no
+reasonable way of avoiding these deadlocks, which would not be okay.
index 90d37b566ae6e2549cd97077c87989a22b7798ae..ba85577f6c5f02907fd2acfdcee1b85f90c7a39a 100644 (file)
@@ -2097,7 +2097,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
             * the latest version of the row was deleted, so we need do
             * nothing.  (Should be safe to examine xmin without getting
             * buffer's content lock, since xmin never changes in an existing
-            * tuple.)
+            * non-promise tuple.)
             */
            if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data),
                                     priorXmax))
@@ -2197,6 +2197,9 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
                        ereport(ERROR,
                                (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
                                 errmsg("could not serialize access due to concurrent update")));
+
+                   /* Should not encounter speculative tuple on recheck */
+                   Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data));
                    if (!ItemPointerEquals(&hufd.ctid, &tuple.t_self))
                    {
                        /* it was updated, so look at the updated version */
@@ -2259,7 +2262,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
         * As above, it should be safe to examine xmax and t_ctid without the
         * buffer content lock, because they can't be changing.
         */
-       if (ItemPointerEquals(&tuple.t_self, &tuple.t_data->t_ctid))
+       if (ItemPointerEquals(&tuple.t_self, &tuple.t_data->t_tidstate.t_ctid))
        {
            /* deleted, so forget about it */
            ReleaseBuffer(buffer);
@@ -2267,7 +2270,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
        }
 
        /* updated, so look at the updated row */
-       tuple.t_self = tuple.t_data->t_ctid;
+       tuple.t_self = tuple.t_data->t_tidstate.t_ctid;
        /* updated row should have xmin matching this xmax */
        priorXmax = HeapTupleHeaderGetUpdateXid(tuple.t_data);
        ReleaseBuffer(buffer);
index 0736d2a310254e959a0fd56077c7d0b41322e587..95fc21e66854a377fcca3f373366c2df49d168d7 100644 (file)
@@ -44,6 +44,7 @@
 
 #include "access/relscan.h"
 #include "access/transam.h"
+#include "access/xact.h"
 #include "catalog/index.h"
 #include "executor/execdebug.h"
 #include "nodes/nodeFuncs.h"
@@ -887,7 +888,7 @@ ExecCloseScanRelation(Relation scanrel)
  * ----------------------------------------------------------------
  */
 void
-ExecOpenIndices(ResultRelInfo *resultRelInfo)
+ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative)
 {
    Relation    resultRelation = resultRelInfo->ri_RelationDesc;
    List       *indexoidlist;
@@ -940,6 +941,13 @@ ExecOpenIndices(ResultRelInfo *resultRelInfo)
        /* extract index key information from the index's pg_index info */
        ii = BuildIndexInfo(indexDesc);
 
+       /*
+        * Iff the indexes are to be used for speculative insertion, add extra
+        * information required by unique index entries
+        */
+       if (speculative && ii->ii_Unique)
+           IndexInfoSpeculative(indexDesc, ii);
+
        relationDescs[i] = indexDesc;
        indexInfoArray[i] = ii;
        i++;
@@ -992,7 +1000,8 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
  *
  *     This returns a list of index OIDs for any unique or exclusion
  *     constraints that are deferred and that had
- *     potential (unconfirmed) conflicts.
+ *     potential (unconfirmed) conflicts. (if noDupErr == true, the
+ *     same is done for non-deferred constraints)
  *
  *     CAUTION: this must not be called for a HOT update.
  *     We can't defend against that here for lack of info.
@@ -1002,7 +1011,9 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
 List *
 ExecInsertIndexTuples(TupleTableSlot *slot,
                      ItemPointer tupleid,
-                     EState *estate)
+                     EState *estate,
+                     bool noDupErr,
+                     List *arbiterIndexes)
 {
    List       *result = NIL;
    ResultRelInfo *resultRelInfo;
@@ -1042,12 +1053,17 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
        IndexInfo  *indexInfo;
        IndexUniqueCheck checkUnique;
        bool        satisfiesConstraint;
+       bool        arbiter;
 
        if (indexRelation == NULL)
            continue;
 
        indexInfo = indexInfoArray[i];
 
+       /* Record if speculative insertion arbiter */
+       arbiter = list_member_oid(arbiterIndexes,
+                                 indexRelation->rd_index->indexrelid);
+
        /* If the index is marked as read-only, ignore it */
        if (!indexInfo->ii_ReadyForInserts)
            continue;
@@ -1072,7 +1088,17 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 
            /* Skip this index-update if the predicate isn't satisfied */
            if (!ExecQual(predicate, econtext, false))
+           {
+               if (arbiter)
+                   ereport(ERROR,
+                           (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
+                            errmsg("inferred arbiter partial unique index has predicate that fails to cover tuple proposed for insertion"),
+                            errdetail("ON CONFLICT inference clause implies that the tuple proposed for insertion must be covered by predicate for partial index \"%s\".",
+                                      RelationGetRelationName(indexRelation)),
+                            errtableconstraint(heapRelation,
+                                               RelationGetRelationName(indexRelation))));
                continue;
+           }
        }
 
        /*
@@ -1094,9 +1120,14 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
         * For a deferrable unique index, we tell the index AM to just detect
         * possible non-uniqueness, and we add the index OID to the result
         * list if further checking is needed.
+        *
+        * For a speculative insertion (used by INSERT ... ON CONFLICT), just
+        * detect possible non-uniqueness, and tell the caller if it failed.
         */
        if (!indexRelation->rd_index->indisunique)
            checkUnique = UNIQUE_CHECK_NO;
+       else if (noDupErr && (arbiterIndexes == NIL || arbiter))
+           checkUnique = UNIQUE_CHECK_PARTIAL;
        else if (indexRelation->rd_index->indimmediate)
            checkUnique = UNIQUE_CHECK_YES;
        else
@@ -1114,8 +1145,11 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
         * If the index has an associated exclusion constraint, check that.
         * This is simpler than the process for uniqueness checks since we
         * always insert first and then check.  If the constraint is deferred,
-        * we check now anyway, but don't throw error on violation; instead
-        * we'll queue a recheck event.
+        * we check now anyway, but don't throw error on violation or wait for
+        * a conclusive outcome from a concurrent insertion; instead we'll
+        * queue a recheck event.  Similarly, noDupErr callers (speculative
+        * inserters) will recheck later, and wait for a conclusive outcome
+        * then.
         *
         * An index for an exclusion constraint can't also be UNIQUE (not an
         * essential property, we just don't allow it in the grammar), so no
@@ -1123,13 +1157,15 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
         */
        if (indexInfo->ii_ExclusionOps != NULL)
        {
-           bool        errorOK = !indexRelation->rd_index->indimmediate;
+           bool        violationOK = (!indexRelation->rd_index->indimmediate ||
+                                      noDupErr);
 
            satisfiesConstraint =
-               check_exclusion_constraint(heapRelation,
-                                          indexRelation, indexInfo,
-                                          tupleid, values, isnull,
-                                          estate, false, errorOK);
+               check_exclusion_or_unique_constraint(heapRelation,
+                                                    indexRelation, indexInfo,
+                                                    tupleid, values, isnull,
+                                                    estate, false,
+                                                    violationOK, false, NULL);
        }
 
        if ((checkUnique == UNIQUE_CHECK_PARTIAL ||
@@ -1148,18 +1184,161 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
    return result;
 }
 
+/* ----------------------------------------------------------------
+ *     ExecCheckIndexConstraints
+ *
+ *     This routine checks if a tuple violates any unique or
+ *     exclusion constraints. If no conflict, returns true.
+ *     Otherwise returns false, and the TID of the conflicting
+ *     tuple is returned in *conflictTid
+ *
+ *     Note that this doesn't lock the values in any way, so it's
+ *     possible that a conflicting tuple is inserted immediately
+ *     after this returns, and a later insert with the same values
+ *     still conflicts. But this can be used for a pre-check before
+ *     insertion.
+ * ----------------------------------------------------------------
+ */
+bool
+ExecCheckIndexConstraints(TupleTableSlot *slot,
+                         EState *estate, ItemPointer conflictTid,
+                         List *arbiterIndexes)
+{
+   ResultRelInfo *resultRelInfo;
+   int         i;
+   int         numIndices;
+   RelationPtr relationDescs;
+   Relation    heapRelation;
+   IndexInfo **indexInfoArray;
+   ExprContext *econtext;
+   Datum       values[INDEX_MAX_KEYS];
+   bool        isnull[INDEX_MAX_KEYS];
+   ItemPointerData invalidItemPtr;
+   bool        checkedIndex = false;
+
+   ItemPointerSetInvalid(conflictTid);
+   ItemPointerSetInvalid(&invalidItemPtr);
+
+   /*
+    * Get information from the result relation info structure.
+    */
+   resultRelInfo = estate->es_result_relation_info;
+   numIndices = resultRelInfo->ri_NumIndices;
+   relationDescs = resultRelInfo->ri_IndexRelationDescs;
+   indexInfoArray = resultRelInfo->ri_IndexRelationInfo;
+   heapRelation = resultRelInfo->ri_RelationDesc;
+
+   /*
+    * We will use the EState's per-tuple context for evaluating predicates
+    * and index expressions (creating it if it's not already there).
+    */
+   econtext = GetPerTupleExprContext(estate);
+
+   /* Arrange for econtext's scan tuple to be the tuple under test */
+   econtext->ecxt_scantuple = slot;
+
+   /*
+    * for each index, form and insert the index tuple
+    */
+   for (i = 0; i < numIndices; i++)
+   {
+       Relation    indexRelation = relationDescs[i];
+       IndexInfo  *indexInfo;
+       bool        satisfiesConstraint;
+       bool        arbiter;
+
+       if (indexRelation == NULL)
+           continue;
+
+       indexInfo = indexInfoArray[i];
+
+       if (!indexInfo->ii_Unique && !indexInfo->ii_ExclusionOps)
+           continue;
+
+       /* Record if speculative insertion arbiter */
+       arbiter = list_member_oid(arbiterIndexes,
+                                 indexRelation->rd_index->indexrelid);
+
+       if (!indexRelation->rd_index->indimmediate)
+           ereport(ERROR,
+                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                    errmsg("ON CONFLICT is not supported on relations with deferred unique constraints/exclusion constraints"),
+                    errtableconstraint(heapRelation,
+                                       RelationGetRelationName(indexRelation))));
+
+       /* If the index is marked as read-only, ignore it */
+       if (!indexInfo->ii_ReadyForInserts)
+           continue;
+
+       /* When specific arbiter indexes requested, only examine them */
+       if (arbiterIndexes != NIL && !arbiter)
+           continue;
+
+       checkedIndex = true;
+
+       /* Check for partial index */
+       if (indexInfo->ii_Predicate != NIL)
+       {
+           List       *predicate;
+
+           /*
+            * If predicate state not set up yet, create it (in the estate's
+            * per-query context)
+            */
+           predicate = indexInfo->ii_PredicateState;
+           if (predicate == NIL)
+           {
+               predicate = (List *)
+                   ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
+                                   estate);
+               indexInfo->ii_PredicateState = predicate;
+           }
+
+           /* Skip this index-update if the predicate isn't satisfied */
+           if (!ExecQual(predicate, econtext, false))
+               continue;
+       }
+
+       /*
+        * FormIndexDatum fills in its values and isnull parameters with the
+        * appropriate values for the column(s) of the index.
+        */
+       FormIndexDatum(indexInfo,
+                      slot,
+                      estate,
+                      values,
+                      isnull);
+
+       satisfiesConstraint =
+           check_exclusion_or_unique_constraint(heapRelation, indexRelation,
+                                                indexInfo, &invalidItemPtr,
+                                                values, isnull, estate, false,
+                                                true, true, conflictTid);
+       if (!satisfiesConstraint)
+           return false;
+   }
+
+   if (arbiterIndexes != NIL && !checkedIndex)
+       elog(ERROR, "unexpected failure to find arbiter unique index");
+
+   return true;
+}
+
 /*
- * Check for violation of an exclusion constraint
+ * Check for violation of an exclusion or unique constraint
  *
  * heap: the table containing the new tuple
  * index: the index supporting the exclusion constraint
  * indexInfo: info about the index, including the exclusion properties
- * tupleid: heap TID of the new tuple we have just inserted
+ * tupleid: heap TID of the new tuple we have just inserted (invalid if we
+ *     haven't inserted a new tuple yet)
  * values, isnull: the *index* column values computed for the new tuple
  * estate: an EState we can do evaluation in
  * newIndex: if true, we are trying to build a new index (this affects
  *     only the wording of error messages)
  * errorOK: if true, don't throw error for violation
+ * wait: if true, wait for conflicting transaction to finish, even if !errorOK
+ * conflictTid: if not-NULL, the TID of conflicting tuple is returned here.
  *
  * Returns true if OK, false if actual or potential violation
  *
@@ -1169,16 +1348,25 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
  * is convenient for deferred exclusion checks; we need not bother queuing
  * a deferred event if there is definitely no conflict at insertion time.
  *
- * When errorOK is false, we'll throw error on violation, so a false result
+ * When violationOK is false, we'll throw error on violation, so a false result
  * is impossible.
+ *
+ * Note: The indexam is normally responsible for checking unique constraints,
+ * so this normally only needs to be used for exclusion constraints. But this
+ * function is also called when doing a "pre-check" for conflicts, for the
+ * benefit of speculative insertion.  Caller may request that conflict TID be
+ * set, to take further steps.
  */
 bool
-check_exclusion_constraint(Relation heap, Relation index, IndexInfo *indexInfo,
-                          ItemPointer tupleid, Datum *values, bool *isnull,
-                          EState *estate, bool newIndex, bool errorOK)
+check_exclusion_or_unique_constraint(Relation heap, Relation index,
+                                    IndexInfo *indexInfo, ItemPointer tupleid,
+                                    Datum *values, bool *isnull,
+                                    EState *estate, bool newIndex,
+                                    bool violationOK, bool wait,
+                                    ItemPointer conflictTid)
 {
-   Oid        *constr_procs = indexInfo->ii_ExclusionProcs;
-   uint16     *constr_strats = indexInfo->ii_ExclusionStrats;
+   Oid        *constr_procs;
+   uint16     *constr_strats;
    Oid        *index_collations = index->rd_indcollation;
    int         index_natts = index->rd_index->indnatts;
    IndexScanDesc index_scan;
@@ -1192,6 +1380,17 @@ check_exclusion_constraint(Relation heap, Relation index, IndexInfo *indexInfo,
    TupleTableSlot *existing_slot;
    TupleTableSlot *save_scantuple;
 
+   if (indexInfo->ii_ExclusionOps)
+   {
+       constr_procs = indexInfo->ii_ExclusionProcs;
+       constr_strats = indexInfo->ii_ExclusionStrats;
+   }
+   else
+   {
+       constr_procs = indexInfo->ii_UniqueProcs;
+       constr_strats = indexInfo->ii_UniqueStrats;
+   }
+
    /*
     * If any of the input values are NULL, the constraint check is assumed to
     * pass (i.e., we assume the operators are strict).
@@ -1256,7 +1455,8 @@ retry:
        /*
         * Ignore the entry for the tuple we're trying to check.
         */
-       if (ItemPointerEquals(tupleid, &tup->t_self))
+       if (ItemPointerIsValid(tupleid) &&
+           ItemPointerEquals(tupleid, &tup->t_self))
        {
            if (found_self)     /* should not happen */
                elog(ERROR, "found self tuple multiple times in index \"%s\"",
@@ -1285,17 +1485,6 @@ retry:
                                 * conflict */
        }
 
-       /*
-        * At this point we have either a conflict or a potential conflict. If
-        * we're not supposed to raise error, just return the fact of the
-        * potential conflict without waiting to see if it's real.
-        */
-       if (errorOK)
-       {
-           conflict = true;
-           break;
-       }
-
        /*
         * If an in-progress transaction is affecting the visibility of this
         * tuple, we need to wait for it to complete and then recheck.  For
@@ -1307,18 +1496,92 @@ retry:
        xwait = TransactionIdIsValid(DirtySnapshot.xmin) ?
            DirtySnapshot.xmin : DirtySnapshot.xmax;
 
+       /*
+        * At this point we have either a conflict or a potential conflict. If
+        * we're not supposed to raise error, just return the fact of the
+        * potential conflict without waiting to see if it's real.
+        */
+       if (violationOK && !wait)
+       {
+           /*
+            * For unique indexes, detecting conflict is coupled with physical
+            * index tuple insertion, so we won't be called for recheck
+            */
+           Assert(!indexInfo->ii_Unique);
+
+           conflict = true;
+           if (conflictTid)
+               *conflictTid = tup->t_self;
+
+           /*
+            * Livelock insurance.
+            *
+            * When doing a speculative insertion pre-check, we cannot have an
+            * "unprincipled deadlock" with another session, fundamentally
+            * because there is no possible mutual dependency, since we only
+            * hold a lock on our token, without attempting to lock anything
+            * else (maybe this is not the first iteration, but no matter;
+            * we'll have super deleted and released insertion token lock if
+            * so, and all locks needed are already held.  Also, our XID lock
+            * is irrelevant.)
+            *
+            * In the second phase, where there is a re-check for conflicts,
+            * we can't deadlock either (we never lock another thing, since we
+            * don't wait in that phase).  However, a theoretical livelock
+            * hazard exists:  Two sessions could each see each other's
+            * conflicting tuple, and each could go and delete, retrying
+            * forever.
+            *
+            * To break the mutual dependency, we may wait on the other xact
+            * here over our caller's request to not do so (in the second
+            * phase).  This does not imply the risk of unprincipled deadlocks
+            * either, because if we end up unexpectedly waiting, the other
+            * session will super delete its own tuple *before* releasing its
+            * token lock and freeing us, and without attempting to wait on us
+            * to release our token lock.  We'll take another iteration here,
+            * after waiting on the other session's token, not find a conflict
+            * this time, and then proceed (assuming we're the oldest XID).
+            *
+            * N.B.:  Unprincipled deadlocks are still theoretically possible
+            * with non-speculative insertion with exclusion constraints, but
+            * this seems inconsequential, since an error was inevitable for
+            * one of the sessions anyway.  We only worry about speculative
+            * insertion's problems, since they're likely with idiomatic usage.
+            *
+            * We don't bother with this for the nbtree AM, because it
+            * guarantees that one session will make progress during concurrent
+            * insertion of duplicate values (the unique index enforcement
+            * mechanism ensures this).
+            */
+           if (index->rd_rel->relam != BTREE_AM_OID &&
+               TransactionIdPrecedes(xwait, GetCurrentTransactionId()))
+               break;  /* go and super delete/restart speculative insertion */
+       }
+
        if (TransactionIdIsValid(xwait))
        {
-           ctid_wait = tup->t_data->t_ctid;
+           ctid_wait = tup->t_data->t_tidstate.t_ctid;
            index_endscan(index_scan);
-           XactLockTableWait(xwait, heap, &ctid_wait,
-                             XLTW_RecheckExclusionConstr);
+           if (DirtySnapshot.speculativeToken)
+               SpeculativeInsertionWait(DirtySnapshot.xmin,
+                                        DirtySnapshot.speculativeToken);
+           else
+               XactLockTableWait(xwait, heap, &ctid_wait,
+                                 XLTW_RecheckExclusionConstr);
            goto retry;
        }
 
        /*
-        * We have a definite conflict.  Report it.
+        * We have a definite conflict.  Return it to caller, or report it.
         */
+       if (violationOK)
+       {
+           conflict = true;
+           if (conflictTid)
+               *conflictTid = tup->t_self;
+           break;
+       }
+
        error_new = BuildIndexValueDescription(index, values, isnull);
        error_existing = BuildIndexValueDescription(index, existing_values,
                                                    existing_isnull);
@@ -1354,6 +1617,9 @@ retry:
     * However, it is possible to define exclusion constraints for which that
     * wouldn't be true --- for instance, if the operator is <>. So we no
     * longer complain if found_self is still false.
+    *
+    * It would also not be true in the pre-check mode, when we haven't
+    * inserted a tuple yet.
     */
 
    econtext->ecxt_scantuple = save_scantuple;
index f96fb2432b6a60a17931201013ca9027e2d6cd64..215127e435d277f724ffb537f14f535eac3e6251 100644 (file)
@@ -46,6 +46,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "storage/bufmgr.h"
+#include "storage/lmgr.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -151,6 +152,35 @@ ExecProcessReturning(ProjectionInfo *projectReturning,
    return ExecProject(projectReturning, NULL);
 }
 
+/*
+ * ExecCheckHeapTupleVisible -- verify heap tuple is visible
+ *
+ * It would not be consistent with guarantees of the higher isolation levels to
+ * proceed with avoiding insertion (taking speculative insertion's alternative
+ * path) on the basis of another tuple that is not visible.  Check for the need
+ * to raise a serialization failure, and do so as necessary.
+ */
+static void
+ExecCheckHeapTupleVisible(EState *estate,
+                         ResultRelInfo *relinfo,
+                         ItemPointer tid)
+{
+   Relation    rel = relinfo->ri_RelationDesc;
+   Buffer      buffer;
+   HeapTupleData tuple;
+
+   if (!IsolationUsesXactSnapshot())
+       return;
+
+   tuple.t_self = *tid;
+   if (!heap_fetch(rel, estate->es_snapshot, &tuple, &buffer, false, NULL))
+       ereport(ERROR,
+               (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+                errmsg("could not serialize access due to concurrent insert or update directing alternative ON CONFLICT path")));
+
+   ReleaseBuffer(buffer);
+}
+
 /* ----------------------------------------------------------------
  *     ExecInsert
  *
@@ -163,6 +193,8 @@ ExecProcessReturning(ProjectionInfo *projectReturning,
 static TupleTableSlot *
 ExecInsert(TupleTableSlot *slot,
           TupleTableSlot *planSlot,
+          List *arbiterIndexes,
+          SpecCmd spec,
           EState *estate,
           bool canSetTag)
 {
@@ -199,7 +231,16 @@ ExecInsert(TupleTableSlot *slot,
    if (resultRelationDesc->rd_rel->relhasoids)
        HeapTupleSetOid(tuple, InvalidOid);
 
-   /* BEFORE ROW INSERT Triggers */
+   /*
+    * BEFORE ROW INSERT Triggers.
+    *
+    * We don't suppress the effects (or, perhaps, side-effects) of BEFORE ROW
+    * INSERT triggers when performing speculative insertion.  We cannot
+    * proceed with even considering violations until these triggers fire on
+    * the one hand, but on the other hand they have the ability to execute
+    * arbitrary user-defined code which may perform operations entirely
+    * outside the system's ability to nullify.
+    */
    if (resultRelInfo->ri_TrigDesc &&
        resultRelInfo->ri_TrigDesc->trig_insert_before_row)
    {
@@ -246,6 +287,8 @@ ExecInsert(TupleTableSlot *slot,
    }
    else
    {
+       uint32      specToken = 0;
+
        /*
         * Constraints might reference the tableoid column, so initialize
         * t_tableOid before evaluating them.
@@ -258,6 +301,49 @@ ExecInsert(TupleTableSlot *slot,
        if (resultRelationDesc->rd_att->constr)
            ExecConstraints(resultRelInfo, slot, estate);
 
+       /*
+        * If we are performing speculative insertion, do a non-conclusive
+        * check for conflicts.
+        *
+        * See the executor README for a full discussion of speculative
+        * insertion.
+        */
+       if (spec != SPEC_NONE && resultRelInfo->ri_NumIndices > 0)
+       {
+           ItemPointerData     conflictTid;
+vlock:
+           /*
+            * Check if it's required to proceed with the second phase
+            * ("insertion proper") of speculative insertion in respect of the
+            * slot.  The check may involve a wait for another session's EOX.
+            */
+           if (!ExecCheckIndexConstraints(slot, estate, &conflictTid,
+                                          arbiterIndexes))
+           {
+               /*
+                * For the SPEC_IGNORE case, it's necessary to verify that the
+                * tuple is visible to the executor's MVCC snapshot at higher
+                * isolation levels
+                */
+               if (spec == SPEC_IGNORE)
+                   ExecCheckHeapTupleVisible(estate, resultRelInfo, &conflictTid);
+
+               /*
+                * The IGNORE path projects no tuples
+                */
+               return NULL;
+           }
+
+           /*
+            * Before we start insertion proper, acquire our "promise tuple
+            * insertion lock".  Others can use that (rather than an XID lock,
+            * which is appropriate only for non-promise tuples) to wait for us
+            * to decide if we're going to go ahead with the insertion.
+            */
+           specToken = SpeculativeInsertionLockAcquire(GetCurrentTransactionId());
+           SpeculativeTokenSet(tuple->t_data->t_tidstate, specToken);
+       }
+
        /*
         * insert the tuple
         *
@@ -265,14 +351,54 @@ ExecInsert(TupleTableSlot *slot,
         * the t_self field.
         */
        newId = heap_insert(resultRelationDesc, tuple,
-                           estate->es_output_cid, 0, NULL);
+                           estate->es_output_cid,
+                           specToken == 0 ? 0 : HEAP_INSERT_SPECULATIVE,
+                           NULL);
 
        /*
         * insert index entries for tuple
         */
        if (resultRelInfo->ri_NumIndices > 0)
            recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-                                                  estate);
+                                                  estate, specToken != 0,
+                                                  arbiterIndexes);
+
+       if (specToken != 0)
+       {
+           bool    conflict = recheckIndexes != NIL;
+
+           /*
+            * Speculative insertion does not support deferred constraints,
+            * so the specific index involved in the violation is
+            * immaterial
+            */
+           if (recheckIndexes)
+               list_free(recheckIndexes);
+
+           /*
+            * Consider possible race:  concurrent insertion conflicts with
+            * our speculative heap tuple (note that this type of
+            * speculative conflict never involves waiting until EOX).
+            *
+            * Update tuple in-place to indicate if there was (or was not) a
+            * concurrent insertion conflict.
+            */
+           heap_finish_speculative(resultRelationDesc, tuple, conflict);
+
+           /*
+            * Now that heap tuple was marked, waiters woken up by this lmgr
+            * lock release cannot livelock due to observing the same token on
+            * our speculatively inserted tuple repeatedly.  They also cannot
+            * deadlock by allowing this session to wait on their XID (or other
+            * heavyweight lock) while this session waits on theirs.
+            */
+           SpeculativeInsertionLockRelease(GetCurrentTransactionId());
+
+           if (conflict)
+               goto vlock;
+
+           /* Since there was no insertion conflict, we're done */
+       }
    }
 
    if (canSetTag)
@@ -399,7 +525,8 @@ ldelete:;
                             estate->es_output_cid,
                             estate->es_crosscheck_snapshot,
                             true /* wait for commit */ ,
-                            &hufd);
+                            &hufd,
+                            false);
        switch (result)
        {
            case HeapTupleSelfUpdated:
@@ -768,7 +895,7 @@ lreplace:;
         */
        if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
            recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
-                                                  estate);
+                                                  estate, false, NIL);
    }
 
    if (canSetTag)
@@ -1022,7 +1149,8 @@ ExecModifyTable(ModifyTableState *node)
        switch (operation)
        {
            case CMD_INSERT:
-               slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
+               slot = ExecInsert(slot, planSlot, node->arbiterIndexes,
+                                 node->spec, estate, node->canSetTag);
                break;
            case CMD_UPDATE:
                slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
@@ -1097,6 +1225,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
    mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
    mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
    mtstate->mt_nplans = nplans;
+   mtstate->spec = node->spec;
+   mtstate->arbiterIndexes = node->arbiterIndexes;
 
    /* set up epqstate with dummy subplan data for the moment */
    EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
@@ -1135,7 +1265,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
        if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
            operation != CMD_DELETE &&
            resultRelInfo->ri_IndexRelationDescs == NULL)
-           ExecOpenIndices(resultRelInfo);
+           ExecOpenIndices(resultRelInfo, mtstate->spec != SPEC_NONE);
 
        /* Now init the plan for this result rel */
        estate->es_result_relation_info = resultRelInfo;
index b3c05025bc08757b7d2d73a9a6bb6552f0678689..816c477ddfa971480afffc6b4610bd5b91c42848 100644 (file)
@@ -798,7 +798,7 @@ SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
         * copy the identification info of the old tuple: t_ctid, t_self, and
         * OID (if any)
         */
-       mtuple->t_data->t_ctid = tuple->t_data->t_ctid;
+       mtuple->t_data->t_tidstate.t_ctid = tuple->t_data->t_tidstate.t_ctid;
        mtuple->t_self = tuple->t_self;
        mtuple->t_tableOid = tuple->t_tableOid;
        if (rel->rd_att->tdhasoid)
index 029761e74f8d7cbfebb4edbdad53e9f9ad0c508e..75e4efaeedd28f2dc69781a452646d4a04f2f189 100644 (file)
@@ -180,6 +180,8 @@ _copyModifyTable(const ModifyTable *from)
    COPY_NODE_FIELD(resultRelations);
    COPY_SCALAR_FIELD(resultRelIndex);
    COPY_NODE_FIELD(plans);
+   COPY_SCALAR_FIELD(spec);
+   COPY_NODE_FIELD(arbiterIndexes);
    COPY_NODE_FIELD(withCheckOptionLists);
    COPY_NODE_FIELD(returningLists);
    COPY_NODE_FIELD(fdwPrivLists);
@@ -1781,6 +1783,22 @@ _copyCurrentOfExpr(const CurrentOfExpr *from)
    return newnode;
 }
 
+/*
+ * _copyInferenceElem
+ */
+static InferenceElem *
+_copyInferenceElem(const InferenceElem *from)
+{
+   InferenceElem *newnode = makeNode(InferenceElem);
+
+   COPY_NODE_FIELD(expr);
+   COPY_SCALAR_FIELD(infercollid);
+   COPY_SCALAR_FIELD(inferopfamily);
+   COPY_SCALAR_FIELD(inferopcinputtype);
+
+   return newnode;
+}
+
 /*
  * _copyTargetEntry
  */
@@ -2128,6 +2146,30 @@ _copyWithClause(const WithClause *from)
    return newnode;
 }
 
+static InferClause *
+_copyInferClause(const InferClause *from)
+{
+   InferClause *newnode = makeNode(InferClause);
+
+   COPY_NODE_FIELD(indexElems);
+   COPY_NODE_FIELD(whereClause);
+   COPY_LOCATION_FIELD(location);
+
+   return newnode;
+}
+
+static ConflictClause *
+_copyConflictClause(const ConflictClause *from)
+{
+   ConflictClause *newnode = makeNode(ConflictClause);
+
+   COPY_SCALAR_FIELD(specclause);
+   COPY_NODE_FIELD(infer);
+   COPY_LOCATION_FIELD(location);
+
+   return newnode;
+}
+
 static CommonTableExpr *
 _copyCommonTableExpr(const CommonTableExpr *from)
 {
@@ -2545,6 +2587,9 @@ _copyQuery(const Query *from)
    COPY_NODE_FIELD(jointree);
    COPY_NODE_FIELD(targetList);
    COPY_NODE_FIELD(withCheckOptions);
+   COPY_SCALAR_FIELD(specClause);
+   COPY_NODE_FIELD(arbiterElems);
+   COPY_NODE_FIELD(arbiterWhere);
    COPY_NODE_FIELD(returningList);
    COPY_NODE_FIELD(groupClause);
    COPY_NODE_FIELD(havingQual);
@@ -2568,6 +2613,7 @@ _copyInsertStmt(const InsertStmt *from)
    COPY_NODE_FIELD(relation);
    COPY_NODE_FIELD(cols);
    COPY_NODE_FIELD(selectStmt);
+   COPY_NODE_FIELD(confClause);
    COPY_NODE_FIELD(returningList);
    COPY_NODE_FIELD(withClause);
 
@@ -4262,6 +4308,9 @@ copyObject(const void *from)
        case T_CurrentOfExpr:
            retval = _copyCurrentOfExpr(from);
            break;
+       case T_InferenceElem:
+           retval = _copyInferenceElem(from);
+           break;
        case T_TargetEntry:
            retval = _copyTargetEntry(from);
            break;
@@ -4729,6 +4778,12 @@ copyObject(const void *from)
        case T_WithClause:
            retval = _copyWithClause(from);
            break;
+       case T_InferClause:
+           retval = _copyInferClause(from);
+           break;
+       case T_ConflictClause:
+           retval = _copyConflictClause(from);
+           break;
        case T_CommonTableExpr:
            retval = _copyCommonTableExpr(from);
            break;
index 190e50ab8c6909d62a7dbb4b40b0c6f53ff48e34..42cc7f9ecb9d229a6985b7ace08e39ecc5719390 100644 (file)
@@ -682,6 +682,17 @@ _equalCurrentOfExpr(const CurrentOfExpr *a, const CurrentOfExpr *b)
    return true;
 }
 
+static bool
+_equalInferenceElem(const InferenceElem *a, const InferenceElem *b)
+{
+   COMPARE_NODE_FIELD(expr);
+   COMPARE_SCALAR_FIELD(infercollid);
+   COMPARE_SCALAR_FIELD(inferopfamily);
+   COMPARE_SCALAR_FIELD(inferopcinputtype);
+
+   return true;
+}
+
 static bool
 _equalTargetEntry(const TargetEntry *a, const TargetEntry *b)
 {
@@ -868,6 +879,9 @@ _equalQuery(const Query *a, const Query *b)
    COMPARE_NODE_FIELD(jointree);
    COMPARE_NODE_FIELD(targetList);
    COMPARE_NODE_FIELD(withCheckOptions);
+   COMPARE_SCALAR_FIELD(specClause);
+   COMPARE_NODE_FIELD(arbiterElems);
+   COMPARE_NODE_FIELD(arbiterWhere);
    COMPARE_NODE_FIELD(returningList);
    COMPARE_NODE_FIELD(groupClause);
    COMPARE_NODE_FIELD(havingQual);
@@ -889,6 +903,7 @@ _equalInsertStmt(const InsertStmt *a, const InsertStmt *b)
    COMPARE_NODE_FIELD(relation);
    COMPARE_NODE_FIELD(cols);
    COMPARE_NODE_FIELD(selectStmt);
+   COMPARE_NODE_FIELD(confClause);
    COMPARE_NODE_FIELD(returningList);
    COMPARE_NODE_FIELD(withClause);
 
@@ -2419,6 +2434,26 @@ _equalWithClause(const WithClause *a, const WithClause *b)
    return true;
 }
 
+static bool
+_equalInferClause(const InferClause *a, const InferClause *b)
+{
+   COMPARE_NODE_FIELD(indexElems);
+   COMPARE_NODE_FIELD(whereClause);
+   COMPARE_LOCATION_FIELD(location);
+
+   return true;
+}
+
+static bool
+_equalConflictClause(const ConflictClause *a, const ConflictClause *b)
+{
+   COMPARE_SCALAR_FIELD(specclause);
+   COMPARE_NODE_FIELD(infer);
+   COMPARE_LOCATION_FIELD(location);
+
+   return true;
+}
+
 static bool
 _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 {
@@ -2698,6 +2733,9 @@ equal(const void *a, const void *b)
        case T_CurrentOfExpr:
            retval = _equalCurrentOfExpr(a, b);
            break;
+       case T_InferenceElem:
+           retval = _equalInferenceElem(a, b);
+           break;
        case T_TargetEntry:
            retval = _equalTargetEntry(a, b);
            break;
@@ -3152,6 +3190,12 @@ equal(const void *a, const void *b)
        case T_WithClause:
            retval = _equalWithClause(a, b);
            break;
+       case T_InferClause:
+           retval = _equalInferClause(a, b);
+           break;
+       case T_ConflictClause:
+           retval = _equalConflictClause(a, b);
+           break;
        case T_CommonTableExpr:
            retval = _equalCommonTableExpr(a, b);
            break;
index d6f1f5bb6d7d3c62af415ad52b8988dcc7e98e68..d815022b83b921011bd5959d3c1e34aa7451fd82 100644 (file)
@@ -235,6 +235,13 @@ exprType(const Node *expr)
        case T_CurrentOfExpr:
            type = BOOLOID;
            break;
+       case T_InferenceElem:
+           {
+               const InferenceElem *n = (const InferenceElem *) expr;
+
+               type = exprType((Node *) n->expr);
+           }
+           break;
        case T_PlaceHolderVar:
            type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
            break;
@@ -894,6 +901,9 @@ exprCollation(const Node *expr)
        case T_CurrentOfExpr:
            coll = InvalidOid;  /* result is always boolean */
            break;
+       case T_InferenceElem:
+           coll = exprCollation((Node *) ((const InferenceElem *) expr)->expr);
+           break;
        case T_PlaceHolderVar:
            coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
            break;
@@ -1484,6 +1494,12 @@ exprLocation(const Node *expr)
        case T_WithClause:
            loc = ((const WithClause *) expr)->location;
            break;
+       case T_InferClause:
+           loc = ((const InferClause *) expr)->location;
+           break;
+       case T_ConflictClause:
+           loc = ((const ConflictClause *) expr)->location;
+           break;
        case T_CommonTableExpr:
            loc = ((const CommonTableExpr *) expr)->location;
            break;
@@ -1491,6 +1507,10 @@ exprLocation(const Node *expr)
            /* just use argument's location */
            loc = exprLocation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
            break;
+       case T_InferenceElem:
+           /* just use nested expr's location */
+           loc = exprLocation((Node *) ((const InferenceElem *) expr)->expr);
+           break;
        default:
            /* for any other node type it's just unknown... */
            loc = -1;
@@ -1920,6 +1940,8 @@ expression_tree_walker(Node *node,
            break;
        case T_PlaceHolderVar:
            return walker(((PlaceHolderVar *) node)->phexpr, context);
+       case T_InferenceElem:
+           return walker(((InferenceElem *) node)->expr, context);
        case T_AppendRelInfo:
            {
                AppendRelInfo *appinfo = (AppendRelInfo *) node;
@@ -1968,6 +1990,10 @@ query_tree_walker(Query *query,
        return true;
    if (walker((Node *) query->withCheckOptions, context))
        return true;
+   if (walker((Node *) query->arbiterElems, context))
+       return true;
+   if (walker(query->arbiterWhere, context))
+       return true;
    if (walker((Node *) query->returningList, context))
        return true;
    if (walker((Node *) query->jointree, context))
@@ -2630,6 +2656,16 @@ expression_tree_mutator(Node *node,
                return (Node *) newnode;
            }
            break;
+       case T_InferenceElem:
+           {
+               InferenceElem *inferenceelemdexpr = (InferenceElem *) node;
+               InferenceElem *newnode;
+
+               FLATCOPY(newnode, inferenceelemdexpr, InferenceElem);
+               MUTATE(newnode->expr, newnode->expr, Node *);
+               return (Node *) newnode;
+           }
+           break;
        case T_AppendRelInfo:
            {
                AppendRelInfo *appinfo = (AppendRelInfo *) node;
@@ -2709,6 +2745,8 @@ query_tree_mutator(Query *query,
 
    MUTATE(query->targetList, query->targetList, List *);
    MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
+   MUTATE(query->arbiterElems, query->arbiterElems, List *);
+   MUTATE(query->arbiterWhere, query->arbiterWhere, Node *);
    MUTATE(query->returningList, query->returningList, List *);
    MUTATE(query->jointree, query->jointree, FromExpr *);
    MUTATE(query->setOperations, query->setOperations, Node *);
@@ -2978,6 +3016,8 @@ raw_expression_tree_walker(Node *node,
                    return true;
                if (walker(stmt->selectStmt, context))
                    return true;
+               if (walker(stmt->confClause, context))
+                   return true;
                if (walker(stmt->returningList, context))
                    return true;
                if (walker(stmt->withClause, context))
@@ -3217,6 +3257,24 @@ raw_expression_tree_walker(Node *node,
            break;
        case T_WithClause:
            return walker(((WithClause *) node)->ctes, context);
+       case T_InferClause:
+           {
+               InferClause *stmt = (InferClause *) node;
+
+               if (walker(stmt->indexElems, context))
+                   return true;
+               if (walker(stmt->whereClause, context))
+                   return true;
+           }
+           break;
+       case T_ConflictClause:
+           {
+               ConflictClause *stmt = (ConflictClause *) node;
+
+               if (walker(stmt->infer, context))
+                   return true;
+           }
+           break;
        case T_CommonTableExpr:
            return walker(((CommonTableExpr *) node)->ctequery, context);
        default:
index 385b289bed2ff17e4ec4382aca632be16ffad384..a64af82d432acf819268ae2360ec0773428b0b8b 100644 (file)
@@ -332,6 +332,8 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
    WRITE_NODE_FIELD(resultRelations);
    WRITE_INT_FIELD(resultRelIndex);
    WRITE_NODE_FIELD(plans);
+   WRITE_ENUM_FIELD(spec, SpecType);
+   WRITE_NODE_FIELD(arbiterIndexes);
    WRITE_NODE_FIELD(withCheckOptionLists);
    WRITE_NODE_FIELD(returningLists);
    WRITE_NODE_FIELD(fdwPrivLists);
@@ -1431,6 +1433,17 @@ _outCurrentOfExpr(StringInfo str, const CurrentOfExpr *node)
    WRITE_INT_FIELD(cursor_param);
 }
 
+static void
+_outInferenceElem(StringInfo str, const InferenceElem *node)
+{
+   WRITE_NODE_TYPE("INFERENCEELEM");
+
+   WRITE_NODE_FIELD(expr);
+   WRITE_OID_FIELD(infercollid);
+   WRITE_OID_FIELD(inferopfamily);
+   WRITE_OID_FIELD(inferopcinputtype);
+}
+
 static void
 _outTargetEntry(StringInfo str, const TargetEntry *node)
 {
@@ -2314,6 +2327,9 @@ _outQuery(StringInfo str, const Query *node)
    WRITE_NODE_FIELD(jointree);
    WRITE_NODE_FIELD(targetList);
    WRITE_NODE_FIELD(withCheckOptions);
+   WRITE_ENUM_FIELD(specClause, SpecType);
+   WRITE_NODE_FIELD(arbiterElems);
+   WRITE_NODE_FIELD(arbiterWhere);
    WRITE_NODE_FIELD(returningList);
    WRITE_NODE_FIELD(groupClause);
    WRITE_NODE_FIELD(havingQual);
@@ -3105,6 +3121,9 @@ _outNode(StringInfo str, const void *obj)
            case T_CurrentOfExpr:
                _outCurrentOfExpr(str, obj);
                break;
+           case T_InferenceElem:
+               _outInferenceElem(str, obj);
+               break;
            case T_TargetEntry:
                _outTargetEntry(str, obj);
                break;
index 563209c56150d4de00bf93ad03a1de0b74741acf..64e153c14c6509c1a69885daf125f9a9f8e8500e 100644 (file)
@@ -214,6 +214,9 @@ _readQuery(void)
    READ_NODE_FIELD(jointree);
    READ_NODE_FIELD(targetList);
    READ_NODE_FIELD(withCheckOptions);
+   READ_ENUM_FIELD(specClause, SpecCmd);
+   READ_NODE_FIELD(arbiterElems);
+   READ_NODE_FIELD(arbiterWhere);
    READ_NODE_FIELD(returningList);
    READ_NODE_FIELD(groupClause);
    READ_NODE_FIELD(havingQual);
@@ -1129,6 +1132,22 @@ _readCurrentOfExpr(void)
    READ_DONE();
 }
 
+/*
+ * _readInferenceElem
+ */
+static InferenceElem *
+_readInferenceElem(void)
+{
+   READ_LOCALS(InferenceElem);
+
+   READ_NODE_FIELD(expr);
+   READ_OID_FIELD(infercollid);
+   READ_OID_FIELD(inferopfamily);
+   READ_OID_FIELD(inferopcinputtype);
+
+   READ_DONE();
+}
+
 /*
  * _readTargetEntry
  */
@@ -1393,6 +1412,8 @@ parseNodeString(void)
        return_value = _readSetToDefault();
    else if (MATCH("CURRENTOFEXPR", 13))
        return_value = _readCurrentOfExpr();
+   else if (MATCH("INFERENCEELEM", 13))
+       return_value = _readInferenceElem();
    else if (MATCH("TARGETENTRY", 11))
        return_value = _readTargetEntry();
    else if (MATCH("RANGETBLREF", 11))
index cb69c03df000887276d52c37e64fa15e2bad9bf7..4677ae9c9b68cec49829dcc5bc9dcd29c5dc13a3 100644 (file)
@@ -4815,7 +4815,7 @@ make_modifytable(PlannerInfo *root,
                 Index nominalRelation,
                 List *resultRelations, List *subplans,
                 List *withCheckOptionLists, List *returningLists,
-                List *rowMarks, int epqParam)
+                List *rowMarks, SpecCmd spec, int epqParam)
 {
    ModifyTable *node = makeNode(ModifyTable);
    Plan       *plan = &node->plan;
@@ -4865,6 +4865,8 @@ make_modifytable(PlannerInfo *root,
    node->resultRelations = resultRelations;
    node->resultRelIndex = -1;  /* will be set correctly in setrefs.c */
    node->plans = subplans;
+   node->spec = spec;
+   node->arbiterIndexes = NIL;
    node->withCheckOptionLists = withCheckOptionLists;
    node->returningLists = returningLists;
    node->rowMarks = rowMarks;
@@ -4917,6 +4919,14 @@ make_modifytable(PlannerInfo *root,
    }
    node->fdwPrivLists = fdw_private_list;
 
+   /*
+    * If a set of unique index inference elements was provided (an INSERT...ON
+    * CONFLICT "inference specification"), then infer appropriate unique
+    * indexes (or throw an error if none are available).
+    */
+   if (root->parse->arbiterElems)
+       node->arbiterIndexes = infer_arbiter_indexes(root);
+
    return node;
 }
 
index 876a87ff52a6cf757d46679eae31c20069a66496..6931e1e588591e34c338079aee409d8519ffe254 100644 (file)
@@ -612,6 +612,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
                                             withCheckOptionLists,
                                             returningLists,
                                             rowMarks,
+                                            parse->specClause,
                                             SS_assign_special_param(root));
        }
    }
@@ -1072,6 +1073,7 @@ inheritance_planner(PlannerInfo *root)
                                     withCheckOptionLists,
                                     returningLists,
                                     rowMarks,
+                                    parse->specClause,
                                     SS_assign_special_param(root));
 }
 
index 8abed2ae0dada01bf56fd080e041f1b481ee612f..ac8c74287617fc3fc70f9feb89cb2b30df134750 100644 (file)
@@ -50,6 +50,8 @@ int           constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
 get_relation_info_hook_type get_relation_info_hook = NULL;
 
 
+static bool infer_collation_opclass_match(InferenceElem *elem, Relation    idxRel,
+                       Bitmapset *inferAttrs, List *idxExprs);
 static int32 get_rel_data_width(Relation rel, int32 *attr_widths);
 static List *get_relation_constraints(PlannerInfo *root,
                         Oid relationObjectId, RelOptInfo *rel,
@@ -394,6 +396,304 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
        (*get_relation_info_hook) (root, relationObjectId, inhparent, rel);
 }
 
+/*
+ * infer_arbiter_indexes -
+ *   Retrieves unique indexes to arbitrate speculative insertion.
+ *
+ * Uses user-supplied inference clause expressions and predicate to match a
+ * unique index from those defined and ready on the heap relation (target).  An
+ * exact match is required on columns/expressions (although they can appear in
+ * any order).  However, the predicate given by the user need only restrict
+ * insertion to a subset of some part of the table covered by some particular
+ * unique index (in particular, a partial unique index) in order to be
+ * inferred.
+ *
+ * The implementation does not consider which B-Tree operator class any
+ * particular available unique index attribute use, unless one appeared in the
+ * user-supplied inference specification (the same is true of collations).  In
+ * particular, there is no system dependency on the default operator class for
+ * the purposes of inference.  If no opclass (or collation) is specified, then
+ * all matching indexes (that may or may not match the default in terms of each
+ * attribute opclass/collation) are used for inference.
+ *
+ * This logic somewhat mirrors get_relation_info().  This process is not
+ * deferred to a get_relation_info() call while planning because there may not
+ * be any such call.
+ */
+List *
+infer_arbiter_indexes(PlannerInfo *root)
+{
+   Query      *parse = root->parse;
+
+   /* Iteration state */
+   Relation    relation;
+   Oid         relationObjectId;
+   List       *indexList;
+   ListCell   *l;
+
+   /* Normalized inference attributes and inference expressions: */
+   Bitmapset  *inferAttrs = NULL;
+   List       *inferElems = NIL;
+
+   /* Result */
+   List       *candidates = NIL;
+
+   Assert(parse->specClause == SPEC_IGNORE);
+
+   /*
+    * We need not lock the relation since it was already locked, either by
+    * the rewriter or when expand_inherited_rtentry() added it to the query's
+    * rangetable.
+    */
+   relationObjectId = rt_fetch(parse->resultRelation, parse->rtable)->relid;
+
+   relation = heap_open(relationObjectId, NoLock);
+
+   /*
+    * Build normalized/BMS representation of plain indexed attributes, as well
+    * as direct list of inference elements.  This is required for matching the
+    * cataloged definition of indexes.
+    */
+   foreach(l, parse->arbiterElems)
+   {
+       InferenceElem  *elem;
+       Var            *var;
+       int             attno;
+
+       elem = (InferenceElem *) lfirst(l);
+
+       /*
+        * Parse analysis of inference elements performs full parse analysis
+        * of Vars, even for non-expression indexes (in contrast with utility
+        * command related use of IndexElem).  However, indexes are cataloged
+        * with simple attribute numbers for non-expression indexes.  Those are
+        * handled later.
+        */
+       if (!IsA(elem->expr, Var))
+       {
+           inferElems = lappend(inferElems, elem->expr);
+           continue;
+       }
+
+       var = (Var *) elem->expr;
+       attno = var->varattno;
+
+       if (attno < 0)
+           ereport(ERROR,
+                   (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+                    errmsg("system columns may not appear in unique index inference specification")));
+       else if (attno == 0)
+           elog(ERROR, "whole row unique index inference specifications are not valid");
+
+       inferAttrs = bms_add_member(inferAttrs, attno);
+   }
+
+   indexList = RelationGetIndexList(relation);
+
+   /*
+    * Using that representation, iterate through the list of indexes on the
+    * target relation to try and find a match
+    */
+   foreach(l, indexList)
+   {
+       Oid         indexoid = lfirst_oid(l);
+       Relation    idxRel;
+       Form_pg_index idxForm;
+       Bitmapset  *indexedAttrs = NULL;
+       List       *idxExprs;
+       List       *predExprs;
+       List       *whereExplicit;
+       AttrNumber  natt;
+       ListCell   *el;
+
+       /*
+        * Extract info from the relation descriptor for the index.  We know
+        * that this is a target, so get lock type it is known will ultimately
+        * be required by the executor.
+        *
+        * Let executor complain about !indimmediate case directly.
+        */
+       idxRel = index_open(indexoid, RowExclusiveLock);
+       idxForm = idxRel->rd_index;
+
+       if (!idxForm->indisunique ||
+           !IndexIsValid(idxForm))
+           goto next;
+
+       /*
+        * If the index is valid, but cannot yet be used, ignore it. See
+        * src/backend/access/heap/README.HOT for discussion.
+        */
+       if (idxForm->indcheckxmin &&
+           !TransactionIdPrecedes(HeapTupleHeaderGetXmin(idxRel->rd_indextuple->t_data),
+                                  TransactionXmin))
+           goto next;
+
+       /* Build BMS representation of cataloged index attributes */
+       for (natt = 0; natt < idxForm->indnatts; natt++)
+       {
+           int         attno = idxRel->rd_index->indkey.values[natt];
+
+           if (attno < 0)
+               elog(ERROR, "system column in index");
+
+           if (attno != 0)
+               indexedAttrs = bms_add_member(indexedAttrs, attno);
+       }
+
+       /* Non-expression attributes (if any) must match */
+       if (!bms_equal(indexedAttrs, inferAttrs))
+           goto next;
+
+       /* Expression attributes (if any) must match */
+       idxExprs = RelationGetIndexExpressions(idxRel);
+       foreach(el, parse->arbiterElems)
+       {
+           InferenceElem   *elem = (InferenceElem *) lfirst(el);
+
+           /*
+            * Ensure that collation/opclass aspects of inference expression
+            * element match.  Even though this loop is primarily concerned
+            * with matching expressions, it is a convenient point to check
+            * this for both expressions and ordinary (non-expression)
+            * attributes appearing as inference elements.
+            */
+           if (!infer_collation_opclass_match(elem, idxRel, inferAttrs,
+                                              idxExprs))
+               goto next;
+
+           /*
+            * Plain Vars don't factor into count of expression elements, and
+            * the question of whether or not they satisfy the index definition
+            * has already been considered (they must)
+            */
+           if (IsA(elem->expr, Var))
+               continue;
+
+           /*
+            * Might as well avoid redundant check in the rare cases where
+            * infer_collation_opclass_match() is required to do real work.
+            * Otherwise, check that element expression appears in cataloged
+            * index definition.
+            */
+           if (elem->infercollid != InvalidOid ||
+               elem->inferopfamily != InvalidOid ||
+               list_member(idxExprs, elem->expr))
+               continue;
+
+           goto next;
+       }
+
+       /*
+        * Now that all inference elements were matched, ensure that the
+        * expression elements from inference clause are not missing any
+        * cataloged expressions.  This does the right thing when unique
+        * indexes redundantly repeat the same attribute, or if attributes
+        * redundantly appear multiple times within an inference clause.
+        */
+       if (list_difference(idxExprs, inferElems) != NIL)
+           goto next;
+
+       /*
+        * Any user-supplied ON CONFLICT unique index inference WHERE clause
+        * need only be implied by the cataloged index definitions predicate
+        */
+       predExprs = RelationGetIndexPredicate(idxRel);
+       whereExplicit = make_ands_implicit((Expr *) parse->arbiterWhere);
+
+       if (!predicate_implied_by(predExprs, whereExplicit))
+           goto next;
+
+       candidates = lappend_oid(candidates, idxForm->indexrelid);
+next:
+       index_close(idxRel, NoLock);
+   }
+
+   list_free(indexList);
+   heap_close(relation, NoLock);
+
+   if (candidates == NIL)
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+                errmsg("could not infer which unique index to use from expressions/columns and predicate provided for ON CONFLICT")));
+
+   return candidates;
+}
+
+/*
+ * infer_collation_opclass_match - ensure infer element opclass/collation match
+ *
+ * Given unique index inference element from inference specification, if
+ * collation was specified, or if opclass (represented here as opfamily +
+ * opcintype) was specified, verify that there is at least one matching indexed
+ * attribute (occasionally, there may be more).  Skip this in the common case
+ * where inference specification does not include collation or opclass (instead
+ * matching everything, regardless of cataloged collation/opclass of indexed
+ * attribute).
+ *
+ * At least historically, Postgres has not offered collations or opclasses with
+ * alternative-to-default notions of equality, so these additional criteria
+ * should only actually be matched on infrequently.
+ *
+ * Don't give up immediately when an inference element matches some attribute
+ * cataloged as indexed but not matching additional opclass/collation criteria.
+ * This is done so that the implementation is as forgiving as possible of
+ * redundancy within cataloged index attributes (or, less usefully, within
+ * inference specification elements).  If collations actually differ between
+ * apparently redundantly indexed attributes (redundant within or across
+ * indexes), then there really is no redundancy as such.
+ *
+ * Note that if an inference element specifies an opclass and a collation at
+ * once, both must match in at least one particular attribute within index
+ * catalog definition in order for that inference element to be considered
+ * inferred/satisfied.
+ */
+static bool
+infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
+                             Bitmapset *inferAttrs, List *idxExprs)
+{
+   AttrNumber  natt;
+
+   /*
+    * If inference specification element lacks collation/opclass, then no need
+    * to check for exact match
+    */
+   if (elem->infercollid == InvalidOid && elem->inferopfamily == InvalidOid)
+       return true;
+
+   for (natt = 1; natt <= idxRel->rd_att->natts; natt++)
+   {
+       Oid     opfamily = idxRel->rd_opfamily[natt - 1];
+       Oid     opcinputtype = idxRel->rd_opcintype[natt - 1];
+       Oid     collation = idxRel->rd_indcollation[natt - 1];
+
+       if (elem->inferopfamily != InvalidOid &&
+           (elem->inferopfamily != opfamily ||
+            elem->inferopcinputtype != opcinputtype))
+       {
+           /* Attribute needed to match opclass, but didn't */
+           continue;
+       }
+
+       if (elem->infercollid != InvalidOid &&
+           elem->infercollid != collation)
+       {
+           /* Attribute needed to match collation, but didn't */
+           continue;
+       }
+
+       if ((IsA(elem->expr, Var) &&
+            bms_is_member(((Var *) elem->expr)->varattno, inferAttrs)) ||
+           list_member(idxExprs, elem->expr))
+       {
+           /* Found one match - good enough */
+           return true;
+       }
+   }
+
+   return false;
+}
+
 /*
  * estimate_rel_size - estimate # pages and # tuples in a table or index
  *
index 4a5a5205391b932d76f71f2e34660f724426543a..642e4e0e2d1b9f03f5f3837ef6a22aa7fd45360c 100644 (file)
@@ -387,6 +387,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
    /* done building the range table and jointree */
    qry->rtable = pstate->p_rtable;
    qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+   qry->specClause = SPEC_NONE;
 
    qry->hasSubLinks = pstate->p_hasSubLinks;
    qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
@@ -408,6 +409,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 {
    Query      *qry = makeNode(Query);
    SelectStmt *selectStmt = (SelectStmt *) stmt->selectStmt;
+   SpecCmd     spec = stmt->confClause ? stmt->confClause->specclause : SPEC_NONE;
    List       *exprList = NIL;
    bool        isGeneralSelect;
    List       *sub_rtable;
@@ -741,12 +743,13 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
    }
 
    /*
-    * If we have a RETURNING clause, we need to add the target relation to
-    * the query namespace before processing it, so that Var references in
-    * RETURNING will work.  Also, remove any namespace entries added in a
-    * sub-SELECT or VALUES list.
+    * If we have a RETURNING clause, or there are inference elements used as
+    * for ON CONFLICT, we need to add the target relation to the query
+    * namespace before processing it, so that Var references in RETURNING
+    * and/or the inference specification will work.  Also, remove any
+    * namespace entries added in a sub-SELECT or VALUES list.
     */
-   if (stmt->returningList)
+   if (stmt->returningList || stmt->confClause)
    {
        pstate->p_namespace = NIL;
        addRTEtoQuery(pstate, pstate->p_target_rangetblentry,
@@ -759,8 +762,22 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
    qry->rtable = pstate->p_rtable;
    qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
+   qry->specClause = spec;
    qry->hasSubLinks = pstate->p_hasSubLinks;
 
+   if (stmt->confClause)
+   {
+       /*
+        * Perform parse analysis of arbiter columns/expressions.  These are
+        * later used to infer a unique index which arbitrates whether or not
+        * to take the alternative ON CONFLICT path (i.e.  whether or not to
+        * INSERT or take the alternative path in respect of each slot proposed
+        * for insertion).
+        */
+       transformConflictClause(pstate, stmt->confClause, &qry->arbiterElems,
+                               &qry->arbiterWhere);
+   }
+
    assign_query_collations(pstate, qry);
 
    return qry;
@@ -1006,6 +1023,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
    qry->rtable = pstate->p_rtable;
    qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+   qry->specClause = SPEC_NONE;
 
    qry->hasSubLinks = pstate->p_hasSubLinks;
    qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
index 5818858a295e6a2be52f162883e020b464cfb578..ec5b27c2c87782764d626eb921083e7f81579934 100644 (file)
@@ -217,6 +217,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
    RangeVar            *range;
    IntoClause          *into;
    WithClause          *with;
+   InferClause         *infer;
+   ConflictClause          *conf;
    A_Indices           *aind;
    ResTarget           *target;
    struct PrivTarget   *privtarget;
@@ -416,6 +418,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <defelt> SeqOptElem
 
 %type <istmt>  insert_rest
+%type <infer>  opt_conf_expr
+%type <conf>   opt_on_conflict
 
 %type <vsetstmt> generic_set set_rest set_rest_more generic_reset reset_rest
                 SetResetClause FunctionSetResetClause
@@ -555,8 +559,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
    CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
    CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
    CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
-   COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
-   CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
+   COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
+   CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
    CROSS CSV CURRENT_P
    CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
    CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@@ -576,7 +580,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
    HANDLER HAVING HEADER_P HOLD HOUR_P
 
-   IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
+   IDENTITY_P IF_P IGNORE_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
    INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
    INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
    INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -657,6 +661,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc  IS ISNULL NOTNULL   /* IS sets precedence for IS NULL, etc */
 %nonassoc  '<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 %nonassoc  BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
+%nonassoc  DISTINCT
+%nonassoc  ON
 %nonassoc  ESCAPE          /* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
 %nonassoc  OVERLAPS
 %left      POSTFIXOP       /* dummy for postfix Op rules */
@@ -9354,10 +9360,12 @@ DeallocateStmt: DEALLOCATE name
  *****************************************************************************/
 
 InsertStmt:
-           opt_with_clause INSERT INTO qualified_name insert_rest returning_clause
+           opt_with_clause INSERT INTO qualified_name insert_rest
+           opt_on_conflict returning_clause
                {
                    $5->relation = $4;
-                   $5->returningList = $6;
+                   $5->confClause = $6;
+                   $5->returningList = $7;
                    $5->withClause = $1;
                    $$ = (Node *) $5;
                }
@@ -9402,6 +9410,34 @@ insert_column_item:
                }
        ;
 
+opt_on_conflict:
+           ON CONFLICT opt_conf_expr IGNORE_P
+               {
+                   $$ = makeNode(ConflictClause);
+                   $$->specclause = SPEC_IGNORE;
+                   $$->infer = $3;
+                   $$->location = @1;
+               }
+           | /*EMPTY*/
+               {
+                   $$ = NULL;
+               }
+       ;
+
+opt_conf_expr:
+           '(' index_params where_clause ')'
+               {
+                   $$ = makeNode(InferClause);
+                   $$->indexElems = $2;
+                   $$->whereClause = $3;
+                   $$->location = @1;
+               }
+           | /*EMPTY*/
+               {
+                   $$ = NULL;
+               }
+       ;
+
 returning_clause:
            RETURNING target_list       { $$ = $2; }
            | /* EMPTY */               { $$ = NIL; }
@@ -13285,6 +13321,7 @@ unreserved_keyword:
            | COMMIT
            | COMMITTED
            | CONFIGURATION
+           | CONFLICT
            | CONNECTION
            | CONSTRAINTS
            | CONTENT_P
@@ -13344,6 +13381,7 @@ unreserved_keyword:
            | HOUR_P
            | IDENTITY_P
            | IF_P
+           | IGNORE_P
            | IMMEDIATE
            | IMMUTABLE
            | IMPLICIT_P
index 8d90b5098a13f05a81df53bcea79d26d6e6f1697..d2bd2651de47cab4926c6b3e4c4f994ca21f3ad6 100644 (file)
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "catalog/catalog.h"
 #include "catalog/heap.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
@@ -32,6 +33,7 @@
 #include "parser/parse_oper.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
+#include "parser/parse_type.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
@@ -75,6 +77,8 @@ static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node,
                         List **tlist, ParseExprKind exprKind);
 static int get_matching_location(int sortgroupref,
                      List *sortgrouprefs, List *exprs);
+static List *resolve_unique_index_expr(ParseState *pstate, InferClause * infer,
+                         Relation heapRel);
 static List *addTargetToGroupList(ParseState *pstate, TargetEntry *tle,
                     List *grouplist, List *targetlist, int location,
                     bool resolveUnknown);
@@ -2166,6 +2170,157 @@ get_matching_location(int sortgroupref, List *sortgrouprefs, List *exprs)
    return -1;                  /* keep compiler quiet */
 }
 
+/*
+ * resolve_unique_index_expr
+ *     Infer a unique index from a list of indexElems, for ON
+ *     CONFLICT clause
+ *
+ * Perform parse analysis of expressions and columns appearing within ON
+ * CONFLICT clause.  During planning, the returned list of expressions is used
+ * to infer which unique index to use.
+ */
+static List *
+resolve_unique_index_expr(ParseState *pstate, InferClause *infer,
+                         Relation heapRel)
+{
+   List       *result = NIL;
+   ListCell   *l;
+
+   foreach(l, infer->indexElems)
+   {
+       IndexElem      *ielem = (IndexElem *) lfirst(l);
+       InferenceElem  *pInfer = makeNode(InferenceElem);
+       Node           *parse;
+
+       /*
+        * Raw grammar re-uses CREATE INDEX infrastructure for unique index
+        * inference clause, and so will accept opclasses by name and so on.
+        *
+        * Make no attempt to match ASC or DESC ordering or NULLS FIRST/NULLS
+        * LAST ordering, since those are not significant for inference
+        * purposes (any unique index matching the inference specification in
+        * other regards is accepted indifferently).  Actively reject this as
+        * wrong-headed.
+        */
+       if (ielem->ordering != SORTBY_DEFAULT ||
+           ielem->nulls_ordering != SORTBY_NULLS_DEFAULT)
+           ereport(ERROR,
+                   (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+                    errmsg("ON CONFLICT does not accept ordering or NULLS FIRST/LAST specifications"),
+                    errhint("These factors do not affect uniqueness of indexed datums."),
+                    parser_errposition(pstate,
+                                       exprLocation((Node *) infer))));
+
+       if (!ielem->expr)
+       {
+           /* Simple index attribute */
+           ColumnRef  *n;
+
+           /*
+            * Grammar won't have built raw expression for us in event of plain
+            * column reference.  Create one directly, and perform expression
+            * transformation.  Planner expects this, and performs its own
+            * normalization for the purposes of matching against pg_index.
+            */
+           n = makeNode(ColumnRef);
+           n->fields = list_make1(makeString(ielem->name));
+           /* Location is approximately that of inference specification */
+           n->location = infer->location;
+           parse = (Node *) n;
+       }
+       else
+       {
+           /* Do parse transformation of the raw expression */
+           parse = (Node *) ielem->expr;
+       }
+
+       /*
+        * transformExpr() should have already rejected subqueries,
+        * aggregates, and window functions, based on the EXPR_KIND_ for an
+        * index expression.  Expressions returning sets won't have been
+        * rejected, but don't bother doing so here; there should be no
+        * available expression unique index to match any such expression
+        * against anyway.
+        */
+       pInfer->expr = transformExpr(pstate, parse, EXPR_KIND_INDEX_EXPRESSION);
+
+       /* Perform lookup of collation and operator class as required */
+       if (!ielem->collation)
+           pInfer->infercollid = InvalidOid;
+       else
+           pInfer->infercollid = LookupCollation(pstate, ielem->collation,
+                                                 exprLocation(pInfer->expr));
+
+       if (!ielem->opclass)
+       {
+           pInfer->inferopfamily = InvalidOid;
+           pInfer->inferopcinputtype = InvalidOid;
+       }
+       else
+       {
+           Oid     opclass = get_opclass_oid(BTREE_AM_OID, ielem->opclass,
+                                             false);
+
+           pInfer->inferopfamily = get_opclass_family(opclass);
+           pInfer->inferopcinputtype = get_opclass_input_type(opclass);
+       }
+
+       result = lappend(result, pInfer);
+   }
+
+   return result;
+}
+
+/*
+ * transformConflictClauseExpr -
+ *     transform expressions of ON CONFLICT.
+ *
+ * Transformed expressions used to infer one unique index relation to serve as
+ * an ON CONFLICT arbiter.  Partial unique indexes may be inferred using WHERE
+ * clause from inference specification clause.
+ */
+void
+transformConflictClause(ParseState *pstate, ConflictClause *confClause,
+                       List **arbiterExpr, Node **arbiterWhere)
+{
+   InferClause *infer = confClause->infer;
+
+   /*
+    * To simplify certain aspects of its design, speculative insertion into
+    * system catalogs is disallowed
+    */
+   if (IsCatalogRelation(pstate->p_target_relation))
+       ereport(ERROR,
+               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                errmsg("ON CONFLICT not supported with catalog relations"),
+                parser_errposition(pstate,
+                                   exprLocation((Node *) confClause))));
+
+   /* ON CONFLICT IGNORE does not require an inference clause */
+   if (infer)
+   {
+       *arbiterExpr = resolve_unique_index_expr(pstate, infer,
+                                                pstate->p_target_relation);
+
+       /*
+        * Handling inference WHERE clause (for partial unique index
+        * inference)
+        */
+       if (infer->whereClause)
+           *arbiterWhere = transformExpr(pstate, infer->whereClause,
+                                         EXPR_KIND_INDEX_PREDICATE);
+   }
+
+   /*
+    * It's convenient to form a list of expressions based on the
+    * representation used by CREATE INDEX, since the same restrictions are
+    * appropriate (e.g. on subqueries).  However, from here on, a dedicated
+    * primnode representation is used for inference elements, and so
+    * assign_query_collations() can be trusted to do the right thing with the
+    * post parse analysis query tree inference clause representation.
+    */
+}
+
 /*
  * addTargetToSortList
  *     If the given targetlist entry isn't already in the SortGroupClause
index 7c6a11c7575e0648b78e39be81d92b170a6861ef..ed266f4d3099c41f28d276d517db9d62b842c44e 100644 (file)
@@ -483,6 +483,7 @@ assign_collations_walker(Node *node, assign_collations_context *context)
        case T_JoinExpr:
        case T_FromExpr:
        case T_SortGroupClause:
+       case T_InferenceElem:
            (void) expression_tree_walker(node,
                                          assign_collations_walker,
                                          (void *) &loccontext);
index eb7293f2f33cc820adc18e337b587c47cfd13255..423376c9fe624b4529c3e56bde8f0ceb2a44df7a 100644 (file)
@@ -412,6 +412,12 @@ DecodeHeapOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
            ReorderBufferXidSetCatalogChanges(ctx->reorder, xid, buf->origptr);
            break;
 
+       case XLOG_HEAP_AFFIRM:
+           /*
+            * Speculative assertion is actually affirmed by the absence of
+            * super deletion;  do nothing with this
+            */
+           break;
        case XLOG_HEAP_LOCK:
            /* we don't care about row level locks for now */
            break;
@@ -538,7 +544,11 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
        return;
 
    change = ReorderBufferGetChange(ctx->reorder);
-   change->action = REORDER_BUFFER_CHANGE_INSERT;
+   if (!(xlrec->flags & XLOG_HEAP_SPECULATIVE_TUPLE))
+       change->action = REORDER_BUFFER_CHANGE_INSERT;
+   else
+       change->action = REORDER_BUFFER_CHANGE_INTERNAL_INSERT;
+
    memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode));
 
    if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE)
@@ -629,7 +639,10 @@ DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
        return;
 
    change = ReorderBufferGetChange(ctx->reorder);
-   change->action = REORDER_BUFFER_CHANGE_DELETE;
+   if (!(xlrec->flags & XLOG_HEAP_SPECULATIVE_TUPLE))
+       change->action = REORDER_BUFFER_CHANGE_DELETE;
+   else
+       change->action = REORDER_BUFFER_CHANGE_INTERNAL_DELETE;
 
    memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode));
 
index dc855830c4e468698da6eb57ebfa13713a47822a..6da8d6fb2cea529ce75971375463dd86b8e76461 100644 (file)
@@ -401,6 +401,8 @@ ReorderBufferReturnChange(ReorderBuffer *rb, ReorderBufferChange *change)
        case REORDER_BUFFER_CHANGE_INSERT:
        case REORDER_BUFFER_CHANGE_UPDATE:
        case REORDER_BUFFER_CHANGE_DELETE:
+       case REORDER_BUFFER_CHANGE_INTERNAL_INSERT:
+       case REORDER_BUFFER_CHANGE_INTERNAL_DELETE:
            if (change->data.tp.newtuple)
            {
                ReorderBufferReturnTupleBuf(rb, change->data.tp.newtuple);
@@ -1314,6 +1316,7 @@ ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
    PG_TRY();
    {
        ReorderBufferChange *change;
+       ReorderBufferChange *specinsert = NULL;
 
        if (using_subtxn)
            BeginInternalSubTransaction("replay");
@@ -1333,6 +1336,8 @@ ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
                case REORDER_BUFFER_CHANGE_INSERT:
                case REORDER_BUFFER_CHANGE_UPDATE:
                case REORDER_BUFFER_CHANGE_DELETE:
+               case REORDER_BUFFER_CHANGE_INTERNAL_INSERT:
+               case REORDER_BUFFER_CHANGE_INTERNAL_DELETE:
                    Assert(snapshot_now);
 
                    reloid = RelidByRelfilenode(change->data.tp.relnode.spcNode,
@@ -1373,8 +1378,73 @@ ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
                        /* user-triggered change */
                        else if (!IsToastRelation(relation))
                        {
+                           /*
+                            * Previous speculative insertion's success
+                            * affirmed by a new (non-superdelete) DML change
+                            */
+                           if (specinsert &&
+                               change->action !=
+                               REORDER_BUFFER_CHANGE_INTERNAL_DELETE)
+                           {
+                               /* Report as proper insert to client */
+                               specinsert->action = REORDER_BUFFER_CHANGE_INSERT;
+                               rb->apply_change(rb, txn, relation, specinsert);
+
+                               /* Free memory from pending tuple */
+                               Assert(specinsert->data.tp.oldtuple == NULL);
+                               ReorderBufferReturnTupleBuf(rb, specinsert->data.tp.newtuple);
+                               specinsert = NULL;
+                           }
+
                            ReorderBufferToastReplace(rb, txn, relation, change);
-                           rb->apply_change(rb, txn, relation, change);
+
+                           /*
+                            * Kludge:  Speculative insertion occasionally
+                            * makes use of "super deletion" -- an
+                            * implementation defined delete of a speculatively
+                            * inserted tuple.  Neither the super deletion, nor
+                            * the insertion (which must be the prior record
+                            * type) are included in the final assembly when
+                            * the tuple was super-deleted.  Otherwise, an
+                            * ordinary insertion is assembled.
+                            */
+                           if (change->action == REORDER_BUFFER_CHANGE_INTERNAL_INSERT)
+                           {
+                               /*
+                                * Need to ensure the memory used by promise
+                                * tuple isn't freed till we're done verifying
+                                * that there is no super deletion that
+                                * immediately follows.  Otherwise it could get
+                                * freed/reused while restoring spooled data
+                                * from disk.
+                                */
+                               dlist_delete(&change->node);
+                               specinsert = change;
+                               /* Don't clear reassembled toast chunks */
+                               continue;
+                           }
+                           else if (change->action ==
+                                    REORDER_BUFFER_CHANGE_INTERNAL_DELETE)
+                           {
+                               Assert(RelFileNodeEquals(change->data.tp.relnode,
+                                                        specinsert->data.tp.relnode));
+
+                               /*
+                                * Free memory from pending tuple.  Do not
+                                * report as logical delete to encoding plugin.
+                                */
+                               Assert(specinsert->data.tp.oldtuple == NULL);
+                               ReorderBufferReturnTupleBuf(rb, specinsert->data.tp.newtuple);
+                               specinsert = NULL;
+                           }
+                           else
+                           {
+                               /*
+                                * Handle non-speculative insertion related
+                                * changes
+                                */
+                               rb->apply_change(rb, txn, relation, change);
+                           }
 
                            /*
                             * Only clear reassembled toast chunks if we're
@@ -1471,6 +1541,36 @@ ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
            }
        }
 
+       /*
+        * Previous speculative insertion's success affirmed by reaching end of
+        * xact's changes
+        */
+       if (specinsert)
+       {
+           Relation    relation;
+           Oid         reloid;
+
+           reloid = RelidByRelfilenode(specinsert->data.tp.relnode.spcNode,
+                                       specinsert->data.tp.relnode.relNode);
+
+           /*
+            * Catalog tuple without data, emitted while catalog was
+            * in the process of being rewritten.
+            */
+           if (reloid == InvalidOid)
+               elog(ERROR, "could not map filenode \"%s\" to relation OID",
+                    relpathperm(specinsert->data.tp.relnode,
+                                MAIN_FORKNUM));
+
+           relation = RelationIdGetRelation(reloid);
+           /* Report as proper insert to client */
+           specinsert->action = REORDER_BUFFER_CHANGE_INSERT;
+           rb->apply_change(rb, txn, relation, specinsert);
+           /* Free memory from pending tuple */
+           Assert(specinsert->data.tp.oldtuple == NULL);
+           ReorderBufferReturnTupleBuf(rb, specinsert->data.tp.newtuple);
+       }
+
        /* clean up the iterator */
        ReorderBufferIterTXNFinish(rb, iterstate);
        iterstate = NULL;
@@ -2003,6 +2103,10 @@ ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
        case REORDER_BUFFER_CHANGE_UPDATE:
            /* fall through */
        case REORDER_BUFFER_CHANGE_DELETE:
+           /* fall through */
+       case REORDER_BUFFER_CHANGE_INTERNAL_INSERT:
+           /* fall through */
+       case REORDER_BUFFER_CHANGE_INTERNAL_DELETE:
            {
                char       *data;
                ReorderBufferTupleBuf *oldtup,
@@ -2258,6 +2362,10 @@ ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
        case REORDER_BUFFER_CHANGE_UPDATE:
            /* fall through */
        case REORDER_BUFFER_CHANGE_DELETE:
+           /* fall through */
+       case REORDER_BUFFER_CHANGE_INTERNAL_INSERT:
+           /* fall through */
+       case REORDER_BUFFER_CHANGE_INTERNAL_DELETE:
            if (change->data.tp.newtuple)
            {
                Size        len = offsetof(ReorderBufferTupleBuf, t_data) +
index 9d2c280b32899a92ba905ac005bb8403b44b5107..70c8e089312335c495f7ab6c56490fe518ebf874 100644 (file)
@@ -66,7 +66,7 @@ static void markQueryForLocking(Query *qry, Node *jtnode,
                    LockClauseStrength strength, LockWaitPolicy waitPolicy,
                    bool pushedDown);
 static List *matchLocks(CmdType event, RuleLock *rulelocks,
-          int varno, Query *parsetree);
+          int varno, Query *parsetree, bool *hasUpdate);
 static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
             bool forUpdatePushedDown);
 static bool view_has_instead_trigger(Relation view, CmdType event);
@@ -1288,7 +1288,8 @@ static List *
 matchLocks(CmdType event,
           RuleLock *rulelocks,
           int varno,
-          Query *parsetree)
+          Query *parsetree,
+          bool *hasUpdate)
 {
    List       *matching_locks = NIL;
    int         nlocks;
@@ -1309,6 +1310,9 @@ matchLocks(CmdType event,
    {
        RewriteRule *oneLock = rulelocks->rules[i];
 
+       if (oneLock->event == CMD_UPDATE)
+           *hasUpdate = true;
+
        /*
         * Suppress ON INSERT/UPDATE/DELETE rules that are disabled or
         * configured to not fire during the current sessions replication
@@ -2953,6 +2957,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
    CmdType     event = parsetree->commandType;
    bool        instead = false;
    bool        returning = false;
+   bool        updatableview = false;
    Query      *qual_product = NULL;
    List       *rewritten = NIL;
    ListCell   *lc1;
@@ -3035,6 +3040,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
        Relation    rt_entry_relation;
        List       *locks;
        List       *product_queries;
+       bool        hasUpdate = false;
 
        result_relation = parsetree->resultRelation;
        Assert(result_relation != 0);
@@ -3103,7 +3109,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
         * Collect and apply the appropriate rules.
         */
        locks = matchLocks(event, rt_entry_relation->rd_rules,
-                          result_relation, parsetree);
+                          result_relation, parsetree, &hasUpdate);
 
        product_queries = fireRules(parsetree,
                                    result_relation,
@@ -3152,6 +3158,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
             */
            instead = true;
            returning = true;
+           updatableview = true;
        }
 
        /*
@@ -3232,6 +3239,17 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
            }
        }
 
+       /*
+        * Updatable views are supported by ON CONFLICT IGNORE, so don't
+        * prevent that case from proceeding
+        */
+       if (parsetree->specClause != SPEC_NONE &&
+           (product_queries != NIL || hasUpdate) &&
+           !updatableview)
+               ereport(ERROR,
+                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("INSERT with ON CONFLICT clause may not target relation with INSERT or UPDATE rules")));
+
        heap_close(rt_entry_relation, NoLock);
    }
 
index d13a1673344b302534ba939cb49421eb43dd2a10..07a7c2d9ca6477e4c21a6db9dcaf064286006a7b 100644 (file)
@@ -575,6 +575,88 @@ ConditionalXactLockTableWait(TransactionId xid)
    return true;
 }
 
+/*
+ * Per-backend final disambiguator of an attempt to insert speculatively.
+ *
+ * This may wraparound, but since it is only a final disambiguator (speculative
+ * waiters also check TID and relfilenode), this is deemed to be acceptable.
+ * There is only a theoretical, vanishingly small chance of a backend
+ * spuriously considering that it must wait on another backend's
+ * end-of-speculative insertion (call to SpeculativeInsertionLockRelease())
+ * when that isn't strictly necessary, and even this is likely to be
+ * inconsequential.  At worst, unprincipled deadlocks are not entirely
+ * eliminated in extreme corner cases.
+ */
+static uint32 speculativeInsertionToken = 0;
+
+/*
+ *     SpeculativeInsertionLockAcquire
+ *
+ * Insert a lock showing that the given transaction ID is inserting a tuple,
+ * but hasn't yet decided whether it's going to keep it. The lock can then be
+ * used to wait for the decision to go ahead with the insertion, or aborting
+ * it.
+ *
+ * The token is used to distinguish multiple insertions by the same
+ * transaction.  It is returned to caller.
+ */
+uint32
+SpeculativeInsertionLockAcquire(TransactionId xid)
+{
+   LOCKTAG     tag;
+
+   speculativeInsertionToken++;
+
+   /*
+    * A zero speculative insertion lock indicates no token is held;  Don't
+    * allow the token to overflow to zero
+    */
+   if (speculativeInsertionToken == 0)
+       speculativeInsertionToken = 1;
+
+   SET_LOCKTAG_SPECULATIVE_INSERTION(tag, xid, speculativeInsertionToken);
+
+   (void) LockAcquire(&tag, ExclusiveLock, false, false);
+
+   return speculativeInsertionToken;
+}
+
+/*
+ *     SpeculativeInsertionLockRelease
+ *
+ * Delete the lock showing that the given transaction is speculatively
+ * inserting a tuple.
+ */
+void
+SpeculativeInsertionLockRelease(TransactionId xid)
+{
+   LOCKTAG     tag;
+
+   SET_LOCKTAG_SPECULATIVE_INSERTION(tag, xid, speculativeInsertionToken);
+
+   LockRelease(&tag, ExclusiveLock, false);
+}
+
+/*
+ *     SpeculativeInsertionWait
+ *
+ * Wait for the specified transaction to finish or abort the insertion of a
+ * tuple.
+ */
+void
+SpeculativeInsertionWait(TransactionId xid, uint32 token)
+{
+   LOCKTAG     tag;
+
+   SET_LOCKTAG_SPECULATIVE_INSERTION(tag, xid, token);
+
+   Assert(TransactionIdIsValid(xid));
+   Assert(token != 0);
+
+   (void) LockAcquire(&tag, ShareLock, false, false);
+   LockRelease(&tag, ShareLock, false);
+}
+
 /*
  * XactLockTableWaitErrorContextCb
  *     Error context callback for transaction lock waits.
@@ -873,6 +955,12 @@ DescribeLockTag(StringInfo buf, const LOCKTAG *tag)
                             tag->locktag_field1,
                             tag->locktag_field2);
            break;
+       case LOCKTAG_SPECULATIVE_TOKEN:
+           appendStringInfo(buf,
+                            _("speculative token %u of transaction %u"),
+                            tag->locktag_field2,
+                            tag->locktag_field1);
+           break;
        case LOCKTAG_OBJECT:
            appendStringInfo(buf,
                             _("object %u of class %u of database %u"),
index a1967b69632f95654a045b9a0fa9eb902233af5c..2ab66ce328514675714976d41e6c3a2b1d9a60d4 100644 (file)
@@ -28,6 +28,7 @@ static const char *const LockTagTypeNames[] = {
    "tuple",
    "transactionid",
    "virtualxid",
+   "speculative token",
    "object",
    "userlock",
    "advisory"
index a4a478d1142d8185c4d2713eabc83368335b1bbf..5982ef9ce1c41e4c965b2e9a1e08a2e3e62d0681 100644 (file)
@@ -262,7 +262,14 @@ HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer)
        }
    }
 
-   /* by here, the inserting transaction has committed */
+   if (HeapTupleHeaderSuperDeleted(tuple))
+       return false;
+
+   /*
+    * By here, the inserting transaction has committed.  Once committed,
+    * tuples can never be speculative.
+    */
+   Assert(!HeapTupleHeaderIsSpeculative(tuple));
 
    if (tuple->t_infomask & HEAP_XMAX_INVALID)  /* xid invalid or aborted */
        return true;
@@ -360,6 +367,7 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
 
    Assert(ItemPointerIsValid(&htup->t_self));
    Assert(htup->t_tableOid != InvalidOid);
+   Assert(!HeapTupleHeaderSuperDeleted(tuple));
 
    if (!HeapTupleHeaderXminCommitted(tuple))
    {
@@ -446,6 +454,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
 
    Assert(ItemPointerIsValid(&htup->t_self));
    Assert(htup->t_tableOid != InvalidOid);
+   Assert(!HeapTupleHeaderSuperDeleted(tuple));
 
    if (!HeapTupleHeaderXminCommitted(tuple))
    {
@@ -727,6 +736,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
    Assert(htup->t_tableOid != InvalidOid);
 
    snapshot->xmin = snapshot->xmax = InvalidTransactionId;
+   snapshot->speculativeToken = 0;
 
    if (!HeapTupleHeaderXminCommitted(tuple))
    {
@@ -808,6 +818,29 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
        }
        else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
        {
+           RelFileNode rnode;
+           ForkNumber  forkno;
+           BlockNumber blockno;
+
+           BufferGetTag(buffer, &rnode, &forkno, &blockno);
+
+           /* tuples can only be in the main fork */
+           Assert(forkno == MAIN_FORKNUM);
+           Assert(blockno == ItemPointerGetBlockNumber(&htup->t_self));
+
+           /*
+            * Set speculative token.  Caller can worry about xmax, since it
+            * requires a conclusively locked row version, and a concurrent
+            * update to this tuple is a conflict of its purposes.
+            */
+           if (HeapTupleHeaderIsSpeculative(tuple))
+           {
+               snapshot->speculativeToken =
+                   SpeculativeTokenGetTokenNumber(&tuple->t_tidstate.t_token);
+
+               Assert(snapshot->speculativeToken != 0);
+           }
+
            snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
            /* XXX shouldn't we fall through to look at xmax? */
            return true;        /* in insertion by other */
@@ -824,7 +857,14 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
        }
    }
 
-   /* by here, the inserting transaction has committed */
+   if (HeapTupleHeaderSuperDeleted(tuple))
+       return false;
+
+   /*
+    * By here, the inserting transaction has committed.  Once committed,
+    * tuples can never be speculative.
+    */
+   Assert(!HeapTupleHeaderIsSpeculative(tuple));
 
    if (tuple->t_infomask & HEAP_XMAX_INVALID)  /* xid invalid or aborted */
        return true;
@@ -1023,10 +1063,16 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
        }
    }
 
+   if (HeapTupleHeaderSuperDeleted(tuple))
+       return false;
+
    /*
-    * By here, the inserting transaction has committed - have to check
-    * when...
+    * By here, the inserting transaction has committed.  Once committed,
+    * tuples can never be speculative.
     */
+   Assert(!HeapTupleHeaderIsSpeculative(tuple));
+
+   /* have to check when committed */
    if (!HeapTupleHeaderXminFrozen(tuple)
        && XidInMVCCSnapshot(HeapTupleHeaderGetRawXmin(tuple), snapshot))
        return false;           /* treat as still in progress */
@@ -1219,10 +1265,16 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
         */
    }
 
+   if (HeapTupleHeaderSuperDeleted(tuple))
+       return HEAPTUPLE_DEAD;
+
    /*
-    * Okay, the inserter committed, so it was good at some point.  Now what
-    * about the deleting transaction?
+    * Okay, the inserter committed, so it was good at some point.  Once
+    * committed, tuples can never be speculative.
     */
+   Assert(!HeapTupleHeaderIsSpeculative(tuple));
+
+   /* Now what about the deleting transaction? */
    if (tuple->t_infomask & HEAP_XMAX_INVALID)
        return HEAPTUPLE_LIVE;
 
@@ -1407,7 +1459,10 @@ HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
    if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
        return false;
 
-   /* Deleter committed, so tuple is dead if the XID is old enough. */
+   /*
+    * Deleter committed, so tuple is dead if the XID is old enough.  This
+    * handles super deleted tuples correctly.
+    */
    return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);
 }
 
@@ -1540,6 +1595,8 @@ HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
 {
    TransactionId xmax;
 
+   Assert(!HeapTupleHeaderSuperDeleted(tuple));
+
    /* if there's no valid Xmax, then there's obviously no update either */
    if (tuple->t_infomask & HEAP_XMAX_INVALID)
        return true;
@@ -1597,6 +1654,9 @@ TransactionIdInArray(TransactionId xid, TransactionId *xip, Size num)
  * We don't need to support HEAP_MOVED_(IN|OFF) for now because we only support
  * reading catalog pages which couldn't have been created in an older version.
  *
+ * We don't support speculative insertion into catalogs, and so there are no
+ * checks for speculative/super deleted tuples.
+ *
  * We don't set any hint bits in here as it seems unlikely to be beneficial as
  * those should already be set by normal access and it seems to be too
  * dangerous to do so as the semantics of doing so during timetravel are more
@@ -1612,6 +1672,8 @@ HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot,
 
    Assert(ItemPointerIsValid(&htup->t_self));
    Assert(htup->t_tableOid != InvalidOid);
+   Assert(!HeapTupleHeaderIsSpeculative(tuple));
+   Assert(!HeapTupleHeaderSuperDeleted(tuple));
 
    /* inserting transaction aborted */
    if (HeapTupleHeaderXminInvalid(tuple))
index 888cce7a2d841396258174d7e38e8b786ce7ec28..70db83e2f5f4336521a3e45557e218a519220aec 100644 (file)
@@ -28,6 +28,7 @@
 #define HEAP_INSERT_SKIP_WAL   0x0001
 #define HEAP_INSERT_SKIP_FSM   0x0002
 #define HEAP_INSERT_FROZEN     0x0004
+#define HEAP_INSERT_SPECULATIVE 0x0008
 
 typedef struct BulkInsertStateData *BulkInsertState;
 
@@ -141,7 +142,9 @@ extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
                  CommandId cid, int options, BulkInsertState bistate);
 extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
            CommandId cid, Snapshot crosscheck, bool wait,
-           HeapUpdateFailureData *hufd);
+           HeapUpdateFailureData *hufd, bool speculative);
+extern void heap_finish_speculative(Relation relation, HeapTuple tuple,
+                                   bool conflict);
 extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
            HeapTuple newtup,
            CommandId cid, Snapshot crosscheck, bool wait,
index f0f89dec0f229a41d4f169647e49f9e18185ce29..58d9f9469cdbe212550fd24d555f550ffb8e6cbd 100644 (file)
@@ -34,7 +34,7 @@
 #define XLOG_HEAP_UPDATE       0x20
 /* 0x030 is free, was XLOG_HEAP_MOVE */
 #define XLOG_HEAP_HOT_UPDATE   0x40
-/* 0x050 is free, was XLOG_HEAP_NEWPAGE */
+#define XLOG_HEAP_AFFIRM       0x50
 #define XLOG_HEAP_LOCK         0x60
 #define XLOG_HEAP_INPLACE      0x70
 
@@ -73,6 +73,8 @@
 #define XLOG_HEAP_SUFFIX_FROM_OLD          (1<<6)
 /* last xl_heap_multi_insert record for one heap_multi_insert() call */
 #define XLOG_HEAP_LAST_MULTI_INSERT            (1<<7)
+/* reuse xl_heap_multi_insert-only bit for xl_heap_insert and xl_heap_delete */
+#define XLOG_HEAP_SPECULATIVE_TUPLE    XLOG_HEAP_LAST_MULTI_INSERT
 
 /* convenience macro for checking whether any form of old tuple was logged */
 #define XLOG_HEAP_CONTAINS_OLD                     \
@@ -243,6 +245,14 @@ typedef struct xl_heap_lock_updated
 
 #define SizeOfHeapLockUpdated  (offsetof(xl_heap_lock_updated, infobits_set) + sizeof(uint8))
 
+/* This is what we need to know about affirmation of speculative insertion */
+typedef struct xl_heap_affirm
+{
+   OffsetNumber offnum;        /* affirmed tuple's offset on page */
+} xl_heap_affirm;
+
+#define SizeOfHeapAffirm   (offsetof(xl_heap_affirm, offnum) + sizeof(OffsetNumber))
+
 /* This is what we need to know about in-place update */
 typedef struct xl_heap_inplace
 {
index b0140298b1fd06bad10bf11baa7b6268e7bb8d12..b3b91e70d50f5e0a5c364268faa3cfa6a66b6add 100644 (file)
@@ -36,7 +36,7 @@ typedef struct BulkInsertStateData
 
 
 extern void RelationPutHeapTuple(Relation relation, Buffer buffer,
-                    HeapTuple tuple);
+                    HeapTuple tuple, bool token);
 extern Buffer RelationGetBufferForTuple(Relation relation, Size len,
                          Buffer otherBuffer, int options,
                          BulkInsertState bistate,
index 0a673cd52679ddde5a25c4f6d3fc14c7db17914b..dd77afa658e2f5c55d1e6bef6beedcf7eb2de61b 100644 (file)
  * unrelated tuple stored into a slot recently freed by VACUUM.  If either
  * check fails, one may assume that there is no live descendant version.
  *
+ * t_ctid is sometimes used to store a speculative token, for speculative
+ * inserters.  Code paths that follow t_ctid chains must also consider that the
+ * apparently pointed to t_ctid is in fact such a token, that should similarly
+ * not be followed.
+ *
  * Following the fixed header fields, the nulls bitmap is stored (beginning
  * at t_bits).  The bitmap is *not* stored if t_infomask shows that there
  * are no nulls in the tuple.  If an OID field is present (as indicated by
@@ -138,7 +143,11 @@ struct HeapTupleHeaderData
        DatumTupleFields t_datum;
    }           t_choice;
 
-   ItemPointerData t_ctid;     /* current TID of this or newer tuple */
+   union
+   {
+       ItemPointerData t_ctid;     /* current TID of this or newer tuple */
+       SpeculativeToken t_token;   /* Speculative insertion token */
+   }           t_tidstate;
 
    /* Fields below here must match MinimalTupleData! */
 
@@ -306,6 +315,18 @@ struct HeapTupleHeaderData
    ((tup)->t_infomask |= HEAP_XMIN_FROZEN) \
 )
 
+/*
+ * Was tuple "super deleted" following unsuccessful speculative insertion (i.e.
+ * conflict was detected at insertion time)?  Is is not sufficient to set
+ * HEAP_XMIN_INVALID to super delete because it is only a hint, and because it
+ * interacts with transaction commit status.  Speculative insertion decouples
+ * visibility from transaction duration for one special purpose.
+ */
+#define HeapTupleHeaderSuperDeleted(tup) \
+( \
+   (!TransactionIdIsValid(HeapTupleHeaderGetRawXmin(tup))) \
+)
+
 /*
  * HeapTupleHeaderGetRawXmax gets you the raw Xmax field.  To find out the Xid
  * that updated a tuple, you might need to resolve the MultiXactId if certain
@@ -455,6 +476,11 @@ do { \
   (tup)->t_infomask2 &= ~HEAP_ONLY_TUPLE \
 )
 
+#define HeapTupleHeaderIsSpeculative(tup) \
+( \
+  (tup)->t_tidstate.t_ctid.ip_posid == MagicOffsetNumber \
+)
+
 #define HeapTupleHeaderHasMatch(tup) \
 ( \
   (tup)->t_infomask2 & HEAP_TUPLE_HAS_MATCH \
index a04def96e4cadd6cc569e59d74ee216ee1979ff7..d091e0b7bcda963292198b77e58d0950c391d4fe 100644 (file)
@@ -81,6 +81,8 @@ extern void index_drop(Oid indexId, bool concurrent);
 
 extern IndexInfo *BuildIndexInfo(Relation index);
 
+extern void IndexInfoSpeculative(Relation index, IndexInfo *ii);
+
 extern void FormIndexDatum(IndexInfo *indexInfo,
               TupleTableSlot *slot,
               EState *estate,
index c1e747795619d1101692cfbb7c9c28bab72a0db3..9229aa3b5dc99ce3044a9154265ba58c28e52a65 100644 (file)
@@ -351,16 +351,19 @@ extern bool ExecRelationIsTargetRelation(EState *estate, Index scanrelid);
 extern Relation ExecOpenScanRelation(EState *estate, Index scanrelid, int eflags);
 extern void ExecCloseScanRelation(Relation scanrel);
 
-extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
+extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
 extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
 extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
-                     EState *estate);
-extern bool check_exclusion_constraint(Relation heap, Relation index,
-                          IndexInfo *indexInfo,
-                          ItemPointer tupleid,
-                          Datum *values, bool *isnull,
-                          EState *estate,
-                          bool newIndex, bool errorOK);
+                     EState *estate, bool noDupErr, List *arbiterIndexes);
+extern bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate,
+                     ItemPointer conflictTid, List *arbiterIndexes);
+extern bool check_exclusion_or_unique_constraint(Relation heap, Relation index,
+                                      IndexInfo *indexInfo,
+                                      ItemPointer tupleid,
+                                      Datum *values, bool *isnull,
+                                      EState *estate,
+                                      bool newIndex, bool errorOK,
+                                      bool wait, ItemPointer conflictTid);
 
 extern void RegisterExprContextCallback(ExprContext *econtext,
                            ExprContextCallbackFunction function,
index ac75f86fef7e5a25f7ece603d4bd88275120d3b9..cc4ed9a638a18aadb92fba70efbe0be04d675207 100644 (file)
@@ -41,6 +41,9 @@
  *     ExclusionOps        Per-column exclusion operators, or NULL if none
  *     ExclusionProcs      Underlying function OIDs for ExclusionOps
  *     ExclusionStrats     Opclass strategy numbers for ExclusionOps
+ *     UniqueOps           Theses are like Exclusion*, but for unique indexes
+ *     UniqueProcs
+ *     UniqueStrats
  *     Unique              is it a unique index?
  *     ReadyForInserts     is it valid for inserts?
  *     Concurrent          are we doing a concurrent index build?
@@ -62,6 +65,9 @@ typedef struct IndexInfo
    Oid        *ii_ExclusionOps;    /* array with one entry per column */
    Oid        *ii_ExclusionProcs;      /* array with one entry per column */
    uint16     *ii_ExclusionStrats;     /* array with one entry per column */
+   Oid        *ii_UniqueOps;   /* array with one entry per column */
+   Oid        *ii_UniqueProcs;     /* array with one entry per column */
+   uint16     *ii_UniqueStrats;        /* array with one entry per column */
    bool        ii_Unique;
    bool        ii_ReadyForInserts;
    bool        ii_Concurrent;
@@ -1092,6 +1098,8 @@ typedef struct ModifyTableState
    int         mt_whichplan;   /* which one is being executed (0..n-1) */
    ResultRelInfo *resultRelInfo;       /* per-subplan target relations */
    List      **mt_arowmarks;   /* per-subplan ExecAuxRowMark lists */
+   SpecCmd     spec;           /* reason for speculative insertion */
+   List       *arbiterIndexes; /* unique index OIDs to arbitrate taking alt path */
    EPQState    mt_epqstate;    /* for evaluating EvalPlanQual rechecks */
    bool        fireBSTriggers; /* do we need to fire stmt triggers? */
 } ModifyTableState;
index 38469ef4d1af4abba24935d91e76f1eb30ed25cf..f1b50297c60adb834e5397d06d5dad4f7355202f 100644 (file)
@@ -168,6 +168,7 @@ typedef enum NodeTag
    T_CoerceToDomainValue,
    T_SetToDefault,
    T_CurrentOfExpr,
+   T_InferenceElem,
    T_TargetEntry,
    T_RangeTblRef,
    T_JoinExpr,
@@ -412,6 +413,8 @@ typedef enum NodeTag
    T_RowMarkClause,
    T_XmlSerialize,
    T_WithClause,
+   T_InferClause,
+   T_ConflictClause,
    T_CommonTableExpr,
    T_RoleSpec,
 
@@ -625,4 +628,16 @@ typedef enum JoinType
       (1 << JOIN_RIGHT) | \
       (1 << JOIN_ANTI))) != 0)
 
+/*
+ * SpecCmd -
+ *   "Speculative insertion" clause
+ *
+ * This is needed in both parsenodes.h and plannodes.h, so put it here...
+ */
+typedef enum
+{
+   SPEC_NONE,      /* Not involved in speculative insertion */
+   SPEC_IGNORE     /* INSERT of "ON CONFLICT IGNORE" */
+} SpecCmd;
+
 #endif   /* NODES_H */
index 0e257ac46ce2104d2d410d6eb347609f5df51d78..704b0ef7b2bd640e82058f9b5e53aee81271d94c 100644 (file)
@@ -132,6 +132,10 @@ typedef struct Query
 
    List       *withCheckOptions;       /* a list of WithCheckOption's */
 
+   SpecCmd     specClause;     /* speculative insertion clause */
+   List       *arbiterElems;   /* unique index arbiter list (of InferenceElem's) */
+   Node       *arbiterWhere;   /* unique index arbiter WHERE clause */
+
    List       *returningList;  /* return-values list (of TargetEntry) */
 
    List       *groupClause;    /* a list of SortGroupClause's */
@@ -591,7 +595,7 @@ typedef enum TableLikeOption
 } TableLikeOption;
 
 /*
- * IndexElem - index parameters (used in CREATE INDEX)
+ * IndexElem - index parameters (used in CREATE INDEX, and in ON CONFLICT)
  *
  * For a plain index attribute, 'name' is the name of the table column to
  * index, and 'expr' is NULL.  For an index expression, 'name' is NULL and
@@ -1014,6 +1018,34 @@ typedef struct WithClause
    int         location;       /* token location, or -1 if unknown */
 } WithClause;
 
+/*
+ * InferClause -
+ *         ON CONFLICT unique index inference clause
+ *
+ * Note: InferClause does not propagate into the Query representation.
+ */
+typedef struct InferClause
+{
+   NodeTag     type;
+   List       *indexElems;     /* IndexElems to infer unique index */
+   Node       *whereClause;    /* qualification (partial-index predicate) */
+   int         location;       /* token location, or -1 if unknown */
+} InferClause;
+
+/*
+ * ConflictClause -
+ *         representation of ON CONFLICT clause
+ *
+ * Note: ConflictClause does not propagate into the Query representation.
+ */
+typedef struct ConflictClause
+{
+   NodeTag         type;
+   SpecCmd         specclause;     /* Variant specified */
+   InferClause    *infer;          /* Optional index inference clause */
+   int             location;       /* token location, or -1 if unknown */
+} ConflictClause;
+
 /*
  * CommonTableExpr -
  *    representation of WITH list element
@@ -1064,6 +1096,7 @@ typedef struct InsertStmt
    RangeVar   *relation;       /* relation to insert into */
    List       *cols;           /* optional: names of the target columns */
    Node       *selectStmt;     /* the source SELECT/VALUES, or NULL */
+   ConflictClause  *confClause;    /* ON CONFLICT clause */
    List       *returningList;  /* list of expressions to return */
    WithClause *withClause;     /* WITH clause */
 } InsertStmt;
index 21cbfa8cf0febf77d67e6b82b85a07aaa8cf746d..0558b7388e19f53983096c9d0831bb315211f08e 100644 (file)
@@ -178,6 +178,8 @@ typedef struct ModifyTable
    List       *resultRelations;    /* integer list of RT indexes */
    int         resultRelIndex; /* index of first resultRel in plan's list */
    List       *plans;          /* plan(s) producing source data */
+   SpecCmd     spec;           /* speculative insertion specification */
+   List       *arbiterIndexes; /* List of ON CONFLICT arbiter index OIDs  */
    List       *withCheckOptionLists;   /* per-target-table WCO lists */
    List       *returningLists; /* per-target-table RETURNING tlists */
    List       *fdwPrivLists;   /* per-target-table FDW private data lists */
index 4f1d234d307772f2f0c23c3a176d808efe7c5c92..249cead65b9f240b9a5dd34011921213d1573161 100644 (file)
@@ -1143,6 +1143,22 @@ typedef struct CurrentOfExpr
    int         cursor_param;   /* refcursor parameter number, or 0 */
 } CurrentOfExpr;
 
+/*
+ * InferenceElem - an element of a unique index inference specification
+ *
+ * This mostly matches the structure of IndexElems, but having a dedicated
+ * primnode allows for a clean separation between the use of index parameters
+ * by utility commands, and this node.
+ */
+typedef struct InferenceElem
+{
+   Expr        xpr;
+   Node       *expr;               /* expression to infer from, or NULL */
+   Oid         infercollid;        /* OID of collation, or InvalidOid */
+   Oid         inferopfamily;      /* OID of att opfamily, or InvalidOid */
+   Oid         inferopcinputtype;  /* OID of att input type, or InvalidOid */
+} InferenceElem;
+
 /*--------------------
  * TargetEntry -
  *    a target entry (used in query target lists)
index 8eb2e57d7b7be3f95afbe7139825a0437eae1844..11e7d4d26bbc766c103b31256a5632a3870a8f6d 100644 (file)
@@ -28,6 +28,8 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
 extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
                  bool inhparent, RelOptInfo *rel);
 
+extern List *infer_arbiter_indexes(PlannerInfo *root);
+
 extern void estimate_rel_size(Relation rel, int32 *attr_widths,
                  BlockNumber *pages, double *tuples, double *allvisfrac);
 
index fa72918d1bbadad9e2865814f58abd08bbe0c579..c3a0634d394895ad79f2166dd6e2273c158f45f8 100644 (file)
@@ -85,7 +85,7 @@ extern ModifyTable *make_modifytable(PlannerInfo *root,
                 Index nominalRelation,
                 List *resultRelations, List *subplans,
                 List *withCheckOptionLists, List *returningLists,
-                List *rowMarks, int epqParam);
+                List *rowMarks, SpecCmd spec, int epqParam);
 extern bool is_projection_capable_plan(Plan *plan);
 
 /*
index 7c243ecc0605687da35961de67d7f957db73ae8b..cf501e601874b12a41f0da86a6343610bdcd858c 100644 (file)
@@ -87,6 +87,7 @@ PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
 PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD)
 PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD)
+PG_KEYWORD("conflict", CONFLICT, UNRESERVED_KEYWORD)
 PG_KEYWORD("connection", CONNECTION, UNRESERVED_KEYWORD)
 PG_KEYWORD("constraint", CONSTRAINT, RESERVED_KEYWORD)
 PG_KEYWORD("constraints", CONSTRAINTS, UNRESERVED_KEYWORD)
@@ -180,6 +181,7 @@ PG_KEYWORD("hold", HOLD, UNRESERVED_KEYWORD)
 PG_KEYWORD("hour", HOUR_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("identity", IDENTITY_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("if", IF_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("ignore", IGNORE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("ilike", ILIKE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("immediate", IMMEDIATE, UNRESERVED_KEYWORD)
 PG_KEYWORD("immutable", IMMUTABLE, UNRESERVED_KEYWORD)
index 6a4438f556658eccca235fc493e41558afb8af18..d1d0d1261b3eeedf608a633540933539c4b326f1 100644 (file)
@@ -41,6 +41,8 @@ extern List *transformDistinctClause(ParseState *pstate,
                        List **targetlist, List *sortClause, bool is_agg);
 extern List *transformDistinctOnClause(ParseState *pstate, List *distinctlist,
                          List **targetlist, List *sortClause);
+extern void transformConflictClause(ParseState *pstate, ConflictClause *confClause,
+                                   List **arbiterExpr, Node **arbiterWhere);
 
 extern List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
                    List *sortlist, List *targetlist, SortBy *sortby,
index f1e0f57e7c2a56355fc90ec55e1d4abd0c7a7e91..ce41d5a81a0c8a082d5d1aee553dc0c8bca27b11 100644 (file)
@@ -43,6 +43,15 @@ typedef struct ReorderBufferTupleBuf
  * and ComboCids in the same list with the user visible INSERT/UPDATE/DELETE
  * changes. Users of the decoding facilities will never see changes with
  * *_INTERNAL_* actions.
+ *
+ * The REORDER_BUFFER_CHANGE_INTERNAL_INSERT and
+ * REORDER_BUFFER_CHANGE_INTERNAL_DELETE changes concern "speculative
+ * insertions", and their "super deletion" respectively. Super deletion is a
+ * mechanism that speculative insertion makes use of to handle conflicts.
+ *
+ * At transaction reassembly these will be consolidated, and so decoding
+ * plugins will only ever handle REORDER_BUFFER_CHANGE_INSERT changes here too
+ * (in the common case where speculative insertion works out).
  */
 enum ReorderBufferChangeType
 {
@@ -51,7 +60,9 @@ enum ReorderBufferChangeType
    REORDER_BUFFER_CHANGE_DELETE,
    REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT,
    REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID,
-   REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID
+   REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID,
+   REORDER_BUFFER_CHANGE_INTERNAL_INSERT,
+   REORDER_BUFFER_CHANGE_INTERNAL_DELETE
 };
 
 /*
index dbdd4dd512a9f023dbe88c982a017cc3dd6b6fd2..6c4bd0662ed7a06c1f23da50e7ad9ee6990f80e5 100644 (file)
@@ -57,6 +57,7 @@ typedef struct BlockIdData
 } BlockIdData;
 
 typedef BlockIdData *BlockId;  /* block identifier */
+typedef BlockIdData SpeculativeToken;  /* token */
 
 /* ----------------
  *     support macros
@@ -88,6 +89,16 @@ typedef BlockIdData *BlockId;    /* block identifier */
    (blockId)->bi_lo = (blockNumber) & 0xffff \
 )
 
+/*
+ * SpeculativeTokenSet
+ *     Sets a speculative token to the specified value.
+ */
+#define SpeculativeTokenSet(tidstate, tokenNumber) \
+( \
+   BlockIdSet(&(tidstate).t_token, tokenNumber), \
+   (tidstate).t_ctid.ip_posid = MagicOffsetNumber \
+)
+
 /*
  * BlockIdCopy
  *     Copy a block identifier.
@@ -118,4 +129,11 @@ typedef BlockIdData *BlockId;  /* block identifier */
    (BlockNumber) (((blockId)->bi_hi << 16) | ((uint16) (blockId)->bi_lo)) \
 )
 
+/*
+ * SpeculativeTokenGetTokenNumber
+ *     Retrieve the token number from a token identifier.
+ */
+#define SpeculativeTokenGetTokenNumber(tokenId) \
+   BlockIdGetBlockNumber(tokenId)
+
 #endif   /* BLOCK_H */
index f5d70e5141eff3de92fd65496ab45f8c8eb5eff3..7cc75fc10654a5210cce0bb6a800eef68573eeaa 100644 (file)
@@ -76,6 +76,11 @@ extern bool ConditionalXactLockTableWait(TransactionId xid);
 extern void WaitForLockers(LOCKTAG heaplocktag, LOCKMODE lockmode);
 extern void WaitForLockersMultiple(List *locktags, LOCKMODE lockmode);
 
+/* Lock an XID for tuple insertion (used to wait for an insertion to finish) */
+extern uint32  SpeculativeInsertionLockAcquire(TransactionId xid);
+extern void        SpeculativeInsertionLockRelease(TransactionId xid);
+extern void        SpeculativeInsertionWait(TransactionId xid, uint32 token);
+
 /* Lock a general object (other than a relation) of the current database */
 extern void LockDatabaseObject(Oid classid, Oid objid, uint16 objsubid,
                   LOCKMODE lockmode);
index dae517f3fe001d7e92a0679e7de3afac488e1666..b4eb1b4a9e309892715334d3f0b757d15fa058e2 100644 (file)
@@ -176,6 +176,8 @@ typedef enum LockTagType
    /* ID info for a transaction is its TransactionId */
    LOCKTAG_VIRTUALTRANSACTION, /* virtual transaction (ditto) */
    /* ID info for a virtual transaction is its VirtualTransactionId */
+   LOCKTAG_SPECULATIVE_TOKEN,      /* speculative insertion Xid and token */
+   /* ID info for a transaction is its TransactionId */
    LOCKTAG_OBJECT,             /* non-relation database object */
    /* ID info for an object is DB OID + CLASS OID + OBJECT OID + SUBID */
 
@@ -261,6 +263,14 @@ typedef struct LOCKTAG
     (locktag).locktag_type = LOCKTAG_VIRTUALTRANSACTION, \
     (locktag).locktag_lockmethodid = DEFAULT_LOCKMETHOD)
 
+#define SET_LOCKTAG_SPECULATIVE_INSERTION(locktag,xid,token) \
+   ((locktag).locktag_field1 = (xid), \
+    (locktag).locktag_field2 = (token),        \
+    (locktag).locktag_field3 = 0, \
+    (locktag).locktag_field4 = 0, \
+    (locktag).locktag_type = LOCKTAG_SPECULATIVE_TOKEN, \
+    (locktag).locktag_lockmethodid = DEFAULT_LOCKMETHOD)
+
 #define SET_LOCKTAG_OBJECT(locktag,dboid,classoid,objoid,objsubid) \
    ((locktag).locktag_field1 = (dboid), \
     (locktag).locktag_field2 = (classoid), \
index 4a4ca5b1a12750693e25de849fa40c5c11f42de9..54f534f7c6319edcde53e6059a696756b6f70a0f 100644 (file)
@@ -26,6 +26,7 @@ typedef uint16 OffsetNumber;
 #define InvalidOffsetNumber        ((OffsetNumber) 0)
 #define FirstOffsetNumber      ((OffsetNumber) 1)
 #define MaxOffsetNumber            ((OffsetNumber) (BLCKSZ / sizeof(ItemIdData)))
+#define MagicOffsetNumber      (0xfffe)
 #define OffsetNumberMask       (0xffff)        /* valid uint16 bits */
 
 /* ----------------
index 26fb2573c7103c8ce01acacadcd1a955e6c18e91..c44a220e13222f5acc388cd26be5e64545fa59d9 100644 (file)
@@ -86,6 +86,21 @@ typedef struct SnapshotData
    bool        takenDuringRecovery;    /* recovery-shaped snapshot? */
    bool        copied;         /* false if it's a static snapshot */
 
+   /*
+    * Snapshot's speculative token is value set only by
+    * HeapTupleSatisfiesDirty, indicating that the tuple is being inserted
+    * speculatively, and may yet be "super-deleted" before inserter's EOX.
+    *
+    * The caller may use the value and the inserting tuple 'xmin' with
+    * SpeculativeInsertionWait to wait for the inserter to decide.  It is only
+    * set when a valid 'xmin' is also set by HeapTupleSatisifiesDirty.  By
+    * convention, when speculativeToken is zero, the caller must assume that
+    * it should wait on a non-speculative tuple (i.e. wait for xmin/xmax to
+    * commit, since speculative insertion either isn't in play anymore, or
+    * never was).
+    */
+   uint32      speculativeToken;
+
    /*
     * note: all ids in subxip[] are >= xmin, but we don't bother filtering
     * out any that are >= xmax
diff --git a/src/test/isolation/expected/insert-conflict-ignore.out b/src/test/isolation/expected/insert-conflict-ignore.out
new file mode 100644 (file)
index 0000000..e6cc2a1
--- /dev/null
@@ -0,0 +1,23 @@
+Parsed test spec with 2 sessions
+
+starting permutation: ignore1 ignore2 c1 select2 c2
+step ignore1: INSERT INTO ints(key, val) VALUES(1, 'ignore1') ON CONFLICT IGNORE;
+step ignore2: INSERT INTO ints(key, val) VALUES(1, 'ignore2') ON CONFLICT IGNORE; <waiting ...>
+step c1: COMMIT;
+step ignore2: <... completed>
+step select2: SELECT * FROM ints;
+key            val            
+
+1              ignore1        
+step c2: COMMIT;
+
+starting permutation: ignore1 ignore2 a1 select2 c2
+step ignore1: INSERT INTO ints(key, val) VALUES(1, 'ignore1') ON CONFLICT IGNORE;
+step ignore2: INSERT INTO ints(key, val) VALUES(1, 'ignore2') ON CONFLICT IGNORE; <waiting ...>
+step a1: ABORT;
+step ignore2: <... completed>
+step select2: SELECT * FROM ints;
+key            val            
+
+1              ignore2        
+step c2: COMMIT;
index 3e2614ecacdb3ed2bbca3f70399ec39beecbfe56..fc357b46535343b31b1866f0c9d65b763f6239aa 100644 (file)
@@ -16,6 +16,7 @@ test: fk-deadlock2
 test: eval-plan-qual
 test: lock-update-delete
 test: lock-update-traversal
+test: insert-conflict-ignore
 test: delete-abort-savept
 test: delete-abort-savept-2
 test: aborted-keyrevoke
diff --git a/src/test/isolation/specs/insert-conflict-ignore.spec b/src/test/isolation/specs/insert-conflict-ignore.spec
new file mode 100644 (file)
index 0000000..fde43b3
--- /dev/null
@@ -0,0 +1,41 @@
+# INSERT...ON CONFLICT IGNORE test
+#
+# This test tries to expose problems with the interaction between concurrent
+# sessions during INSERT...ON CONFLICT IGNORE.
+#
+# The convention here is that session 1 always ends up inserting, and session 2
+# always ends up ignoring.
+
+setup
+{
+  CREATE TABLE ints (key int primary key, val text);
+}
+
+teardown
+{
+  DROP TABLE ints;
+}
+
+session "s1"
+setup
+{
+  BEGIN ISOLATION LEVEL READ COMMITTED;
+}
+step "ignore1" { INSERT INTO ints(key, val) VALUES(1, 'ignore1') ON CONFLICT IGNORE; }
+step "c1" { COMMIT; }
+step "a1" { ABORT; }
+
+session "s2"
+setup
+{
+  BEGIN ISOLATION LEVEL READ COMMITTED;
+}
+step "ignore2" { INSERT INTO ints(key, val) VALUES(1, 'ignore2') ON CONFLICT IGNORE; }
+step "select2" { SELECT * FROM ints; }
+step "c2" { COMMIT; }
+step "a2" { ABORT; }
+
+# Regular case where one session block-waits on another to determine if it
+# should proceed with an insert or ignore.
+permutation "ignore1" "ignore2" "c1" "select2" "c2"
+permutation "ignore1" "ignore2" "a1" "select2" "c2"
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
new file mode 100644 (file)
index 0000000..e1dadac
--- /dev/null
@@ -0,0 +1,178 @@
+--
+-- insert...on conflict unique index inference
+--
+create table insertconflicttest(key int4, fruit text);
+--
+-- Test unique index inference with operator class specifications and
+-- named collations
+--
+create unique index op_index_key on insertconflicttest(key, fruit text_pattern_ops);
+create unique index collation_index_key on insertconflicttest(key, fruit collate "C");
+create unique index both_index_key on insertconflicttest(key, fruit collate "C" text_pattern_ops);
+create unique index both_index_expr_key on insertconflicttest(key, lower(fruit) collate "C" text_pattern_ops);
+-- fails
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key) ignore;
+ERROR:  could not infer which unique index to use from expressions/columns and predicate provided for ON CONFLICT
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit) ignore;
+ERROR:  could not infer which unique index to use from expressions/columns and predicate provided for ON CONFLICT
+-- succeeds
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit) ignore;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Insert on insertconflicttest
+   Conflict Arbiter Indexes: op_index_key, collation_index_key, both_index_key
+   ->  Result
+(3 rows)
+
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit, key, fruit, key) ignore;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Insert on insertconflicttest
+   Conflict Arbiter Indexes: op_index_key, collation_index_key, both_index_key
+   ->  Result
+(3 rows)
+
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit), key, lower(fruit), key) ignore;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Insert on insertconflicttest
+   Conflict Arbiter Indexes: both_index_expr_key
+   ->  Result
+(3 rows)
+
+-- Neither collation nor operator class specifications are required --
+-- supplying them merely *limits* matches to indexes with matching opclasses
+-- used for relevant indexes
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit text_pattern_ops) ignore;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Insert on insertconflicttest
+   Conflict Arbiter Indexes: op_index_key, both_index_key
+   ->  Result
+(3 rows)
+
+-- Okay, arbitrates using both index where text_pattern_ops opclass does and
+-- does not appear.
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit collate "C") ignore;
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Insert on insertconflicttest
+   Conflict Arbiter Indexes: collation_index_key, both_index_key
+   ->  Result
+(3 rows)
+
+-- Okay, but only accepts the single index where both opclass and collation are
+-- specified
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit collate "C" text_pattern_ops, key) ignore;
+                 QUERY PLAN                 
+--------------------------------------------
+ Insert on insertconflicttest
+   Conflict Arbiter Indexes: both_index_key
+   ->  Result
+(3 rows)
+
+-- Okay, but only accepts the single index where both opclass and collation are
+-- specified (plus expression variant)
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit) collate "C", key, key) ignore;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Insert on insertconflicttest
+   Conflict Arbiter Indexes: both_index_expr_key
+   ->  Result
+(3 rows)
+
+-- Attribute appears twice, while not all attributes/expressions on attributes
+-- appearing within index definition match in terms of both opclass and
+-- collation.
+--
+-- Works because every attribute in inference specification needs to be
+-- satisfied once or more by cataloged index attribute, and as always when an
+-- attribute in the cataloged definition has a non-default opclass/collation,
+-- it still satisfied some inference attribute lacking any particular
+-- opclass/collation specification.
+--
+-- The implementation is liberal in accepting inference specifications on the
+-- assumption that multiple inferred unique indexes will prevent problematic
+-- cases.  It rolls with unique indexes where attributes redundantly appear
+-- multiple times, too (which is not tested here).
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit, key, fruit text_pattern_ops, key) ignore;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Insert on insertconflicttest
+   Conflict Arbiter Indexes: op_index_key, both_index_key
+   ->  Result
+(3 rows)
+
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit) collate "C" text_pattern_ops, key, key) ignore;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Insert on insertconflicttest
+   Conflict Arbiter Indexes: both_index_expr_key
+   ->  Result
+(3 rows)
+
+drop index op_index_key;
+drop index collation_index_key;
+drop index both_index_key;
+drop index both_index_expr_key;
+--
+-- Test partial unique index inference
+--
+create unique index partial_key_index on insertconflicttest(key) where fruit like '%berry';
+-- Succeeds
+insert into insertconflicttest values (23, 'Blackberry') on conflict (key where fruit like '%berry' and fruit = 'inconsequential') ignore;
+-- fails
+insert into insertconflicttest values (23, 'Blackberry') on conflict (key where fruit like '%berry' or fruit = 'consequential') ignore;
+ERROR:  could not infer which unique index to use from expressions/columns and predicate provided for ON CONFLICT
+insert into insertconflicttest values (23, 'Uncovered by Index') on conflict (key where fruit like '%berry') ignore;
+ERROR:  inferred arbiter partial unique index has predicate that fails to cover tuple proposed for insertion
+DETAIL:  ON CONFLICT inference clause implies that the tuple proposed for insertion must be covered by predicate for partial index "partial_key_index".
+drop index partial_key_index;
+-- Cleanup
+drop table insertconflicttest;
+-- ******************************************************************
+-- *                                                                *
+-- * Test inheritance (example taken from tutorial)                 *
+-- *                                                                *
+-- ******************************************************************
+create table cities (
+   name        text,
+   population  float8,
+   altitude    int     -- (in ft)
+);
+create table capitals (
+   state       char(2)
+) inherits (cities);
+-- Create unique indexes.  Due to a general limitation of inheritance,
+-- uniqueness is only enforced per-relation.  Unique index inference
+-- specification will do the right thing, though.
+create unique index cities_names_unique on cities (name);
+create unique index capitals_names_unique on capitals (name);
+-- prepopulate the tables.
+insert into cities values ('San Francisco', 7.24E+5, 63);
+insert into cities values ('Las Vegas', 2.583E+5, 2174);
+insert into cities values ('Mariposa', 1200, 1953);
+insert into capitals values ('Sacramento', 3.694E+5, 30, 'CA');
+insert into capitals values ('Madison', 1.913E+5, 845, 'WI');
+-- Tests proper for inheritance:
+select * from capitals;
+    name    | population | altitude | state 
+------------+------------+----------+-------
+ Sacramento |     369400 |       30 | CA
+ Madison    |     191300 |      845 | WI
+(2 rows)
+
+-- Succeeds:
+insert into cities values ('Las Vegas', 2.583E+5, 2174) on conflict ignore;
+-- Wrong "Sacramento", ignored:
+insert into capitals values ('Sacramento', 50, 2267, 'NE') on conflict (name) ignore;
+select * from capitals;
+    name    | population | altitude | state 
+------------+------------+----------+-------
+ Sacramento |     369400 |       30 | CA
+ Madison    |     191300 |      845 | WI
+(2 rows)
+
+-- clean up
+drop table capitals;
+drop table cities;
index 71fa44a6beaa359642f9067efb97c6fcdf87d996..2340265714d45b63c265af09b891e48230285b40 100644 (file)
@@ -1123,6 +1123,10 @@ SELECT * FROM shoelace_log ORDER BY sl_name;
    SELECT * FROM shoelace_obsolete WHERE sl_avail = 0;
 insert into shoelace values ('sl9', 0, 'pink', 35.0, 'inch', 0.0);
 insert into shoelace values ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0);
+-- Unsupported (even though a similar updatable view construct is)
+insert into shoelace values ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0)
+  on conflict ignore;
+ERROR:  INSERT with ON CONFLICT clause may not target relation with INSERT or UPDATE rules
 SELECT * FROM shoelace_obsolete ORDER BY sl_len_cm;
   sl_name   | sl_avail |  sl_color  | sl_len | sl_unit  | sl_len_cm 
 ------------+----------+------------+--------+----------+-----------
@@ -2356,6 +2360,11 @@ DETAIL:  Key (id3a, id3c)=(1, 13) is not present in table "rule_and_refint_t2".
 insert into rule_and_refint_t3 values (1, 13, 11, 'row6');
 ERROR:  insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey"
 DETAIL:  Key (id3a, id3b)=(1, 13) is not present in table "rule_and_refint_t1".
+-- Ordinary table
+insert into rule_and_refint_t3 values (1, 13, 11, 'row6')
+  on conflict ignore;
+ERROR:  insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey"
+DETAIL:  Key (id3a, id3b)=(1, 13) is not present in table "rule_and_refint_t1".
 create rule rule_and_refint_t3_ins as on insert to rule_and_refint_t3
    where (exists (select 1 from rule_and_refint_t3
            where (((rule_and_refint_t3.id3a = new.id3a)
index 9e7ba7247103edc997617cb1fd169476d4730d31..71067aa65f741987736c7c69eb91425dd21a37d7 100644 (file)
@@ -215,6 +215,8 @@ INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
 ERROR:  cannot insert into column "upper" of view "rw_view15"
 DETAIL:  View columns that are not columns of their base relation are not updatable.
 INSERT INTO rw_view15 (a) VALUES (3); -- should be OK
+INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT IGNORE; -- succeeds
+INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) IGNORE; -- succeeds
 ALTER VIEW rw_view15 ALTER COLUMN upper SET DEFAULT 'NOT SET';
 INSERT INTO rw_view15 (a) VALUES (4); -- should fail
 ERROR:  cannot insert into column "upper" of view "rw_view15"
index 6d3b865351d370f0ed52ca42f8f11a3445bf7b9b..b0ebb6b3f4c657ce8030434ea0a846948226d988 100644 (file)
@@ -36,6 +36,7 @@ test: geometry horology regex oidjoins type_sanity opr_sanity
 # These four each depend on the previous one
 # ----------
 test: insert
+test: insert_conflict
 test: create_function_1
 test: create_type
 test: create_table
index 8326894ed9d9cece29fc4eda4ede34f731383050..8409c0f3ef205b3e5e178013e62af56d02cd7c18 100644 (file)
@@ -50,6 +50,7 @@ test: oidjoins
 test: type_sanity
 test: opr_sanity
 test: insert
+test: insert_conflict
 test: create_function_1
 test: create_type
 test: create_table
diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
new file mode 100644 (file)
index 0000000..551b4cf
--- /dev/null
@@ -0,0 +1,115 @@
+--
+-- insert...on conflict unique index inference
+--
+create table insertconflicttest(key int4, fruit text);
+
+--
+-- Test unique index inference with operator class specifications and
+-- named collations
+--
+create unique index op_index_key on insertconflicttest(key, fruit text_pattern_ops);
+create unique index collation_index_key on insertconflicttest(key, fruit collate "C");
+create unique index both_index_key on insertconflicttest(key, fruit collate "C" text_pattern_ops);
+create unique index both_index_expr_key on insertconflicttest(key, lower(fruit) collate "C" text_pattern_ops);
+
+-- fails
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key) ignore;
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit) ignore;
+
+-- succeeds
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit) ignore;
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit, key, fruit, key) ignore;
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit), key, lower(fruit), key) ignore;
+-- Neither collation nor operator class specifications are required --
+-- supplying them merely *limits* matches to indexes with matching opclasses
+-- used for relevant indexes
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit text_pattern_ops) ignore;
+-- Okay, arbitrates using both index where text_pattern_ops opclass does and
+-- does not appear.
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit collate "C") ignore;
+-- Okay, but only accepts the single index where both opclass and collation are
+-- specified
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit collate "C" text_pattern_ops, key) ignore;
+-- Okay, but only accepts the single index where both opclass and collation are
+-- specified (plus expression variant)
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit) collate "C", key, key) ignore;
+-- Attribute appears twice, while not all attributes/expressions on attributes
+-- appearing within index definition match in terms of both opclass and
+-- collation.
+--
+-- Works because every attribute in inference specification needs to be
+-- satisfied once or more by cataloged index attribute, and as always when an
+-- attribute in the cataloged definition has a non-default opclass/collation,
+-- it still satisfied some inference attribute lacking any particular
+-- opclass/collation specification.
+--
+-- The implementation is liberal in accepting inference specifications on the
+-- assumption that multiple inferred unique indexes will prevent problematic
+-- cases.  It rolls with unique indexes where attributes redundantly appear
+-- multiple times, too (which is not tested here).
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit, key, fruit text_pattern_ops, key) ignore;
+explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit) collate "C" text_pattern_ops, key, key) ignore;
+
+drop index op_index_key;
+drop index collation_index_key;
+drop index both_index_key;
+drop index both_index_expr_key;
+
+--
+-- Test partial unique index inference
+--
+create unique index partial_key_index on insertconflicttest(key) where fruit like '%berry';
+
+-- Succeeds
+insert into insertconflicttest values (23, 'Blackberry') on conflict (key where fruit like '%berry' and fruit = 'inconsequential') ignore;
+
+-- fails
+insert into insertconflicttest values (23, 'Blackberry') on conflict (key where fruit like '%berry' or fruit = 'consequential') ignore;
+insert into insertconflicttest values (23, 'Uncovered by Index') on conflict (key where fruit like '%berry') ignore;
+
+drop index partial_key_index;
+
+-- Cleanup
+drop table insertconflicttest;
+
+-- ******************************************************************
+-- *                                                                *
+-- * Test inheritance (example taken from tutorial)                 *
+-- *                                                                *
+-- ******************************************************************
+create table cities (
+   name        text,
+   population  float8,
+   altitude    int     -- (in ft)
+);
+
+create table capitals (
+   state       char(2)
+) inherits (cities);
+
+-- Create unique indexes.  Due to a general limitation of inheritance,
+-- uniqueness is only enforced per-relation.  Unique index inference
+-- specification will do the right thing, though.
+create unique index cities_names_unique on cities (name);
+create unique index capitals_names_unique on capitals (name);
+
+-- prepopulate the tables.
+insert into cities values ('San Francisco', 7.24E+5, 63);
+insert into cities values ('Las Vegas', 2.583E+5, 2174);
+insert into cities values ('Mariposa', 1200, 1953);
+
+insert into capitals values ('Sacramento', 3.694E+5, 30, 'CA');
+insert into capitals values ('Madison', 1.913E+5, 845, 'WI');
+
+-- Tests proper for inheritance:
+select * from capitals;
+
+-- Succeeds:
+insert into cities values ('Las Vegas', 2.583E+5, 2174) on conflict ignore;
+-- Wrong "Sacramento", ignored:
+insert into capitals values ('Sacramento', 50, 2267, 'NE') on conflict (name) ignore;
+select * from capitals;
+
+-- clean up
+drop table capitals;
+drop table cities;
index c385e414578fae8fa54e155f0c337c4e2388b0b3..58073318828e4d9e3c334711ccf10d2db6096094 100644 (file)
@@ -680,6 +680,9 @@ SELECT * FROM shoelace_log ORDER BY sl_name;
 
 insert into shoelace values ('sl9', 0, 'pink', 35.0, 'inch', 0.0);
 insert into shoelace values ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0);
+-- Unsupported (even though a similar updatable view construct is)
+insert into shoelace values ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0)
+  on conflict ignore;
 
 SELECT * FROM shoelace_obsolete ORDER BY sl_len_cm;
 SELECT * FROM shoelace_candelete;
@@ -844,6 +847,9 @@ insert into rule_and_refint_t3 values (1, 12, 11, 'row3');
 insert into rule_and_refint_t3 values (1, 12, 12, 'row4');
 insert into rule_and_refint_t3 values (1, 11, 13, 'row5');
 insert into rule_and_refint_t3 values (1, 13, 11, 'row6');
+-- Ordinary table
+insert into rule_and_refint_t3 values (1, 13, 11, 'row6')
+  on conflict ignore;
 
 create rule rule_and_refint_t3_ins as on insert to rule_and_refint_t3
    where (exists (select 1 from rule_and_refint_t3
index 60c7e292212547cb0f6a36abc9fa3537c374faf7..201075a0ddbcf513670de936e5f2c6c74c74b2f5 100644 (file)
@@ -69,6 +69,8 @@ DELETE FROM rw_view14 WHERE a=3; -- should be OK
 -- Partially updatable view
 INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
 INSERT INTO rw_view15 (a) VALUES (3); -- should be OK
+INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT IGNORE; -- succeeds
+INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) IGNORE; -- succeeds
 ALTER VIEW rw_view15 ALTER COLUMN upper SET DEFAULT 'NOT SET';
 INSERT INTO rw_view15 (a) VALUES (4); -- should fail
 UPDATE rw_view15 SET upper='ROW 3' WHERE a=3; -- should fail