ExecSetSlotDescriptor(mtstate->mt_existing,
resultRelInfo->ri_RelationDesc->rd_att);
+ /* carried forward solely for the benefit of explain */
mtstate->mt_excludedtlist = node->exclRelTlist;
/* create target slot for UPDATE SET projection */
return true;
if (walker(onconflict->onConflictWhere, context))
return true;
+ if (walker(onconflict->exclRelTlist, context))
+ return true;
}
break;
case T_JoinExpr:
MUTATE(newnode->arbiterWhere, oc->arbiterWhere, Node *);
MUTATE(newnode->onConflictSet, oc->onConflictSet, List *);
MUTATE(newnode->onConflictWhere, oc->onConflictWhere, Node *);
+ MUTATE(newnode->exclRelTlist, oc->exclRelTlist, List *);
return (Node *) newnode;
}
/*
* We treat ModifyTable with ON CONFLICT as a form of 'pseudo
- * join', where the inner side is the EXLUDED tuple. Therefore
- * use fix_join_expr to setup the relevant variables to
- * INNER_VAR. We explicitly don't create any OUTER_VARs as
+ * join', where the inner side is the EXCLUDED tuple.
+ * Therefore use fix_join_expr to setup the relevant variables
+ * to INNER_VAR. We explicitly don't create any OUTER_VARs as
* those are already used by RETURNING and it seems better to
* be non-conflicting.
*/
NULL, itlist,
linitial_int(splan->resultRelations),
rtoffset);
+
+ splan->exclRelTlist =
+ fix_scan_list(root, splan->exclRelTlist, rtoffset);
}
splan->nominalRelation += rtoffset;
/*
* If we are starting at a Query, and sublevels_up is zero, then we
* must also fix rangetable indexes in the Query itself --- namely
- * resultRelation and rowMarks entries. sublevels_up cannot be zero
- * when recursing into a subquery, so there's no need to have the same
- * logic inside OffsetVarNodes_walker.
+ * resultRelation, exclRelIndex and rowMarks entries. sublevels_up
+ * cannot be zero when recursing into a subquery, so there's no need
+ * to have the same logic inside OffsetVarNodes_walker.
*/
if (sublevels_up == 0)
{
if (qry->resultRelation)
qry->resultRelation += offset;
+
+ if (qry->onConflict && qry->onConflict->exclRelIndex)
+ qry->onConflict->exclRelIndex += offset;
+
foreach(l, qry->rowMarks)
{
RowMarkClause *rc = (RowMarkClause *) lfirst(l);
if (qry->resultRelation == rt_index)
qry->resultRelation = new_index;
+
+ /* this is unlikely to ever be used, but ... */
+ if (qry->onConflict && qry->onConflict->exclRelIndex == rt_index)
+ qry->onConflict->exclRelIndex = new_index;
+
foreach(l, qry->rowMarks)
{
RowMarkClause *rc = (RowMarkClause *) lfirst(l);
INSERT INTO hat_data VALUES (
NEW.hat_name,
NEW.hat_color)
- ON CONFLICT (hat_name) DO UPDATE SET hat_color = 'Orange' RETURNING *;
+ ON CONFLICT (hat_name)
+ DO UPDATE
+ SET hat_name = hat_data.hat_name, hat_color = excluded.hat_color
+ WHERE excluded.hat_color <> 'forbidden'
+ RETURNING *;
-- Works (does upsert)
-INSERT INTO hats VALUES ('h7', 'black') RETURNING *;
+INSERT INTO hats VALUES ('h8', 'black') RETURNING *;
+ hat_name | hat_color
+------------+------------
+ h8 | black
+(1 row)
+
+SELECT * FROM hat_data WHERE hat_name = 'h8';
+ hat_name | hat_color
+------------+------------
+ h8 | black
+(1 row)
+
+INSERT INTO hats VALUES ('h8', 'white') RETURNING *;
+ hat_name | hat_color
+------------+------------
+ h8 | white
+(1 row)
+
+SELECT * FROM hat_data WHERE hat_name = 'h8';
+ hat_name | hat_color
+------------+------------
+ h8 | white
+(1 row)
+
+INSERT INTO hats VALUES ('h8', 'forbidden') RETURNING *;
+ hat_name | hat_color
+----------+-----------
+(0 rows)
+
+SELECT * FROM hat_data WHERE hat_name = 'h8';
hat_name | hat_color
------------+------------
- h7 | Orange
+ h8 | white
(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, +
+ 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_name = hat_data.hat_name, hat_color = excluded.hat_color+
+ | | WHERE (excluded.hat_color <> 'forbidden'::bpchar) +
+ | | RETURNING hat_data.hat_name, +
| | hat_data.hat_color;
(1 row)
+-- ensure explain works for on insert conflict rules
+explain (costs off) INSERT INTO hats VALUES ('h8', 'forbidden') RETURNING *;
+ QUERY PLAN
+----------------------------------------------------------------
+ Insert on hat_data
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: hat_data_pkey
+ Conflict Filter: (excluded.hat_color <> 'forbidden'::bpchar)
+ -> Result
+(5 rows)
+
+-- ensure upserting into a rule, with a CTE (different offsets!) works
+WITH data(hat_name, hat_color) AS (
+ VALUES ('h8', 'green'),
+ ('h9', 'blue'),
+ ('h7', 'forbidden')
+)
+INSERT INTO hats
+ SELECT * FROM data
+RETURNING *;
+ hat_name | hat_color
+------------+------------
+ h8 | green
+ h9 | blue
+(2 rows)
+
+EXPLAIN (costs off) WITH data(hat_name, hat_color) AS (
+ VALUES ('h8', 'green'),
+ ('h9', 'blue'),
+ ('h7', 'forbidden')
+)
+INSERT INTO hats
+ SELECT * FROM data
+RETURNING *;
+ QUERY PLAN
+----------------------------------------------------------------
+ Insert on hat_data
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: hat_data_pkey
+ Conflict Filter: (excluded.hat_color <> 'forbidden'::bpchar)
+ CTE data
+ -> Values Scan on "*VALUES*"
+ -> CTE Scan on data
+(7 rows)
+
+SELECT * FROM hat_data WHERE hat_name IN ('h8', 'h9', 'h7') ORDER BY hat_name;
+ hat_name | hat_color
+------------+------------
+ h7 | black
+ h8 | green
+ h9 | blue
+(3 rows)
+
DROP RULE hat_upsert ON hats;
drop table hats;
drop table hat_data;
INSERT INTO hat_data VALUES (
NEW.hat_name,
NEW.hat_color)
- ON CONFLICT (hat_name) DO UPDATE SET hat_color = 'Orange' RETURNING *;
+ ON CONFLICT (hat_name)
+ DO UPDATE
+ SET hat_name = hat_data.hat_name, hat_color = excluded.hat_color
+ WHERE excluded.hat_color <> 'forbidden'
+ RETURNING *;
-- Works (does upsert)
-INSERT INTO hats VALUES ('h7', 'black') RETURNING *;
+INSERT INTO hats VALUES ('h8', 'black') RETURNING *;
+SELECT * FROM hat_data WHERE hat_name = 'h8';
+INSERT INTO hats VALUES ('h8', 'white') RETURNING *;
+SELECT * FROM hat_data WHERE hat_name = 'h8';
+INSERT INTO hats VALUES ('h8', 'forbidden') RETURNING *;
+SELECT * FROM hat_data WHERE hat_name = 'h8';
SELECT tablename, rulename, definition FROM pg_rules
WHERE tablename = 'hats';
+-- ensure explain works for on insert conflict rules
+explain (costs off) INSERT INTO hats VALUES ('h8', 'forbidden') RETURNING *;
+
+-- ensure upserting into a rule, with a CTE (different offsets!) works
+WITH data(hat_name, hat_color) AS (
+ VALUES ('h8', 'green'),
+ ('h9', 'blue'),
+ ('h7', 'forbidden')
+)
+INSERT INTO hats
+ SELECT * FROM data
+RETURNING *;
+EXPLAIN (costs off) WITH data(hat_name, hat_color) AS (
+ VALUES ('h8', 'green'),
+ ('h9', 'blue'),
+ ('h7', 'forbidden')
+)
+INSERT INTO hats
+ SELECT * FROM data
+RETURNING *;
+SELECT * FROM hat_data WHERE hat_name IN ('h8', 'h9', 'h7') ORDER BY hat_name;
+
DROP RULE hat_upsert ON hats;
drop table hats;