View
@@ -78,11 +78,13 @@ CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable
Policies can be applied for specific commands or for specific roles. The
default for newly created policies is that they apply for all commands and
roles, unless otherwise specified. If multiple policies apply to a given
query, they will be combined using OR. Further, for commands which can have
both USING and WITH CHECK policies (ALL and UPDATE), if no WITH CHECK policy
is defined then the USING policy will be used for both what rows are visible
(normal USING case) and which rows will be allowed to be added (WITH CHECK
case).
query, they will be combined using OR (although <literal>ON CONFLICT DO
UPDATE</> and <literal>INSERT</> policies are not combined in this way, but
rather enforced as noted at each stage of <literal>ON CONFLICT</> execution).
Further, for commands which can have both USING and WITH CHECK policies (ALL
and UPDATE), if no WITH CHECK policy is defined then the USING policy will be
used for both what rows are visible (normal USING case) and which rows will
be allowed to be added (WITH CHECK case).
</para>
<para>
@@ -263,6 +265,12 @@ CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable
as it only ever applies in cases where records are being added to the
relation.
</para>
<para>
Note that <literal>INSERT</literal> with <literal>ON CONFLICT DO
UPDATE</literal> requires that any <literal>INSERT</literal> policy
WITH CHECK expression passes for any rows appended to the relation by
the INSERT path only.
</para>
</listitem>
</varlistentry>
@@ -271,22 +279,39 @@ CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable
<listitem>
<para>
Using <literal>UPDATE</literal> for a policy means that it will apply
to <literal>UPDATE</literal> commands. As <literal>UPDATE</literal>
involves pulling an existing record and then making changes to some
portion (but possibly not all) of the record, the
<literal>UPDATE</literal> policy accepts both a USING expression and
a WITH CHECK expression. The USING expression will be used to
determine which records the <literal>UPDATE</literal> command will
see to operate against, while the <literal>WITH CHECK</literal>
expression defines what rows are allowed to be added back into the
relation (similar to the <literal>INSERT</literal> policy).
Any rows whose resulting values do not pass the
<literal>WITH CHECK</literal> expression will cause an ERROR and the
entire command will be aborted. Note that if only a
<literal>USING</literal> clause is specified then that clause will be
used for both <literal>USING</literal> and
to <literal>UPDATE</literal> commands (or auxiliary <literal>ON
CONFLICT DO UPDATE</literal> clauses of <literal>INSERT</literal>
commands). As <literal>UPDATE</literal> involves pulling an existing
record and then making changes to some portion (but possibly not all)
of the record, the <literal>UPDATE</literal> policy accepts both a
<literal>USING</literal> expression and a <literal>WITH CHECK</literal>
expression. The <literal>USING</literal> expression will be used to
determine which records the <literal>UPDATE</literal> command will see
to operate against, while the <literal>WITH CHECK</literal> expression
defines what rows are allowed to be added back into the relation
(similar to the <literal>INSERT</literal> policy). Any rows whose
resulting values do not pass the <literal>WITH CHECK</literal>
expression will cause an ERROR and the entire command will be aborted.
Note that if only a <literal>USING</literal> clause is specified then
that clause will be used for both <literal>USING</literal> and
<literal>WITH CHECK</literal> cases.
</para>
<para>
Note, however, that <literal>INSERT</literal> with <literal>ON CONFLICT
DO UPDATE</literal> requires that an <literal>UPDATE</literal> policy
<literal>USING</literal> expression always be enforced as a
<literal>WITH CHECK</literal> expression. This
<literal>UPDATE</literal> policy must always pass when the
<literal>UPDATE</literal> path is taken. Any existing row that
necessitates that the <literal>UPDATE</literal> path be taken must pass
the (UPDATE or ALL) <literal>USING</literal> qualifications (combined
using <literal>OR</literal>), which are always enforced as WTIH CHECK
options in this context (the <literal>UPDATE</literal> path will
<emphasis>never</> be silently avoided; an error will be thrown
instead). Finally, the final row appended to the relation must pass
any <literal>WITH CHECK</literal> options that a conventional
<literal>UPDATE</literal> is required to pass.
</para>
</listitem>
</varlistentry>
View
@@ -136,7 +136,11 @@ 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</literal> clause cannot be used on tables that have
either <literal>INSERT</literal> or <literal>UPDATE</literal>
rules. Consider using an updatable view instead.
</para>
</listitem>
</varlistentry>
View
@@ -717,7 +717,9 @@ 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 deferrable constraints cannot be used as
conflict arbitrators in an <command>INSERT</command> statement that
includes an <literal>ON CONFLICT DO UPDATE</> clause.
</para>
</listitem>
</varlistentry>
View
@@ -76,7 +76,10 @@ CREATE [ CONSTRAINT ] TRIGGER <replaceable class="PARAMETER">name</replaceable>
executes once for any given operation, regardless of how many rows
it modifies (in particular, an operation that modifies zero rows
will still result in the execution of any applicable <literal>FOR
EACH STATEMENT</literal> triggers).
EACH STATEMENT</literal> triggers). Note that with an
<command>INSERT</command> with an <literal>ON CONFLICT DO UPDATE</>
clause, both <command>INSERT</command> and
<command>UPDATE</command> statement level trigger will be fired.
</para>
<para>
View
@@ -333,7 +333,8 @@ CREATE VIEW vista AS SELECT text 'Hello World' AS hello;
If the view is automatically updatable the system will convert any
<command>INSERT</>, <command>UPDATE</> or <command>DELETE</> statement
on the view into the corresponding statement on the underlying base
relation.
relation. <command>INSERT</> statements that have an <literal>ON
CONFLICT UPDATE</> clause are fully supported.
</para>
<para>
@@ -345,8 +346,10 @@ CREATE VIEW vista AS SELECT text 'Hello World' AS hello;
condition, and thus is no longer visible through the view. Similarly,
an <command>INSERT</> command can potentially insert base-relation rows
that do not satisfy the <literal>WHERE</> condition and thus are not
visible through the view. The <literal>CHECK OPTION</> may be used to
prevent <command>INSERT</> and <command>UPDATE</> commands from creating
visible through the view (<literal>ON CONFLICT UPDATE</> may
similarly affect an existing row not visible through the view).
The <literal>CHECK OPTION</> may be used to prevent
<command>INSERT</> and <command>UPDATE</> commands from creating
such rows that are not visible through the view.
</para>
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -40,14 +40,17 @@
On tables and foreign tables, triggers can be defined to execute either
before or after any <command>INSERT</command>, <command>UPDATE</command>,
or <command>DELETE</command> operation, either once per modified row,
or once per <acronym>SQL</acronym> statement.
<command>UPDATE</command> triggers can moreover be set to fire only if
certain columns are mentioned in the <literal>SET</literal> clause of the
<command>UPDATE</command> statement.
Triggers can also fire for <command>TRUNCATE</command> statements.
If a trigger event occurs, the trigger's function is called at the
appropriate time to handle the event. Foreign tables do not support the
TRUNCATE statement at all.
or once per <acronym>SQL</acronym> statement. If an
<command>INSERT</command> contains an <literal>ON CONFLICT DO UPDATE</>
clause, it is possible that the effects of a BEFORE insert trigger and
a BEFORE update trigger can both be applied together, if a reference to
an <varname>EXCLUDED</> column appears. <command>UPDATE</command>
triggers can moreover be set to fire only if certain columns are
mentioned in the <literal>SET</literal> clause of the
<command>UPDATE</command> statement. Triggers can also fire for
<command>TRUNCATE</command> statements. If a trigger event occurs,
the trigger's function is called at the appropriate time to handle the
event. Foreign tables do not support the TRUNCATE statement at all.
</para>
<para>
@@ -118,6 +121,35 @@
be operated on.
</para>
<para>
If an <command>INSERT</command> contains an <literal>ON CONFLICT
DO UPDATE</> clause, it is possible that the effects of all
row-level <literal>BEFORE</> <command>INSERT</command> triggers
and all row-level BEFORE <command>UPDATE</command> triggers can
both be applied in a way that is apparent from the final state of
the updated row, if an <varname>EXCLUDED</> column is referenced.
There need not be an <varname>EXCLUDED</> column reference for
both sets of BEFORE row-level triggers to execute, though. The
possibility of surprising outcomes should be considered when there
are both <literal>BEFORE</> <command>INSERT</command> and
<literal>BEFORE</> <command>UPDATE</command> row-level triggers
that both affect a row being inserted/updated (this can still be
problematic if the modifications are more or less equivalent if
they're not also idempotent). Note that statement-level
<command>UPDATE</command> triggers are executed when <literal>ON
CONFLICT DO UPDATE</> is specified, regardless of whether or not
any rows were affected by the <command>UPDATE</command> (and
regardless of whether the alternative <command>UPDATE</command>
path was ever taken). An <command>INSERT</command> with an
<literal>ON CONFLICT DO UPDATE</> clause will execute
statement-level <literal>BEFORE</> <command>INSERT</command>
triggers first, then statement-level <literal>BEFORE</>
<command>UPDATE</command> triggers, followed by statement-level
<literal>AFTER</> <command>UPDATE</command> triggers and finally
statement-level <literal>AFTER</> <command>INSERT</command>
triggers.
</para>
<para>
Trigger functions invoked by per-statement triggers should always
return <symbol>NULL</symbol>. Trigger functions invoked by per-row
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -35,12 +35,17 @@
void
RelationPutHeapTuple(Relation relation,
Buffer buffer,
HeapTuple tuple)
HeapTuple tuple,
bool token)
{
Page pageHeader;
OffsetNumber offnum;
ItemId itemId;
Item item;
/*
* A tuple that's being inserted speculatively should already have its
* token set.
*/
Assert(!token || HeapTupleHeaderIsSpeculative(tuple->t_data));
/* Add the tuple to the page */
pageHeader = BufferGetPage(buffer);
@@ -54,10 +59,18 @@ 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;
/*
* Insert the correct position into CTID of the stored tuple, too
* (unless this is a speculative insertion, in which case the token is
* held in CTID field instead)
*/
if (!token)
{
ItemId itemId = PageGetItemId(pageHeader, offnum);
Item item = PageGetItem(pageHeader, itemId);
((HeapTupleHeader) item)->t_ctid = tuple->t_self;
}
}
/*
View
@@ -522,6 +522,14 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
bool toast_free[MaxHeapAttributeNumber];
bool toast_delold[MaxHeapAttributeNumber];
/*
* Ignore the INSERT_SPECULATIVE option. Speculative insertions/super
* deletions just normally insert/delete the toast values. It seems
* easiest to deal with that here, instead on, potentially, multiple
* callers.
*/
options &= ~HEAP_INSERT_SPECULATIVE;
/*
* We should only ever be called for tuples of plain relations or
* materialized views --- recursing on a toast rel is bad news.
View
@@ -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 @@ _bt_doinsert(Relation rel, IndexTuple itup,
*/
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;
@@ -213,7 +224,10 @@ _bt_doinsert(Relation rel, IndexTuple itup,
*
* 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().
* 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 @@ _bt_doinsert(Relation rel, IndexTuple itup,
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;
}
View
@@ -75,6 +75,12 @@ heap_desc(StringInfo buf, XLogReaderState *record)
xlrec->new_offnum,
xlrec->new_xmax);
}
else if (info == XLOG_HEAP_CONFIRM)
{
xl_heap_confirm *xlrec = (xl_heap_confirm *) 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_CONFIRM:
id = "HEAP_CONFIRM";
break;
case XLOG_HEAP_LOCK:
id = "LOCK";
break;
View
@@ -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;
}
/* ----------------
* BuildSpeculativeIndexInfo
* Add 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, additional support is required.
*
* Do this processing here rather than in BuildIndexInfo() to not incur the
* overhead in the common non-speculative cases.
* ----------------
*/
void
BuildSpeculativeIndexInfo(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.
@@ -2612,7 +2663,7 @@ IndexCheckExclusion(Relation heapRelation,
check_exclusion_constraint(heapRelation,
indexRelation, indexInfo,
&(heapTuple->t_self), values, isnull,
estate, true, false);
estate, true);
}
heap_endscan(scan);
View
@@ -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;
}
View
@@ -229,7 +229,7 @@ F311 Schema definition statement 02 CREATE TABLE for persistent base tables YES
F311 Schema definition statement 03 CREATE VIEW YES
F311 Schema definition statement 04 CREATE VIEW: WITH CHECK OPTION YES
F311 Schema definition statement 05 GRANT statement YES
F312 MERGE statement NO
F312 MERGE statement NO Consider INSERT ... ON CONFLICT DO UPDATE
F313 Enhanced MERGE statement NO
F314 MERGE statement with DELETE branch NO
F321 User authorization YES
View
@@ -172,7 +172,7 @@ unique_key_recheck(PG_FUNCTION_ARGS)
*/
check_exclusion_constraint(trigdata->tg_relation, indexRel, indexInfo,
&(new_row->t_self), values, isnull,
estate, false, false);
estate, false);
}
/*
View
@@ -2284,7 +2284,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;
@@ -2439,7 +2439,8 @@ CopyFrom(CopyState cstate)
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
estate);
estate, false, NULL,
NIL);
/* AFTER ROW INSERT Triggers */
ExecARInsertTriggers(estate, resultRelInfo, tuple,
@@ -2553,7 +2554,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false);
recheckIndexes =
ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self),
estate);
estate, false, NULL, NIL);
ExecARInsertTriggers(estate, resultRelInfo,
bufferedTuples[i],
recheckIndexes);
View
@@ -103,7 +103,8 @@ static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
static void ExplainScanTarget(Scan *plan, ExplainState *es);
static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
static void show_modifytable_info(ModifyTableState *mtstate, ExplainState *es);
static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstates,
List *ancestors, ExplainState *es);
static void ExplainSubPlans(List *plans, List *ancestors,
@@ -744,6 +745,9 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
case T_ModifyTable:
*rels_used = bms_add_member(*rels_used,
((ModifyTable *) plan)->nominalRelation);
if (((ModifyTable *) plan)->exclRelRTI)
*rels_used = bms_add_member(*rels_used,
((ModifyTable *) plan)->exclRelRTI);
break;
default:
break;
@@ -1466,7 +1470,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
break;
case T_ModifyTable:
show_modifytable_info((ModifyTableState *) planstate, es);
show_modifytable_info((ModifyTableState *) planstate, ancestors,
es);
break;
case T_Hash:
show_hash_info((HashState *) planstate, es);
@@ -2317,18 +2322,22 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
/*
* Show extra information for a ModifyTable node
*
* We have two objectives here. First, if there's more than one target table
* or it's different from the nominal target, identify the actual target(s).
* Second, give FDWs a chance to display extra info about foreign targets.
* We have three objectives here. First, if there's more than one target
* table or it's different from the nominal target, identify the actual
* target(s). Second, give FDWs a chance to display extra info about foreign
* targets. Third, show information about ON CONFLICT.
*/
static void
show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
ExplainState *es)
{
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
const char *operation;
const char *foperation;
bool labeltargets;
int j;
List *idxNames = NIL;
ListCell *lst;
switch (node->operation)
{
@@ -2414,6 +2423,55 @@ show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
}
}
/* Gather names of ON CONFLICT arbiter indexes */
foreach(lst, node->arbiterIndexes)
{
char *indexname = get_rel_name(lfirst_oid(lst));
idxNames = lappend(idxNames, indexname);
}
if (node->onConflictAction != ONCONFLICT_NONE)
{
ExplainProperty("Conflict Resolution",
node->onConflictAction == ONCONFLICT_NOTHING ?
"NOTHING" : "UPDATE",
false, es);
/*
* Don't display arbiter indexes at all when DO NOTHING variant
* implicitly ignores all conflicts
*/
if (idxNames)
ExplainPropertyList("Conflict Arbiter Indexes", idxNames, es);
/* ON CONFLICT DO UPDATE WHERE qual is specially displayed */
if (node->onConflictWhere)
{
show_upper_qual((List *) node->onConflictWhere, "Conflict Filter",
&mtstate->ps, ancestors, es);
show_instrumentation_count("Rows Removed by Conflict Filter", 1, &mtstate->ps, es);
}
/* EXPLAIN ANALYZE display of actual outcome for each tuple proposed */
if (es->analyze && mtstate->ps.instrument)
{
double total;
double insert_path;
double other_path;
InstrEndLoop(mtstate->mt_plans[0]->instrument);
/* count the number of source rows */
total = mtstate->mt_plans[0]->instrument->ntuples;
other_path = mtstate->ps.instrument->nfiltered2;
insert_path = total - other_path;
ExplainPropertyFloat("Tuples Inserted", insert_path, 0, es);
ExplainPropertyFloat("Conflicting Tuples", other_path, 0, es);
}
}
if (labeltargets)
ExplainCloseGroup("Target Tables", "Target Tables", false, es);
}
View
@@ -2421,21 +2421,10 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
TupleTableSlot *newSlot;
int i;
Bitmapset *updatedCols;
Bitmapset *keyCols;
LockTupleMode lockmode;
/*
* Compute lock mode to use. If columns that are part of the key have not
* been modified, then we can use a weaker lock, allowing for better
* concurrency.
*/
updatedCols = GetUpdatedColumns(relinfo, estate);
keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
INDEX_ATTR_BITMAP_KEY);
if (bms_overlap(keyCols, updatedCols))
lockmode = LockTupleExclusive;
else
lockmode = LockTupleNoKeyExclusive;
/* Determine lock mode to use */
lockmode = ExecUpdateLockMode(estate, relinfo);
Assert(HeapTupleIsValid(fdw_trigtuple) ^ ItemPointerIsValid(tupleid));
if (fdw_trigtuple == NULL)
@@ -2476,6 +2465,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
TRIGGER_EVENT_ROW |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
updatedCols = GetUpdatedColumns(relinfo, estate);
for (i = 0; i < trigdesc->numtriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[i];
@@ -2783,6 +2773,9 @@ ltrmark:;
*/
return NULL;
case HeapTupleInvisible:
elog(ERROR, "attempted to lock invisible tuple");
default:
ReleaseBuffer(buffer);
elog(ERROR, "unrecognized heap_lock_tuple status: %u", test);
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -1813,6 +1813,12 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
errmsg("new row violates row level security policy for \"%s\"",
wco->relname)));
break;
case WCO_RLS_CONFLICT_CHECK:
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("new row violates row level security policy (USING expression) for \"%s\"",
wco->relname)));
break;
default:
elog(ERROR, "unrecognized WCO kind: %u", wco->kind);
break;
@@ -1972,6 +1978,31 @@ ExecBuildSlotValueDescription(Oid reloid,
}
/*
* ExecUpdateLockMode -- find the appropriate UPDATE tuple lock mode for a
* given ResultRelInfo
*/
LockTupleMode
ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo)
{
Bitmapset *keyCols;
Bitmapset *updatedCols;
/*
* Compute lock mode to use. If columns that are part of the key have not
* been modified, then we can use a weaker lock, allowing for better
* concurrency.
*/
updatedCols = GetUpdatedColumns(relinfo, estate);
keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
INDEX_ATTR_BITMAP_KEY);
if (bms_overlap(keyCols, updatedCols))
return LockTupleExclusive;
return LockTupleNoKeyExclusive;
}
/*
* ExecFindRowMark -- find the ExecRowMark struct for given rangetable index
*/
@@ -2186,8 +2217,9 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
* recycled and reused for an unrelated tuple. This implies that
* 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.)
* buffer's content lock. We assume reading a TransactionId to be
* atomic, and Xmin never changes in an existing tuple, except to
* invalid or frozen, and neither of those can match priorXmax.)
*/
if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data),
priorXmax))
@@ -2268,11 +2300,12 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
* case, so as to avoid the "Halloween problem" of
* repeated update attempts. In the latter case it might
* be sensible to fetch the updated tuple instead, but
* doing so would require changing heap_lock_tuple as well
* as heap_update and heap_delete to not complain about
* updating "invisible" tuples, which seems pretty scary.
* So for now, treat the tuple as deleted and do not
* process.
* doing so would require changing heap_update and
* heap_delete to not complain about updating "invisible"
* tuples, which seems pretty scary (heap_lock_tuple will
* not complain, but few callers expect HeapTupleInvisible,
* and we're not one of them). So for now, treat the tuple
* as deleted and do not process.
*/
ReleaseBuffer(buffer);
return NULL;
@@ -2287,6 +2320,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 */
@@ -2302,6 +2338,9 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
ReleaseBuffer(buffer);
return NULL;
case HeapTupleInvisible:
elog(ERROR, "attempted to lock invisible tuple");
default:
ReleaseBuffer(buffer);
elog(ERROR, "unrecognized heap_lock_tuple status: %u",
View
@@ -152,10 +152,11 @@ ExecLockRows(LockRowsState *node)
* case, so as to avoid the "Halloween problem" of repeated
* update attempts. In the latter case it might be sensible
* to fetch the updated tuple instead, but doing so would
* require changing heap_lock_tuple as well as heap_update and
* heap_delete to not complain about updating "invisible"
* tuples, which seems pretty scary. So for now, treat the
* tuple as deleted and do not process.
* require changing heap_update and heap_delete to not complain
* about updating "invisible" tuples, which seems pretty scary
* (heap_lock_tuple will not complain, but few callers expect
* HeapTupleInvisible, and we're not one of them). So for now,
* treat the tuple as deleted and do not process.
*/
goto lnext;
@@ -228,6 +229,9 @@ ExecLockRows(LockRowsState *node)
/* Continue loop until we have all target tuples */
break;
case HeapTupleInvisible:
elog(ERROR, "attempted to lock invisible tuple");
default:
elog(ERROR, "unrecognized heap_lock_tuple status: %u",
test);
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -81,6 +81,7 @@ _copyPlannedStmt(const PlannedStmt *from)
COPY_SCALAR_FIELD(queryId);
COPY_SCALAR_FIELD(hasReturning);
COPY_SCALAR_FIELD(hasModifyingCTE);
COPY_SCALAR_FIELD(isUpsert);
COPY_SCALAR_FIELD(canSetTag);
COPY_SCALAR_FIELD(transientPlan);
COPY_NODE_FIELD(planTree);
@@ -185,6 +186,12 @@ _copyModifyTable(const ModifyTable *from)
COPY_NODE_FIELD(fdwPrivLists);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
COPY_SCALAR_FIELD(onConflictAction);
COPY_NODE_FIELD(arbiterIndexes);
COPY_NODE_FIELD(onConflictSet);
COPY_NODE_FIELD(onConflictWhere);
COPY_SCALAR_FIELD(exclRelRTI);
COPY_NODE_FIELD(exclRelTlist);
return newnode;
}
@@ -1786,6 +1793,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
*/
@@ -1852,6 +1875,26 @@ _copyFromExpr(const FromExpr *from)
return newnode;
}
/*
* _copyOnConflictExpr
*/
static OnConflictExpr *
_copyOnConflictExpr(const OnConflictExpr *from)
{
OnConflictExpr *newnode = makeNode(OnConflictExpr);
COPY_SCALAR_FIELD(action);
COPY_NODE_FIELD(arbiterElems);
COPY_NODE_FIELD(arbiterWhere);
COPY_NODE_FIELD(onConflictSet);
COPY_NODE_FIELD(onConflictWhere);
COPY_SCALAR_FIELD(constraint);
COPY_SCALAR_FIELD(exclRelIndex);
COPY_NODE_FIELD(exclRelTlist);
return newnode;
}
/* ****************************************************************
* relation.h copy functions
*
@@ -2135,6 +2178,33 @@ _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_STRING_FIELD(conname);
COPY_LOCATION_FIELD(location);
return newnode;
}
static OnConflictClause *
_copyOnConflictClause(const OnConflictClause *from)
{
OnConflictClause *newnode = makeNode(OnConflictClause);
COPY_SCALAR_FIELD(action);
COPY_NODE_FIELD(infer);
COPY_NODE_FIELD(targetList);
COPY_NODE_FIELD(whereClause);
COPY_LOCATION_FIELD(location);
return newnode;
}
static CommonTableExpr *
_copyCommonTableExpr(const CommonTableExpr *from)
{
@@ -2552,6 +2622,7 @@ _copyQuery(const Query *from)
COPY_NODE_FIELD(jointree);
COPY_NODE_FIELD(targetList);
COPY_NODE_FIELD(withCheckOptions);
COPY_NODE_FIELD(onConflict);
COPY_NODE_FIELD(returningList);
COPY_NODE_FIELD(groupClause);
COPY_NODE_FIELD(havingQual);
@@ -2575,6 +2646,7 @@ _copyInsertStmt(const InsertStmt *from)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(cols);
COPY_NODE_FIELD(selectStmt);
COPY_NODE_FIELD(onConflictClause);
COPY_NODE_FIELD(returningList);
COPY_NODE_FIELD(withClause);
@@ -4283,6 +4355,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;
@@ -4295,6 +4370,9 @@ copyObject(const void *from)
case T_FromExpr:
retval = _copyFromExpr(from);
break;
case T_OnConflictExpr:
retval = _copyOnConflictExpr(from);
break;
/*
* RELATION NODES
@@ -4753,6 +4831,12 @@ copyObject(const void *from)
case T_WithClause:
retval = _copyWithClause(from);
break;
case T_InferClause:
retval = _copyInferClause(from);
break;
case T_OnConflictClause:
retval = _copyOnConflictClause(from);
break;
case T_CommonTableExpr:
retval = _copyCommonTableExpr(from);
break;
View
@@ -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)
{
@@ -728,6 +739,20 @@ _equalFromExpr(const FromExpr *a, const FromExpr *b)
return true;
}
static bool
_equalOnConflictExpr(const OnConflictExpr *a, const OnConflictExpr *b)
{
COMPARE_SCALAR_FIELD(action);
COMPARE_NODE_FIELD(arbiterElems);
COMPARE_NODE_FIELD(arbiterWhere);
COMPARE_NODE_FIELD(onConflictSet);
COMPARE_NODE_FIELD(onConflictWhere);
COMPARE_SCALAR_FIELD(constraint);
COMPARE_SCALAR_FIELD(exclRelIndex);
COMPARE_NODE_FIELD(exclRelTlist);
return true;
}
/*
* Stuff from relation.h
@@ -868,6 +893,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_NODE_FIELD(jointree);
COMPARE_NODE_FIELD(targetList);
COMPARE_NODE_FIELD(withCheckOptions);
COMPARE_NODE_FIELD(onConflict);
COMPARE_NODE_FIELD(returningList);
COMPARE_NODE_FIELD(groupClause);
COMPARE_NODE_FIELD(havingQual);
@@ -889,6 +915,7 @@ _equalInsertStmt(const InsertStmt *a, const InsertStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(cols);
COMPARE_NODE_FIELD(selectStmt);
COMPARE_NODE_FIELD(onConflictClause);
COMPARE_NODE_FIELD(returningList);
COMPARE_NODE_FIELD(withClause);
@@ -2433,6 +2460,29 @@ _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_STRING_FIELD(conname);
COMPARE_LOCATION_FIELD(location);
return true;
}
static bool
_equalOnConflictClause(const OnConflictClause *a, const OnConflictClause *b)
{
COMPARE_SCALAR_FIELD(action);
COMPARE_NODE_FIELD(infer);
COMPARE_NODE_FIELD(targetList);
COMPARE_NODE_FIELD(whereClause);
COMPARE_LOCATION_FIELD(location);
return true;
}
static bool
_equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
{
@@ -2712,6 +2762,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;
@@ -2721,6 +2774,9 @@ equal(const void *a, const void *b)
case T_FromExpr:
retval = _equalFromExpr(a, b);
break;
case T_OnConflictExpr:
retval = _equalOnConflictExpr(a, b);
break;
case T_JoinExpr:
retval = _equalJoinExpr(a, b);
break;
@@ -3169,6 +3225,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_OnConflictClause:
retval = _equalOnConflictClause(a, b);
break;
case T_CommonTableExpr:
retval = _equalCommonTableExpr(a, b);
break;
View
@@ -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,13 +1494,23 @@ exprLocation(const Node *expr)
case T_WithClause:
loc = ((const WithClause *) expr)->location;
break;
case T_InferClause:
loc = ((const InferClause *) expr)->location;
break;
case T_OnConflictClause:
loc = ((const OnConflictClause *) expr)->location;
break;
case T_CommonTableExpr:
loc = ((const CommonTableExpr *) expr)->location;
break;
case T_PlaceHolderVar:
/* 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;
@@ -1890,6 +1910,20 @@ expression_tree_walker(Node *node,
return true;
}
break;
case T_OnConflictExpr:
{
OnConflictExpr *onconflict = (OnConflictExpr *) node;
if (walker((Node *) onconflict->arbiterElems, context))
return true;
if (walker(onconflict->arbiterWhere, context))
return true;
if (walker(onconflict->onConflictSet, context))
return true;
if (walker(onconflict->onConflictWhere, context))
return true;
}
break;
case T_JoinExpr:
{
JoinExpr *join = (JoinExpr *) node;
@@ -1920,6 +1954,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 +2004,8 @@ query_tree_walker(Query *query,
return true;
if (walker((Node *) query->withCheckOptions, context))
return true;
if (walker((Node *) query->onConflict, context))
return true;
if (walker((Node *) query->returningList, context))
return true;
if (walker((Node *) query->jointree, context))
@@ -2594,6 +2632,20 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
break;
case T_OnConflictExpr:
{
OnConflictExpr *oc = (OnConflictExpr *) node;
OnConflictExpr *newnode;
FLATCOPY(newnode, oc, OnConflictExpr);
MUTATE(newnode->arbiterElems, oc->arbiterElems, List *);
MUTATE(newnode->arbiterWhere, oc->arbiterWhere, Node *);
MUTATE(newnode->onConflictSet, oc->onConflictSet, List *);
MUTATE(newnode->onConflictWhere, oc->onConflictWhere, Node *);
return (Node *) newnode;
}
break;
case T_JoinExpr:
{
JoinExpr *join = (JoinExpr *) node;
@@ -2630,6 +2682,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 +2771,7 @@ query_tree_mutator(Query *query,
MUTATE(query->targetList, query->targetList, List *);
MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
MUTATE(query->onConflict, query->onConflict, OnConflictExpr *);
MUTATE(query->returningList, query->returningList, List *);
MUTATE(query->jointree, query->jointree, FromExpr *);
MUTATE(query->setOperations, query->setOperations, Node *);
@@ -2978,6 +3041,8 @@ raw_expression_tree_walker(Node *node,
return true;
if (walker(stmt->selectStmt, context))
return true;
if (walker(stmt->onConflictClause, context))
return true;
if (walker(stmt->returningList, context))
return true;
if (walker(stmt->withClause, context))
@@ -3217,6 +3282,28 @@ 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_OnConflictClause:
{
OnConflictClause *stmt = (OnConflictClause *) node;
if (walker(stmt->infer, context))
return true;
if (walker(stmt->targetList, context))
return true;
if (walker(stmt->whereClause, context))
return true;
}
break;
case T_CommonTableExpr:
return walker(((CommonTableExpr *) node)->ctequery, context);
default:
View
@@ -243,6 +243,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
WRITE_UINT_FIELD(queryId);
WRITE_BOOL_FIELD(hasReturning);
WRITE_BOOL_FIELD(hasModifyingCTE);
WRITE_BOOL_FIELD(isUpsert);
WRITE_BOOL_FIELD(canSetTag);
WRITE_BOOL_FIELD(transientPlan);
WRITE_NODE_FIELD(planTree);
@@ -337,6 +338,12 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_NODE_FIELD(fdwPrivLists);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
WRITE_ENUM_FIELD(onConflictAction, OnConflictAction);
WRITE_NODE_FIELD(arbiterIndexes);
WRITE_NODE_FIELD(onConflictSet);
WRITE_NODE_FIELD(onConflictWhere);
WRITE_INT_FIELD(exclRelRTI);
WRITE_NODE_FIELD(exclRelTlist);
}
static void
@@ -1436,6 +1443,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)
{
@@ -1482,6 +1500,21 @@ _outFromExpr(StringInfo str, const FromExpr *node)
WRITE_NODE_FIELD(quals);
}
static void
_outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
{
WRITE_NODE_TYPE("ONCONFLICTEXPR");
WRITE_ENUM_FIELD(action, OnConflictAction);
WRITE_NODE_FIELD(arbiterElems);
WRITE_NODE_FIELD(arbiterWhere);
WRITE_NODE_FIELD(onConflictSet);
WRITE_NODE_FIELD(onConflictWhere);
WRITE_OID_FIELD(constraint);
WRITE_INT_FIELD(exclRelIndex);
WRITE_NODE_FIELD(exclRelTlist);
}
/*****************************************************************************
*
* Stuff from relation.h.
@@ -2319,6 +2352,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_NODE_FIELD(jointree);
WRITE_NODE_FIELD(targetList);
WRITE_NODE_FIELD(withCheckOptions);
WRITE_NODE_FIELD(onConflict);
WRITE_NODE_FIELD(returningList);
WRITE_NODE_FIELD(groupClause);
WRITE_NODE_FIELD(havingQual);
@@ -3112,6 +3146,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;
@@ -3124,7 +3161,9 @@ _outNode(StringInfo str, const void *obj)
case T_FromExpr:
_outFromExpr(str, obj);
break;
case T_OnConflictExpr:
_outOnConflictExpr(str, obj);
break;
case T_Path:
_outPath(str, obj);
break;
View
@@ -214,6 +214,7 @@ _readQuery(void)
READ_NODE_FIELD(jointree);
READ_NODE_FIELD(targetList);
READ_NODE_FIELD(withCheckOptions);
READ_NODE_FIELD(onConflict);
READ_NODE_FIELD(returningList);
READ_NODE_FIELD(groupClause);
READ_NODE_FIELD(havingQual);
@@ -1130,6 +1131,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
*/
@@ -1196,6 +1213,25 @@ _readFromExpr(void)
READ_DONE();
}
/*
* _readOnConflictExpr
*/
static OnConflictExpr *
_readOnConflictExpr(void)
{
READ_LOCALS(OnConflictExpr);
READ_ENUM_FIELD(action, OnConflictAction);
READ_NODE_FIELD(arbiterElems);
READ_NODE_FIELD(arbiterWhere);
READ_NODE_FIELD(onConflictSet);
READ_NODE_FIELD(onConflictWhere);
READ_OID_FIELD(constraint);
READ_INT_FIELD(exclRelIndex);
READ_NODE_FIELD(exclRelTlist);
READ_DONE();
}
/*
* Stuff from parsenodes.h.
@@ -1395,6 +1431,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))
@@ -1403,6 +1441,8 @@ parseNodeString(void)
return_value = _readJoinExpr();
else if (MATCH("FROMEXPR", 8))
return_value = _readFromExpr();
else if (MATCH("ONCONFLICTEXPR", 14))
return_value = _readOnConflictExpr();
else if (MATCH("RTE", 3))
return_value = _readRangeTblEntry();
else if (MATCH("RANGETBLFUNCTION", 16))
View
@@ -4868,7 +4868,7 @@ make_modifytable(PlannerInfo *root,
Index nominalRelation,
List *resultRelations, List *subplans,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, int epqParam)
List *rowMarks, OnConflictExpr *onconflict, int epqParam)
{
ModifyTable *node = makeNode(ModifyTable);
Plan *plan = &node->plan;
@@ -4918,6 +4918,30 @@ make_modifytable(PlannerInfo *root,
node->resultRelations = resultRelations;
node->resultRelIndex = -1; /* will be set correctly in setrefs.c */
node->plans = subplans;
if (!onconflict)
{
node->onConflictAction = ONCONFLICT_NONE;
node->onConflictSet = NIL;
node->onConflictWhere = NULL;
node->arbiterIndexes = NIL;
}
else
{
node->onConflictAction = onconflict->action;
node->onConflictSet = onconflict->onConflictSet;
node->onConflictWhere = onconflict->onConflictWhere;
/*
* 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).
*/
node->arbiterIndexes = infer_arbiter_indexes(root);
node->exclRelRTI = onconflict->exclRelIndex;
node->exclRelTlist = onconflict->exclRelTlist;
}
node->withCheckOptionLists = withCheckOptionLists;
node->returningLists = returningLists;
node->rowMarks = rowMarks;
View
@@ -243,6 +243,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
result->queryId = parse->queryId;
result->hasReturning = (parse->returningList != NIL);
result->hasModifyingCTE = parse->hasModifyingCTE;
result->isUpsert =
(parse->onConflict && parse->onConflict->action == ONCONFLICT_UPDATE);
result->canSetTag = parse->canSetTag;
result->transientPlan = glob->transientPlan;
result->planTree = top_plan;
@@ -462,6 +464,17 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
parse->limitCount = preprocess_expression(root, parse->limitCount,
EXPRKIND_LIMIT);
if (parse->onConflict)
{
parse->onConflict->onConflictSet = (List *)
preprocess_expression(root, (Node *) parse->onConflict->onConflictSet,
EXPRKIND_TARGET);
parse->onConflict->onConflictWhere =
preprocess_expression(root, (Node *) parse->onConflict->onConflictWhere,
EXPRKIND_QUAL);
}
root->append_rel_list = (List *)
preprocess_expression(root, (Node *) root->append_rel_list,
EXPRKIND_APPINFO);
@@ -612,6 +625,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
withCheckOptionLists,
returningLists,
rowMarks,
parse->onConflict,
SS_assign_special_param(root));
}
}
@@ -802,6 +816,8 @@ inheritance_planner(PlannerInfo *root)
List *rowMarks;
ListCell *lc;
Assert(parse->commandType != CMD_INSERT);
/*
* We generate a modified instance of the original Query for each target
* relation, plan that, and put all the plans into a list that will be
@@ -1046,6 +1062,8 @@ inheritance_planner(PlannerInfo *root)
if (parse->returningList)
returningLists = lappend(returningLists,
subroot.parse->returningList);
Assert(!parse->onConflict);
}
/* Mark result as unordered (probably unnecessary) */
@@ -1095,6 +1113,7 @@ inheritance_planner(PlannerInfo *root)
withCheckOptionLists,
returningLists,
rowMarks,
NULL,
SS_assign_special_param(root));
}
@@ -1228,6 +1247,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
bool use_hashed_grouping = false;
WindowFuncLists *wflists = NULL;
List *activeWindows = NIL;
OnConflictExpr *onconfl;
MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
@@ -1242,6 +1262,13 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
/* Preprocess targetlist */
tlist = preprocess_targetlist(root, tlist);
onconfl = parse->onConflict;
if (onconfl)
onconfl->onConflictSet =
preprocess_onconflict_targetlist(onconfl->onConflictSet,
parse->resultRelation,
parse->rtable);
/*
* Expand any rangetable entries that have security barrier quals.
* This may add new security barrier subquery RTEs to the rangetable.
View
@@ -739,7 +739,35 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
splan->plan.targetlist = copyObject(linitial(newRL));
}
/*
* We treat ModifyTable with ON CONFLICT as a form of 'pseudo
* join', where the inner side is the EXLUDED tuple. Therefore
* use fix_join_expr to setup the relevant variables to
* INNER_VAR. We explicitly don't create any OUTER_VARs as
* those are already used by RETURNING and it seems better to
* be non-conflicting.
*/
if (splan->onConflictSet)
{
indexed_tlist *itlist;
itlist = build_tlist_index(splan->exclRelTlist);
splan->onConflictSet =
fix_join_expr(root, splan->onConflictSet,
NULL, itlist,
linitial_int(splan->resultRelations),
rtoffset);
splan->onConflictWhere = (Node *)
fix_join_expr(root, (List *) splan->onConflictWhere,
NULL, itlist,
linitial_int(splan->resultRelations),
rtoffset);
}
splan->nominalRelation += rtoffset;
splan->exclRelRTI += rtoffset;
foreach(l, splan->resultRelations)
{
@@ -1846,7 +1874,8 @@ search_indexed_tlist_for_sortgroupref(Node *node,
* inner_itlist = NULL and acceptable_rel = the ID of the target relation.
*
* 'clauses' is the targetlist or list of join clauses
* 'outer_itlist' is the indexed target list of the outer join relation
* 'outer_itlist' is the indexed target list of the outer join relation,
* or NULL
* 'inner_itlist' is the indexed target list of the inner join relation,
* or NULL
* 'acceptable_rel' is either zero or the rangetable index of a relation
@@ -1886,12 +1915,17 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
Var *var = (Var *) node;
/* First look for the var in the input tlists */
newvar = search_indexed_tlist_for_var(var,
context->outer_itlist,
OUTER_VAR,
context->rtoffset);
if (newvar)
return (Node *) newvar;
if (context->outer_itlist)
{
newvar = search_indexed_tlist_for_var(var,
context->outer_itlist,
OUTER_VAR,
context->rtoffset);
if (newvar)
return (Node *) newvar;
}
/* Then in the outer */
if (context->inner_itlist)
{
newvar = search_indexed_tlist_for_var(var,
@@ -1920,7 +1954,7 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
PlaceHolderVar *phv = (PlaceHolderVar *) node;
/* See if the PlaceHolderVar has bubbled up from a lower plan node */
if (context->outer_itlist->has_ph_vars)
if (context->outer_itlist && context->outer_itlist->has_ph_vars)
{
newvar = search_indexed_tlist_for_non_var((Node *) phv,
context->outer_itlist,
@@ -1943,7 +1977,7 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
if (IsA(node, Param))
return fix_param_node(context->root, (Param *) node);
/* Try matching more complex expressions too, if tlists have any */
if (context->outer_itlist->has_non_vars)
if (context->outer_itlist && context->outer_itlist->has_non_vars)
{
newvar = search_indexed_tlist_for_non_var(node,
context->outer_itlist,
View
@@ -2340,6 +2340,10 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
locally_added_param);
finalize_primnode((Node *) mtplan->returningLists,
&context);
finalize_primnode((Node *) mtplan->onConflictSet,
&context);
finalize_primnode((Node *) mtplan->onConflictWhere,
&context);
foreach(l, mtplan->plans)
{
context.paramids =
View
@@ -1030,6 +1030,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
pullup_replace_vars((Node *) parse->targetList, &rvcontext);
parse->returningList = (List *)
pullup_replace_vars((Node *) parse->returningList, &rvcontext);
if (parse->onConflict)
parse->onConflict->onConflictSet = (List *)
pullup_replace_vars((Node *) parse->onConflict->onConflictSet, &rvcontext);
replace_vars_in_jointree((Node *) parse->jointree, &rvcontext,
lowest_nulling_outer_join);
Assert(parse->setOperations == NULL);
@@ -1605,6 +1608,9 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
pullup_replace_vars((Node *) parse->targetList, &rvcontext);
parse->returningList = (List *)
pullup_replace_vars((Node *) parse->returningList, &rvcontext);
if (parse->onConflict)
parse->onConflict->onConflictSet = (List *)
pullup_replace_vars((Node *) parse->onConflict->onConflictSet, &rvcontext);
replace_vars_in_jointree((Node *) parse->jointree, &rvcontext, NULL);
Assert(parse->setOperations == NULL);
parse->havingQual = pullup_replace_vars(parse->havingQual, &rvcontext);
View
@@ -181,6 +181,19 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
return tlist;
}
/*
* preprocess_onconflict_targetlist
* Process ON CONFLICT SET targetlist.
*
* Returns the new targetlist.
*/
List *
preprocess_onconflict_targetlist(List *tlist, int result_relation, List *range_table)
{
return expand_targetlist(tlist, CMD_UPDATE, result_relation, range_table);
}
/*****************************************************************************
*
* TARGETLIST EXPANSION
View
@@ -25,6 +25,7 @@
#include "access/transam.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
@@ -50,6 +51,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,
@@ -399,6 +402,355 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
(*get_relation_info_hook) (root, relationObjectId, inhparent, rel);
}
/*
* infer_arbiter_indexes -
* Determine the unique indexes used 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 uses, unless one was specified
* in the 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.
*/
List *
infer_arbiter_indexes(PlannerInfo *root)
{
OnConflictExpr *onconflict = root->parse->onConflict;
/* Iteration state */
Relation relation;
Oid relationObjectId;
Oid indexOidFromConstraint = InvalidOid;
List *indexList;
ListCell *l;
/* Normalized inference attributes and inference expressions: */
Bitmapset *inferAttrs = NULL;
List *inferElems = NIL;
/* Result */
List *candidates = NIL;
/*
* Quickly return NIL for ON CONFLICT DO NOTHING without an inference
* specification or named constraint. ON CONFLICT DO UPDATE statements
* must always provide one or the other (but parser ought to have caught
* that already).
*/
if (onconflict->arbiterElems == NIL &&
onconflict->constraint == InvalidOid)
return NIL;
/*
* 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(root->parse->resultRelation,
root->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, onconflict->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 cannot be used in an ON CONFLICT clause")));
else if (attno == 0)
elog(ERROR, "whole row unique index inference specifications are not valid");
inferAttrs = bms_add_member(inferAttrs, attno);
}
/*
* Lookup named constraint's index. This is not immediately returned
* because some additional sanity checks are required.
*/
if (onconflict->constraint != InvalidOid)
{
indexOidFromConstraint = get_constraint_index(onconflict->constraint);
if (indexOidFromConstraint == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("constraint in ON CONFLICT clause has no associated index")));
}
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, because
* enforcement needs to occur there anyway when an inference clause is
* omitted.
*/
idxRel = index_open(indexoid, RowExclusiveLock);
idxForm = idxRel->rd_index;
if (!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;
/*
* Look for match on "ON constraint_name" variant, which may not be
* unique constraint. This can only be a constraint name.
*/
if (indexOidFromConstraint == idxForm->indexrelid)
{
if (!idxForm->indisunique && onconflict->action == ONCONFLICT_UPDATE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("ON CONFLICT DO UPDATE not supported with exclusion constraints")));
list_free(indexList);
index_close(idxRel, NoLock);
heap_close(relation, NoLock);
candidates = lappend_oid(candidates, idxForm->indexrelid);
return candidates;
}
else if (indexOidFromConstraint != InvalidOid)
{
/* No point in further work for index in named constraint case */
goto next;
}
/*
* Only considering conventional inference at this point (not named
* constraints), so index under consideration can be immediately
* skipped if it's not unique
*/
if (!idxForm->indisunique)
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, onconflict->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 *) onconflict->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("there is no unique or exclusion constraint matching the ON CONFLICT specification")));
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 be required 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
*
View
@@ -52,6 +52,8 @@ static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
static List *transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos);
static OnConflictExpr *transformOnConflictClause(ParseState *pstate,
OnConflictClause *onConflictClause);
static int count_rowexpr_columns(ParseState *pstate, Node *expr);
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
@@ -62,6 +64,8 @@ static void determineRecursiveColTypes(ParseState *pstate,
Node *larg, List *nrtargetlist);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
static List *transformUpdateTargetList(ParseState *pstate,
List *targetList);
static Query *transformDeclareCursorStmt(ParseState *pstate,
DeclareCursorStmt *stmt);
static Query *transformExplainStmt(ParseState *pstate,
@@ -419,6 +423,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
ListCell *icols;
ListCell *attnos;
ListCell *lc;
bool isOnConflictUpdate;
AclMode targetPerms;
/* There can't be any outer WITH to worry about */
Assert(pstate->p_ctenamespace == NIL);
@@ -434,6 +440,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
isOnConflictUpdate = (stmt->onConflictClause &&
stmt->onConflictClause->action == ONCONFLICT_UPDATE);
/*
* We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
* VALUES list, or general SELECT input. We special-case VALUES, both for
@@ -478,8 +487,11 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* mentioned in the SELECT part. Note that the target table is not added
* to the joinlist or namespace.
*/
targetPerms = ACL_INSERT;
if (isOnConflictUpdate)
targetPerms |= ACL_UPDATE;
qry->resultRelation = setTargetTable(pstate, stmt->relation,
false, false, ACL_INSERT);
false, false, targetPerms);
/* Validate stmt->cols list, or build default list if no list given */
icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
@@ -740,6 +752,11 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
attnos = lnext(attnos);
}
/* Process ON CONFLICT, if any. */
if (stmt->onConflictClause)
qry->onConflict = transformOnConflictClause(pstate,
stmt->onConflictClause);
/*
* 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
@@ -849,6 +866,85 @@ transformInsertRow(ParseState *pstate, List *exprlist,
return result;
}
/*
* transformSelectStmt -
* transforms an OnConflictClause in an INSERT
*/
static OnConflictExpr *
transformOnConflictClause(ParseState *pstate,
OnConflictClause *onConflictClause)
{
List *arbiterElems;
Node *arbiterWhere;
Oid arbiterConstraint;
List *onConflictSet = NIL;
Node *onConflictWhere = NULL;
RangeTblEntry *exclRte = NULL;
int exclRelIndex = 0;
List *exclRelTlist = NIL;
OnConflictExpr *result;
/* Process the arbiter clause, ON CONFLICT ON (...) */
transformOnConflictArbiter(pstate, onConflictClause, &arbiterElems,
&arbiterWhere, &arbiterConstraint);
/* Process DO UPDATE */
if (onConflictClause->action == ONCONFLICT_UPDATE)
{
exclRte = addRangeTableEntryForRelation(pstate,
pstate->p_target_relation,
makeAlias("excluded", NIL),
false, false);
exclRelIndex = list_length(pstate->p_rtable);
/*
* Build a targetlist for the EXCLUDED pseudo relation. Out of
* simplicity we do that here, because expandRelAttrs() happens to
* nearly do the right thing; specifically it also works with views.
* It'd be more proper to instead scan some pseudo scan node, but it
* doesn't seem worth the amount of code required.
*
* The only caveat of this hack is that the permissions expandRelAttrs
* adds have to be reset. markVarForSelectPriv() will add the exact
* required permissions back.
*/
exclRelTlist = expandRelAttrs(pstate, exclRte,
exclRelIndex, 0, -1);
exclRte->requiredPerms = 0;
exclRte->selectedCols = NULL;
/*
* Add EXCLUDED and the target RTE to the namespace, so that they can
* be used in the UPDATE statement.
*/
addRTEtoQuery(pstate, exclRte, false, true, true);
addRTEtoQuery(pstate, pstate->p_target_rangetblentry,
false, true, true);
onConflictSet =
transformUpdateTargetList(pstate, onConflictClause->targetList);
onConflictWhere = transformWhereClause(pstate,
onConflictClause->whereClause,
EXPR_KIND_WHERE, "WHERE");
}
/* Finally, build ON CONFLICT DO [NOTHING | UPDATE] expression */
result = makeNode(OnConflictExpr);
result->action = onConflictClause->action;
result->arbiterElems = arbiterElems;
result->arbiterWhere = arbiterWhere;
result->constraint = arbiterConstraint;
result->onConflictSet = onConflictSet;
result->onConflictWhere = onConflictWhere;
result->exclRelIndex = exclRelIndex;
result->exclRelTlist = exclRelTlist;
return result;
}
/*
* count_rowexpr_columns -
* get number of columns contained in a ROW() expression;
@@ -1899,10 +1995,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
{
Query *qry = makeNode(Query);
ParseNamespaceItem *nsitem;
RangeTblEntry *target_rte;
Node *qual;
ListCell *origTargetList;
ListCell *tl;
qry->commandType = CMD_UPDATE;
pstate->p_is_update = true;
@@ -1937,33 +2030,51 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
nsitem->p_lateral_only = false;
nsitem->p_lateral_ok = true;
qry->targetList = transformTargetList(pstate, stmt->targetList,
EXPR_KIND_UPDATE_SOURCE);
qual = transformWhereClause(pstate, stmt->whereClause,
EXPR_KIND_WHERE, "WHERE");
qry->returningList = transformReturningList(pstate, stmt->returningList);
/*
* Now we are done with SELECT-like processing, and can get on with
* transforming the target list to match the UPDATE target columns.
*/
qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
qry->hasSubLinks = pstate->p_hasSubLinks;
/*
* Now we are done with SELECT-like processing, and can get on with
* transforming the target list to match the UPDATE target columns.
*/
assign_query_collations(pstate, qry);
return qry;
}
/*
* transformUpdateTargetList -
* handle SET clause in UPDATE/INSERT ... ON CONFLICT UPDATE
*/
static List *
transformUpdateTargetList(ParseState *pstate, List *origTlist)
{
List *tlist = NIL;
RangeTblEntry *target_rte;
ListCell *orig_tl;
ListCell *tl;
tlist = transformTargetList(pstate, origTlist,
EXPR_KIND_UPDATE_SOURCE);
/* Prepare to assign non-conflicting resnos to resjunk attributes */
if (pstate->p_next_resno <= pstate->p_target_relation->rd_rel->relnatts)
pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
/* Prepare non-junk columns for assignment to target table */
target_rte = pstate->p_target_rangetblentry;
origTargetList = list_head(stmt->targetList);
orig_tl = list_head(origTlist);
foreach(tl, qry->targetList)
foreach(tl, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(tl);
ResTarget *origTarget;
@@ -1981,9 +2092,9 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
tle->resname = NULL;
continue;
}
if (origTargetList == NULL)
if (orig_tl == NULL)
elog(ERROR, "UPDATE target count mismatch --- internal error");
origTarget = (ResTarget *) lfirst(origTargetList);
origTarget = (ResTarget *) lfirst(orig_tl);
Assert(IsA(origTarget, ResTarget));
attrno = attnameAttNum(pstate->p_target_relation,
@@ -2005,14 +2116,12 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
attrno - FirstLowInvalidHeapAttributeNumber);
origTargetList = lnext(origTargetList);
orig_tl = lnext(orig_tl);
}
if (origTargetList != NULL)
if (orig_tl != NULL)
elog(ERROR, "UPDATE target count mismatch --- internal error");
assign_query_collations(pstate, qry);
return qry;
return tlist;
}
/*
View
@@ -217,6 +217,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RangeVar *range;
IntoClause *into;
WithClause *with;
InferClause *infer;
OnConflictClause *onconflict;
A_Indices *aind;
ResTarget *target;
struct PrivTarget *privtarget;
@@ -318,7 +320,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_class opt_inline_handler opt_validator validator_clause
opt_collate
%type <range> qualified_name OptConstrFromTable
%type <range> qualified_name insert_target OptConstrFromTable
%type <str> all_Op MathOp
@@ -344,7 +346,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
reloptions opt_reloptions
OptWith opt_distinct opt_definition func_args func_args_list
OptWith distinct_clause opt_all_clause opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
aggr_args aggr_args_list
func_as createfunc_opt_list alterfunc_opt_list
@@ -389,7 +391,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> for_locking_item
%type <list> for_locking_clause opt_for_locking_clause for_locking_items
%type <list> locked_rels_list
%type <boolean> opt_all
%type <boolean> all_or_distinct
%type <node> join_outer join_qual
%type <jtype> join_type
@@ -418,6 +420,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> SeqOptElem
%type <istmt> insert_rest
%type <infer> opt_conf_expr
%type <onconflict> opt_on_conflict
%type <vsetstmt> generic_set set_rest set_rest_more generic_reset reset_rest
SetResetClause FunctionSetResetClause
@@ -557,8 +561,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
@@ -9436,15 +9440,35 @@ DeallocateStmt: DEALLOCATE name
*****************************************************************************/
InsertStmt:
opt_with_clause INSERT INTO qualified_name insert_rest returning_clause
opt_with_clause INSERT INTO insert_target insert_rest
opt_on_conflict returning_clause
{
$5->relation = $4;
$5->returningList = $6;
$5->onConflictClause = $6;
$5->returningList = $7;
$5->withClause = $1;
$$ = (Node *) $5;
}
;
/*
* Can't easily make AS optional here, because VALUES in insert_rest would
* have a shift/reduce conflict with a values as a optional alias. We could
* easily allow unreserved_keywords as optional aliases, but that'd be a odd
* divergance from other places. So just require AS for now.
*/
insert_target:
qualified_name
{
$$ = $1;
}
| qualified_name AS ColId
{
$1->alias = makeAlias($3, NIL);
$$ = $1;
}
;
insert_rest:
SelectStmt
{
@@ -9484,6 +9508,56 @@ insert_column_item:
}
;
opt_on_conflict:
ON CONFLICT opt_conf_expr DO UPDATE SET set_clause_list where_clause
{
$$ = makeNode(OnConflictClause);
$$->action = ONCONFLICT_UPDATE;
$$->infer = $3;
$$->targetList = $7;
$$->whereClause = $8;
$$->location = @1;
}
|
ON CONFLICT opt_conf_expr DO NOTHING
{
$$ = makeNode(OnConflictClause);
$$->action = ONCONFLICT_NOTHING;
$$->infer = $3;
$$->targetList = NIL;
$$->whereClause = NULL;
$$->location = @1;
}
| /*EMPTY*/
{
$$ = NULL;
}
;
opt_conf_expr:
'(' index_params ')' where_clause
{
$$ = makeNode(InferClause);
$$->indexElems = $2;
$$->whereClause = $4;
$$->conname = NULL;
$$->location = @1;
}
|
ON CONSTRAINT name
{
$$ = makeNode(InferClause);
$$->indexElems = NIL;
$$->whereClause = NULL;
$$->conname = $3;
$$->location = @1;
}
| /*EMPTY*/
{
$$ = NULL;
}
;
returning_clause:
RETURNING target_list { $$ = $2; }
| /* EMPTY */ { $$ = NIL; }
@@ -9870,7 +9944,21 @@ select_clause:
* However, this is not checked by the grammar; parse analysis must check it.
*/
simple_select:
SELECT opt_distinct opt_target_list
SELECT opt_all_clause opt_target_list
into_clause from_clause where_clause
group_clause having_clause window_clause
{
SelectStmt *n = makeNode(SelectStmt);
n->targetList = $3;
n->intoClause = $4;
n->fromClause = $5;
n->whereClause = $6;
n->groupClause = $7;
n->havingClause = $8;
n->windowClause = $9;
$$ = (Node *)n;
}
| SELECT distinct_clause target_list
into_clause from_clause where_clause
group_clause having_clause window_clause
{
@@ -9905,15 +9993,15 @@ simple_select:
n->fromClause = list_make1($2);
$$ = (Node *)n;
}
| select_clause UNION opt_all select_clause
| select_clause UNION all_or_distinct select_clause
{
$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
}
| select_clause INTERSECT opt_all select_clause
| select_clause INTERSECT all_or_distinct select_clause
{
$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
}
| select_clause EXCEPT opt_all select_clause
| select_clause EXCEPT all_or_distinct select_clause
{
$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
}
@@ -10052,18 +10140,22 @@ opt_table: TABLE {}
| /*EMPTY*/ {}
;
opt_all: ALL { $$ = TRUE; }
all_or_distinct:
ALL { $$ = TRUE; }
| DISTINCT { $$ = FALSE; }
| /*EMPTY*/ { $$ = FALSE; }
;
/* We use (NIL) as a placeholder to indicate that all target expressions
* should be placed in the DISTINCT list during parsetree analysis.
*/
opt_distinct:
distinct_clause:
DISTINCT { $$ = list_make1(NIL); }
| DISTINCT ON '(' expr_list ')' { $$ = $4; }
| ALL { $$ = NIL; }
;
opt_all_clause:
ALL { $$ = NIL;}
| /*EMPTY*/ { $$ = NIL; }
;
@@ -13367,6 +13459,7 @@ unreserved_keyword:
| COMMIT
| COMMITTED
| CONFIGURATION
| CONFLICT
| CONNECTION
| CONSTRAINTS
| CONTENT_P
View
@@ -16,7 +16,9 @@
#include "postgres.h"
#include "access/heapam.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "nodes/makefuncs.h"
@@ -32,6 +34,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 +78,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 +2171,204 @@ 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)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("ASC/DESC is not allowed in ON CONFLICT clause"),
parser_errposition(pstate,
exprLocation((Node *) infer))));
if (ielem->nulls_ordering != SORTBY_NULLS_DEFAULT)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("NULLS FIRST/LAST is not allowed in ON CONFLICT clause"),
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;
}
/*
* transformOnConflictArbiter -
* transform arbiter expressions in an ON CONFLICT clause.
*
* 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
transformOnConflictArbiter(ParseState *pstate,
OnConflictClause *onConflictClause,
List **arbiterExpr, Node **arbiterWhere,
Oid *constraint)
{
InferClause *infer = onConflictClause->infer;
*arbiterExpr = NIL;
*arbiterWhere = NULL;
*constraint = InvalidOid;
if (onConflictClause->action == ONCONFLICT_UPDATE && !infer)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("ON CONFLICT DO UPDATE requires inference specification or constraint name"),
errhint("For example, ON CONFLICT ON CONFLICT (<column>)."),
parser_errposition(pstate,
exprLocation((Node *) onConflictClause))));
/*
* 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 system catalog tables"),
parser_errposition(pstate,
exprLocation((Node *) onConflictClause))));
/* Same applies to table used by logical decoding as catalog table */
if (RelationIsUsedAsCatalogTable(pstate->p_target_relation))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ON CONFLICT not supported on table \"%s\" used as a catalog table",
RelationGetRelationName(pstate->p_target_relation)),
parser_errposition(pstate,
exprLocation((Node *) onConflictClause))));
/* ON CONFLICT DO NOTHING does not require an inference clause */
if (infer)
{
List *save_namespace;
/*
* While we process the arbiter expressions, accept only
* non-qualified references to the target table. Hide any other
* relations.
*/
save_namespace = pstate->p_namespace;
pstate->p_namespace = NIL;
addRTEtoQuery(pstate, pstate->p_target_rangetblentry,
false, false, true);
if (infer->indexElems)
*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);
pstate->p_namespace = save_namespace;
if (infer->conname)
*constraint = get_relation_constraint_oid(RelationGetRelid(pstate->p_target_relation),
infer->conname, false);
}
/*
* 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
View
@@ -479,9 +479,11 @@ assign_collations_walker(Node *node, assign_collations_context *context)
parser_errposition(context->pstate,
loccontext.location2)));
break;
case T_InferenceElem:
case T_RangeTblRef:
case T_JoinExpr:
case T_FromExpr:
case T_OnConflictExpr:
case T_SortGroupClause:
(void) expression_tree_walker(node,
assign_collations_walker,
View
@@ -537,11 +537,12 @@ transformAssignedExpr(ParseState *pstate,
/*
* updateTargetListEntry()
* This is used in UPDATE statements only. It prepares an UPDATE
* TargetEntry for assignment to a column of the target table.
* This includes coercing the given value to the target column's type
* (if necessary), and dealing with any subfield names or subscripts
* attached to the target column itself.
* This is used in UPDATE statements (and ON CONFLICT DO UPDATE)
* only. It prepares an UPDATE TargetEntry for assignment to a
* column of the target table. This includes coercing the given
* value to the target column's type (if necessary), and dealing with
* any subfield names or subscripts attached to the target column
* itself.
*
* pstate parse state
* tle target list entry to be modified
View
@@ -64,6 +64,8 @@ static void DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
static void DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
static void DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
static void DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
static void DecodeSpecConfirm(LogicalDecodingContext *ctx, XLogRecordBuffer *buf);
static void DecodeCommit(LogicalDecodingContext *ctx, XLogRecordBuffer *buf,
xl_xact_parsed_commit *parsed, TransactionId xid);
static void DecodeAbort(LogicalDecodingContext *ctx, XLogRecordBuffer *buf,
@@ -414,6 +416,11 @@ DecodeHeapOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
ReorderBufferXidSetCatalogChanges(ctx->reorder, xid, buf->origptr);
break;
case XLOG_HEAP_CONFIRM:
if (SnapBuildProcessChange(builder, xid, buf->origptr))
DecodeSpecConfirm(ctx, buf);
break;
case XLOG_HEAP_LOCK:
/* we don't care about row level locks for now */
break;
@@ -564,11 +571,15 @@ DecodeInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
return;
change = ReorderBufferGetChange(ctx->reorder);
change->action = REORDER_BUFFER_CHANGE_INSERT;
if (!(xlrec->flags & XLH_INSERT_IS_SPECULATIVE))
change->action = REORDER_BUFFER_CHANGE_INSERT;
else
change->action = REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT;
change->origin_id = XLogRecGetOrigin(r);
memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode));
if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE)
if (xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE)
{
Size tuplelen;
char *tupledata = XLogRecGetBlockData(r, 0, &tuplelen);
@@ -615,7 +626,7 @@ DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
change->origin_id = XLogRecGetOrigin(r);
memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode));
if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE)
if (xlrec->flags & XLH_UPDATE_CONTAINS_NEW_TUPLE)
{
data = XLogRecGetBlockData(r, 0, &datalen);
@@ -624,7 +635,7 @@ DecodeUpdate(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
DecodeXLogTuple(data, datalen, change->data.tp.newtuple);
}
if (xlrec->flags & XLOG_HEAP_CONTAINS_OLD)
if (xlrec->flags & XLH_UPDATE_CONTAINS_OLD)
{
/* caution, remaining data in record is not aligned */
data = XLogRecGetData(r) + SizeOfHeapUpdate;
@@ -660,6 +671,13 @@ DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
if (target_node.dbNode != ctx->slot->data.database)
return;
/*
* Super deletions are irrelevant for logical decoding, it's driven by the
* confirmation records.
*/
if (xlrec->flags & XLH_DELETE_IS_SUPER)
return;
/* output plugin doesn't look for this origin, no need to queue */
if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
return;
@@ -671,7 +689,7 @@ DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode));
/* old primary key stored */
if (xlrec->flags & XLOG_HEAP_CONTAINS_OLD)
if (xlrec->flags & XLH_DELETE_CONTAINS_OLD)
{
Assert(XLogRecGetDataLen(r) > (SizeOfHeapDelete + SizeOfHeapHeader));
@@ -737,7 +755,7 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
* We decode the tuple in pretty much the same way as DecodeXLogTuple,
* but since the layout is slightly different, we can't use it here.
*/
if (xlrec->flags & XLOG_HEAP_CONTAINS_NEW_TUPLE)
if (xlrec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE)
{
change->data.tp.newtuple = ReorderBufferGetTupleBuf(ctx->reorder);
@@ -775,7 +793,7 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
* xl_multi_insert_tuple record emitted by one heap_multi_insert()
* call.
*/
if (xlrec->flags & XLOG_HEAP_LAST_MULTI_INSERT &&
if (xlrec->flags & XLH_INSERT_LAST_IN_MULTI &&
(i + 1) == xlrec->ntuples)
change->data.tp.clear_toast_afterwards = true;
else
@@ -787,6 +805,40 @@ DecodeMultiInsert(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
Assert(data == tupledata + tuplelen);
}
/*
* Parse XLOG_HEAP_CONFIRM from wal into a confirmation change.
*
* This is pretty trivial, all the state essentially already setup by the
* speculative insertion.
*/
static void
DecodeSpecConfirm(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
{
XLogReaderState *r = buf->record;
ReorderBufferChange *change;
RelFileNode target_node;
/* only interested in our database */
XLogRecGetBlockTag(r, 0, &target_node, NULL, NULL);
if (target_node.dbNode != ctx->slot->data.database)
return;
/* output plugin doesn't look for this origin, no need to queue */
if (FilterByOrigin(ctx, XLogRecGetOrigin(r)))
return;
change = ReorderBufferGetChange(ctx->reorder);
change->action = REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM;
change->origin_id = XLogRecGetOrigin(r);
memcpy(&change->data.tp.relnode, &target_node, sizeof(RelFileNode));
change->data.tp.clear_toast_afterwards = true;
ReorderBufferQueueChange(ctx->reorder, XLogRecGetXid(r), buf->origptr, change);
}
/*
* Read a HeapTuple as WAL logged by heap_insert, heap_update and heap_delete
* (but not by heap_multi_insert) into a tuplebuf.
Oops, something went wrong.