View
@@ -401,6 +401,7 @@ ReorderBufferReturnChange(ReorderBuffer *rb, ReorderBufferChange *change)
case REORDER_BUFFER_CHANGE_INSERT:
case REORDER_BUFFER_CHANGE_UPDATE:
case REORDER_BUFFER_CHANGE_DELETE:
case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
if (change->data.tp.newtuple)
{
ReorderBufferReturnTupleBuf(rb, change->data.tp.newtuple);
@@ -420,8 +421,9 @@ ReorderBufferReturnChange(ReorderBuffer *rb, ReorderBufferChange *change)
change->data.snapshot = NULL;
}
break;
/* no data in addition to the struct itself */
case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
break;
case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
break;
}
@@ -1317,6 +1319,7 @@ ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
PG_TRY();
{
ReorderBufferChange *change;
ReorderBufferChange *specinsert = NULL;
if (using_subtxn)
BeginInternalSubTransaction("replay");
@@ -1333,6 +1336,17 @@ ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
switch (change->action)
{
case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
/*
* Confirmation for speculative insertion arrived. Simply
* use as a normal record. It'll be cleaned up at the end
* of INSERT processing.
*/
Assert(specinsert->data.tp.oldtuple == NULL);
change = specinsert;
change->action = REORDER_BUFFER_CHANGE_INSERT;
/* intentionally fall through */
case REORDER_BUFFER_CHANGE_INSERT:
case REORDER_BUFFER_CHANGE_UPDATE:
case REORDER_BUFFER_CHANGE_DELETE:
@@ -1348,7 +1362,7 @@ ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
if (reloid == InvalidOid &&
change->data.tp.newtuple == NULL &&
change->data.tp.oldtuple == NULL)
continue;
goto change_done;
else if (reloid == InvalidOid)
elog(ERROR, "could not map filenode \"%s\" to relation OID",
relpathperm(change->data.tp.relnode,
@@ -1362,50 +1376,92 @@ ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
relpathperm(change->data.tp.relnode,
MAIN_FORKNUM));
if (RelationIsLogicallyLogged(relation))
if (!RelationIsLogicallyLogged(relation))
goto change_done;
/*
* For now ignore sequence changes entirely. Most of
* the time they don't log changes using records we
* understand, so it doesn't make sense to handle the
* few cases we do.
*/
if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
goto change_done;
/* user-triggered change */
if (!IsToastRelation(relation))
{
ReorderBufferToastReplace(rb, txn, relation, change);
rb->apply_change(rb, txn, relation, change);
/*
* For now ignore sequence changes entirely. Most of
* the time they don't log changes using records we
* understand, so it doesn't make sense to handle the
* few cases we do.
* Only clear reassembled toast chunks if we're
* sure they're not required anymore. The creator
* of the tuple tells us.
*/
if (relation->rd_rel->relkind == RELKIND_SEQUENCE)
{
}
/* user-triggered change */
else if (!IsToastRelation(relation))
{
ReorderBufferToastReplace(rb, txn, relation, change);
rb->apply_change(rb, txn, relation, change);
/*
* Only clear reassembled toast chunks if we're
* sure they're not required anymore. The creator
* of the tuple tells us.
*/
if (change->data.tp.clear_toast_afterwards)
ReorderBufferToastReset(rb, txn);
}
/* we're not interested in toast deletions */
else if (change->action == REORDER_BUFFER_CHANGE_INSERT)
{
/*
* Need to reassemble the full toasted Datum in
* memory, to ensure the chunks don't get reused
* till we're done remove it from the list of this
* transaction's changes. Otherwise it will get
* freed/reused while restoring spooled data from
* disk.
*/
dlist_delete(&change->node);
ReorderBufferToastAppendChunk(rb, txn, relation,
change);
}
if (change->data.tp.clear_toast_afterwards)
ReorderBufferToastReset(rb, txn);
}
/* we're not interested in toast deletions */
else if (change->action == REORDER_BUFFER_CHANGE_INSERT)
{
/*
* Need to reassemble the full toasted Datum in
* memory, to ensure the chunks don't get reused till
* we're done remove it from the list of this
* transaction's changes. Otherwise it will get
* freed/reused while restoring spooled data from
* disk.
*/
dlist_delete(&change->node);
ReorderBufferToastAppendChunk(rb, txn, relation,
change);
}
change_done:
/*
* Either speculative insertion was confirmed, or it was
* unsuccessful and the record isn't needed anymore.
*/
if (specinsert != NULL)
{
ReorderBufferReturnChange(rb, specinsert);
specinsert = NULL;
}
if (relation != NULL)
{
RelationClose(relation);
relation = NULL;
}
RelationClose(relation);
break;
case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
/*
* Speculative insertions are dealt with by delaying the
* processing of the insert until the confirmation record
* arrives. For that we simply unlink the record from the
* chain, so it does not get freed/reused while restoring
* spooled data from disk.
*
* This is safe in the face of concurrent catalog changes
* because the relevant relation can't be changed between
* speculative insertion and confirmation due to
* CheckTableNotInUse() and locking.
*/
/* clear out a pending (and thus failed) speculation */
if (specinsert != NULL)
{
ReorderBufferReturnChange(rb, specinsert);
specinsert = NULL;
}
/* and memorize the pending insertion */
dlist_delete(&change->node);
specinsert = change;
break;
case REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT:
/* get rid of the old */
TeardownHistoricSnapshot(false);
@@ -1474,6 +1530,17 @@ ReorderBufferCommit(ReorderBuffer *rb, TransactionId xid,
}
}
/*
* There's a a speculative insertion remaining, just clean in up, it
* can't have been successful, otherwise we'd gotten a confirmation
* record.
*/
if (specinsert)
{
ReorderBufferReturnChange(rb, specinsert);
specinsert = NULL;
}
/* clean up the iterator */
ReorderBufferIterTXNFinish(rb, iterstate);
iterstate = NULL;
@@ -2001,11 +2068,11 @@ ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
switch (change->action)
{
/* fall through these, they're all similar enough */
case REORDER_BUFFER_CHANGE_INSERT:
/* fall through */
case REORDER_BUFFER_CHANGE_UPDATE:
/* fall through */
case REORDER_BUFFER_CHANGE_DELETE:
case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
{
char *data;
ReorderBufferTupleBuf *oldtup,
@@ -2083,9 +2150,8 @@ ReorderBufferSerializeChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
}
break;
}
case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
/* ReorderBufferChange contains everything important */
break;
case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
/* ReorderBufferChange contains everything important */
break;
@@ -2256,11 +2322,11 @@ ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
/* restore individual stuff */
switch (change->action)
{
/* fall through these, they're all similar enough */
case REORDER_BUFFER_CHANGE_INSERT:
/* fall through */
case REORDER_BUFFER_CHANGE_UPDATE:
/* fall through */
case REORDER_BUFFER_CHANGE_DELETE:
case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT:
if (change->data.tp.newtuple)
{
Size len = offsetof(ReorderBufferTupleBuf, t_data) +
@@ -2309,6 +2375,7 @@ ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
break;
}
/* the base struct contains all the data, easy peasy */
case REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM:
case REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID:
case REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID:
break;
View
@@ -52,7 +52,10 @@ static Query *rewriteRuleAction(Query *parsetree,
CmdType event,
bool *returning_flag);
static List *adjustJoinTreeList(Query *parsetree, bool removert, int rt_index);
static void rewriteTargetListIU(Query *parsetree, Relation target_relation,
static List *rewriteTargetListIU(List *targetList,
CmdType commandType,
Relation target_relation,
int result_rti,
List **attrno_list);
static TargetEntry *process_matched_tle(TargetEntry *src_tle,
TargetEntry *prior_tle,
@@ -66,7 +69,7 @@ static void markQueryForLocking(Query *qry, Node *jtnode,
LockClauseStrength strength, LockWaitPolicy waitPolicy,
bool pushedDown);
static List *matchLocks(CmdType event, RuleLock *rulelocks,
int varno, Query *parsetree);
int varno, Query *parsetree, bool *hasUpdate);
static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
bool forUpdatePushedDown);
static bool view_has_instead_trigger(Relation view, CmdType event);
@@ -679,11 +682,13 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index)
* order of the original tlist's non-junk entries. This is needed for
* processing VALUES RTEs.
*/
static void
rewriteTargetListIU(Query *parsetree, Relation target_relation,
static List*
rewriteTargetListIU(List *targetList,
CmdType commandType,
Relation target_relation,
int result_rti,
List **attrno_list)
{
CmdType commandType = parsetree->commandType;
TargetEntry **new_tles;
List *new_tlist = NIL;
List *junk_tlist = NIL;
@@ -709,7 +714,7 @@ rewriteTargetListIU(Query *parsetree, Relation target_relation,
new_tles = (TargetEntry **) palloc0(numattrs * sizeof(TargetEntry *));
next_junk_attrno = numattrs + 1;
foreach(temp, parsetree->targetList)
foreach(temp, targetList)
{
TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
@@ -827,7 +832,7 @@ rewriteTargetListIU(Query *parsetree, Relation target_relation,
{
Node *new_expr;
new_expr = (Node *) makeVar(parsetree->resultRelation,
new_expr = (Node *) makeVar(result_rti,
attrno,
att_tup->atttypid,
att_tup->atttypmod,
@@ -846,7 +851,7 @@ rewriteTargetListIU(Query *parsetree, Relation target_relation,
pfree(new_tles);
parsetree->targetList = list_concat(new_tlist, junk_tlist);
return list_concat(new_tlist, junk_tlist);
}
@@ -1288,7 +1293,8 @@ static List *
matchLocks(CmdType event,
RuleLock *rulelocks,
int varno,
Query *parsetree)
Query *parsetree,
bool *hasUpdate)
{
List *matching_locks = NIL;
int nlocks;
@@ -1309,6 +1315,9 @@ matchLocks(CmdType event,
{
RewriteRule *oneLock = rulelocks->rules[i];
if (oneLock->event == CMD_UPDATE)
*hasUpdate = true;
/*
* Suppress ON INSERT/UPDATE/DELETE rules that are disabled or
* configured to not fire during the current sessions replication
@@ -1766,8 +1775,8 @@ fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown)
/*
* Fetch any new security quals that must be applied to this RTE.
*/
get_row_security_policies(parsetree, rte, rt_index,
&securityQuals, &withCheckOptions,
get_row_security_policies(parsetree, parsetree->commandType, rte,
rt_index, &securityQuals, &withCheckOptions,
&hasRowSecurity, &hasSubLinks);
if (securityQuals != NIL || withCheckOptions != NIL)
@@ -2642,6 +2651,18 @@ rewriteTargetView(Query *parsetree, Relation view)
tle->resno - FirstLowInvalidHeapAttributeNumber);
}
if (parsetree->onConflict)
{
foreach(lc, parsetree->onConflict->onConflictSet)
{
TargetEntry *tle = (TargetEntry *) lfirst(lc);
if (!tle->resjunk)
modified_cols = bms_add_member(modified_cols,
tle->resno - FirstLowInvalidHeapAttributeNumber);
}
}
auto_update_detail = view_cols_are_auto_updatable(viewquery,
modified_cols,
NULL,
@@ -2999,6 +3020,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
CmdType event = parsetree->commandType;
bool instead = false;
bool returning = false;
bool updatableview = false;
Query *qual_product = NULL;
List *rewritten = NIL;
ListCell *lc1;
@@ -3081,6 +3103,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
Relation rt_entry_relation;
List *locks;
List *product_queries;
bool hasUpdate = false;
result_relation = parsetree->resultRelation;
Assert(result_relation != 0);
@@ -3123,19 +3146,41 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
List *attrnos;
/* Process the main targetlist ... */
rewriteTargetListIU(parsetree, rt_entry_relation, &attrnos);
parsetree->targetList = rewriteTargetListIU(parsetree->targetList,
parsetree->commandType,
rt_entry_relation,
parsetree->resultRelation,
&attrnos);
/* ... and the VALUES expression lists */
rewriteValuesRTE(values_rte, rt_entry_relation, attrnos);
}
else
{
/* Process just the main targetlist */
rewriteTargetListIU(parsetree, rt_entry_relation, NULL);
parsetree->targetList =
rewriteTargetListIU(parsetree->targetList,
parsetree->commandType,
rt_entry_relation,
parsetree->resultRelation, NULL);
}
if (parsetree->onConflict &&
parsetree->onConflict->action == ONCONFLICT_UPDATE)
{
parsetree->onConflict->onConflictSet =
rewriteTargetListIU(parsetree->onConflict->onConflictSet,
CMD_UPDATE,
rt_entry_relation,
parsetree->resultRelation,
NULL);
}
}
else if (event == CMD_UPDATE)
{
rewriteTargetListIU(parsetree, rt_entry_relation, NULL);
parsetree->targetList =
rewriteTargetListIU(parsetree->targetList,
parsetree->commandType, rt_entry_relation,
parsetree->resultRelation, NULL);
rewriteTargetListUD(parsetree, rt_entry, rt_entry_relation);
}
else if (event == CMD_DELETE)
@@ -3149,7 +3194,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
* Collect and apply the appropriate rules.
*/
locks = matchLocks(event, rt_entry_relation->rd_rules,
result_relation, parsetree);
result_relation, parsetree, &hasUpdate);
product_queries = fireRules(parsetree,
result_relation,
@@ -3198,6 +3243,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
*/
instead = true;
returning = true;
updatableview = true;
}
/*
@@ -3278,6 +3324,17 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
}
}
/*
* Updatable views are supported by ON CONFLICT, so don't prevent that
* case from proceeding
*/
if (parsetree->onConflict &&
(product_queries != NIL || hasUpdate) &&
!updatableview)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("INSERT with ON CONFLICT clause cannot be used with table that has INSERT or UPDATE rules")));
heap_close(rt_entry_relation, NoLock);
}
View
@@ -89,9 +89,10 @@ row_security_policy_hook_type row_security_policy_hook_restrictive = NULL;
* set to true if any of the quals returned contain sublinks.
*/
void
get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
List **securityQuals, List **withCheckOptions,
bool *hasRowSecurity, bool *hasSubLinks)
get_row_security_policies(Query* root, CmdType commandType, RangeTblEntry* rte,
int rt_index, List **securityQuals,
List **withCheckOptions, bool *hasRowSecurity,
bool *hasSubLinks)
{
Expr *rowsec_expr = NULL;
Expr *rowsec_with_check_expr = NULL;
@@ -159,7 +160,7 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
/* Grab the built-in policies which should be applied to this relation. */
rel = heap_open(rte->relid, NoLock);
rowsec_policies = pull_row_security_policies(root->commandType, rel,
rowsec_policies = pull_row_security_policies(commandType, rel,
user_id);
/*
@@ -201,7 +202,7 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
*/
if (row_security_policy_hook_restrictive)
{
hook_policies_restrictive = (*row_security_policy_hook_restrictive)(root->commandType, rel);
hook_policies_restrictive = (*row_security_policy_hook_restrictive)(commandType, rel);
/* Build the expression from any policies returned. */
if (hook_policies_restrictive != NIL)
@@ -214,7 +215,7 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
if (row_security_policy_hook_permissive)
{
hook_policies_permissive = (*row_security_policy_hook_permissive)(root->commandType, rel);
hook_policies_permissive = (*row_security_policy_hook_permissive)(commandType, rel);
/* Build the expression from any policies returned. */
if (hook_policies_permissive != NIL)
@@ -242,7 +243,7 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
* WITH CHECK policy (this will be a copy of the USING policy, if no
* explicit WITH CHECK policy exists).
*/
if (root->commandType == CMD_INSERT || root->commandType == CMD_UPDATE)
if (commandType == CMD_INSERT || commandType == CMD_UPDATE)
{
/*
* WITH CHECK OPTIONS wants a WCO node which wraps each Expr, so
@@ -259,7 +260,7 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
WithCheckOption *wco;
wco = (WithCheckOption *) makeNode(WithCheckOption);
wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
wco->kind = commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
WCO_RLS_UPDATE_CHECK;
wco->relname = pstrdup(RelationGetRelationName(rel));
wco->qual = (Node *) hook_with_check_expr_restrictive;
@@ -276,7 +277,7 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
WithCheckOption *wco;
wco = (WithCheckOption *) makeNode(WithCheckOption);
wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
wco->kind = commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
WCO_RLS_UPDATE_CHECK;
wco->relname = pstrdup(RelationGetRelationName(rel));
wco->qual = (Node *) rowsec_with_check_expr;
@@ -289,7 +290,7 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
WithCheckOption *wco;
wco = (WithCheckOption *) makeNode(WithCheckOption);
wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
wco->kind = commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
WCO_RLS_UPDATE_CHECK;
wco->relname = pstrdup(RelationGetRelationName(rel));
wco->qual = (Node *) hook_with_check_expr_permissive;
@@ -312,19 +313,72 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
combined_qual_eval = makeBoolExpr(OR_EXPR, combined_quals, -1);
wco = (WithCheckOption *) makeNode(WithCheckOption);
wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
wco->kind = commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
WCO_RLS_UPDATE_CHECK;
wco->relname = pstrdup(RelationGetRelationName(rel));
wco->qual = (Node *) combined_qual_eval;
wco->cascaded = false;
*withCheckOptions = lappend(*withCheckOptions, wco);
}
/*
* ON CONFLICT DO UPDATE has an RTE that is subject to both INSERT and
* UPDATE RLS enforcement. Those are enforced (as a special, distinct
* kind of WCO) on the target tuple.
*
* Make a second, recursive pass over the RTE for this, gathering
* UPDATE-applicable RLS checks/WCOs, and gathering and converting
* UPDATE-applicable security quals into WCO_RLS_CONFLICT_CHECK RLS
* checks/WCOs. Finally, these distinct kinds of RLS checks/WCOs are
* concatenated with our own INSERT-applicable list.
*/
if (root->onConflict && root->onConflict->action == ONCONFLICT_UPDATE &&
commandType == CMD_INSERT)
{
List *conflictSecurityQuals = NIL;
List *conflictWCOs = NIL;
ListCell *item;
bool conflictHasRowSecurity = false;
bool conflictHasSublinks = false;
/* Assume that RTE is target resultRelation */
get_row_security_policies(root, CMD_UPDATE, rte, rt_index,
&conflictSecurityQuals, &conflictWCOs,
&conflictHasRowSecurity,
&conflictHasSublinks);
if (conflictHasRowSecurity)
*hasRowSecurity = true;
if (conflictHasSublinks)
*hasSubLinks = true;
/*
* Append WITH CHECK OPTIONs/RLS checks, which should not conflict
* between this INSERT and the auxiliary UPDATE
*/
*withCheckOptions = list_concat(*withCheckOptions,
conflictWCOs);
foreach(item, conflictSecurityQuals)
{
Expr *conflict_rowsec_expr = (Expr *) lfirst(item);
WithCheckOption *wco;
wco = (WithCheckOption *) makeNode(WithCheckOption);
wco->kind = WCO_RLS_CONFLICT_CHECK;
wco->relname = pstrdup(RelationGetRelationName(rel));
wco->qual = (Node *) copyObject(conflict_rowsec_expr);
wco->cascaded = false;
*withCheckOptions = lappend(*withCheckOptions, wco);
}
}
}
/* For SELECT, UPDATE, and DELETE, set the security quals */
if (root->commandType == CMD_SELECT
|| root->commandType == CMD_UPDATE
|| root->commandType == CMD_DELETE)
if (commandType == CMD_SELECT
|| commandType == CMD_UPDATE
|| commandType == CMD_DELETE)
{
/* restrictive policies can simply be added to the list first */
if (hook_expr_restrictive)
View
@@ -25,6 +25,24 @@
#include "utils/inval.h"
/*
* Per-backend counter for generating speculative insertion tokens.
*
* This may wrap around, but that's OK as it's only used for the short
* duration between inserting a tuple and checking that there are no (unique)
* constraint violations. It's theoretically possible that a backend sees a
* tuple that was speculatively inserted by another backend, but before it has
* started waiting on the token, the other backend completes its insertion,
* and then then performs 2^32 unrelated insertions. And after all that, the
* first backend finally calls SpeculativeInsertionLockAcquire(), with the
* intention of waiting for the first insertion to complete, but ends up
* waiting for the latest unrelated insertion instead. Even then, nothing
* particularly bad happens: in the worst case they deadlock, causing one of
* the transactions to abort.
*/
static uint32 speculativeInsertionToken = 0;
/*
* Struct to hold context info for transaction lock waits.
*
@@ -575,6 +593,73 @@ ConditionalXactLockTableWait(TransactionId xid)
return true;
}
/*
* SpeculativeInsertionLockAcquire
*
* Insert a lock showing that the given transaction ID is inserting a tuple,
* but hasn't yet decided whether it's going to keep it. The lock can then be
* used to wait for the decision to go ahead with the insertion, or aborting
* it.
*
* The token is used to distinguish multiple insertions by the same
* transaction. It is returned to caller.
*/
uint32
SpeculativeInsertionLockAcquire(TransactionId xid)
{
LOCKTAG tag;
speculativeInsertionToken++;
/*
* Check for wrap-around. Zero means no token is held, so don't use that.
*/
if (speculativeInsertionToken == 0)
speculativeInsertionToken = 1;
SET_LOCKTAG_SPECULATIVE_INSERTION(tag, xid, speculativeInsertionToken);
(void) LockAcquire(&tag, ExclusiveLock, false, false);
return speculativeInsertionToken;
}
/*
* SpeculativeInsertionLockRelease
*
* Delete the lock showing that the given transaction is speculatively
* inserting a tuple.
*/
void
SpeculativeInsertionLockRelease(TransactionId xid)
{
LOCKTAG tag;
SET_LOCKTAG_SPECULATIVE_INSERTION(tag, xid, speculativeInsertionToken);
LockRelease(&tag, ExclusiveLock, false);
}
/*
* SpeculativeInsertionWait
*
* Wait for the specified transaction to finish or abort the insertion of a
* tuple.
*/
void
SpeculativeInsertionWait(TransactionId xid, uint32 token)
{
LOCKTAG tag;
SET_LOCKTAG_SPECULATIVE_INSERTION(tag, xid, token);
Assert(TransactionIdIsValid(xid));
Assert(token != 0);
(void) LockAcquire(&tag, ShareLock, false, false);
LockRelease(&tag, ShareLock, false);
}
/*
* XactLockTableWaitErrorContextCb
* Error context callback for transaction lock waits.
@@ -873,6 +958,12 @@ DescribeLockTag(StringInfo buf, const LOCKTAG *tag)
tag->locktag_field1,
tag->locktag_field2);
break;
case LOCKTAG_SPECULATIVE_TOKEN:
appendStringInfo(buf,
_("speculative token %u of transaction %u"),
tag->locktag_field2,
tag->locktag_field1);
break;
case LOCKTAG_OBJECT:
appendStringInfo(buf,
_("object %u of class %u of database %u"),
View
@@ -202,8 +202,14 @@ ProcessQuery(PlannedStmt *plan,
lastOid = queryDesc->estate->es_lastoid;
else
lastOid = InvalidOid;
snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
"INSERT %u %u", lastOid, queryDesc->estate->es_processed);
if (plan->isUpsert)
snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
"UPSERT %u %u",
lastOid, queryDesc->estate->es_processed);
else
snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
"INSERT %u %u",
lastOid, queryDesc->estate->es_processed);
break;
case CMD_UPDATE:
snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
@@ -1356,7 +1362,10 @@ PortalRunMulti(Portal portal, bool isTopLevel,
* 0" here because technically there is no query of the matching tag type,
* and printing a non-zero count for a different query type seems wrong,
* e.g. an INSERT that does an UPDATE instead should not print "0 1" if
* one row was updated. See QueryRewrite(), step 3, for details.
* one row was updated (unless the ON CONFLICT DO UPDATE, or "UPSERT"
* variant of INSERT was used to update the row, where it's logically a
* direct effect of the top level command). See QueryRewrite(), step 3,
* for details.
*/
if (completionTag && completionTag[0] == '\0')
{
@@ -1366,6 +1375,8 @@ PortalRunMulti(Portal portal, bool isTopLevel,
sprintf(completionTag, "SELECT 0 0");
else if (strcmp(completionTag, "INSERT") == 0)
strcpy(completionTag, "INSERT 0 0");
else if (strcmp(completionTag, "UPSERT") == 0)
strcpy(completionTag, "UPSERT 0 0");
else if (strcmp(completionTag, "UPDATE") == 0)
strcpy(completionTag, "UPDATE 0");
else if (strcmp(completionTag, "DELETE") == 0)
View
@@ -29,6 +29,7 @@ static const char *const LockTagTypeNames[] = {
"tuple",
"transactionid",
"virtualxid",
"speculative token",
"object",
"userlock",
"advisory"
View
@@ -354,6 +354,9 @@ static void get_select_query_def(Query *query, deparse_context *context,
TupleDesc resultDesc);
static void get_insert_query_def(Query *query, deparse_context *context);
static void get_update_query_def(Query *query, deparse_context *context);
static void get_update_query_targetlist_def(Query *query, List *targetList,
deparse_context *context,
RangeTblEntry *rte);
static void get_delete_query_def(Query *query, deparse_context *context);
static void get_utility_query_def(Query *query, deparse_context *context);
static void get_basic_select_query(Query *query, deparse_context *context,
@@ -3846,15 +3849,23 @@ set_deparse_planstate(deparse_namespace *dpns, PlanState *ps)
* For a SubqueryScan, pretend the subplan is INNER referent. (We don't
* use OUTER because that could someday conflict with the normal meaning.)
* Likewise, for a CteScan, pretend the subquery's plan is INNER referent.
* For ON CONFLICT .. UPDATE we just need the inner tlist to point to the
* excluded expression's tlist. (Similar to the SubqueryScan we don't want
* to reuse OUTER, it's used for RETURNING in some modify table cases,
* although not INSERT .. CONFLICT).
*/
if (IsA(ps, SubqueryScanState))
dpns->inner_planstate = ((SubqueryScanState *) ps)->subplan;
else if (IsA(ps, CteScanState))
dpns->inner_planstate = ((CteScanState *) ps)->cteplanstate;
else if (IsA(ps, ModifyTableState))
dpns->inner_planstate = ps;
else
dpns->inner_planstate = innerPlanState(ps);
if (dpns->inner_planstate)
if (IsA(ps, ModifyTableState))
dpns->inner_tlist = ((ModifyTableState *) ps)->mt_excludedtlist;
else if (dpns->inner_planstate)
dpns->inner_tlist = dpns->inner_planstate->plan->targetlist;
else
dpns->inner_tlist = NIL;
@@ -5302,6 +5313,32 @@ get_insert_query_def(Query *query, deparse_context *context)
appendStringInfoString(buf, "DEFAULT VALUES");
}
/* Add ON CONFLICT if present */
if (query->onConflict)
{
OnConflictExpr *confl = query->onConflict;
if (confl->action == ONCONFLICT_NOTHING)
{
appendStringInfoString(buf, " ON CONFLICT DO NOTHING");
}
else
{
appendStringInfoString(buf, " ON CONFLICT DO UPDATE SET ");
/* Deparse targetlist */
get_update_query_targetlist_def(query, confl->onConflictSet,
context, rte);
/* Add a WHERE clause if given */
if (confl->onConflictWhere != NULL)
{
appendContextKeyword(context, " WHERE ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_rule_expr(confl->onConflictWhere, context, false);
}
}
}
/* Add RETURNING if present */
if (query->returningList)
{
@@ -5321,12 +5358,6 @@ get_update_query_def(Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
RangeTblEntry *rte;
List *ma_sublinks;
ListCell *next_ma_cell;
SubLink *cur_ma_sublink;
int remaining_ma_columns;
const char *sep;
ListCell *l;
/* Insert the WITH clause if given */
get_with_clause(query, context);
@@ -5349,6 +5380,46 @@ get_update_query_def(Query *query, deparse_context *context)
quote_identifier(rte->alias->aliasname));
appendStringInfoString(buf, " SET ");
/* Deparse targetlist */
get_update_query_targetlist_def(query, query->targetList, context, rte);
/* Add the FROM clause if needed */
get_from_clause(query, " FROM ", context);
/* Add a WHERE clause if given */
if (query->jointree->quals != NULL)
{
appendContextKeyword(context, " WHERE ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_rule_expr(query->jointree->quals, context, false);
}
/* Add RETURNING if present */
if (query->returningList)
{
appendContextKeyword(context, " RETURNING",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_target_list(query->returningList, context, NULL);
}
}
/* ----------
* get_update_query_targetlist_def - Parse back an UPDATE targetlist
* ----------
*/
static void
get_update_query_targetlist_def(Query *query, List *targetList,
deparse_context *context, RangeTblEntry *rte)
{
StringInfo buf = context->buf;
ListCell *l;
ListCell *next_ma_cell;
int remaining_ma_columns;
const char *sep;
SubLink *cur_ma_sublink;
List *ma_sublinks;
/*
* Prepare to deal with MULTIEXPR assignments: collect the source SubLinks
* into a list. We expect them to appear, in ID order, in resjunk tlist
@@ -5357,7 +5428,7 @@ get_update_query_def(Query *query, deparse_context *context)
ma_sublinks = NIL;
if (query->hasSubLinks) /* else there can't be any */
{
foreach(l, query->targetList)
foreach(l, targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
@@ -5379,7 +5450,7 @@ get_update_query_def(Query *query, deparse_context *context)
/* Add the comma separated list of 'attname = value' */
sep = "";
foreach(l, query->targetList)
foreach(l, targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
Node *expr;
@@ -5470,25 +5541,6 @@ get_update_query_def(Query *query, deparse_context *context)
get_rule_expr(expr, context, false);
}
/* Add the FROM clause if needed */
get_from_clause(query, " FROM ", context);
/* Add a WHERE clause if given */
if (query->jointree->quals != NULL)
{
appendContextKeyword(context, " WHERE ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_rule_expr(query->jointree->quals, context, false);
}
/* Add RETURNING if present */
if (query->returningList)
{
appendContextKeyword(context, " RETURNING",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_target_list(query->returningList, context, NULL);
}
}
View
@@ -405,6 +405,13 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot,
}
}
}
/*
* An invalid Xmin can be left behind by a speculative insertion that
* is cancelled by super-deleting the tuple. We shouldn't see any of
* those in TOAST tables, but better safe than sorry.
*/
else if (!TransactionIdIsValid(HeapTupleHeaderGetXmin(tuple)))
return false;
}
/* otherwise assume the tuple is valid for TOAST. */
@@ -714,8 +721,11 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid,
* output argument to return the xids of concurrent xacts that affected the
* tuple. snapshot->xmin is set to the tuple's xmin if that is another
* transaction that's still in progress; or to InvalidTransactionId if the
* tuple's xmin is committed good, committed dead, or my own xact. Similarly
* for snapshot->xmax and the tuple's xmax.
* tuple's xmin is committed good, committed dead, or my own xact.
* Similarly for snapshot->xmax and the tuple's xmax. If the tuple was
* inserted speculatively, meaning that the inserter might still back down
* on the insertion without aborting the whole transaction, the associated
* token is also returned in snapshot->speculativeToken.
*/
bool
HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
@@ -727,6 +737,7 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
Assert(htup->t_tableOid != InvalidOid);
snapshot->xmin = snapshot->xmax = InvalidTransactionId;
snapshot->speculativeToken = 0;
if (!HeapTupleHeaderXminCommitted(tuple))
{
@@ -808,6 +819,20 @@ HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot,
}
else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple)))
{
/*
* Return the speculative token to caller. Caller can worry
* about xmax, since it requires a conclusively locked row
* version, and a concurrent update to this tuple is a conflict
* of its purposes.
*/
if (HeapTupleHeaderIsSpeculative(tuple))
{
snapshot->speculativeToken =
HeapTupleHeaderGetSpeculativeToken(tuple);
Assert(snapshot->speculativeToken != 0);
}
snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple);
/* XXX shouldn't we fall through to look at xmax? */
return true; /* in insertion by other */
View
@@ -894,9 +894,12 @@ PrintQueryResults(PGresult *results)
success = StoreQueryTuple(results);
else
success = PrintQueryTuples(results);
/* if it's INSERT/UPDATE/DELETE RETURNING, also print status */
/*
* if it's INSERT/UPSERT/UPDATE/DELETE RETURNING, also print status
*/
cmdstatus = PQcmdStatus(results);
if (strncmp(cmdstatus, "INSERT", 6) == 0 ||
strncmp(cmdstatus, "UPSERT", 6) == 0 ||
strncmp(cmdstatus, "UPDATE", 6) == 0 ||
strncmp(cmdstatus, "DELETE", 6) == 0)
PrintQueryStatus(results);
View
@@ -28,6 +28,7 @@
#define HEAP_INSERT_SKIP_WAL 0x0001
#define HEAP_INSERT_SKIP_FSM 0x0002
#define HEAP_INSERT_FROZEN 0x0004
#define HEAP_INSERT_SPECULATIVE 0x0008
typedef struct BulkInsertStateData *BulkInsertState;
@@ -142,6 +143,8 @@ extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
CommandId cid, Snapshot crosscheck, bool wait,
HeapUpdateFailureData *hufd);
extern void heap_finish_speculative(Relation relation, HeapTuple tuple);
extern void heap_abort_speculative(Relation relation, HeapTuple tuple);
extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
HeapTuple newtup,
CommandId cid, Snapshot crosscheck, bool wait,
View
@@ -34,7 +34,7 @@
#define XLOG_HEAP_UPDATE 0x20
/* 0x030 is free, was XLOG_HEAP_MOVE */
#define XLOG_HEAP_HOT_UPDATE 0x40
/* 0x050 is free, was XLOG_HEAP_NEWPAGE */
#define XLOG_HEAP_CONFIRM 0x50
#define XLOG_HEAP_LOCK 0x60
#define XLOG_HEAP_INPLACE 0x70
@@ -60,23 +60,43 @@
#define XLOG_HEAP2_NEW_CID 0x70
/*
* xl_heap_* ->flag values, 8 bits are available.
* xl_heap_insert/xl_heap_multi_insert flag values, 8 bits are available.
*/
/* PD_ALL_VISIBLE was cleared */
#define XLOG_HEAP_ALL_VISIBLE_CLEARED (1<<0)
#define XLH_INSERT_ALL_VISIBLE_CLEARED (1<<0)
#define XLH_INSERT_LAST_IN_MULTI (1<<1)
#define XLH_INSERT_IS_SPECULATIVE (1<<2)
#define XLH_INSERT_CONTAINS_NEW_TUPLE (1<<3)
/*
* xl_heap_update flag values, 8 bits are available.
*/
/* PD_ALL_VISIBLE was cleared */
#define XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED (1<<0)
/* PD_ALL_VISIBLE was cleared in the 2nd page */
#define XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED (1<<1)
#define XLOG_HEAP_CONTAINS_OLD_TUPLE (1<<2)
#define XLOG_HEAP_CONTAINS_OLD_KEY (1<<3)
#define XLOG_HEAP_CONTAINS_NEW_TUPLE (1<<4)
#define XLOG_HEAP_PREFIX_FROM_OLD (1<<5)
#define XLOG_HEAP_SUFFIX_FROM_OLD (1<<6)
/* last xl_heap_multi_insert record for one heap_multi_insert() call */
#define XLOG_HEAP_LAST_MULTI_INSERT (1<<7)
#define XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED (1<<1)
#define XLH_UPDATE_CONTAINS_OLD_TUPLE (1<<2)
#define XLH_UPDATE_CONTAINS_OLD_KEY (1<<3)
#define XLH_UPDATE_CONTAINS_NEW_TUPLE (1<<4)
#define XLH_UPDATE_PREFIX_FROM_OLD (1<<5)
#define XLH_UPDATE_SUFFIX_FROM_OLD (1<<6)
/* convenience macro for checking whether any form of old tuple was logged */
#define XLOG_HEAP_CONTAINS_OLD \
(XLOG_HEAP_CONTAINS_OLD_TUPLE | XLOG_HEAP_CONTAINS_OLD_KEY)
#define XLH_UPDATE_CONTAINS_OLD \
(XLH_UPDATE_CONTAINS_OLD_TUPLE | XLH_UPDATE_CONTAINS_OLD_KEY)
/*
* xl_heap_delete flag values, 8 bits are available.
*/
/* PD_ALL_VISIBLE was cleared */
#define XLH_DELETE_ALL_VISIBLE_CLEARED (1<<0)
#define XLH_DELETE_CONTAINS_OLD_TUPLE (1<<1)
#define XLH_DELETE_CONTAINS_OLD_KEY (1<<2)
#define XLH_DELETE_IS_SUPER (1<<3)
/* convenience macro for checking whether any form of old tuple was logged */
#define XLH_DELETE_CONTAINS_OLD \
(XLH_DELETE_CONTAINS_OLD_TUPLE | XLH_DELETE_CONTAINS_OLD_KEY)
/* This is what we need to know about delete */
typedef struct xl_heap_delete
@@ -243,6 +263,14 @@ typedef struct xl_heap_lock_updated
#define SizeOfHeapLockUpdated (offsetof(xl_heap_lock_updated, infobits_set) + sizeof(uint8))
/* This is what we need to know about confirmation of speculative insertion */
typedef struct xl_heap_confirm
{
OffsetNumber offnum; /* confirmed tuple's offset on page */
} xl_heap_confirm;
#define SizeOfHeapConfirm (offsetof(xl_heap_confirm, offnum) + sizeof(OffsetNumber))
/* This is what we need to know about in-place update */
typedef struct xl_heap_inplace
{
View
@@ -36,7 +36,7 @@ typedef struct BulkInsertStateData
extern void RelationPutHeapTuple(Relation relation, Buffer buffer,
HeapTuple tuple);
HeapTuple tuple, bool token);
extern Buffer RelationGetBufferForTuple(Relation relation, Size len,
Buffer otherBuffer, int options,
BulkInsertState bistate,
View
@@ -96,6 +96,15 @@
* unrelated tuple stored into a slot recently freed by VACUUM. If either
* check fails, one may assume that there is no live descendant version.
*
* t_ctid is sometimes used to store a speculative insertion token, instead
* of a real TID. A speculative token is set on a tuple that's being
* inserted, until the inserter is sure that it wants to go ahead with the
* insertion. Hence a token should only be seen on a tuple with an XMAX
* that's still in-progress, or invalid/aborted. The token is replaced with
* the tuple's real TID when the insertion is confirmed. One should never
* see a speculative insertion token while following a chain of t_ctid links,
* because they are not used on updates, only insertions.
*
* Following the fixed header fields, the nulls bitmap is stored (beginning
* at t_bits). The bitmap is *not* stored if t_infomask shows that there
* are no nulls in the tuple. If an OID field is present (as indicated by
@@ -138,7 +147,8 @@ struct HeapTupleHeaderData
DatumTupleFields t_datum;
} t_choice;
ItemPointerData t_ctid; /* current TID of this or newer tuple */
ItemPointerData t_ctid; /* current TID of this or newer tuple (or a
* speculative insertion token) */
/* Fields below here must match MinimalTupleData! */
@@ -241,6 +251,14 @@ struct HeapTupleHeaderData
*/
#define HEAP_TUPLE_HAS_MATCH HEAP_ONLY_TUPLE /* tuple has a join match */
/*
* Special value used in t_ctid.ip_posid, to indicate that it holds a
* speculative insertion token rather than a real TID. This must be higher
* than MaxOffsetNumber, so that it can be distinguished from a valid
* offset number in a regular item pointer.
*/
#define SpecTokenOffsetNumber 0xfffe
/*
* HeapTupleHeader accessor macros
*
@@ -377,6 +395,22 @@ do { \
(tup)->t_choice.t_heap.t_field3.t_xvac = (xid); \
} while (0)
#define HeapTupleHeaderIsSpeculative(tup) \
( \
(tup)->t_ctid.ip_posid == SpecTokenOffsetNumber \
)
#define HeapTupleHeaderGetSpeculativeToken(tup) \
( \
AssertMacro(HeapTupleHeaderIsSpeculative(tup)), \
ItemPointerGetBlockNumber(&(tup)->t_ctid) \
)
#define HeapTupleHeaderSetSpeculativeToken(tup, token) \
( \
ItemPointerSet(&(tup)->t_ctid, token, SpecTokenOffsetNumber) \
)
#define HeapTupleHeaderGetDatumLength(tup) \
VARSIZE(tup)
View
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201505081
#define CATALOG_VERSION_NO 201505082
#endif
View
@@ -81,6 +81,8 @@ extern void index_drop(Oid indexId, bool concurrent);
extern IndexInfo *BuildIndexInfo(Relation index);
extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
extern void FormIndexDatum(IndexInfo *indexInfo,
TupleTableSlot *slot,
EState *estate,
View
@@ -195,6 +195,7 @@ extern void ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate);
extern void ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate);
extern LockTupleMode ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo);
extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti);
extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist);
extern TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate,
@@ -361,16 +362,18 @@ extern void UnregisterExprContextCallback(ExprContext *econtext,
/*
* prototypes from functions in execIndexing.c
*/
extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative);
extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
EState *estate);
extern bool check_exclusion_constraint(Relation heap, Relation index,
EState *estate, bool noDupErr, bool *specConflict,
List *arbiterIndexes);
extern bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate,
ItemPointer conflictTid, List *arbiterIndexes);
extern void check_exclusion_constraint(Relation heap, Relation index,
IndexInfo *indexInfo,
ItemPointer tupleid,
Datum *values, bool *isnull,
EState *estate,
bool newIndex, bool errorOK);
EState *estate, bool newIndex);
#endif /* EXECUTOR_H */
View
@@ -41,6 +41,9 @@
* ExclusionOps Per-column exclusion operators, or NULL if none
* ExclusionProcs Underlying function OIDs for ExclusionOps
* ExclusionStrats Opclass strategy numbers for ExclusionOps
* UniqueOps Theses are like Exclusion*, but for unique indexes
* UniqueProcs
* UniqueStrats
* Unique is it a unique index?
* ReadyForInserts is it valid for inserts?
* Concurrent are we doing a concurrent index build?
@@ -62,6 +65,9 @@ typedef struct IndexInfo
Oid *ii_ExclusionOps; /* array with one entry per column */
Oid *ii_ExclusionProcs; /* array with one entry per column */
uint16 *ii_ExclusionStrats; /* array with one entry per column */
Oid *ii_UniqueOps; /* array with one entry per column */
Oid *ii_UniqueProcs; /* array with one entry per column */
uint16 *ii_UniqueStrats; /* array with one entry per column */
bool ii_Unique;
bool ii_ReadyForInserts;
bool ii_Concurrent;
@@ -308,6 +314,8 @@ typedef struct JunkFilter
* ConstraintExprs array of constraint-checking expr states
* junkFilter for removing junk attributes from tuples
* projectReturning for computing a RETURNING list
* onConflictSetProj for computing ON CONFLICT DO UPDATE SET
* onConflictSetWhere list of ON CONFLICT DO UPDATE exprs (qual)
* ----------------
*/
typedef struct ResultRelInfo
@@ -329,6 +337,8 @@ typedef struct ResultRelInfo
List **ri_ConstraintExprs;
JunkFilter *ri_junkFilter;
ProjectionInfo *ri_projectReturning;
ProjectionInfo *ri_onConflictSetProj;
List *ri_onConflictSetWhere;
} ResultRelInfo;
/* ----------------
@@ -1094,6 +1104,11 @@ typedef struct ModifyTableState
List **mt_arowmarks; /* per-subplan ExecAuxRowMark lists */
EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */
bool fireBSTriggers; /* do we need to fire stmt triggers? */
OnConflictAction mt_onconflict; /* ON CONFLICT type */
List *mt_arbiterindexes; /* unique index OIDs to arbitrate taking alt path */
TupleTableSlot *mt_existing; /* slot to store existing target tuple in */
List *mt_excludedtlist; /* the excluded pseudo relation's tlist */
TupleTableSlot *mt_conflproj; /* FIXME*/
} ModifyTableState;
/* ----------------
View
@@ -168,10 +168,12 @@ typedef enum NodeTag
T_CoerceToDomainValue,
T_SetToDefault,
T_CurrentOfExpr,
T_InferenceElem,
T_TargetEntry,
T_RangeTblRef,
T_JoinExpr,
T_FromExpr,
T_OnConflictExpr,
T_IntoClause,
/*
@@ -413,6 +415,8 @@ typedef enum NodeTag
T_RowMarkClause,
T_XmlSerialize,
T_WithClause,
T_InferClause,
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
@@ -626,4 +630,17 @@ typedef enum JoinType
(1 << JOIN_RIGHT) | \
(1 << JOIN_ANTI))) != 0)
/*
* OnConflictAction -
* "ON CONFLICT" clause type of query
*
* This is needed in both parsenodes.h and plannodes.h, so put it here...
*/
typedef enum OnConflictAction
{
ONCONFLICT_NONE, /* No "ON CONFLICT" clause */
ONCONFLICT_NOTHING, /* ON CONFLICT ... DO NOTHING */
ONCONFLICT_UPDATE /* ON CONFLICT ... DO UPDATE */
} OnConflictAction;
#endif /* NODES_H */
View
@@ -132,6 +132,8 @@ typedef struct Query
List *withCheckOptions; /* a list of WithCheckOption's */
OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */
List *returningList; /* return-values list (of TargetEntry) */
List *groupClause; /* a list of SortGroupClause's */
@@ -591,7 +593,7 @@ typedef enum TableLikeOption
} TableLikeOption;
/*
* IndexElem - index parameters (used in CREATE INDEX)
* IndexElem - index parameters (used in CREATE INDEX, and in ON CONFLICT)
*
* For a plain index attribute, 'name' is the name of the table column to
* index, and 'expr' is NULL. For an index expression, 'name' is NULL and
@@ -735,9 +737,9 @@ typedef struct XmlSerialize
* For SELECT/INSERT/UPDATE permissions, if the user doesn't have
* table-wide permissions then it is sufficient to have the permissions
* on all columns identified in selectedCols (for SELECT) and/or
* insertedCols and/or updatedCols (INSERT with ON CONFLICT UPDATE may
* have all 3). selectedCols, insertedCols and updatedCols are
* bitmapsets, which cannot have negative integer members, so we subtract
* insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
* have all 3). selectedCols, insertedCols and updatedCols are bitmapsets,
* which cannot have negative integer members, so we subtract
* FirstLowInvalidHeapAttributeNumber from column numbers before storing
* them in these fields. A whole-row Var reference is represented by
* setting the bit for InvalidAttrNumber.
@@ -881,7 +883,8 @@ typedef enum WCOKind
{
WCO_VIEW_CHECK, /* WCO on an auto-updatable view */
WCO_RLS_INSERT_CHECK, /* RLS INSERT WITH CHECK policy */
WCO_RLS_UPDATE_CHECK /* RLS UPDATE WITH CHECK policy */
WCO_RLS_UPDATE_CHECK, /* RLS UPDATE WITH CHECK policy */
WCO_RLS_CONFLICT_CHECK /* RLS ON CONFLICT DO UPDATE USING policy */
} WCOKind;
typedef struct WithCheckOption
@@ -1025,6 +1028,37 @@ typedef struct WithClause
int location; /* token location, or -1 if unknown */
} WithClause;
/*
* InferClause -
* ON CONFLICT unique index inference clause
*
* Note: InferClause does not propagate into the Query representation.
*/
typedef struct InferClause
{
NodeTag type;
List *indexElems; /* IndexElems to infer unique index */
Node *whereClause; /* qualification (partial-index predicate) */
char *conname; /* Constraint name, or NULL if unnamed */
int location; /* token location, or -1 if unknown */
} InferClause;
/*
* OnConflictClause -
* representation of ON CONFLICT clause
*
* Note: OnConflictClause does not propagate into the Query representation.
*/
typedef struct OnConflictClause
{
NodeTag type;
OnConflictAction action; /* DO NOTHING or UPDATE? */
InferClause *infer; /* Optional index inference clause */
List *targetList; /* the target list (of ResTarget) */
Node *whereClause; /* qualifications */
int location; /* token location, or -1 if unknown */
} OnConflictClause;
/*
* CommonTableExpr -
* representation of WITH list element
@@ -1075,6 +1109,7 @@ typedef struct InsertStmt
RangeVar *relation; /* relation to insert into */
List *cols; /* optional: names of the target columns */
Node *selectStmt; /* the source SELECT/VALUES, or NULL */
OnConflictClause *onConflictClause; /* ON CONFLICT clause */
List *returningList; /* list of expressions to return */
WithClause *withClause; /* WITH clause */
} InsertStmt;
View
@@ -45,6 +45,8 @@ typedef struct PlannedStmt
bool hasModifyingCTE; /* has insert|update|delete in WITH? */
bool isUpsert; /* is it insert ... ON CONFLICT UPDATE? */
bool canSetTag; /* do I set the command result tag? */
bool transientPlan; /* redo plan when TransactionXmin changes? */
@@ -183,6 +185,12 @@ typedef struct ModifyTable
List *fdwPrivLists; /* per-target-table FDW private data lists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
OnConflictAction onConflictAction; /* ON CONFLICT action */
List *arbiterIndexes; /* List of ON CONFLICT arbiter index OIDs */
List *onConflictSet; /* SET for INSERT ON CONFLICT DO UPDATE */
Node *onConflictWhere;/* WHERE for ON CONFLICT UPDATE */
Index exclRelRTI; /* RTI of the EXCLUDED pseudo relation */
List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */
} ModifyTable;
/* ----------------
View
@@ -1143,6 +1143,22 @@ typedef struct CurrentOfExpr
int cursor_param; /* refcursor parameter number, or 0 */
} CurrentOfExpr;
/*
* InferenceElem - an element of a unique index inference specification
*
* This mostly matches the structure of IndexElems, but having a dedicated
* primnode allows for a clean separation between the use of index parameters
* by utility commands, and this node.
*/
typedef struct InferenceElem
{
Expr xpr;
Node *expr; /* expression to infer from, or NULL */
Oid infercollid; /* OID of collation, or InvalidOid */
Oid inferopfamily; /* OID of att opfamily, or InvalidOid */
Oid inferopcinputtype; /* OID of att input type, or InvalidOid */
} InferenceElem;
/*--------------------
* TargetEntry -
* a target entry (used in query target lists)
@@ -1307,4 +1323,30 @@ typedef struct FromExpr
Node *quals; /* qualifiers on join, if any */
} FromExpr;
/*----------
* OnConflictExpr - represents an ON CONFLICT DO ... expression
*
* The optimizer requires a list of inference elements, and optionally a WHERE
* clause to infer a unique index. The unique index (or, occasionally,
* indexes) inferred are used to arbitrate whether or not the alternative ON
* CONFLICT path is taken.
*----------
*/
typedef struct OnConflictExpr
{
NodeTag type;
OnConflictAction action; /* DO NOTHING or UPDATE? */
/* Arbiter */
List *arbiterElems; /* unique index arbiter list (of InferenceElem's) */
Node *arbiterWhere; /* unique index arbiter WHERE clause */
Oid constraint; /* pg_constraint OID for arbiter */
/* ON CONFLICT UPDATE */
List *onConflictSet; /* List of ON CONFLICT SET TargetEntrys */
Node *onConflictWhere;/* qualifiers to restrict UPDATE to */
int exclRelIndex; /* RT index of 'excluded' relation */
List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */
} OnConflictExpr;
#endif /* PRIMNODES_H */
View
@@ -28,6 +28,8 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
bool inhparent, RelOptInfo *rel);
extern List *infer_arbiter_indexes(PlannerInfo *root);
extern void estimate_rel_size(Relation rel, int32 *attr_widths,
BlockNumber *pages, double *tuples, double *allvisfrac);
View
@@ -86,7 +86,7 @@ extern ModifyTable *make_modifytable(PlannerInfo *root,
Index nominalRelation,
List *resultRelations, List *subplans,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, int epqParam);
List *rowMarks, OnConflictExpr *onconflict, int epqParam);
extern bool is_projection_capable_plan(Plan *plan);
/*
View
@@ -45,6 +45,9 @@ extern void expand_security_quals(PlannerInfo *root, List *tlist);
*/
extern List *preprocess_targetlist(PlannerInfo *root, List *tlist);
extern List *preprocess_onconflict_targetlist(List *tlist,
int result_relation, List *range_table);
extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
/*
View
@@ -87,6 +87,7 @@ PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD)
PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD)
PG_KEYWORD("conflict", CONFLICT, UNRESERVED_KEYWORD)
PG_KEYWORD("connection", CONNECTION, UNRESERVED_KEYWORD)
PG_KEYWORD("constraint", CONSTRAINT, RESERVED_KEYWORD)
PG_KEYWORD("constraints", CONSTRAINTS, UNRESERVED_KEYWORD)
View
@@ -41,6 +41,10 @@ extern List *transformDistinctClause(ParseState *pstate,
List **targetlist, List *sortClause, bool is_agg);
extern List *transformDistinctOnClause(ParseState *pstate, List *distinctlist,
List **targetlist, List *sortClause);
extern void transformOnConflictArbiter(ParseState *pstate,
OnConflictClause *onConflictClause,
List **arbiterExpr, Node **arbiterWhere,
Oid *constraint);
extern List *addTargetToSortList(ParseState *pstate, TargetEntry *tle,
List *sortlist, List *targetlist, SortBy *sortby,
View
@@ -43,6 +43,11 @@ typedef struct ReorderBufferTupleBuf
* and ComboCids in the same list with the user visible INSERT/UPDATE/DELETE
* changes. Users of the decoding facilities will never see changes with
* *_INTERNAL_* actions.
*
* The INTERNAL_SPEC_INSERT and INTERNAL_SPEC_CONFIRM changes concern
* "speculative insertions", and their confirmation respectively. They're
* used by INSERT .. ON CONFLICT .. UPDATE. Users of logical decoding don't
* have to care about these.
*/
enum ReorderBufferChangeType
{
@@ -51,7 +56,9 @@ enum ReorderBufferChangeType
REORDER_BUFFER_CHANGE_DELETE,
REORDER_BUFFER_CHANGE_INTERNAL_SNAPSHOT,
REORDER_BUFFER_CHANGE_INTERNAL_COMMAND_ID,
REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID
REORDER_BUFFER_CHANGE_INTERNAL_TUPLECID,
REORDER_BUFFER_CHANGE_INTERNAL_SPEC_INSERT,
REORDER_BUFFER_CHANGE_INTERNAL_SPEC_CONFIRM
};
/*
View
@@ -41,7 +41,8 @@ extern PGDLLIMPORT row_security_policy_hook_type row_security_policy_hook_permis
extern PGDLLIMPORT row_security_policy_hook_type row_security_policy_hook_restrictive;
extern void get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
extern void get_row_security_policies(Query *root, CmdType commandType,
RangeTblEntry *rte, int rt_index,
List **securityQuals, List **withCheckOptions,
bool *hasRowSecurity, bool *hasSubLinks);
View
@@ -76,6 +76,11 @@ extern bool ConditionalXactLockTableWait(TransactionId xid);
extern void WaitForLockers(LOCKTAG heaplocktag, LOCKMODE lockmode);
extern void WaitForLockersMultiple(List *locktags, LOCKMODE lockmode);
/* Lock an XID for tuple insertion (used to wait for an insertion to finish) */
extern uint32 SpeculativeInsertionLockAcquire(TransactionId xid);
extern void SpeculativeInsertionLockRelease(TransactionId xid);
extern void SpeculativeInsertionWait(TransactionId xid, uint32 token);
/* Lock a general object (other than a relation) of the current database */
extern void LockDatabaseObject(Oid classid, Oid objid, uint16 objsubid,
LOCKMODE lockmode);
View
@@ -176,6 +176,8 @@ typedef enum LockTagType
/* ID info for a transaction is its TransactionId */
LOCKTAG_VIRTUALTRANSACTION, /* virtual transaction (ditto) */
/* ID info for a virtual transaction is its VirtualTransactionId */
LOCKTAG_SPECULATIVE_TOKEN, /* speculative insertion Xid and token */
/* ID info for a transaction is its TransactionId */
LOCKTAG_OBJECT, /* non-relation database object */
/* ID info for an object is DB OID + CLASS OID + OBJECT OID + SUBID */
@@ -261,6 +263,14 @@ typedef struct LOCKTAG
(locktag).locktag_type = LOCKTAG_VIRTUALTRANSACTION, \
(locktag).locktag_lockmethodid = DEFAULT_LOCKMETHOD)
#define SET_LOCKTAG_SPECULATIVE_INSERTION(locktag,xid,token) \
((locktag).locktag_field1 = (xid), \
(locktag).locktag_field2 = (token), \
(locktag).locktag_field3 = 0, \
(locktag).locktag_field4 = 0, \
(locktag).locktag_type = LOCKTAG_SPECULATIVE_TOKEN, \
(locktag).locktag_lockmethodid = DEFAULT_LOCKMETHOD)
#define SET_LOCKTAG_OBJECT(locktag,dboid,classoid,objoid,objsubid) \
((locktag).locktag_field1 = (dboid), \
(locktag).locktag_field2 = (classoid), \
View
@@ -69,31 +69,41 @@ typedef struct SnapshotData
* progress, unless the snapshot was taken during recovery in which case
* it's empty. For historic MVCC snapshots, the meaning is inverted, i.e.
* it contains *committed* transactions between xmin and xmax.
*
* note: all ids in xip[] satisfy xmin <= xip[i] < xmax
*/
TransactionId *xip;
uint32 xcnt; /* # of xact ids in xip[] */
/* note: all ids in xip[] satisfy xmin <= xip[i] < xmax */
int32 subxcnt; /* # of xact ids in subxip[] */
/*
* For non-historic MVCC snapshots, this contains subxact IDs that are in
* progress (and other transactions that are in progress if taken during
* recovery). For historic snapshot it contains *all* xids assigned to the
* replayed transaction, including the toplevel xid.
*
* note: all ids in subxip[] are >= xmin, but we don't bother filtering
* out any that are >= xmax
*/
TransactionId *subxip;
int32 subxcnt; /* # of xact ids in subxip[] */
bool suboverflowed; /* has the subxip array overflowed? */
bool takenDuringRecovery; /* recovery-shaped snapshot? */
bool copied; /* false if it's a static snapshot */
CommandId curcid; /* in my xact, CID < curcid are visible */
/*
* note: all ids in subxip[] are >= xmin, but we don't bother filtering
* out any that are >= xmax
* An extra return value for HeapTupleSatisfiesDirty, not used in MVCC
* snapshots.
*/
uint32 speculativeToken;
/*
* Book-keeping information, used by the snapshot manager
*/
CommandId curcid; /* in my xact, CID < curcid are visible */
uint32 active_count; /* refcount on ActiveSnapshot stack */
uint32 regd_count; /* refcount on RegisteredSnapshots */
pairingheap_node ph_node; /* link in the RegisteredSnapshots heap */
} SnapshotData;
View
@@ -0,0 +1,23 @@
Parsed test spec with 2 sessions
starting permutation: donothing1 donothing2 c1 select2 c2
step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2') ON CONFLICT DO NOTHING; <waiting ...>
step c1: COMMIT;
step donothing2: <... completed>
step select2: SELECT * FROM ints;
key val
1 donothing1
step c2: COMMIT;
starting permutation: donothing1 donothing2 a1 select2 c2
step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2') ON CONFLICT DO NOTHING; <waiting ...>
step a1: ABORT;
step donothing2: <... completed>
step select2: SELECT * FROM ints;
key val
1 donothing2
step c2: COMMIT;
View
@@ -0,0 +1,23 @@
Parsed test spec with 2 sessions
starting permutation: insert1 insert2 c1 select2 c2
step insert1: INSERT INTO upsert(key, payload) VALUES('FooFoo', 'insert1') ON CONFLICT (lower(key)) DO UPDATE set key = EXCLUDED.key, payload = upsert.payload || ' updated by insert1';
step insert2: INSERT INTO upsert(key, payload) VALUES('FOOFOO', 'insert2') ON CONFLICT (lower(key)) DO UPDATE set key = EXCLUDED.key, payload = upsert.payload || ' updated by insert2'; <waiting ...>
step c1: COMMIT;
step insert2: <... completed>
step select2: SELECT * FROM upsert;
key payload
FOOFOO insert1 updated by insert2
step c2: COMMIT;
starting permutation: insert1 insert2 a1 select2 c2
step insert1: INSERT INTO upsert(key, payload) VALUES('FooFoo', 'insert1') ON CONFLICT (lower(key)) DO UPDATE set key = EXCLUDED.key, payload = upsert.payload || ' updated by insert1';
step insert2: INSERT INTO upsert(key, payload) VALUES('FOOFOO', 'insert2') ON CONFLICT (lower(key)) DO UPDATE set key = EXCLUDED.key, payload = upsert.payload || ' updated by insert2'; <waiting ...>
step a1: ABORT;
step insert2: <... completed>
step select2: SELECT * FROM upsert;
key payload
FOOFOO insert2
step c2: COMMIT;
View
@@ -0,0 +1,26 @@
Parsed test spec with 2 sessions
starting permutation: update2 insert1 c2 select1surprise c1
step update2: UPDATE colors SET is_active = true WHERE key = 1;
step insert1:
WITH t AS (
INSERT INTO colors(key, color, is_active)
VALUES(1, 'Brown', true), (2, 'Gray', true)
ON CONFLICT (key) DO UPDATE
SET color = EXCLUDED.color
WHERE colors.is_active)
SELECT * FROM colors ORDER BY key; <waiting ...>
step c2: COMMIT;
step insert1: <... completed>
key color is_active
1 Red f
2 Green f
3 Blue f
step select1surprise: SELECT * FROM colors ORDER BY key;
key color is_active
1 Brown t
2 Green f
3 Blue f
step c1: COMMIT;
View
@@ -0,0 +1,23 @@
Parsed test spec with 2 sessions
starting permutation: insert1 insert2 c1 select2 c2
step insert1: INSERT INTO upsert(key, val) VALUES(1, 'insert1') ON CONFLICT (key) DO UPDATE set val = upsert.val || ' updated by insert1';
step insert2: INSERT INTO upsert(key, val) VALUES(1, 'insert2') ON CONFLICT (key) DO UPDATE set val = upsert.val || ' updated by insert2'; <waiting ...>
step c1: COMMIT;
step insert2: <... completed>
step select2: SELECT * FROM upsert;
key val
1 insert1 updated by insert2
step c2: COMMIT;
starting permutation: insert1 insert2 a1 select2 c2
step insert1: INSERT INTO upsert(key, val) VALUES(1, 'insert1') ON CONFLICT (key) DO UPDATE set val = upsert.val || ' updated by insert1';
step insert2: INSERT INTO upsert(key, val) VALUES(1, 'insert2') ON CONFLICT (key) DO UPDATE set val = upsert.val || ' updated by insert2'; <waiting ...>
step a1: ABORT;
step insert2: <... completed>
step select2: SELECT * FROM upsert;
key val
1 insert2
step c2: COMMIT;
View
@@ -16,6 +16,10 @@ test: fk-deadlock2
test: eval-plan-qual
test: lock-update-delete
test: lock-update-traversal
test: insert-conflict-do-nothing
test: insert-conflict-do-update
test: insert-conflict-do-update-2
test: insert-conflict-do-update-3
test: delete-abort-savept
test: delete-abort-savept-2
test: aborted-keyrevoke
View
@@ -0,0 +1,41 @@
# INSERT...ON CONFLICT DO NOTHING test
#
# This test tries to expose problems with the interaction between concurrent
# sessions during INSERT...ON CONFLICT DO NOTHING.
#
# The convention here is that session 1 always ends up inserting, and session 2
# always ends up doing nothing.
setup
{
CREATE TABLE ints (key int primary key, val text);
}
teardown
{
DROP TABLE ints;
}
session "s1"
setup
{
BEGIN ISOLATION LEVEL READ COMMITTED;
}
step "donothing1" { INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING; }
step "c1" { COMMIT; }
step "a1" { ABORT; }
session "s2"
setup
{
BEGIN ISOLATION LEVEL READ COMMITTED;
}
step "donothing2" { INSERT INTO ints(key, val) VALUES(1, 'donothing2') ON CONFLICT DO NOTHING; }
step "select2" { SELECT * FROM ints; }
step "c2" { COMMIT; }
step "a2" { ABORT; }
# Regular case where one session block-waits on another to determine if it
# should proceed with an insert or do nothing.
permutation "donothing1" "donothing2" "c1" "select2" "c2"
permutation "donothing1" "donothing2" "a1" "select2" "c2"
View
@@ -0,0 +1,41 @@
# INSERT...ON CONFLICT DO UPDATE test
#
# This test shows a plausible scenario in which the user might wish to UPDATE a
# value that is also constrained by the unique index that is the arbiter of
# whether the alternative path should be taken.
setup
{
CREATE TABLE upsert (key text not null, payload text);
CREATE UNIQUE INDEX ON upsert(lower(key));
}
teardown
{
DROP TABLE upsert;
}
session "s1"
setup
{
BEGIN ISOLATION LEVEL READ COMMITTED;
}
step "insert1" { INSERT INTO upsert(key, payload) VALUES('FooFoo', 'insert1') ON CONFLICT (lower(key)) DO UPDATE set key = EXCLUDED.key, payload = upsert.payload || ' updated by insert1'; }
step "c1" { COMMIT; }
step "a1" { ABORT; }
session "s2"
setup
{
BEGIN ISOLATION LEVEL READ COMMITTED;
}
step "insert2" { INSERT INTO upsert(key, payload) VALUES('FOOFOO', 'insert2') ON CONFLICT (lower(key)) DO UPDATE set key = EXCLUDED.key, payload = upsert.payload || ' updated by insert2'; }
step "select2" { SELECT * FROM upsert; }
step "c2" { COMMIT; }
step "a2" { ABORT; }
# One session (session 2) block-waits on another (session 1) to determine if it
# should proceed with an insert or update. The user can still usefully UPDATE
# a column constrained by a unique index, as the example illustrates.
permutation "insert1" "insert2" "c1" "select2" "c2"
permutation "insert1" "insert2" "a1" "select2" "c2"
View
@@ -0,0 +1,69 @@
# INSERT...ON CONFLICT DO UPDATE test
#
# Other INSERT...ON CONFLICT DO UPDATE isolation tests illustrate the "MVCC
# violation" added to facilitate the feature, whereby a
# not-visible-to-our-snapshot tuple can be updated by our command all the same.
# This is generally needed to provide a guarantee of a successful INSERT or
# UPDATE in READ COMMITTED mode. This MVCC violation is quite distinct from
# the putative "MVCC violation" that has existed in PostgreSQL for many years,
# the EvalPlanQual() mechanism, because that mechanism always starts from a
# tuple that is visible to the command's MVCC snapshot. This test illustrates
# a slightly distinct user-visible consequence of the same MVCC violation
# generally associated with INSERT...ON CONFLICT DO UPDATE. The impact of the
# MVCC violation goes a little beyond updating MVCC-invisible tuples.
#
# With INSERT...ON CONFLICT DO UPDATE, the UPDATE predicate is only evaluated
# once, on this conclusively-locked tuple, and not any other version of the
# same tuple. It is therefore possible (in READ COMMITTED mode) that the
# predicate "fail to be satisfied" according to the command's MVCC snapshot.
# It might simply be that there is no row version visible, but it's also
# possible that there is some row version visible, but only as a version that
# doesn't satisfy the predicate. If, however, the conclusively-locked version
# satisfies the predicate, that's good enough, and the tuple is updated. The
# MVCC-snapshot-visible row version is denied the opportunity to prevent the
# UPDATE from taking place, because we don't walk the UPDATE chain in the usual
# way.
setup
{
CREATE TABLE colors (key int4 PRIMARY KEY, color text, is_active boolean);
INSERT INTO colors (key, color, is_active) VALUES(1, 'Red', false);
INSERT INTO colors (key, color, is_active) VALUES(2, 'Green', false);
INSERT INTO colors (key, color, is_active) VALUES(3, 'Blue', false);
}
teardown
{
DROP TABLE colors;
}
session "s1"
setup
{
BEGIN ISOLATION LEVEL READ COMMITTED;
}
step "insert1" {
WITH t AS (
INSERT INTO colors(key, color, is_active)
VALUES(1, 'Brown', true), (2, 'Gray', true)
ON CONFLICT (key) DO UPDATE
SET color = EXCLUDED.color
WHERE colors.is_active)
SELECT * FROM colors ORDER BY key;}
step "select1surprise" { SELECT * FROM colors ORDER BY key; }
step "c1" { COMMIT; }
session "s2"
setup
{
BEGIN ISOLATION LEVEL READ COMMITTED;
}
step "update2" { UPDATE colors SET is_active = true WHERE key = 1; }
step "c2" { COMMIT; }
# Perhaps surprisingly, the session 1 MVCC-snapshot-visible tuple (the tuple
# with the pre-populated color 'Red') is denied the opportunity to prevent the
# UPDATE from taking place -- only the conclusively-locked tuple version
# matters, and so the tuple with key value 1 was updated to 'Brown' (but not
# tuple with key value 2, since nothing changed there):
permutation "update2" "insert1" "c2" "select1surprise" "c1"
View
@@ -0,0 +1,40 @@
# INSERT...ON CONFLICT DO UPDATE test
#
# This test tries to expose problems with the interaction between concurrent
# sessions.
setup
{
CREATE TABLE upsert (key int primary key, val text);
}
teardown
{
DROP TABLE upsert;
}
session "s1"
setup
{
BEGIN ISOLATION LEVEL READ COMMITTED;
}
step "insert1" { INSERT INTO upsert(key, val) VALUES(1, 'insert1') ON CONFLICT (key) DO UPDATE set val = upsert.val || ' updated by insert1'; }
step "c1" { COMMIT; }
step "a1" { ABORT; }
session "s2"
setup
{
BEGIN ISOLATION LEVEL READ COMMITTED;
}
step "insert2" { INSERT INTO upsert(key, val) VALUES(1, 'insert2') ON CONFLICT (key) DO UPDATE set val = upsert.val || ' updated by insert2'; }
step "select2" { SELECT * FROM upsert; }
step "c2" { COMMIT; }
step "a2" { ABORT; }
# One session (session 2) block-waits on another (session 1) to determine if it
# should proceed with an insert or update. Notably, this entails updating a
# tuple while there is no version of that tuple visible to the updating
# session's snapshot. This is permitted only in READ COMMITTED mode.
permutation "insert1" "insert2" "c1" "select2" "c2"
permutation "insert1" "insert2" "a1" "select2" "c2"
View
@@ -32,7 +32,9 @@ LINE 1: select nonesuch from pg_database;
^
-- empty distinct list isn't OK
select distinct from pg_database;
ERROR: SELECT DISTINCT must have at least one column
ERROR: syntax error at or near "from"
LINE 1: select distinct from pg_database;
^
-- bad attribute name on lhs of operator
select * from pg_database where nonesuch = pg_database.datname;
ERROR: column "nonesuch" does not exist
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -269,7 +269,7 @@ SELECT * FROM atestv2; -- fail (even though regressuser2 can access underlying a
ERROR: permission denied for relation atest2
-- Test column level permissions
SET SESSION AUTHORIZATION regressuser1;
CREATE TABLE atest5 (one int, two int, three int);
CREATE TABLE atest5 (one int, two int unique, three int, four int unique);
CREATE TABLE atest6 (one int, two int, blue int);
GRANT SELECT (one), INSERT (two), UPDATE (three) ON atest5 TO regressuser4;
GRANT ALL (one) ON atest5 TO regressuser3;
@@ -367,6 +367,33 @@ UPDATE atest5 SET one = 8; -- fail
ERROR: permission denied for relation atest5
UPDATE atest5 SET three = 5, one = 2; -- fail
ERROR: permission denied for relation atest5
-- Check that column level privs are enforced in RETURNING
-- Ok.
INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = 10;
-- Error. No SELECT on column three.
INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = 10 RETURNING atest5.three;
ERROR: permission denied for relation atest5
-- Ok. May SELECT on column "one":
INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = 10 RETURNING atest5.one;
one
-----
(1 row)
-- Check that column level privileges are enforced for EXCLUDED
-- Ok. we may select one
INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = EXCLUDED.one;
-- Error. No select rights on three
INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = EXCLUDED.three;
ERROR: permission denied for relation atest5
INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set one = 8; -- fails (due to UPDATE)
ERROR: permission denied for relation atest5
INSERT INTO atest5(three) VALUES (4) ON CONFLICT (two) DO UPDATE set three = 10; -- fails (due to INSERT)
ERROR: permission denied for relation atest5
-- Check that the the columns in the inference require select privileges
-- Error. No privs on four
INSERT INTO atest5(three) VALUES (4) ON CONFLICT (four) DO UPDATE set three = 10;
ERROR: permission denied for relation atest5
SET SESSION AUTHORIZATION regressuser1;
REVOKE ALL (one) ON atest5 FROM regressuser4;
GRANT SELECT (one,two,blue) ON atest6 TO regressuser4;
View
@@ -331,3 +331,27 @@ SELECT * FROM voo;
17 | zoo2
(2 rows)
-- Check aliased target relation
INSERT INTO foo AS bar DEFAULT VALUES RETURNING *; -- ok
f1 | f2 | f3 | f4
----+----+----+----
4 | | 42 | 99
(1 row)
INSERT INTO foo AS bar DEFAULT VALUES RETURNING foo.*; -- fails, wrong name
ERROR: invalid reference to FROM-clause entry for table "foo"
LINE 1: INSERT INTO foo AS bar DEFAULT VALUES RETURNING foo.*;
^
HINT: Perhaps you meant to reference the table alias "bar".
INSERT INTO foo AS bar DEFAULT VALUES RETURNING bar.*; -- ok
f1 | f2 | f3 | f4
----+----+----+----
5 | | 42 | 99
(1 row)
INSERT INTO foo AS bar DEFAULT VALUES RETURNING bar.f3; -- ok
f3
----
42
(1 row)
View
@@ -1490,6 +1490,138 @@ SELECT * FROM b1;
4 | yyy
(21 rows)
--
-- INSERT ... ON CONFLICT DO UPDATE and Row-level security
--
SET SESSION AUTHORIZATION rls_regress_user0;
DROP POLICY p1 ON document;
CREATE POLICY p1 ON document FOR SELECT USING (true);
CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);
CREATE POLICY p3 ON document FOR UPDATE
USING (cid = (SELECT cid from category WHERE cname = 'novel'))
WITH CHECK (dauthor = current_user);
SET SESSION AUTHORIZATION rls_regress_user1;
-- Exists...
SELECT * FROM document WHERE did = 2;
did | cid | dlevel | dauthor | dtitle
-----+-----+--------+-------------------+-----------------
2 | 11 | 2 | rls_regress_user1 | my second novel
(1 row)
-- ...so violates actual WITH CHECK OPTION within UPDATE (not INSERT, since
-- alternative UPDATE path happens to be taken):
INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'rls_regress_user2', 'my first novel')
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, dauthor = EXCLUDED.dauthor;
ERROR: new row violates row level security policy for "document"
-- Violates USING qual for UPDATE policy p3.
--
-- UPDATE path is taken, but UPDATE fails purely because *existing* row to be
-- updated is not a "novel"/cid 11 (row is not leaked, even though we have
-- SELECT privileges sufficient to see the row in this instance):
INSERT INTO document VALUES (33, 22, 1, 'rls_regress_user1', 'okay science fiction'); -- preparation for next statement
INSERT INTO document VALUES (33, (SELECT cid from category WHERE cname = 'novel'), 1, 'rls_regress_user1', 'Some novel, replaces sci-fi') -- takes UPDATE path
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle;
ERROR: new row violates row level security policy (USING expression) for "document"
-- Fine (we UPDATE, since INSERT WCOs and UPDATE security barrier quals + WCOs
-- not violated):
INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'rls_regress_user1', 'my first novel')
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;
did | cid | dlevel | dauthor | dtitle
-----+-----+--------+-------------------+----------------
2 | 11 | 2 | rls_regress_user1 | my first novel
(1 row)
-- Fine (we INSERT, so "cid = 33" ("technology") isn't evaluated):
INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'rls_regress_user1', 'some technology novel')
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;
did | cid | dlevel | dauthor | dtitle
-----+-----+--------+-------------------+-----------------------
78 | 11 | 1 | rls_regress_user1 | some technology novel
(1 row)
-- Fine (same query, but we UPDATE, so "cid = 33", ("technology") is not the
-- case in respect of *existing* tuple):
INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'rls_regress_user1', 'some technology novel')
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;
did | cid | dlevel | dauthor | dtitle
-----+-----+--------+-------------------+-----------------------
78 | 33 | 1 | rls_regress_user1 | some technology novel
(1 row)
-- Same query a third time, but now fails due to existing tuple finally not
-- passing quals:
INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'rls_regress_user1', 'some technology novel')
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;
ERROR: new row violates row level security policy (USING expression) for "document"
-- Don't fail just because INSERT doesn't satisfy WITH CHECK option that
-- originated as a barrier/USING() qual from the UPDATE. Note that the UPDATE
-- path *isn't* taken, and so UPDATE-related policy does not apply:
INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'rls_regress_user1', 'technology book, can only insert')
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;
did | cid | dlevel | dauthor | dtitle
-----+-----+--------+-------------------+----------------------------------
79 | 33 | 1 | rls_regress_user1 | technology book, can only insert
(1 row)
-- But this time, the same statement fails, because the UPDATE path is taken,
-- and updating the row just inserted falls afoul of security barrier qual
-- (enforced as WCO) -- what we might have updated target tuple to is
-- irrelevant, in fact.
INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'rls_regress_user1', 'technology book, can only insert')
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;
ERROR: new row violates row level security policy (USING expression) for "document"
-- Test default USING qual enforced as WCO
SET SESSION AUTHORIZATION rls_regress_user0;
DROP POLICY p1 ON document;
DROP POLICY p2 ON document;
DROP POLICY p3 ON document;
CREATE POLICY p3_with_default ON document FOR UPDATE
USING (cid = (SELECT cid from category WHERE cname = 'novel'));
SET SESSION AUTHORIZATION rls_regress_user1;
-- Just because WCO-style enforcement of USING quals occurs with
-- existing/target tuple does not mean that the implementation can be allowed
-- to fail to also enforce this qual against the final tuple appended to
-- relation (since in the absence of an explicit WCO, this is also interpreted
-- as an UPDATE/ALL WCO in general).
--
-- UPDATE path is taken here (fails due to existing tuple). Note that this is
-- not reported as a "USING expression", because it's an RLS UPDATE check that originated as
-- a USING qual for the purposes of RLS in general, as opposed to an explicit
-- USING qual that is ordinarily a security barrier. We leave it up to the
-- UPDATE to make this fail:
INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'rls_regress_user1', 'technology book, can only insert')
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;
ERROR: new row violates row level security policy for "document"
-- UPDATE path is taken here. Existing tuple passes, since it's cid
-- corresponds to "novel", but default USING qual is enforced against
-- post-UPDATE tuple too (as always when updating with a policy that lacks an
-- explicit WCO), and so this fails:
INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'technology'), 1, 'rls_regress_user1', 'my first novel')
ON CONFLICT (did) DO UPDATE SET cid = EXCLUDED.cid, dtitle = EXCLUDED.dtitle RETURNING *;
ERROR: new row violates row level security policy for "document"
SET SESSION AUTHORIZATION rls_regress_user0;
DROP POLICY p3_with_default ON document;
--
-- Test ALL policies with ON CONFLICT DO UPDATE (much the same as existing UPDATE
-- tests)
--
CREATE POLICY p3_with_all ON document FOR ALL
USING (cid = (SELECT cid from category WHERE cname = 'novel'))
WITH CHECK (dauthor = current_user);
SET SESSION AUTHORIZATION rls_regress_user1;
-- Fails, since ALL WCO is enforced in insert path:
INSERT INTO document VALUES (80, (SELECT cid from category WHERE cname = 'novel'), 1, 'rls_regress_user2', 'my first novel')
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33;
ERROR: new row violates row level security policy for "document"
-- Fails, since ALL policy USING qual is enforced (existing, target tuple is in
-- violation, since it has the "manga" cid):
INSERT INTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel'), 1, 'rls_regress_user1', 'my first novel')
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle;
ERROR: new row violates row level security policy (USING expression) for "document"
-- Fails, since ALL WCO are enforced:
INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'rls_regress_user1', 'my first novel')
ON CONFLICT (did) DO UPDATE SET dauthor = 'rls_regress_user2';
ERROR: new row violates row level security policy for "document"
--
-- ROLE/GROUP
--
View
@@ -1123,6 +1123,10 @@ SELECT * FROM shoelace_log ORDER BY sl_name;
SELECT * FROM shoelace_obsolete WHERE sl_avail = 0;
insert into shoelace values ('sl9', 0, 'pink', 35.0, 'inch', 0.0);
insert into shoelace values ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0);
-- Unsupported (even though a similar updatable view construct is)
insert into shoelace values ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0)
on conflict do nothing;
ERROR: INSERT with ON CONFLICT clause cannot be used with table that has INSERT or UPDATE rules
SELECT * FROM shoelace_obsolete ORDER BY sl_len_cm;
sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm
------------+----------+------------+--------+----------+-----------
@@ -2362,6 +2366,22 @@ DETAIL: Key (id3a, id3c)=(1, 13) is not present in table "rule_and_refint_t2".
insert into rule_and_refint_t3 values (1, 13, 11, 'row6');
ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey"
DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "rule_and_refint_t1".
-- Ordinary table
insert into rule_and_refint_t3 values (1, 13, 11, 'row6')
on conflict do nothing;
ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey"
DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "rule_and_refint_t1".
-- rule not fired, so fk violation
insert into rule_and_refint_t3 values (1, 13, 11, 'row6')
on conflict (id3a, id3b, id3c) do update
set id3b = excluded.id3b;
ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey"
DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "rule_and_refint_t1".
-- rule fired, so unsupported
insert into shoelace values ('sl9', 0, 'pink', 35.0, 'inch', 0.0)
on conflict (sl_name) do update
set sl_avail = excluded.sl_avail;
ERROR: INSERT with ON CONFLICT clause cannot be used with table that has INSERT or UPDATE rules
create rule rule_and_refint_t3_ins as on insert to rule_and_refint_t3
where (exists (select 1 from rule_and_refint_t3
where (((rule_and_refint_t3.id3a = new.id3a)
@@ -2743,3 +2763,73 @@ View definition:
FROM ( VALUES (1,2)) v(q, w);
drop view rule_v1;
--
-- Check DO INSTEAD rules with ON CONFLICT
--
CREATE TABLE hats (
hat_name char(10) primary key,
hat_color char(10) -- hat color
);
CREATE TABLE hat_data (
hat_name char(10) primary key,
hat_color char(10) -- hat color
);
-- okay
CREATE RULE hat_nosert AS ON INSERT TO hats
DO INSTEAD
INSERT INTO hat_data VALUES (
NEW.hat_name,
NEW.hat_color)
ON CONFLICT (hat_name) DO NOTHING RETURNING *;
-- Works (projects row)
INSERT INTO hats VALUES ('h7', 'black') RETURNING *;
hat_name | hat_color
------------+------------
h7 | black
(1 row)
-- Works (does nothing)
INSERT INTO hats VALUES ('h7', 'black') RETURNING *;
hat_name | hat_color
----------+-----------
(0 rows)
SELECT tablename, rulename, definition FROM pg_rules
WHERE tablename = 'hats';
tablename | rulename | definition
-----------+------------+------------------------------------------------------------------------------
hats | hat_nosert | CREATE RULE hat_nosert AS +
| | ON INSERT TO hats DO INSTEAD INSERT INTO hat_data (hat_name, hat_color)+
| | VALUES (new.hat_name, new.hat_color) ON CONFLICT DO NOTHING +
| | RETURNING hat_data.hat_name, +
| | hat_data.hat_color;
(1 row)
DROP RULE hat_nosert ON hats;
CREATE RULE hat_upsert AS ON INSERT TO hats
DO INSTEAD
INSERT INTO hat_data VALUES (
NEW.hat_name,
NEW.hat_color)
ON CONFLICT (hat_name) DO UPDATE SET hat_color = 'Orange' RETURNING *;
-- Works (does upsert)
INSERT INTO hats VALUES ('h7', 'black') RETURNING *;
hat_name | hat_color
------------+------------
h7 | Orange
(1 row)
SELECT tablename, rulename, definition FROM pg_rules
WHERE tablename = 'hats';
tablename | rulename | definition
-----------+------------+-----------------------------------------------------------------------------------------------
hats | hat_upsert | CREATE RULE hat_upsert AS +
| | ON INSERT TO hats DO INSTEAD INSERT INTO hat_data (hat_name, hat_color) +
| | VALUES (new.hat_name, new.hat_color) ON CONFLICT DO UPDATE SET hat_color = 'Orange'::bpchar+
| | RETURNING hat_data.hat_name, +
| | hat_data.hat_color;
(1 row)
DROP RULE hat_upsert ON hats;
drop table hats;
drop table hat_data;
View
@@ -638,6 +638,28 @@ from
-----
(0 rows)
--
-- Test case for subselect within UPDATE of INSERT...ON CONFLICT DO UPDATE
--
create temp table upsert(key int4 primary key, val text);
insert into upsert values(1, 'val') on conflict (key) do update set val = 'not seen';
insert into upsert values(1, 'val') on conflict (key) do update set val = 'seen with subselect ' || (select f1 from int4_tbl where f1 != 0 limit 1)::text;
select * from upsert;
key | val
-----+----------------------------
1 | seen with subselect 123456
(1 row)
with aa as (select 'int4_tbl' u from int4_tbl limit 1)
insert into upsert values (1, 'x'), (999, 'y')
on conflict (key) do update set val = (select u from aa)
returning *;
key | val
-----+----------
1 | int4_tbl
999 | y
(2 rows)
--
-- Test case for cross-type partial matching in hashed subplan (bug #7597)
--
View
@@ -274,7 +274,7 @@ drop sequence ttdummy_seq;
-- tests for per-statement triggers
--
CREATE TABLE log_table (tstamp timestamp default timeofday()::timestamp);
CREATE TABLE main_table (a int, b int);
CREATE TABLE main_table (a int unique, b int);
COPY main_table (a,b) FROM stdin;
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS '
BEGIN
@@ -291,6 +291,14 @@ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt');
--
CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_table
EXECUTE PROCEDURE trigger_func('after_upd_stmt');
-- Both insert and update statement level triggers (before and after) should
-- fire. Doesn't fire UPDATE before trigger, but only because one isn't
-- defined.
INSERT INTO main_table (a, b) VALUES (5, 10) ON CONFLICT (a)
DO UPDATE SET b = EXCLUDED.b;
NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
NOTICE: trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
CREATE TRIGGER after_upd_row_trig AFTER UPDATE ON main_table
FOR EACH ROW EXECUTE PROCEDURE trigger_func('after_upd_row');
INSERT INTO main_table DEFAULT VALUES;
@@ -305,6 +313,8 @@ NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, lev
-- UPDATE that effects zero rows should still call per-statement trigger
UPDATE main_table SET a = a + 2 WHERE b > 100;
NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
-- constraint now unneeded
ALTER TABLE main_table DROP CONSTRAINT main_table_a_key;
-- COPY should fire per-row and per-statement INSERT triggers
COPY main_table (a, b) FROM stdin;
NOTICE: trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
@@ -1731,3 +1741,93 @@ select * from self_ref_trigger;
drop table self_ref_trigger;
drop function self_ref_trigger_ins_func();
drop function self_ref_trigger_del_func();
--
-- Verify behavior of before and after triggers with INSERT...ON CONFLICT
-- DO UPDATE
--
create table upsert (key int4 primary key, color text);
create function upsert_before_func()
returns trigger language plpgsql as
$$
begin
if (TG_OP = 'UPDATE') then
raise warning 'before update (old): %', old.*::text;
raise warning 'before update (new): %', new.*::text;
elsif (TG_OP = 'INSERT') then
raise warning 'before insert (new): %', new.*::text;
if new.key % 2 = 0 then
new.key := new.key + 1;
new.color := new.color || ' trig modified';
raise warning 'before insert (new, modified): %', new.*::text;
end if;
end if;
return new;
end;
$$;
create trigger upsert_before_trig before insert or update on upsert
for each row execute procedure upsert_before_func();
create function upsert_after_func()
returns trigger language plpgsql as
$$
begin
if (TG_OP = 'UPDATE') then
raise warning 'after update (old): %', new.*::text;
raise warning 'after update (new): %', new.*::text;
elsif (TG_OP = 'INSERT') then
raise warning 'after insert (new): %', new.*::text;
end if;
return null;
end;
$$;
create trigger upsert_after_trig after insert or update on upsert
for each row execute procedure upsert_after_func();
insert into upsert values(1, 'black') on conflict (key) do update set color = 'updated ' || upsert.color;
WARNING: before insert (new): (1,black)
WARNING: after insert (new): (1,black)
insert into upsert values(2, 'red') on conflict (key) do update set color = 'updated ' || upsert.color;
WARNING: before insert (new): (2,red)
WARNING: before insert (new, modified): (3,"red trig modified")
WARNING: after insert (new): (3,"red trig modified")
insert into upsert values(3, 'orange') on conflict (key) do update set color = 'updated ' || upsert.color;
WARNING: before insert (new): (3,orange)
WARNING: before update (old): (3,"red trig modified")
WARNING: before update (new): (3,"updated red trig modified")
WARNING: after update (old): (3,"updated red trig modified")
WARNING: after update (new): (3,"updated red trig modified")
insert into upsert values(4, 'green') on conflict (key) do update set color = 'updated ' || upsert.color;
WARNING: before insert (new): (4,green)
WARNING: before insert (new, modified): (5,"green trig modified")
WARNING: after insert (new): (5,"green trig modified")
insert into upsert values(5, 'purple') on conflict (key) do update set color = 'updated ' || upsert.color;
WARNING: before insert (new): (5,purple)
WARNING: before update (old): (5,"green trig modified")
WARNING: before update (new): (5,"updated green trig modified")
WARNING: after update (old): (5,"updated green trig modified")
WARNING: after update (new): (5,"updated green trig modified")
insert into upsert values(6, 'white') on conflict (key) do update set color = 'updated ' || upsert.color;
WARNING: before insert (new): (6,white)
WARNING: before insert (new, modified): (7,"white trig modified")
WARNING: after insert (new): (7,"white trig modified")
insert into upsert values(7, 'pink') on conflict (key) do update set color = 'updated ' || upsert.color;
WARNING: before insert (new): (7,pink)
WARNING: before update (old): (7,"white trig modified")
WARNING: before update (new): (7,"updated white trig modified")
WARNING: after update (old): (7,"updated white trig modified")
WARNING: after update (new): (7,"updated white trig modified")
insert into upsert values(8, 'yellow') on conflict (key) do update set color = 'updated ' || upsert.color;
WARNING: before insert (new): (8,yellow)
WARNING: before insert (new, modified): (9,"yellow trig modified")
WARNING: after insert (new): (9,"yellow trig modified")
select * from upsert;
key | color
-----+-----------------------------
1 | black
3 | updated red trig modified
5 | updated green trig modified
7 | updated white trig modified
9 | yellow trig modified
(5 rows)
drop table upsert;
drop function upsert_before_func();
drop function upsert_after_func();
View
@@ -215,6 +215,67 @@ INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
ERROR: cannot insert into column "upper" of view "rw_view15"
DETAIL: View columns that are not columns of their base relation are not updatable.
INSERT INTO rw_view15 (a) VALUES (3); -- should be OK
INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT DO NOTHING; -- succeeds
SELECT * FROM rw_view15;
a | upper
----+-------------
-2 | ROW -2
-1 | ROW -1
0 | ROW 0
1 | ROW 1
2 | ROW 2
3 | UNSPECIFIED
(6 rows)
INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO NOTHING; -- succeeds
SELECT * FROM rw_view15;
a | upper
----+-------------
-2 | ROW -2
-1 | ROW -1
0 | ROW 0
1 | ROW 1
2 | ROW 2
3 | UNSPECIFIED
(6 rows)
INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO UPDATE set a = excluded.a; -- succeeds
SELECT * FROM rw_view15;
a | upper
----+-------------
-2 | ROW -2
-1 | ROW -1
0 | ROW 0
1 | ROW 1
2 | ROW 2
3 | UNSPECIFIED
(6 rows)
INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO UPDATE set upper = 'blarg'; -- fails
ERROR: cannot insert into column "upper" of view "rw_view15"
DETAIL: View columns that are not columns of their base relation are not updatable.
SELECT * FROM rw_view15;
a | upper
----+-------------
-2 | ROW -2
-1 | ROW -1
0 | ROW 0
1 | ROW 1
2 | ROW 2
3 | UNSPECIFIED
(6 rows)
SELECT * FROM rw_view15;
a | upper
----+-------------
-2 | ROW -2
-1 | ROW -1
0 | ROW 0
1 | ROW 1
2 | ROW 2
3 | UNSPECIFIED
(6 rows)
ALTER VIEW rw_view15 ALTER COLUMN upper SET DEFAULT 'NOT SET';
INSERT INTO rw_view15 (a) VALUES (4); -- should fail
ERROR: cannot insert into column "upper" of view "rw_view15"
View
@@ -6,6 +6,10 @@ CREATE TABLE update_test (
b INT,
c TEXT
);
CREATE TABLE upsert_test (
a INT PRIMARY KEY,
b TEXT
);
INSERT INTO update_test VALUES (5, 10, 'foo');
INSERT INTO update_test(b, a) VALUES (15, 10);
SELECT * FROM update_test;
@@ -147,4 +151,34 @@ SELECT a, b, char_length(c) FROM update_test;
42 | 12 | 10000
(4 rows)
-- Test ON CONFLICT DO UPDATE
INSERT INTO upsert_test VALUES(1, 'Boo');
-- uncorrelated sub-select:
WITH aaa AS (SELECT 1 AS a, 'Foo' AS b) INSERT INTO upsert_test
VALUES (1, 'Bar') ON CONFLICT(a)
DO UPDATE SET (b, a) = (SELECT b, a FROM aaa) RETURNING *;
a | b
---+-----
1 | Foo
(1 row)
-- correlated sub-select:
INSERT INTO upsert_test VALUES (1, 'Baz') ON CONFLICT(a)
DO UPDATE SET (b, a) = (SELECT b || ', Correlated', a from upsert_test i WHERE i.a = upsert_test.a)
RETURNING *;
a | b
---+-----------------
1 | Foo, Correlated
(1 row)
-- correlated sub-select (EXCLUDED.* alias):
INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
DO UPDATE SET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a)
RETURNING *;
a | b
---+---------------------------
1 | Foo, Correlated, Excluded
(1 row)
DROP TABLE update_test;
DROP TABLE upsert_test;
Oops, something went wrong.