@@ -165,6 +165,14 @@ static bool ExecOnConflictUpdate(ModifyTableContext *context,
165165 TupleTableSlot * excludedSlot ,
166166 bool canSetTag ,
167167 TupleTableSlot * * returning );
168+ #if PG19_GE
169+ static bool ExecOnConflictSelect (ModifyTableContext * context ,
170+ ResultRelInfo * resultRelInfo ,
171+ ItemPointer conflictTid ,
172+ TupleTableSlot * excludedSlot ,
173+ bool canSetTag ,
174+ TupleTableSlot * * returning );
175+ #endif
168176
169177static TupleTableSlot * ExecPrepareTupleRouting (ModifyTableState * mtstate ,
170178 EState * estate ,
@@ -850,6 +858,31 @@ ExecInsert(ModifyTableContext *context,
850858 else
851859 goto vlock ;
852860 }
861+ #if PG19_GE
862+ else if (onconflict == ONCONFLICT_SELECT )
863+ {
864+ /*
865+ * In case of ON CONFLICT DO SELECT, optionally lock the
866+ * conflicting tuple, fetch it and project RETURNING on it.
867+ * Be prepared to retry if locking fails because of a
868+ * concurrent UPDATE/DELETE to the conflict tuple.
869+ */
870+ TupleTableSlot * returning = NULL ;
871+
872+ if (ExecOnConflictSelect (context ,
873+ resultRelInfo ,
874+ & conflictTid ,
875+ slot ,
876+ canSetTag ,
877+ & returning ))
878+ {
879+ InstrCountTuples2 (& mtstate -> ps , 1 );
880+ return returning ;
881+ }
882+ else
883+ goto vlock ;
884+ }
885+ #endif
853886 else
854887 {
855888 /*
@@ -2235,6 +2268,200 @@ ExecOnConflictUpdate(ModifyTableContext *context,
22352268 return true;
22362269}
22372270
2271+ #if PG19_GE
2272+ /*
2273+ * ExecOnConflictSelect --- execute SELECT of INSERT ON CONFLICT DO SELECT
2274+ *
2275+ * If SELECT FOR UPDATE/SHARE is specified, try to lock the conflicting tuple
2276+ * as part of speculative insertion. If the qual originating from ON CONFLICT
2277+ * DO SELECT is satisfied, project RETURNING on the conflicting row.
2278+ *
2279+ * Returns true if we're done (with or without a select), or false if the
2280+ * caller must retry the INSERT from scratch.
2281+ *
2282+ * copied and modified version of ExecOnConflictSelect from
2283+ * executor/nodeModifyTable.c
2284+ */
2285+ static bool
2286+ ExecOnConflictSelect (ModifyTableContext * context , ResultRelInfo * resultRelInfo ,
2287+ ItemPointer conflictTid , TupleTableSlot * excludedSlot , bool canSetTag ,
2288+ TupleTableSlot * * returning )
2289+ {
2290+ ModifyTableState * mtstate = context -> mtstate ;
2291+ ExprContext * econtext = mtstate -> ps .ps_ExprContext ;
2292+ Relation relation = resultRelInfo -> ri_RelationDesc ;
2293+ ExprState * onConflictSelectWhere = resultRelInfo -> ri_onConflict -> oc_WhereClause ;
2294+ TupleTableSlot * existing = resultRelInfo -> ri_onConflict -> oc_Existing ;
2295+ LockClauseStrength lockStrength = resultRelInfo -> ri_onConflict -> oc_LockStrength ;
2296+
2297+ Assert (!resultRelInfo -> ri_needLockTagTuple );
2298+
2299+ /* Fetch/lock the existing tuple, according to the requested lock strength */
2300+ if (lockStrength == LCS_NONE )
2301+ {
2302+ if (!table_tuple_fetch_row_version (relation , conflictTid , SnapshotAny , existing ))
2303+ elog (ERROR , "failed to fetch conflicting tuple for ON CONFLICT" );
2304+ }
2305+ else
2306+ {
2307+ LockTupleMode lockmode ;
2308+ TM_FailureData tmfd ;
2309+ TM_Result test ;
2310+ Datum xminDatum ;
2311+ TransactionId xmin ;
2312+ bool isnull ;
2313+
2314+ switch (lockStrength )
2315+ {
2316+ case LCS_FORKEYSHARE :
2317+ lockmode = LockTupleKeyShare ;
2318+ break ;
2319+ case LCS_FORSHARE :
2320+ lockmode = LockTupleShare ;
2321+ break ;
2322+ case LCS_FORNOKEYUPDATE :
2323+ lockmode = LockTupleNoKeyExclusive ;
2324+ break ;
2325+ case LCS_FORUPDATE :
2326+ lockmode = LockTupleExclusive ;
2327+ break ;
2328+ default :
2329+ elog (ERROR , "unexpected lock strength %d" , (int ) lockStrength );
2330+ }
2331+
2332+ /*
2333+ * Lock the tuple with the requested mode. A row locking conflict here
2334+ * means our previous conclusion that the tuple is conclusively
2335+ * committed is not true anymore.
2336+ */
2337+ test = table_tuple_lock (relation ,
2338+ conflictTid ,
2339+ context -> estate -> es_snapshot ,
2340+ existing ,
2341+ context -> estate -> es_output_cid ,
2342+ lockmode ,
2343+ LockWaitBlock ,
2344+ 0 ,
2345+ & tmfd );
2346+ switch (test )
2347+ {
2348+ case TM_Ok :
2349+ /* success! */
2350+ break ;
2351+
2352+ case TM_Invisible :
2353+
2354+ /*
2355+ * This can occur when a just inserted tuple is locked again in
2356+ * the same command, e.g. because multiple rows with the same
2357+ * conflicting key values are inserted.
2358+ */
2359+ xminDatum = slot_getsysattr (existing , MinTransactionIdAttributeNumber , & isnull );
2360+ Assert (!isnull );
2361+ xmin = DatumGetTransactionId (xminDatum );
2362+
2363+ if (TransactionIdIsCurrentTransactionId (xmin ))
2364+ ereport (ERROR ,
2365+ (errcode (ERRCODE_CARDINALITY_VIOLATION ),
2366+ /* translator: %s is a SQL command name */
2367+ errmsg ("%s command cannot affect row a second time" ,
2368+ "ON CONFLICT DO SELECT" ),
2369+ errhint ("Ensure that no rows proposed for insertion within the same "
2370+ "command have duplicate constrained values." )));
2371+
2372+ /* This shouldn't happen */
2373+ elog (ERROR , "attempted to lock invisible tuple" );
2374+ break ;
2375+
2376+ case TM_SelfModified :
2377+
2378+ /*
2379+ * This state should never be reached. As a dirty snapshot is
2380+ * used to find conflicting tuples, speculative insertion
2381+ * wouldn't have seen this row to conflict with.
2382+ */
2383+ elog (ERROR , "unexpected self-updated tuple" );
2384+ break ;
2385+
2386+ case TM_Updated :
2387+ if (IsolationUsesXactSnapshot ())
2388+ ereport (ERROR ,
2389+ (errcode (ERRCODE_T_R_SERIALIZATION_FAILURE ),
2390+ errmsg ("could not serialize access due to concurrent update" )));
2391+
2392+ /*
2393+ * As long as we don't support ON CONFLICT for a partitioned
2394+ * table we shouldn't reach a case where the tuple to be locked
2395+ * is moved to another partition due to a concurrent update of
2396+ * the partition key.
2397+ */
2398+ Assert (!ItemPointerIndicatesMovedPartitions (& tmfd .ctid ));
2399+
2400+ /* Tell caller to try again from the very start. */
2401+ ExecClearTuple (existing );
2402+ return false;
2403+
2404+ case TM_Deleted :
2405+ if (IsolationUsesXactSnapshot ())
2406+ ereport (ERROR ,
2407+ (errcode (ERRCODE_T_R_SERIALIZATION_FAILURE ),
2408+ errmsg ("could not serialize access due to concurrent delete" )));
2409+
2410+ /* see TM_Updated case */
2411+ Assert (!ItemPointerIndicatesMovedPartitions (& tmfd .ctid ));
2412+ ExecClearTuple (existing );
2413+ return false;
2414+
2415+ default :
2416+ elog (ERROR , "unrecognized table_tuple_lock status: %u" , test );
2417+ }
2418+ }
2419+
2420+ /*
2421+ * Verify that the tuple is visible to our MVCC snapshot if the current
2422+ * isolation level mandates that. See comments in ExecOnConflictUpdate().
2423+ */
2424+ ExecCheckTupleVisible (context -> estate , relation , existing );
2425+
2426+ /*
2427+ * Make the tuple and any needed join variables available to ExecQual. The
2428+ * EXCLUDED tuple is installed in ecxt_innertuple, while the target's
2429+ * existing tuple is installed in the scantuple. EXCLUDED has been made to
2430+ * reference INNER_VAR in setrefs.c, but there is no other redirection.
2431+ */
2432+ econtext -> ecxt_scantuple = existing ;
2433+ econtext -> ecxt_innertuple = excludedSlot ;
2434+ econtext -> ecxt_outertuple = NULL ;
2435+
2436+ if (!ExecQual (onConflictSelectWhere , econtext ))
2437+ {
2438+ ExecClearTuple (existing ); /* see return below */
2439+ InstrCountFiltered1 (& mtstate -> ps , 1 );
2440+ return true; /* done with the tuple */
2441+ }
2442+
2443+ if (resultRelInfo -> ri_WithCheckOptions != NIL )
2444+ ExecWithCheckOptions (WCO_RLS_CONFLICT_CHECK , resultRelInfo , existing , mtstate -> ps .state );
2445+
2446+ /* RETURNING is required for DO SELECT */
2447+ Assert (resultRelInfo -> ri_projectReturning );
2448+
2449+ * returning =
2450+ ExecProcessReturning (resultRelInfo , CMD_INSERT , existing , existing , context -> planSlot );
2451+
2452+ if (canSetTag )
2453+ context -> estate -> es_processed ++ ;
2454+
2455+ /*
2456+ * Clear out the existing tuple, as there might not be another conflict
2457+ * among the next input rows. First though, make sure that the returning
2458+ * slot has a local copy of any pass-by-reference values.
2459+ */
2460+ ExecMaterializeSlot (* returning );
2461+ ExecClearTuple (existing );
2462+ return true;
2463+ }
2464+ #endif
22382465
22392466static void fireASTriggers (ModifyTableState * node );
22402467static void fireBSTriggers (ModifyTableState * node );
0 commit comments