summaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
authorDean Rasheed2024-03-17 13:58:59 +0000
committerDean Rasheed2024-03-17 13:58:59 +0000
commitc649fa24a42ba89bf5460c7110e4fc8eeca65959 (patch)
treede7d51489c6c6fff56fddad66c0ced2aa427d6a5 /src/test
parent6a004f1be87d34cfe51acf2fe2552d2b08a79273 (diff)
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to return values based on each row inserted, updated, or deleted. As with plain INSERT, UPDATE, and DELETE commands, the returned values are based on the new contents of the target table for INSERT and UPDATE actions, and on its old contents for DELETE actions. Values from the source relation may also be returned. As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be used as the source relation for other operations such as WITH queries and COPY commands. Additionally, a special function merge_action() is provided, which returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action executed for each row. The merge_action() function can be used anywhere in the RETURNING list, including in arbitrary expressions and subqueries, but it is an error to use it anywhere outside of a MERGE query's RETURNING list. Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera, Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut, and Wolfgang Walther. Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
Diffstat (limited to 'src/test')
-rw-r--r--src/test/regress/expected/merge.out266
-rw-r--r--src/test/regress/expected/rowsecurity.out32
-rw-r--r--src/test/regress/expected/rules.out16
-rw-r--r--src/test/regress/expected/updatable_views.out30
-rw-r--r--src/test/regress/expected/with.out10
-rw-r--r--src/test/regress/sql/merge.sql169
-rw-r--r--src/test/regress/sql/rowsecurity.sql21
-rw-r--r--src/test/regress/sql/rules.sql6
-rw-r--r--src/test/regress/sql/updatable_views.sql9
-rw-r--r--src/test/regress/sql/with.sql8
10 files changed, 521 insertions, 46 deletions
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
index 1a6f6ad43d1..07561f02407 100644
--- a/src/test/regress/expected/merge.out
+++ b/src/test/regress/expected/merge.out
@@ -124,20 +124,20 @@ ON tid = tid
WHEN MATCHED THEN DO NOTHING;
ERROR: name "target" specified more than once
DETAIL: The name is used both as MERGE target table and data source.
--- used in a CTE
+-- used in a CTE without RETURNING
WITH foo AS (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE
) SELECT * FROM foo;
-ERROR: MERGE not supported in WITH query
-LINE 1: WITH foo AS (
- ^
--- used in COPY
+ERROR: WITH query "foo" does not have a RETURNING clause
+LINE 4: ) SELECT * FROM foo;
+ ^
+-- used in COPY without RETURNING
COPY (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE
) TO stdout;
-ERROR: MERGE not supported in COPY
+ERROR: COPY query must have a RETURNING clause
-- unsupported relation types
-- materialized view
CREATE MATERIALIZED VIEW mv AS SELECT * FROM target;
@@ -1255,7 +1255,7 @@ BEGIN;
MERGE INTO sq_target
USING v
ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
+WHEN MATCHED AND tid >= 2 THEN
UPDATE SET balance = balance + delta
WHEN NOT MATCHED THEN
INSERT (balance, tid) VALUES (balance + delta, sid)
@@ -1270,7 +1270,7 @@ INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
MERGE INTO sq_target t
USING v
ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
+WHEN MATCHED AND tid >= 2 THEN
UPDATE SET balance = t.balance + delta
WHEN NOT MATCHED THEN
INSERT (balance, tid) VALUES (balance + delta, sid)
@@ -1294,7 +1294,7 @@ WITH targq AS (
MERGE INTO sq_target t
USING v
ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
+WHEN MATCHED AND tid >= 2 THEN
UPDATE SET balance = t.balance + delta
WHEN NOT MATCHED THEN
INSERT (balance, tid) VALUES (balance + delta, sid)
@@ -1302,21 +1302,207 @@ WHEN MATCHED AND tid < 2 THEN
DELETE;
ROLLBACK;
-- RETURNING
+SELECT * FROM sq_source ORDER BY sid;
+ delta | sid | balance
+-------+-----+---------
+ 10 | 1 | 0
+ 20 | 2 | 0
+ 40 | 4 | 0
+(3 rows)
+
+SELECT * FROM sq_target ORDER BY tid;
+ tid | balance
+-----+---------
+ 1 | 100
+ 2 | 200
+ 3 | 300
+(3 rows)
+
BEGIN;
-INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
+CREATE TABLE merge_actions(action text, abbrev text);
+INSERT INTO merge_actions VALUES ('INSERT', 'ins'), ('UPDATE', 'upd'), ('DELETE', 'del');
MERGE INTO sq_target t
-USING v
+USING sq_source s
ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
+WHEN MATCHED AND tid >= 2 THEN
UPDATE SET balance = t.balance + delta
WHEN NOT MATCHED THEN
- INSERT (balance, tid) VALUES (balance + delta, sid)
+ INSERT (balance, tid) VALUES (balance + delta, sid)
WHEN MATCHED AND tid < 2 THEN
- DELETE
-RETURNING *;
-ERROR: syntax error at or near "RETURNING"
-LINE 10: RETURNING *;
- ^
+ DELETE
+RETURNING (SELECT abbrev FROM merge_actions
+ WHERE action = merge_action()) AS action,
+ t.*,
+ CASE merge_action()
+ WHEN 'INSERT' THEN 'Inserted '||t
+ WHEN 'UPDATE' THEN 'Added '||delta||' to balance'
+ WHEN 'DELETE' THEN 'Removed '||t
+ END AS description;
+ action | tid | balance | description
+--------+-----+---------+---------------------
+ del | 1 | 100 | Removed (1,100)
+ upd | 2 | 220 | Added 20 to balance
+ ins | 4 | 40 | Inserted (4,40)
+(3 rows)
+
+ROLLBACK;
+-- error when using merge_action() outside MERGE
+SELECT merge_action() FROM sq_target;
+ERROR: MERGE_ACTION() can only be used in the RETURNING list of a MERGE command
+LINE 1: SELECT merge_action() FROM sq_target;
+ ^
+UPDATE sq_target SET balance = balance + 1 RETURNING merge_action();
+ERROR: MERGE_ACTION() can only be used in the RETURNING list of a MERGE command
+LINE 1: ...ATE sq_target SET balance = balance + 1 RETURNING merge_acti...
+ ^
+-- RETURNING in CTEs
+CREATE TABLE sq_target_merge_log (tid integer NOT NULL, last_change text);
+INSERT INTO sq_target_merge_log VALUES (1, 'Original value');
+BEGIN;
+WITH m AS (
+ MERGE INTO sq_target t
+ USING sq_source s
+ ON tid = sid
+ WHEN MATCHED AND tid >= 2 THEN
+ UPDATE SET balance = t.balance + delta
+ WHEN NOT MATCHED THEN
+ INSERT (balance, tid) VALUES (balance + delta, sid)
+ WHEN MATCHED AND tid < 2 THEN
+ DELETE
+ RETURNING merge_action() AS action, t.*,
+ CASE merge_action()
+ WHEN 'INSERT' THEN 'Inserted '||t
+ WHEN 'UPDATE' THEN 'Added '||delta||' to balance'
+ WHEN 'DELETE' THEN 'Removed '||t
+ END AS description
+), m2 AS (
+ MERGE INTO sq_target_merge_log l
+ USING m
+ ON l.tid = m.tid
+ WHEN MATCHED THEN
+ UPDATE SET last_change = description
+ WHEN NOT MATCHED THEN
+ INSERT VALUES (m.tid, description)
+ RETURNING action, merge_action() AS log_action, l.*
+)
+SELECT * FROM m2;
+ action | log_action | tid | last_change
+--------+------------+-----+---------------------
+ DELETE | UPDATE | 1 | Removed (1,100)
+ UPDATE | INSERT | 2 | Added 20 to balance
+ INSERT | INSERT | 4 | Inserted (4,40)
+(3 rows)
+
+SELECT * FROM sq_target_merge_log ORDER BY tid;
+ tid | last_change
+-----+---------------------
+ 1 | Removed (1,100)
+ 2 | Added 20 to balance
+ 4 | Inserted (4,40)
+(3 rows)
+
+ROLLBACK;
+-- COPY (MERGE ... RETURNING) TO ...
+BEGIN;
+COPY (
+ MERGE INTO sq_target t
+ USING sq_source s
+ ON tid = sid
+ WHEN MATCHED AND tid >= 2 THEN
+ UPDATE SET balance = t.balance + delta
+ WHEN NOT MATCHED THEN
+ INSERT (balance, tid) VALUES (balance + delta, sid)
+ WHEN MATCHED AND tid < 2 THEN
+ DELETE
+ RETURNING merge_action(), t.*
+) TO stdout;
+DELETE 1 100
+UPDATE 2 220
+INSERT 4 40
+ROLLBACK;
+-- SQL function with MERGE ... RETURNING
+BEGIN;
+CREATE FUNCTION merge_into_sq_target(sid int, balance int, delta int,
+ OUT action text, OUT tid int, OUT new_balance int)
+LANGUAGE sql AS
+$$
+ MERGE INTO sq_target t
+ USING (VALUES ($1, $2, $3)) AS v(sid, balance, delta)
+ ON tid = v.sid
+ WHEN MATCHED AND tid >= 2 THEN
+ UPDATE SET balance = t.balance + v.delta
+ WHEN NOT MATCHED THEN
+ INSERT (balance, tid) VALUES (v.balance + v.delta, v.sid)
+ WHEN MATCHED AND tid < 2 THEN
+ DELETE
+ RETURNING merge_action(), t.*;
+$$;
+SELECT m.*
+FROM (VALUES (1, 0, 0), (3, 0, 20), (4, 100, 10)) AS v(sid, balance, delta),
+LATERAL (SELECT action, tid, new_balance FROM merge_into_sq_target(sid, balance, delta)) m;
+ action | tid | new_balance
+--------+-----+-------------
+ DELETE | 1 | 100
+ UPDATE | 3 | 320
+ INSERT | 4 | 110
+(3 rows)
+
+ROLLBACK;
+-- SQL SRF with MERGE ... RETURNING
+BEGIN;
+CREATE FUNCTION merge_sq_source_into_sq_target()
+RETURNS TABLE (action text, tid int, balance int)
+LANGUAGE sql AS
+$$
+ MERGE INTO sq_target t
+ USING sq_source s
+ ON tid = sid
+ WHEN MATCHED AND tid >= 2 THEN
+ UPDATE SET balance = t.balance + delta
+ WHEN NOT MATCHED THEN
+ INSERT (balance, tid) VALUES (balance + delta, sid)
+ WHEN MATCHED AND tid < 2 THEN
+ DELETE
+ RETURNING merge_action(), t.*;
+$$;
+SELECT * FROM merge_sq_source_into_sq_target();
+ action | tid | balance
+--------+-----+---------
+ DELETE | 1 | 100
+ UPDATE | 2 | 220
+ INSERT | 4 | 40
+(3 rows)
+
+ROLLBACK;
+-- PL/pgSQL function with MERGE ... RETURNING ... INTO
+BEGIN;
+CREATE FUNCTION merge_into_sq_target(sid int, balance int, delta int,
+ OUT r_action text, OUT r_tid int, OUT r_balance int)
+LANGUAGE plpgsql AS
+$$
+BEGIN
+ MERGE INTO sq_target t
+ USING (VALUES ($1, $2, $3)) AS v(sid, balance, delta)
+ ON tid = v.sid
+ WHEN MATCHED AND tid >= 2 THEN
+ UPDATE SET balance = t.balance + v.delta
+ WHEN NOT MATCHED THEN
+ INSERT (balance, tid) VALUES (v.balance + v.delta, v.sid)
+ WHEN MATCHED AND tid < 2 THEN
+ DELETE
+ RETURNING merge_action(), t.* INTO r_action, r_tid, r_balance;
+END;
+$$;
+SELECT m.*
+FROM (VALUES (1, 0, 0), (3, 0, 20), (4, 100, 10)) AS v(sid, balance, delta),
+LATERAL (SELECT r_action, r_tid, r_balance FROM merge_into_sq_target(sid, balance, delta)) m;
+ r_action | r_tid | r_balance
+----------+-------+-----------
+ DELETE | 1 | 100
+ UPDATE | 3 | 320
+ INSERT | 4 | 110
+(3 rows)
+
ROLLBACK;
-- EXPLAIN
CREATE TABLE ex_mtarget (a int, b int)
@@ -1563,7 +1749,7 @@ SELECT * FROM sq_target WHERE tid = 1;
(1 row)
ROLLBACK;
-DROP TABLE sq_target, sq_source CASCADE;
+DROP TABLE sq_target, sq_target_merge_log, sq_source CASCADE;
NOTICE: drop cascades to view v
CREATE TABLE pa_target (tid integer, balance float, val text)
PARTITION BY LIST (tid);
@@ -1689,6 +1875,32 @@ SELECT * FROM pa_target ORDER BY tid;
(14 rows)
ROLLBACK;
+-- update partition key to partition not initially scanned
+BEGIN;
+MERGE INTO pa_target t
+ USING pa_source s
+ ON t.tid = s.sid AND t.tid = 1
+ WHEN MATCHED THEN
+ UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'
+ RETURNING merge_action(), t.*;
+ merge_action | tid | balance | val
+--------------+-----+---------+--------------------------
+ UPDATE | 2 | 110 | initial updated by merge
+(1 row)
+
+SELECT * FROM pa_target ORDER BY tid;
+ tid | balance | val
+-----+---------+--------------------------
+ 2 | 110 | initial updated by merge
+ 3 | 300 | initial
+ 5 | 500 | initial
+ 7 | 700 | initial
+ 9 | 900 | initial
+ 11 | 1100 | initial
+ 13 | 1300 | initial
+(7 rows)
+
+ROLLBACK;
DROP TABLE pa_target CASCADE;
-- The target table is partitioned in the same way, but this time by attaching
-- partitions which have columns in different order, dropped columns etc.
@@ -1938,7 +2150,21 @@ MERGE INTO pa_target t
WHEN MATCHED THEN
UPDATE SET balance = balance + delta, val = val || ' updated by merge'
WHEN NOT MATCHED THEN
- INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge');
+ INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge')
+ RETURNING merge_action(), t.*;
+ merge_action | logts | tid | balance | val
+--------------+--------------------------+-----+---------+--------------------------
+ UPDATE | Tue Jan 31 00:00:00 2017 | 1 | 110 | initial updated by merge
+ UPDATE | Tue Feb 28 00:00:00 2017 | 2 | 220 | initial updated by merge
+ INSERT | Sun Jan 15 00:00:00 2017 | 3 | 30 | inserted by merge
+ UPDATE | Tue Jan 31 00:00:00 2017 | 4 | 440 | initial updated by merge
+ UPDATE | Tue Feb 28 00:00:00 2017 | 5 | 550 | initial updated by merge
+ INSERT | Sun Jan 15 00:00:00 2017 | 6 | 60 | inserted by merge
+ UPDATE | Tue Jan 31 00:00:00 2017 | 7 | 770 | initial updated by merge
+ UPDATE | Tue Feb 28 00:00:00 2017 | 8 | 880 | initial updated by merge
+ INSERT | Sun Jan 15 00:00:00 2017 | 9 | 90 | inserted by merge
+(9 rows)
+
SELECT * FROM pa_target ORDER BY tid;
logts | tid | balance | val
--------------------------+-----+---------+--------------------------
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 4538f0c37d5..d507a2c8ca5 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -2302,6 +2302,35 @@ WHEN MATCHED THEN
UPDATE SET dnotes = dnotes || ' notes added by merge8 '
WHEN NOT MATCHED THEN
INSERT VALUES (13, 44, 1, 'regress_rls_bob', 'new manga');
+SELECT * FROM document WHERE did = 13;
+ did | cid | dlevel | dauthor | dtitle | dnotes
+-----+-----+--------+---------+--------+--------
+(0 rows)
+
+-- but not OK if RETURNING is used
+MERGE INTO document d
+USING (SELECT 14 as sdid) s
+ON did = s.sdid
+WHEN MATCHED THEN
+ UPDATE SET dnotes = dnotes || ' notes added by merge9 '
+WHEN NOT MATCHED THEN
+ INSERT VALUES (14, 44, 1, 'regress_rls_bob', 'new manga')
+RETURNING *;
+ERROR: new row violates row-level security policy for table "document"
+-- but OK if new row is visible
+MERGE INTO document d
+USING (SELECT 14 as sdid) s
+ON did = s.sdid
+WHEN MATCHED THEN
+ UPDATE SET dnotes = dnotes || ' notes added by merge10 '
+WHEN NOT MATCHED THEN
+ INSERT VALUES (14, 11, 1, 'regress_rls_bob', 'new novel')
+RETURNING *;
+ sdid | did | cid | dlevel | dauthor | dtitle | dnotes
+------+-----+-----+--------+-----------------+-----------+--------
+ 14 | 14 | 11 | 1 | regress_rls_bob | new novel |
+(1 row)
+
RESET SESSION AUTHORIZATION;
-- drop the restrictive SELECT policy so that we can look at the
-- final state of the table
@@ -2325,7 +2354,8 @@ SELECT * FROM document;
12 | 11 | 1 | regress_rls_bob | another novel |
1 | 11 | 1 | regress_rls_bob | my first novel | notes added by merge2 notes added by merge3 notes added by merge4 notes added by merge7
13 | 44 | 1 | regress_rls_bob | new manga |
-(15 rows)
+ 14 | 11 | 1 | regress_rls_bob | new novel |
+(16 rows)
--
-- ROLE/GROUP
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 0cd2c64fca1..84e359f6ed9 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -3638,7 +3638,7 @@ MERGE INTO rule_merge1 t USING (SELECT 1 AS a) s
-- test deparsing
CREATE TABLE sf_target(id int, data text, filling int[]);
CREATE FUNCTION merge_sf_test()
- RETURNS void
+ RETURNS TABLE(action text, a int, b text, id int, data text, filling int[])
LANGUAGE sql
BEGIN ATOMIC
MERGE INTO sf_target t
@@ -3675,11 +3675,13 @@ WHEN NOT MATCHED
VALUES (s.a, s.b, DEFAULT)
WHEN NOT MATCHED
THEN INSERT (filling[1], id)
- VALUES (s.a, s.a);
+ VALUES (s.a, s.a)
+RETURNING
+ merge_action() AS action, *;
END;
\sf merge_sf_test
CREATE OR REPLACE FUNCTION public.merge_sf_test()
- RETURNS void
+ RETURNS TABLE(action text, a integer, b text, id integer, data text, filling integer[])
LANGUAGE sql
BEGIN ATOMIC
MERGE INTO sf_target t
@@ -3716,7 +3718,13 @@ BEGIN ATOMIC
VALUES (s.a, s.b, DEFAULT)
WHEN NOT MATCHED
THEN INSERT (filling[1], id)
- VALUES (s.a, s.a);
+ VALUES (s.a, s.a)
+ RETURNING MERGE_ACTION() AS action,
+ s.a,
+ s.b,
+ t.id,
+ t.data,
+ t.filling;
END
DROP FUNCTION merge_sf_test;
DROP TABLE sf_target;
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 794cf9cf930..1062c341d8e 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -479,7 +479,15 @@ MERGE INTO rw_view1 t
(2, 'ROW 2'), (3, 'ROW 3')) AS v(a,b) ON t.a = v.a
WHEN MATCHED AND t.a <= 1 THEN UPDATE SET b = v.b
WHEN MATCHED THEN DELETE
- WHEN NOT MATCHED AND a > 0 THEN INSERT (a) VALUES (v.a);
+ WHEN NOT MATCHED AND a > 0 THEN INSERT (a) VALUES (v.a)
+ RETURNING merge_action(), v.*, t.*;
+ merge_action | a | b | a | b
+--------------+---+-------+---+-------------
+ UPDATE | 1 | ROW 1 | 1 | ROW 1
+ DELETE | 3 | ROW 3 | 3 | Row 3
+ INSERT | 2 | ROW 2 | 2 | Unspecified
+(3 rows)
+
SELECT * FROM base_tbl ORDER BY a;
a | b
----+-------------
@@ -631,7 +639,15 @@ MERGE INTO rw_view2 t
USING (VALUES (3, 'R3'), (4, 'R4'), (5, 'R5')) AS v(a,b) ON aaa = v.a
WHEN MATCHED AND aaa = 3 THEN DELETE
WHEN MATCHED THEN UPDATE SET bbb = v.b
- WHEN NOT MATCHED THEN INSERT (aaa) VALUES (v.a);
+ WHEN NOT MATCHED THEN INSERT (aaa) VALUES (v.a)
+ RETURNING merge_action(), v.*, t.*;
+ merge_action | a | b | aaa | bbb
+--------------+---+----+-----+-------------
+ DELETE | 3 | R3 | 3 | Row 3
+ UPDATE | 4 | R4 | 4 | R4
+ INSERT | 5 | R5 | 5 | Unspecified
+(3 rows)
+
SELECT * FROM rw_view2 ORDER BY aaa;
aaa | bbb
-----+-------------
@@ -1071,7 +1087,15 @@ MERGE INTO rw_view2 t
USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
WHEN MATCHED AND t.a <= 1 THEN DELETE
WHEN MATCHED THEN UPDATE SET b = s.b
- WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b);
+ WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b)
+ RETURNING merge_action(), s.*, t.*;
+ merge_action | a | b | a | b
+--------------+---+----+---+-------
+ DELETE | 1 | R1 | 1 | Row 1
+ UPDATE | 2 | R2 | 2 | R2
+ INSERT | 3 | R3 | 3 | R3
+(3 rows)
+
SELECT * FROM base_tbl ORDER BY a;
a | b
----+--------
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 7d796ea69cd..6de347b528d 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -3493,6 +3493,16 @@ SELECT * FROM t;
ERROR: WITH query "t" does not have a RETURNING clause
LINE 4: SELECT * FROM t;
^
+-- RETURNING tries to return its own output
+WITH RECURSIVE t(action, a) AS (
+ MERGE INTO y USING (VALUES (11)) v(a) ON y.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a)
+ RETURNING merge_action(), (SELECT a FROM t)
+)
+SELECT * FROM t;
+ERROR: recursive query "t" must not contain data-modifying statements
+LINE 1: WITH RECURSIVE t(action, a) AS (
+ ^
-- data-modifying WITH allowed only at the top level
SELECT * FROM (
WITH t AS (UPDATE y SET a=a+1 RETURNING *)
diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql
index dbbba2a1cdd..875cf6f93d7 100644
--- a/src/test/regress/sql/merge.sql
+++ b/src/test/regress/sql/merge.sql
@@ -88,12 +88,12 @@ MERGE INTO target
USING target
ON tid = tid
WHEN MATCHED THEN DO NOTHING;
--- used in a CTE
+-- used in a CTE without RETURNING
WITH foo AS (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE
) SELECT * FROM foo;
--- used in COPY
+-- used in COPY without RETURNING
COPY (
MERGE INTO target USING source ON (true)
WHEN MATCHED THEN DELETE
@@ -817,7 +817,7 @@ BEGIN;
MERGE INTO sq_target
USING v
ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
+WHEN MATCHED AND tid >= 2 THEN
UPDATE SET balance = balance + delta
WHEN NOT MATCHED THEN
INSERT (balance, tid) VALUES (balance + delta, sid)
@@ -830,7 +830,7 @@ INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
MERGE INTO sq_target t
USING v
ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
+WHEN MATCHED AND tid >= 2 THEN
UPDATE SET balance = t.balance + delta
WHEN NOT MATCHED THEN
INSERT (balance, tid) VALUES (balance + delta, sid)
@@ -848,7 +848,7 @@ WITH targq AS (
MERGE INTO sq_target t
USING v
ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
+WHEN MATCHED AND tid >= 2 THEN
UPDATE SET balance = t.balance + delta
WHEN NOT MATCHED THEN
INSERT (balance, tid) VALUES (balance + delta, sid)
@@ -857,18 +857,149 @@ WHEN MATCHED AND tid < 2 THEN
ROLLBACK;
-- RETURNING
+SELECT * FROM sq_source ORDER BY sid;
+SELECT * FROM sq_target ORDER BY tid;
+
BEGIN;
-INSERT INTO sq_source (sid, balance, delta) VALUES (-1, -1, -10);
+CREATE TABLE merge_actions(action text, abbrev text);
+INSERT INTO merge_actions VALUES ('INSERT', 'ins'), ('UPDATE', 'upd'), ('DELETE', 'del');
MERGE INTO sq_target t
-USING v
+USING sq_source s
ON tid = sid
-WHEN MATCHED AND tid > 2 THEN
+WHEN MATCHED AND tid >= 2 THEN
UPDATE SET balance = t.balance + delta
WHEN NOT MATCHED THEN
- INSERT (balance, tid) VALUES (balance + delta, sid)
+ INSERT (balance, tid) VALUES (balance + delta, sid)
WHEN MATCHED AND tid < 2 THEN
- DELETE
-RETURNING *;
+ DELETE
+RETURNING (SELECT abbrev FROM merge_actions
+ WHERE action = merge_action()) AS action,
+ t.*,
+ CASE merge_action()
+ WHEN 'INSERT' THEN 'Inserted '||t
+ WHEN 'UPDATE' THEN 'Added '||delta||' to balance'
+ WHEN 'DELETE' THEN 'Removed '||t
+ END AS description;
+ROLLBACK;
+
+-- error when using merge_action() outside MERGE
+SELECT merge_action() FROM sq_target;
+UPDATE sq_target SET balance = balance + 1 RETURNING merge_action();
+
+-- RETURNING in CTEs
+CREATE TABLE sq_target_merge_log (tid integer NOT NULL, last_change text);
+INSERT INTO sq_target_merge_log VALUES (1, 'Original value');
+BEGIN;
+WITH m AS (
+ MERGE INTO sq_target t
+ USING sq_source s
+ ON tid = sid
+ WHEN MATCHED AND tid >= 2 THEN
+ UPDATE SET balance = t.balance + delta
+ WHEN NOT MATCHED THEN
+ INSERT (balance, tid) VALUES (balance + delta, sid)
+ WHEN MATCHED AND tid < 2 THEN
+ DELETE
+ RETURNING merge_action() AS action, t.*,
+ CASE merge_action()
+ WHEN 'INSERT' THEN 'Inserted '||t
+ WHEN 'UPDATE' THEN 'Added '||delta||' to balance'
+ WHEN 'DELETE' THEN 'Removed '||t
+ END AS description
+), m2 AS (
+ MERGE INTO sq_target_merge_log l
+ USING m
+ ON l.tid = m.tid
+ WHEN MATCHED THEN
+ UPDATE SET last_change = description
+ WHEN NOT MATCHED THEN
+ INSERT VALUES (m.tid, description)
+ RETURNING action, merge_action() AS log_action, l.*
+)
+SELECT * FROM m2;
+SELECT * FROM sq_target_merge_log ORDER BY tid;
+ROLLBACK;
+
+-- COPY (MERGE ... RETURNING) TO ...
+BEGIN;
+COPY (
+ MERGE INTO sq_target t
+ USING sq_source s
+ ON tid = sid
+ WHEN MATCHED AND tid >= 2 THEN
+ UPDATE SET balance = t.balance + delta
+ WHEN NOT MATCHED THEN
+ INSERT (balance, tid) VALUES (balance + delta, sid)
+ WHEN MATCHED AND tid < 2 THEN
+ DELETE
+ RETURNING merge_action(), t.*
+) TO stdout;
+ROLLBACK;
+
+-- SQL function with MERGE ... RETURNING
+BEGIN;
+CREATE FUNCTION merge_into_sq_target(sid int, balance int, delta int,
+ OUT action text, OUT tid int, OUT new_balance int)
+LANGUAGE sql AS
+$$
+ MERGE INTO sq_target t
+ USING (VALUES ($1, $2, $3)) AS v(sid, balance, delta)
+ ON tid = v.sid
+ WHEN MATCHED AND tid >= 2 THEN
+ UPDATE SET balance = t.balance + v.delta
+ WHEN NOT MATCHED THEN
+ INSERT (balance, tid) VALUES (v.balance + v.delta, v.sid)
+ WHEN MATCHED AND tid < 2 THEN
+ DELETE
+ RETURNING merge_action(), t.*;
+$$;
+SELECT m.*
+FROM (VALUES (1, 0, 0), (3, 0, 20), (4, 100, 10)) AS v(sid, balance, delta),
+LATERAL (SELECT action, tid, new_balance FROM merge_into_sq_target(sid, balance, delta)) m;
+ROLLBACK;
+
+-- SQL SRF with MERGE ... RETURNING
+BEGIN;
+CREATE FUNCTION merge_sq_source_into_sq_target()
+RETURNS TABLE (action text, tid int, balance int)
+LANGUAGE sql AS
+$$
+ MERGE INTO sq_target t
+ USING sq_source s
+ ON tid = sid
+ WHEN MATCHED AND tid >= 2 THEN
+ UPDATE SET balance = t.balance + delta
+ WHEN NOT MATCHED THEN
+ INSERT (balance, tid) VALUES (balance + delta, sid)
+ WHEN MATCHED AND tid < 2 THEN
+ DELETE
+ RETURNING merge_action(), t.*;
+$$;
+SELECT * FROM merge_sq_source_into_sq_target();
+ROLLBACK;
+
+-- PL/pgSQL function with MERGE ... RETURNING ... INTO
+BEGIN;
+CREATE FUNCTION merge_into_sq_target(sid int, balance int, delta int,
+ OUT r_action text, OUT r_tid int, OUT r_balance int)
+LANGUAGE plpgsql AS
+$$
+BEGIN
+ MERGE INTO sq_target t
+ USING (VALUES ($1, $2, $3)) AS v(sid, balance, delta)
+ ON tid = v.sid
+ WHEN MATCHED AND tid >= 2 THEN
+ UPDATE SET balance = t.balance + v.delta
+ WHEN NOT MATCHED THEN
+ INSERT (balance, tid) VALUES (v.balance + v.delta, v.sid)
+ WHEN MATCHED AND tid < 2 THEN
+ DELETE
+ RETURNING merge_action(), t.* INTO r_action, r_tid, r_balance;
+END;
+$$;
+SELECT m.*
+FROM (VALUES (1, 0, 0), (3, 0, 20), (4, 100, 10)) AS v(sid, balance, delta),
+LATERAL (SELECT r_action, r_tid, r_balance FROM merge_into_sq_target(sid, balance, delta)) m;
ROLLBACK;
-- EXPLAIN
@@ -984,7 +1115,7 @@ WHEN MATCHED THEN
SELECT * FROM sq_target WHERE tid = 1;
ROLLBACK;
-DROP TABLE sq_target, sq_source CASCADE;
+DROP TABLE sq_target, sq_target_merge_log, sq_source CASCADE;
CREATE TABLE pa_target (tid integer, balance float, val text)
PARTITION BY LIST (tid);
@@ -1051,6 +1182,17 @@ SELECT merge_func();
SELECT * FROM pa_target ORDER BY tid;
ROLLBACK;
+-- update partition key to partition not initially scanned
+BEGIN;
+MERGE INTO pa_target t
+ USING pa_source s
+ ON t.tid = s.sid AND t.tid = 1
+ WHEN MATCHED THEN
+ UPDATE SET tid = tid + 1, balance = balance + delta, val = val || ' updated by merge'
+ RETURNING merge_action(), t.*;
+SELECT * FROM pa_target ORDER BY tid;
+ROLLBACK;
+
DROP TABLE pa_target CASCADE;
-- The target table is partitioned in the same way, but this time by attaching
@@ -1227,7 +1369,8 @@ MERGE INTO pa_target t
WHEN MATCHED THEN
UPDATE SET balance = balance + delta, val = val || ' updated by merge'
WHEN NOT MATCHED THEN
- INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge');
+ INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge')
+ RETURNING merge_action(), t.*;
SELECT * FROM pa_target ORDER BY tid;
ROLLBACK;
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
index dec7340538c..1d5ed0a647b 100644
--- a/src/test/regress/sql/rowsecurity.sql
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -992,6 +992,27 @@ WHEN MATCHED THEN
UPDATE SET dnotes = dnotes || ' notes added by merge8 '
WHEN NOT MATCHED THEN
INSERT VALUES (13, 44, 1, 'regress_rls_bob', 'new manga');
+SELECT * FROM document WHERE did = 13;
+
+-- but not OK if RETURNING is used
+MERGE INTO document d
+USING (SELECT 14 as sdid) s
+ON did = s.sdid
+WHEN MATCHED THEN
+ UPDATE SET dnotes = dnotes || ' notes added by merge9 '
+WHEN NOT MATCHED THEN
+ INSERT VALUES (14, 44, 1, 'regress_rls_bob', 'new manga')
+RETURNING *;
+
+-- but OK if new row is visible
+MERGE INTO document d
+USING (SELECT 14 as sdid) s
+ON did = s.sdid
+WHEN MATCHED THEN
+ UPDATE SET dnotes = dnotes || ' notes added by merge10 '
+WHEN NOT MATCHED THEN
+ INSERT VALUES (14, 11, 1, 'regress_rls_bob', 'new novel')
+RETURNING *;
RESET SESSION AUTHORIZATION;
-- drop the restrictive SELECT policy so that we can look at the
diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql
index 6924012a10c..27340bacbc5 100644
--- a/src/test/regress/sql/rules.sql
+++ b/src/test/regress/sql/rules.sql
@@ -1294,7 +1294,7 @@ MERGE INTO rule_merge1 t USING (SELECT 1 AS a) s
CREATE TABLE sf_target(id int, data text, filling int[]);
CREATE FUNCTION merge_sf_test()
- RETURNS void
+ RETURNS TABLE(action text, a int, b text, id int, data text, filling int[])
LANGUAGE sql
BEGIN ATOMIC
MERGE INTO sf_target t
@@ -1331,7 +1331,9 @@ WHEN NOT MATCHED
VALUES (s.a, s.b, DEFAULT)
WHEN NOT MATCHED
THEN INSERT (filling[1], id)
- VALUES (s.a, s.a);
+ VALUES (s.a, s.a)
+RETURNING
+ merge_action() AS action, *;
END;
\sf merge_sf_test
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
index ae11e46da28..afdf331fed6 100644
--- a/src/test/regress/sql/updatable_views.sql
+++ b/src/test/regress/sql/updatable_views.sql
@@ -175,7 +175,8 @@ MERGE INTO rw_view1 t
(2, 'ROW 2'), (3, 'ROW 3')) AS v(a,b) ON t.a = v.a
WHEN MATCHED AND t.a <= 1 THEN UPDATE SET b = v.b
WHEN MATCHED THEN DELETE
- WHEN NOT MATCHED AND a > 0 THEN INSERT (a) VALUES (v.a);
+ WHEN NOT MATCHED AND a > 0 THEN INSERT (a) VALUES (v.a)
+ RETURNING merge_action(), v.*, t.*;
SELECT * FROM base_tbl ORDER BY a;
EXPLAIN (costs off) UPDATE rw_view1 SET a=6 WHERE a=5;
@@ -246,7 +247,8 @@ MERGE INTO rw_view2 t
USING (VALUES (3, 'R3'), (4, 'R4'), (5, 'R5')) AS v(a,b) ON aaa = v.a
WHEN MATCHED AND aaa = 3 THEN DELETE
WHEN MATCHED THEN UPDATE SET bbb = v.b
- WHEN NOT MATCHED THEN INSERT (aaa) VALUES (v.a);
+ WHEN NOT MATCHED THEN INSERT (aaa) VALUES (v.a)
+ RETURNING merge_action(), v.*, t.*;
SELECT * FROM rw_view2 ORDER BY aaa;
EXPLAIN (costs off) UPDATE rw_view2 SET aaa=5 WHERE aaa=4;
@@ -458,7 +460,8 @@ MERGE INTO rw_view2 t
USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
WHEN MATCHED AND t.a <= 1 THEN DELETE
WHEN MATCHED THEN UPDATE SET b = s.b
- WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b);
+ WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b)
+ RETURNING merge_action(), s.*, t.*;
SELECT * FROM base_tbl ORDER BY a;
EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index f8a213e3570..037bc0a511b 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -1591,6 +1591,14 @@ WITH t AS (
)
SELECT * FROM t;
+-- RETURNING tries to return its own output
+WITH RECURSIVE t(action, a) AS (
+ MERGE INTO y USING (VALUES (11)) v(a) ON y.a = v.a
+ WHEN NOT MATCHED THEN INSERT VALUES (v.a)
+ RETURNING merge_action(), (SELECT a FROM t)
+)
+SELECT * FROM t;
+
-- data-modifying WITH allowed only at the top level
SELECT * FROM (
WITH t AS (UPDATE y SET a=a+1 RETURNING *)