diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml index 94dad00..8ca6bcf 100644 *** a/doc/src/sgml/ref/insert.sgml --- b/doc/src/sgml/ref/insert.sgml *************** *** 36,41 **** INSERT INTO table_name [ AS and conflict_action is one of: DO NOTHING + DO SELECT [ FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE } ] DO UPDATE SET { column_name = { expression | DEFAULT } | ( column_name [, ...] ) = [ ROW ] ( { expression | DEFAULT } [, ...] ) | ( column_name [, ...] ) = ( sub-SELECT ) *************** *** 83,108 **** INSERT INTO table_name [ AS The optional RETURNING clause causes INSERT ! to compute and return value(s) based on each row actually inserted ! (or updated, if an ON CONFLICT DO UPDATE clause was ! used). This is primarily useful for obtaining values that were supplied by defaults, such as a serial sequence number. However, any expression using the table's columns is allowed. The syntax of the RETURNING list is identical to that of the output ! list of SELECT. Only rows that were successfully inserted or updated will be returned. For example, if a row was locked but not updated because an ON CONFLICT DO UPDATE ... WHERE clause condition was not satisfied, the ! row will not be returned. You must have INSERT privilege on a table in order to insert into it. If ON CONFLICT DO UPDATE is present, UPDATE privilege on the table is also ! required. ! If a column list is specified, you only need --- 84,114 ---- The optional RETURNING clause causes INSERT ! to compute and return value(s) based on each row actually inserted. ! If an ON CONFLICT DO UPDATE clause was used, ! RETURNING also returns tuples which were updated, and ! in the presence of an ON CONFLICT DO SELECT clause all ! input rows are returned. With a traditional INSERT, ! the RETURNING clause is primarily useful for obtaining ! values that were supplied by defaults, such as a serial sequence number. However, any expression using the table's columns is allowed. The syntax of the RETURNING list is identical to that of the output ! list of SELECT. If an ON CONFLICT DO SELECT ! clause is not present, only rows that were successfully inserted or updated will be returned. For example, if a row was locked but not updated because an ON CONFLICT DO UPDATE ... WHERE clause condition was not satisfied, the ! row will not be returned. ON CONFLICT DO SELECT ! works similarly, except no update takes place. You must have INSERT privilege on a table in order to insert into it. If ON CONFLICT DO UPDATE is present, UPDATE privilege on the table is also ! required. If a column list is specified, you only need diff --git a/src/backend/executor/nindex 30add8e..bcb719a 100644 *** a/src/backend/executor/nodeModifyTable.c --- b/src/backend/executor/nodeModifyTable.c *************** *** 54,59 **** --- 54,66 ---- #include "utils/tqual.h" + static bool + ExecOnConflictLockRow(ModifyTableState *mtstate, + Relation relation, + LockTupleMode lockmode, + HeapTuple tuple, + Buffer *buffer, + EState *estate); static bool ExecOnConflictUpdate(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo, ItemPointer conflictTid, *************** *** 62,67 **** static bool ExecOnConflictUpdate(ModifyTableState *mtstate, --- 69,81 ---- EState *estate, bool canSetTag, TupleTableSlot **returning); + static bool ExecOnConflictSelect(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, + ItemPointer conflictTid, + TupleTableSlot *planSlot, + EState *estate, + TupleTableSlot **returning); + /* * Verify that the tuples to be produced by INSERT or UPDATE match the *************** *** 528,533 **** ExecInsert(ModifyTableState *mtstate, --- 542,566 ---- else goto vlock; } + else if (onconflict == ONCONFLICT_SELECT) + { + /* + * In case of ON CONFLICT DO SELECT, simply fetch the + * conflicting tuple and project RETURNING on it. + */ + TupleTableSlot *returning = NULL; + + if (ExecOnConflictSelect(mtstate, resultRelInfo, + &conflictTid, planSlot, + estate, &returning)) + { + InstrCountFiltered2(&mtstate->ps, 1); + return returning; + } + else + goto vlock; + } + else { /* *************** *** 1181,1227 **** lreplace:; } /* ! * ExecOnConflictUpdate --- execute UPDATE of INSERT ON CONFLICT DO UPDATE ! * ! * Try to lock tuple for update as part of speculative insertion. If ! * a qual originating from ON CONFLICT DO UPDATE is satisfied, update ! * (but still lock row, even though it may not satisfy estate's ! * snapshot). ! * ! * Returns true if if we're done (with or without an update), or false if ! * the caller must retry the INSERT from scratch. */ static bool ! ExecOnConflictUpdate(ModifyTableState *mtstate, ! ResultRelInfo *resultRelInfo, ! ItemPointer conflictTid, ! TupleTableSlot *planSlot, ! TupleTableSlot *excludedSlot, ! EState *estate, ! bool canSetTag, ! TupleTableSlot **returning) { - ExprContext *econtext = mtstate->ps.ps_ExprContext; - Relation relation = resultRelInfo->ri_RelationDesc; - ExprState *onConflictSetWhere = resultRelInfo->ri_onConflictSetWhere; - HeapTupleData tuple; HeapUpdateFailureData hufd; - LockTupleMode lockmode; HTSU_Result test; - Buffer buffer; - - /* Determine lock mode to use */ - lockmode = ExecUpdateLockMode(estate, resultRelInfo); /* ! * Lock tuple for update. Don't follow updates when tuple cannot be ! * locked without doing so. A row locking conflict here means our ! * previous conclusion that the tuple is conclusively committed is not ! * true anymore. */ ! tuple.t_self = *conflictTid; ! test = heap_lock_tuple(relation, &tuple, estate->es_output_cid, ! lockmode, LockWaitBlock, false, &buffer, &hufd); switch (test) { --- 1214,1239 ---- } /* ! * ExecOnConflictLockRow --- lock the row for ON CONFLICT DO UPDATE/SELECT */ static bool ! ExecOnConflictLockRow(ModifyTableState *mtstate, ! Relation relation, ! LockTupleMode lockmode, ! HeapTuple tuple, ! Buffer *buffer, ! EState *estate) { HeapUpdateFailureData hufd; HTSU_Result test; /* ! * Don't follow updates when tuple cannot be locked without doing so. A ! * row locking conflict here means our previous conclusion that the tuple ! * is conclusively committed is not true anymore. */ ! test = heap_lock_tuple(relation, tuple, estate->es_output_cid, ! lockmode, LockWaitBlock, false, buffer, &hufd); switch (test) { *************** *** 1247,1256 **** ExecOnConflictUpdate(ModifyTableState *mtstate, * that for SQL MERGE, an exception must be raised in the event of * an attempt to update the same row twice. */ ! if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple.t_data))) ereport(ERROR, (errcode(ERRCODE_CARDINALITY_VIOLATION), ! errmsg("ON CONFLICT DO UPDATE command cannot affect row a second time"), errhint("Ensure that no rows proposed for insertion within the same command have duplicate constrained values."))); /* This shouldn't happen */ --- 1259,1268 ---- * that for SQL MERGE, an exception must be raised in the event of * an attempt to update the same row twice. */ ! if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data))) ereport(ERROR, (errcode(ERRCODE_CARDINALITY_VIOLATION), ! errmsg("ON CONFLICT command cannot affect row a second time"), errhint("Ensure that no rows proposed for insertion within the same command have duplicate constrained values."))); /* This shouldn't happen */ *************** *** 1278,1290 **** ExecOnConflictUpdate(ModifyTableState *mtstate, * loop here, as the new version of the row might not conflict * anymore, or the conflicting tuple has actually been deleted. */ ! ReleaseBuffer(buffer); return false; default: elog(ERROR, "unrecognized heap_lock_tuple status: %u", test); } /* * Success, the tuple is locked. * --- 1290,1346 ---- * loop here, as the new version of the row might not conflict * anymore, or the conflicting tuple has actually been deleted. */ ! ReleaseBuffer(*buffer); return false; default: elog(ERROR, "unrecognized heap_lock_tuple status: %u", test); } + return true; + } + + + /* + * ExecOnConflictUpdate --- execute UPDATE of INSERT ON CONFLICT DO UPDATE + * + * Try to lock tuple for update as part of speculative insertion. If + * a qual originating from ON CONFLICT DO UPDATE is satisfied, update + * (but still lock row, even though it may not satisfy estate's + * snapshot). + * + * Returns true if if we're done (with or without an update), or false if + * the caller must retry the INSERT from scratch. + */ + static bool + ExecOnConflictUpdate(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, + ItemPointer conflictTid, + TupleTableSlot *planSlot, + TupleTableSlot *excludedSlot, + EState *estate, + bool canSetTag, + TupleTableSlot **returning) + { + ExprContext *econtext = mtstate->ps.ps_ExprContext; + ExprState *onConflictSetWhere = resultRelInfo->ri_onConflictActionWhere; + HeapTupleData tuple; + LockTupleMode lockmode; + Buffer buffer; + + /* Determine lock mode to use */ + lockmode = ExecUpdateLockMode(estate, resultRelInfo); + + /* + * Lock tuple for update. Don't follow updates when tuple cannot be + * locked without doing so. A row locking conflict here means our + * previous conclusion that the tuple is conclusively committed is not + * true anymore. + */ + tuple.t_self = *conflictTid; + if (!ExecOnConflictLockRow(mtstate, resultRelInfo->ri_RelationDesc, lockmode, &tuple, &buffer, estate)) + return false; + /* * Success, the tuple is locked. * *************** *** 1373,1378 **** ExecOnConflictUpdate(ModifyTableState *mtstate, --- 1429,1522 ---- return true; } + /* + * ExecOnConflictSelect --- execute SELECT of INSERT ON CONFLICT DO UPDATE + * + * Returns true if if we're done (with or without an update), or false if the + * caller must retry the INSERT from scratch. + */ + static bool + ExecOnConflictSelect(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, + ItemPointer conflictTid, + TupleTableSlot *planSlot, + EState *estate, + TupleTableSlot **returning) + { + ExprContext *econtext = mtstate->ps.ps_ExprContext; + Relation relation = resultRelInfo->ri_RelationDesc; + ExprState *onConflictSelectWhere = resultRelInfo->ri_onConflictActionWhere; + LockClauseStrength lockstrength = resultRelInfo->ri_onConflictLockingStrength; + HeapTupleData tuple; + Buffer buffer; + + tuple.t_self = *conflictTid; + if (lockstrength != LCS_NONE) + { + LockTupleMode lockmode; + + switch (lockstrength) + { + case LCS_FORKEYSHARE: + lockmode = LockTupleKeyShare; + break; + case LCS_FORSHARE: + lockmode = LockTupleShare; + break; + case LCS_FORNOKEYUPDATE: + lockmode = LockTupleNoKeyExclusive; + break; + case LCS_FORUPDATE: + lockmode = LockTupleExclusive; + break; + default: + elog(ERROR, "unexpected lock strength %d", lockstrength); + } + if (!ExecOnConflictLockRow(mtstate, resultRelInfo->ri_RelationDesc, lockmode, &tuple, &buffer, estate)) + return false; + } + else + { + if (!heap_fetch(relation, SnapshotAny, &tuple, &buffer, false, relation)) + return false; + } + + /* + * Reset per-tuple memory context to free any expression evaluation + * storage allocated in the previous cycle. + */ + ResetExprContext(econtext); + + /* + * For the same reasons as ExecOnConflictUpdate, we must verify that the + * tuple is visible to our snapshot. + */ + ExecCheckHeapTupleVisible(estate, &tuple, buffer); + + /* Store target's existing tuple in the state's dedicated slot */ + ExecStoreTuple(&tuple, mtstate->mt_existing, buffer, false); + + /* + * Make the tuple available to ExecQual and ExecProject. EXCLUDED is not + * used at all. + */ + econtext->ecxt_scantuple = mtstate->mt_existing; + econtext->ecxt_innertuple = NULL; + econtext->ecxt_outertuple = NULL; + + if (!ExecQual(onConflictSelectWhere, econtext)) + { + ReleaseBuffer(buffer); + InstrCountFiltered1(&mtstate->ps, 1); + return true; /* done with the tuple */ + } + + *returning = ExecProcessReturning(resultRelInfo, mtstate->mt_existing, planSlot); + + ReleaseBuffer(buffer); + return true; + } + /* * Process BEFORE EACH STATEMENT triggers *************** *** 2140,2148 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) qualexpr = ExecInitQual((List *) node->onConflictWhere, &mtstate->ps); ! resultRelInfo->ri_onConflictSetWhere = qualexpr; } } /* * If we have any secondary relations in an UPDATE or DELETE, they need to --- 2284,2316 ---- qualexpr = ExecInitQual((List *) node->onConflictWhere, &mtstate->ps); ! resultRelInfo->ri_onConflictActionWhere = qualexpr; } } + else if (node->onConflictAction == ONCONFLICT_SELECT) + { + /* already exists if created by RETURNING processing above */ + if (mtstate->ps.ps_ExprContext == NULL) + ExecAssignExprContext(estate, &mtstate->ps); + + /* initialize slot for the existing tuple */ + mtstate->mt_existing = ExecInitExtraTupleSlot(mtstate->ps.state); + ExecSetSlotDescriptor(mtstate->mt_existing, + resultRelInfo->ri_RelationDesc->rd_att); + + /* build DO SELECT WHERE clause expression */ + if (node->onConflictWhere) + { + ExprState *qualexpr; + + qualexpr = ExecInitQual((List *) node->onConflictWhere, + &mtstate->ps); + + resultRelInfo->ri_onConflictActionWhere = qualexpr; + } + + resultRelInfo->ri_onConflictLockingStrength = node->onConflictLockingStrength; + } /* * If we have any secondary relations in an UPDATE or DELETE, they need to diff --git a/src/backend/nodes/copyfuncs.c b/index 45a04b0..7b75993 100644 *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 2455,2460 **** _copyOnConflictClause(const OnConflictClause *from) --- 2455,2461 ---- COPY_SCALAR_FIELD(action); COPY_NODE_FIELD(infer); COPY_NODE_FIELD(targetList); + COPY_SCALAR_FIELD(lockingStrength); COPY_NODE_FIELD(whereClause); COPY_LOCATION_FIELD(location); diff --git a/src/backend/nodes/equalindex 8d92c03..c332d6a 100644 *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** *** 2758,2763 **** _equalOnConflictClause(const OnConflictClause *a, const OnConflictClause *b) --- 2758,2764 ---- COMPARE_SCALAR_FIELD(action); COMPARE_NODE_FIELD(infer); COMPARE_NODE_FIELD(targetList); + COMPARE_SCALAR_FIELD(lockingStrength); COMPARE_NODE_FIELD(whereClause); COMPARE_LOCATION_FIELD(location); diff --git a/src/backend/optimizer/plindex 5c934f2..73dacde 100644 *** a/src/backend/optimizer/plan/createplan.c --- b/src/backend/optimizer/plan/createplan.c *************** *** 6434,6439 **** make_modifytable(PlannerInfo *root, --- 6434,6440 ---- node->onConflictAction = ONCONFLICT_NONE; node->onConflictSet = NIL; node->onConflictWhere = NULL; + node->onConflictLockingStrength = LCS_NONE; node->arbiterIndexes = NIL; node->exclRelRTI = 0; node->exclRelTlist = NIL; *************** *** 6443,6448 **** make_modifytable(PlannerInfo *root, --- 6444,6450 ---- node->onConflictAction = onconflict->action; node->onConflictSet = onconflict->onConflictSet; node->onConflictWhere = onconflict->onConflictWhere; + node->onConflictLockingStrength = onconflict->lockingStrength; /* * If a set of unique index inference elements was provided (an diff --git a/src/backend/parser/analyze.c b/srindex 4fb793c..628e431 100644 *** a/src/backend/parser/analyze.c --- b/src/backend/parser/analyze.c *************** *** 1108,1123 **** transformOnConflictClause(ParseState *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; --- 1108,1137 ---- onConflictClause->whereClause, EXPR_KIND_WHERE, "WHERE"); } + else if (onConflictClause->action == ONCONFLICT_SELECT) + { + /* + * References to EXCLUDED are not allowed, but we need the main + * relation to be visible to the WHERE clause. + */ + addRTEtoQuery(pstate, pstate->p_target_rangetblentry, + false, true, true); + + onConflictWhere = transformWhereClause(pstate, + onConflictClause->whereClause, + EXPR_KIND_WHERE, "WHERE"); + } ! /* Finally, build ON CONFLICT DO [NOTHING | SELECT | UPDATE] expression */ result = makeNode(OnConflictExpr); result->action = onConflictClause->action; result->arbiterElems = arbiterElems; result->arbiterWhere = arbiterWhere; result->constraint = arbiterConstraint; result->onConflictWhere = onConflictWhere; + result->lockingStrength = onConflictClause->lockingStrength; + result->onConflictSet = onConflictSet; result->exclRelIndex = exclRelIndex; result->exclRelTlist = exclRelTlist; diff --git a/src/backend/parser/graindex 7d0de99..9751a13 100644 *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 419,425 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type OptNoLog %type OnCommitOption ! %type for_locking_strength %type for_locking_item %type for_locking_clause opt_for_locking_clause for_locking_items %type locked_rels_list --- 419,425 ---- %type OptNoLog %type OnCommitOption ! %type for_locking_strength opt_for_locking_strength %type for_locking_item %type for_locking_clause opt_for_locking_clause for_locking_items %type locked_rels_list *************** *** 10523,10534 **** insert_column_item: --- 10523,10546 ---- ; opt_on_conflict: + ON CONFLICT opt_conf_expr DO SELECT opt_for_locking_strength where_clause + { + $$ = makeNode(OnConflictClause); + $$->action = ONCONFLICT_SELECT; + $$->infer = $3; + $$->targetList = NIL; + $$->lockingStrength = $6; + $$->whereClause = $7; + $$->location = @1; + } + | ON CONFLICT opt_conf_expr DO UPDATE SET set_clause_list where_clause { $$ = makeNode(OnConflictClause); $$->action = ONCONFLICT_UPDATE; $$->infer = $3; $$->targetList = $7; + $$->lockingStrength = LCS_NONE; $$->whereClause = $8; $$->location = @1; } *************** *** 10539,10544 **** opt_on_conflict: --- 10551,10557 ---- $$->action = ONCONFLICT_NOTHING; $$->infer = $3; $$->targetList = NIL; + $$->lockingStrength = LCS_NONE; $$->whereClause = NULL; $$->location = @1; } *************** *** 11347,11352 **** for_locking_strength: --- 11360,11370 ---- | FOR KEY SHARE { $$ = LCS_FORKEYSHARE; } ; + opt_for_locking_strength: + for_locking_strength { $$ = $1; } + | /* EMPTY */ { $$ = LCS_NONE; } + ; + locked_rels_list: OF qualified_name_list { $$ = $2; } | /* EMPTY */ { $$ = NIL; } diff --git a/src/backend/parser/index 9ff80b8..dee1231 100644 *** a/src/backend/parser/parse_clause.c --- b/src/backend/parser/parse_clause.c *************** *** 3135,3140 **** transformOnConflictArbiter(ParseState *pstate, --- 3135,3147 ---- errhint("For example, ON CONFLICT (column_name)."), parser_errposition(pstate, exprLocation((Node *) onConflictClause)))); + else if (onConflictClause->action == ONCONFLICT_SELECT && !infer) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ON CONFLICT DO SELECT requires inference specification or constraint name"), + errhint("For example, ON CONFLICT (column_name)."), + parser_errposition(pstate, + exprLocation((Node *) onConflictClause)))); /* * To simplify certain aspects of its design, speculative insertion into diff --git a/src/include/nodes/execnodesindex 35c28a6..827c1a7 100644 *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** *** 401,408 **** typedef struct ResultRelInfo /* for computing ON CONFLICT DO UPDATE SET */ ProjectionInfo *ri_onConflictSetProj; ! /* list of ON CONFLICT DO UPDATE exprs (qual) */ ! ExprState *ri_onConflictSetWhere; /* partition check expression */ List *ri_PartitionCheck; --- 401,411 ---- /* for computing ON CONFLICT DO UPDATE SET */ ProjectionInfo *ri_onConflictSetProj; ! /* list of ON CONFLICT DO SELECT/UPDATE exprs (qual) */ ! ExprState *ri_onConflictActionWhere; ! ! /* strengh of lock for ON CONFLICT DO SELECT, or LCS_NONE */ ! LockClauseStrength ri_onConflictLockingStrength; /* partition check expression */ List *ri_PartitionCheck; diff --git a/src/include/nodes/lockoindex e0981da..eae4210 100644 *** a/src/include/nodes/lockoptions.h --- b/src/include/nodes/lockoptions.h *************** *** 20,26 **** */ typedef enum LockClauseStrength { ! LCS_NONE, /* no such clause - only used in PlanRowMark */ LCS_FORKEYSHARE, /* FOR KEY SHARE */ LCS_FORSHARE, /* FOR SHARE */ LCS_FORNOKEYUPDATE, /* FOR NO KEY UPDATE */ --- 20,26 ---- */ typedef enum LockClauseStrength { ! LCS_NONE, /* no such clause - only used in PlanRowMark and ON CONFLICT SELECT */ LCS_FORKEYSHARE, /* FOR KEY SHARE */ LCS_FORSHARE, /* FOR SHARE */ LCS_FORNOKEYUPDATE, /* FOR NO KEY UPDATE */ diff --git a/src/include/nodes/nodes.hindex 27bd4f3..6c80bb7 100644 *** a/src/include/nodes/nodes.h --- b/src/include/nodes/nodes.h *************** *** 800,806 **** 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 */ --- 800,807 ---- { ONCONFLICT_NONE, /* No "ON CONFLICT" clause */ ONCONFLICT_NOTHING, /* ON CONFLICT ... DO NOTHING */ ! ONCONFLICT_UPDATE, /* ON CONFLICT ... DO UPDATE */ ! ONCONFLICT_SELECT /* ON CONFLICT ... DO SELECT */ } OnConflictAction; #endif /* NODES_H */ diff --git a/src/include/nodes/pindex 5f2a4a7..9afc693 100644 *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** *** 1343,1351 **** typedef struct InferClause 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; --- 1343,1352 ---- typedef struct OnConflictClause { NodeTag type; ! OnConflictAction action; /* DO NOTHING, SELECT or UPDATE? */ InferClause *infer; /* Optional index inference clause */ List *targetList; /* the target list (of ResTarget) */ + LockClauseStrength lockingStrength; /* strengh of lock for DO SELECT, or LCS_NONE */ Node *whereClause; /* qualifications */ int location; /* token location, or -1 if unknown */ } OnConflictClause; diff --git a/src/include/nodes/plannoindex 7c51e7f..9975680 100644 *** a/src/include/nodes/plannodes.h --- b/src/include/nodes/plannodes.h *************** *** 232,238 **** typedef struct ModifyTable 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; --- 232,239 ---- OnConflictAction onConflictAction; /* ON CONFLICT action */ List *arbiterIndexes; /* List of ON CONFLICT arbiter index OIDs */ List *onConflictSet; /* SET for INSERT ON CONFLICT DO UPDATE */ ! LockClauseStrength onConflictLockingStrength; /* lock strength for ON CONFLICT SELECT */ ! Node *onConflictWhere; /* WHERE for ON CONFLICT SELECT and UPDATE */ Index exclRelRTI; /* RTI of the EXCLUDED pseudo relation */ List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */ } ModifyTable; diff --git a/src/include/nodes/primnindex 8c536a8..5d28ff1 100644 *** a/src/include/nodes/primnodes.h --- b/src/include/nodes/primnodes.h *************** *** 20,25 **** --- 20,26 ---- #include "access/attnum.h" #include "nodes/bitmapset.h" #include "nodes/pg_list.h" + #include "nodes/lockoptions.h" /* ---------------------------------------------------------------- *************** *** 1492,1500 **** typedef struct OnConflictExpr 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; --- 1493,1506 ---- Node *arbiterWhere; /* unique index arbiter WHERE clause */ Oid constraint; /* pg_constraint OID for arbiter */ + /* both ON CONFLICT SELECT and UPDATE */ + Node *onConflictWhere; /* qualifiers to restrict SELECT/UPDATE to */ + + /* ON CONFLICT SELECT */ + LockClauseStrength lockingStrength; /* strengh of lock for DO SELECT, or LCS_NONE */ + /* ON CONFLICT UPDATE */ List *onConflictSet; /* List of ON CONFLICT SET TargetEntrys */ int exclRelIndex; /* RT index of 'excluded' relation */ List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */ } OnConflictExpr; diff --git a/src/test/regress/expectindex 8d005fd..258ab77 100644 *** a/src/test/regress/expected/insert_conflict.out --- b/src/test/regress/expected/insert_conflict.out *************** *** 236,241 **** insert into insertconflicttest values (2, 'Orange') on conflict (key, key, key) --- 236,278 ---- insert into insertconflicttest values (1, 'Apple'), (2, 'Orange') on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key); + -- DO SELECT + delete from insertconflicttest where fruit = 'Apple'; + insert into insertconflicttest values (1, 'Apple') on conflict (key) do select returning *; + key | fruit + -----+------- + 1 | Apple + (1 row) + + insert into insertconflicttest values (1, 'Apple') on conflict (key) do select returning *; + key | fruit + -----+------- + 1 | Apple + (1 row) + + insert into insertconflicttest values (1, 'Apple') on conflict (key) do select where fruit <> 'Apple' returning *; + key | fruit + -----+------- + (0 rows) + + delete from insertconflicttest where fruit = 'Apple'; + insert into insertconflicttest values (1, 'Apple') on conflict (key) do select for update returning *; + key | fruit + -----+------- + 1 | Apple + (1 row) + + insert into insertconflicttest values (1, 'Apple') on conflict (key) do select for update returning *; + key | fruit + -----+------- + 1 | Apple + (1 row) + + insert into insertconflicttest values (1, 'Apple') on conflict (key) do select for update where fruit <> 'Apple' returning *; + key | fruit + -----+------- + (0 rows) + -- Give good diagnostic message when EXCLUDED.* spuriously referenced from -- RETURNING: insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit; *************** *** 764,780 **** insert into selfconflict values (3,1), (3,2) on conflict do nothing; commit; begin transaction isolation level read committed; insert into selfconflict values (4,1), (4,2) on conflict(f1) do update set f2 = 0; ! ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. commit; begin transaction isolation level repeatable read; insert into selfconflict values (5,1), (5,2) on conflict(f1) do update set f2 = 0; ! ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. commit; begin transaction isolation level serializable; insert into selfconflict values (6,1), (6,2) on conflict(f1) do update set f2 = 0; ! ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. commit; select * from selfconflict; --- 801,859 ---- commit; begin transaction isolation level read committed; insert into selfconflict values (4,1), (4,2) on conflict(f1) do update set f2 = 0; ! ERROR: ON CONFLICT command cannot affect row a second time HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. commit; begin transaction isolation level repeatable read; insert into selfconflict values (5,1), (5,2) on conflict(f1) do update set f2 = 0; ! ERROR: ON CONFLICT command cannot affect row a second time HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. commit; begin transaction isolation level serializable; insert into selfconflict values (6,1), (6,2) on conflict(f1) do update set f2 = 0; ! ERROR: ON CONFLICT command cannot affect row a second time ! HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. ! commit; ! begin transaction isolation level read committed; ! insert into selfconflict values (7,1), (7,2) on conflict(f1) do select returning *; ! f1 | f2 ! ----+---- ! 7 | 1 ! 7 | 1 ! (2 rows) ! ! commit; ! begin transaction isolation level repeatable read; ! insert into selfconflict values (8,1), (8,2) on conflict(f1) do select returning *; ! f1 | f2 ! ----+---- ! 8 | 1 ! 8 | 1 ! (2 rows) ! ! commit; ! begin transaction isolation level serializable; ! insert into selfconflict values (9,1), (9,2) on conflict(f1) do select returning *; ! f1 | f2 ! ----+---- ! 9 | 1 ! 9 | 1 ! (2 rows) ! ! commit; ! begin transaction isolation level read committed; ! insert into selfconflict values (10,1), (10,2) on conflict(f1) do select for update returning *; ! ERROR: ON CONFLICT command cannot affect row a second time ! HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. ! commit; ! begin transaction isolation level repeatable read; ! insert into selfconflict values (11,1), (11,2) on conflict(f1) do select for update returning *; ! ERROR: ON CONFLICT command cannot affect row a second time ! HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. ! commit; ! begin transaction isolation level serializable; ! insert into selfconflict values (12,1), (12,2) on conflict(f1) do select for update returning *; ! ERROR: ON CONFLICT command cannot affect row a second time HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. commit; select * from selfconflict; *************** *** 783,788 **** select * from selfconflict; 1 | 1 2 | 1 3 | 1 ! (3 rows) drop table selfconflict; --- 862,870 ---- 1 | 1 2 | 1 3 | 1 ! 7 | 1 ! 8 | 1 ! 9 | 1 ! (6 rows) drop table selfconflict; diff --git a/src/test/regress/output/constraints.souindex bb75165..b39e39b 100644 *** a/src/test/regress/output/constraints.source --- b/src/test/regress/output/constraints.source *************** *** 425,431 **** INSERT INTO UNIQUE_TBL VALUES (5, 'five-upsert-insert') ON CONFLICT (i) DO UPDAT INSERT INTO UNIQUE_TBL VALUES (6, 'six-upsert-insert') ON CONFLICT (i) DO UPDATE SET t = 'six-upsert-update'; -- should fail INSERT INTO UNIQUE_TBL VALUES (1, 'a'), (2, 'b'), (2, 'b') ON CONFLICT (i) DO UPDATE SET t = 'fails'; ! ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. SELECT '' AS five, * FROM UNIQUE_TBL; five | i | t --- 425,431 ---- INSERT INTO UNIQUE_TBL VALUES (6, 'six-upsert-insert') ON CONFLICT (i) DO UPDATE SET t = 'six-upsert-update'; -- should fail INSERT INTO UNIQUE_TBL VALUES (1, 'a'), (2, 'b'), (2, 'b') ON CONFLICT (i) DO UPDATE SET t = 'fails'; ! ERROR: ON CONFLICT command cannot affect row a second time HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. SELECT '' AS five, * FROM UNIQUE_TBL; five | i | t diff --git a/src/test/regress/sql/insert_conflictindex df3a9b5..ae553d8 100644 *** a/src/test/regress/sql/insert_conflict.sql --- b/src/test/regress/sql/insert_conflict.sql *************** *** 97,102 **** insert into insertconflicttest --- 97,112 ---- values (1, 'Apple'), (2, 'Orange') on conflict (key) do update set (fruit, key) = (excluded.fruit, excluded.key); + -- DO SELECT + delete from insertconflicttest where fruit = 'Apple'; + insert into insertconflicttest values (1, 'Apple') on conflict (key) do select returning *; + insert into insertconflicttest values (1, 'Apple') on conflict (key) do select returning *; + insert into insertconflicttest values (1, 'Apple') on conflict (key) do select where fruit <> 'Apple' returning *; + delete from insertconflicttest where fruit = 'Apple'; + insert into insertconflicttest values (1, 'Apple') on conflict (key) do select for update returning *; + insert into insertconflicttest values (1, 'Apple') on conflict (key) do select for update returning *; + insert into insertconflicttest values (1, 'Apple') on conflict (key) do select for update where fruit <> 'Apple' returning *; + -- Give good diagnostic message when EXCLUDED.* spuriously referenced from -- RETURNING: insert into insertconflicttest values (1, 'Apple') on conflict (key) do update set fruit = excluded.fruit RETURNING excluded.fruit; *************** *** 468,473 **** begin transaction isolation level serializable; --- 478,507 ---- insert into selfconflict values (6,1), (6,2) on conflict(f1) do update set f2 = 0; commit; + begin transaction isolation level read committed; + insert into selfconflict values (7,1), (7,2) on conflict(f1) do select returning *; + commit; + + begin transaction isolation level repeatable read; + insert into selfconflict values (8,1), (8,2) on conflict(f1) do select returning *; + commit; + + begin transaction isolation level serializable; + insert into selfconflict values (9,1), (9,2) on conflict(f1) do select returning *; + commit; + + begin transaction isolation level read committed; + insert into selfconflict values (10,1), (10,2) on conflict(f1) do select for update returning *; + commit; + + begin transaction isolation level repeatable read; + insert into selfconflict values (11,1), (11,2) on conflict(f1) do select for update returning *; + commit; + + begin transaction isolation level serializable; + insert into selfconflict values (12,1), (12,2) on conflict(f1) do select for update returning *; + commit; + select * from selfconflict; drop table selfconflict;