1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-03 09:13:20 +03:00

Add OLD/NEW support to RETURNING in DML queries.

This allows the RETURNING list of INSERT/UPDATE/DELETE/MERGE queries
to explicitly return old and new values by using the special aliases
"old" and "new", which are automatically added to the query (if not
already defined) while parsing its RETURNING list, allowing things
like:

  RETURNING old.colname, new.colname, ...

  RETURNING old.*, new.*

Additionally, a new syntax is supported, allowing the names "old" and
"new" to be changed to user-supplied alias names, e.g.:

  RETURNING WITH (OLD AS o, NEW AS n) o.colname, n.colname, ...

This is useful when the names "old" and "new" are already defined,
such as inside trigger functions, allowing backwards compatibility to
be maintained -- the interpretation of any existing queries that
happen to already refer to relations called "old" or "new", or use
those as aliases for other relations, is not changed.

For an INSERT, old values will generally be NULL, and for a DELETE,
new values will generally be NULL, but that may change for an INSERT
with an ON CONFLICT ... DO UPDATE clause, or if a query rewrite rule
changes the command type. Therefore, we put no restrictions on the use
of old and new in any DML queries.

Dean Rasheed, reviewed by Jian He and Jeff Davis.

Discussion: https://postgr.es/m/CAEZATCWx0J0-v=Qjc6gXzR=KtsdvAE7Ow=D=mu50AgOe+pvisQ@mail.gmail.com
This commit is contained in:
Dean Rasheed
2025-01-16 14:57:35 +00:00
parent 7407b2d48c
commit 80feb727c8
61 changed files with 2910 additions and 390 deletions

View File

@@ -235,7 +235,7 @@ WHEN NOT MATCHED BY SOURCE THEN
DELETE
WHEN NOT MATCHED BY TARGET THEN
INSERT VALUES (s.sid, s.delta)
RETURNING merge_action(), t.*;
RETURNING merge_action(), old, new, t.*;
SELECT * FROM target ORDER BY tid;
ROLLBACK;
@@ -677,7 +677,7 @@ WHEN NOT MATCHED BY SOURCE AND tid = 1 THEN
UPDATE SET balance = 0
WHEN NOT MATCHED BY SOURCE THEN
DELETE
RETURNING merge_action(), t.*;
RETURNING merge_action(), old, new, t.*;
SELECT * FROM target ORDER BY tid;
ROLLBACK;
@@ -930,7 +930,9 @@ WHEN MATCHED AND tid < 2 THEN
DELETE
RETURNING (SELECT abbrev FROM merge_actions
WHERE action = merge_action()) AS action,
t.*,
old.tid AS old_tid, old.balance AS old_balance,
new.tid AS new_tid, new.balance AS new_balance,
(SELECT new.balance - old.balance AS delta_balance), t.*,
CASE merge_action()
WHEN 'INSERT' THEN 'Inserted '||t
WHEN 'UPDATE' THEN 'Added '||delta||' to balance'
@@ -956,7 +958,7 @@ WITH m AS (
INSERT (balance, tid) VALUES (balance + delta, sid)
WHEN MATCHED AND tid < 2 THEN
DELETE
RETURNING merge_action() AS action, t.*,
RETURNING merge_action() AS action, old AS old_data, new AS new_data, t.*,
CASE merge_action()
WHEN 'INSERT' THEN 'Inserted '||t
WHEN 'UPDATE' THEN 'Added '||delta||' to balance'
@@ -970,7 +972,7 @@ WITH m AS (
UPDATE SET last_change = description
WHEN NOT MATCHED THEN
INSERT VALUES (m.tid, description)
RETURNING action, merge_action() AS log_action, l.*
RETURNING m.*, merge_action() AS log_action, old AS old_log, new AS new_log, l.*
)
SELECT * FROM m2;
SELECT * FROM sq_target_merge_log ORDER BY tid;
@@ -988,7 +990,7 @@ COPY (
INSERT (balance, tid) VALUES (balance + delta, sid)
WHEN MATCHED AND tid < 2 THEN
DELETE
RETURNING merge_action(), t.*
RETURNING merge_action(), old.*, new.*
) TO stdout;
ROLLBACK;
@@ -1265,7 +1267,7 @@ MERGE INTO pa_target t
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.*;
RETURNING merge_action(), old, new, t.*;
SELECT * FROM pa_target ORDER BY tid;
ROLLBACK;
@@ -1456,7 +1458,7 @@ MERGE INTO pa_target t
UPDATE SET balance = balance + delta, val = val || ' updated by merge'
WHEN NOT MATCHED THEN
INSERT VALUES (slogts::timestamp, sid, delta, 'inserted by merge')
RETURNING merge_action(), t.*;
RETURNING merge_action(), old, new, t.*;
SELECT * FROM pa_target ORDER BY tid;
ROLLBACK;

View File

@@ -160,3 +160,221 @@ INSERT INTO foo AS bar DEFAULT VALUES RETURNING *; -- ok
INSERT INTO foo AS bar DEFAULT VALUES RETURNING foo.*; -- fails, wrong name
INSERT INTO foo AS bar DEFAULT VALUES RETURNING bar.*; -- ok
INSERT INTO foo AS bar DEFAULT VALUES RETURNING bar.f3; -- ok
--
-- Test RETURNING OLD/NEW.
--
-- Start with new data, to ensure predictable TIDs.
--
TRUNCATE foo;
INSERT INTO foo VALUES (1, 'xxx', 10, 20), (2, 'more', 42, 141), (3, 'zoo2', 57, 99);
-- Error cases
INSERT INTO foo DEFAULT VALUES RETURNING WITH (nonsuch AS something) *;
INSERT INTO foo DEFAULT VALUES RETURNING WITH (new AS foo) *;
INSERT INTO foo DEFAULT VALUES RETURNING WITH (old AS o, new AS n, old AS o) *;
INSERT INTO foo DEFAULT VALUES RETURNING WITH (old AS o, new AS n, new AS n) *;
INSERT INTO foo DEFAULT VALUES RETURNING WITH (old AS x, new AS x) *;
-- INSERT has NEW, but not OLD
EXPLAIN (verbose, costs off)
INSERT INTO foo VALUES (4)
RETURNING old.tableoid::regclass, old.ctid, old.*,
new.tableoid::regclass, new.ctid, new.*, *;
INSERT INTO foo VALUES (4)
RETURNING old.tableoid::regclass, old.ctid, old.*,
new.tableoid::regclass, new.ctid, new.*, *;
-- INSERT ... ON CONFLICT ... UPDATE has OLD and NEW
CREATE UNIQUE INDEX foo_f1_idx ON foo (f1);
EXPLAIN (verbose, costs off)
INSERT INTO foo VALUES (4, 'conflict'), (5, 'ok')
ON CONFLICT (f1) DO UPDATE SET f2 = excluded.f2||'ed', f3 = -1
RETURNING WITH (OLD AS o, NEW AS n)
o.tableoid::regclass, o.ctid, o.*,
n.tableoid::regclass, n.ctid, n.*, *;
INSERT INTO foo VALUES (4, 'conflict'), (5, 'ok')
ON CONFLICT (f1) DO UPDATE SET f2 = excluded.f2||'ed', f3 = -1
RETURNING WITH (OLD AS o, NEW AS n)
o.tableoid::regclass, o.ctid, o.*,
n.tableoid::regclass, n.ctid, n.*, *;
-- UPDATE has OLD and NEW
EXPLAIN (verbose, costs off)
UPDATE foo SET f4 = 100 WHERE f1 = 5
RETURNING old.tableoid::regclass, old.ctid, old.*, old,
new.tableoid::regclass, new.ctid, new.*, new,
old.f4::text||'->'||new.f4::text AS change;
UPDATE foo SET f4 = 100 WHERE f1 = 5
RETURNING old.tableoid::regclass, old.ctid, old.*, old,
new.tableoid::regclass, new.ctid, new.*, new,
old.f4::text||'->'||new.f4::text AS change;
-- DELETE has OLD, but not NEW
EXPLAIN (verbose, costs off)
DELETE FROM foo WHERE f1 = 5
RETURNING old.tableoid::regclass, old.ctid, old.*,
new.tableoid::regclass, new.ctid, new.*, *;
DELETE FROM foo WHERE f1 = 5
RETURNING old.tableoid::regclass, old.ctid, old.*,
new.tableoid::regclass, new.ctid, new.*, *;
-- RETURNING OLD and NEW from subquery
EXPLAIN (verbose, costs off)
INSERT INTO foo VALUES (5, 'subquery test')
RETURNING (SELECT max(old.f4 + x) FROM generate_series(1, 10) x) old_max,
(SELECT max(new.f4 + x) FROM generate_series(1, 10) x) new_max;
INSERT INTO foo VALUES (5, 'subquery test')
RETURNING (SELECT max(old.f4 + x) FROM generate_series(1, 10) x) old_max,
(SELECT max(new.f4 + x) FROM generate_series(1, 10) x) new_max;
EXPLAIN (verbose, costs off)
UPDATE foo SET f4 = 100 WHERE f1 = 5
RETURNING (SELECT old.f4 = new.f4),
(SELECT max(old.f4 + x) FROM generate_series(1, 10) x) old_max,
(SELECT max(new.f4 + x) FROM generate_series(1, 10) x) new_max;
UPDATE foo SET f4 = 100 WHERE f1 = 5
RETURNING (SELECT old.f4 = new.f4),
(SELECT max(old.f4 + x) FROM generate_series(1, 10) x) old_max,
(SELECT max(new.f4 + x) FROM generate_series(1, 10) x) new_max;
EXPLAIN (verbose, costs off)
DELETE FROM foo WHERE f1 = 5
RETURNING (SELECT max(old.f4 + x) FROM generate_series(1, 10) x) old_max,
(SELECT max(new.f4 + x) FROM generate_series(1, 10) x) new_max;
DELETE FROM foo WHERE f1 = 5
RETURNING (SELECT max(old.f4 + x) FROM generate_series(1, 10) x) old_max,
(SELECT max(new.f4 + x) FROM generate_series(1, 10) x) new_max;
-- DELETE turned into UPDATE by a rule has OLD and NEW
CREATE RULE foo_del_rule AS ON DELETE TO foo DO INSTEAD
UPDATE foo SET f2 = f2||' (deleted)', f3 = -1, f4 = -1 WHERE f1 = OLD.f1
RETURNING *;
EXPLAIN (verbose, costs off)
DELETE FROM foo WHERE f1 = 4 RETURNING old.*,new.*, *;
DELETE FROM foo WHERE f1 = 4 RETURNING old.*,new.*, *;
-- UPDATE on view with rule
EXPLAIN (verbose, costs off)
UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
RETURNING old.*, new.*, *, new.f3 - old.f3 AS delta_f3;
UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
RETURNING old.*, new.*, *, new.f3 - old.f3 AS delta_f3;
-- UPDATE on view with INSTEAD OF trigger
CREATE FUNCTION joinview_upd_trig_fn() RETURNS trigger
LANGUAGE plpgsql AS
$$
BEGIN
RAISE NOTICE 'UPDATE: % -> %', old, new;
UPDATE foo SET f1 = new.f1, f3 = new.f3, f4 = new.f4 * 10
FROM joinme WHERE f2 = f2j AND f2 = old.f2
RETURNING new.f1, new.f4 INTO new.f1, new.f4; -- should fail
RETURN NEW;
END;
$$;
CREATE TRIGGER joinview_upd_trig INSTEAD OF UPDATE ON joinview
FOR EACH ROW EXECUTE FUNCTION joinview_upd_trig_fn();
DROP RULE joinview_u ON joinview;
UPDATE joinview SET f3 = f3 + 1, f4 = 7 WHERE f3 = 58
RETURNING old.*, new.*, *, new.f3 - old.f3 AS delta_f3; -- should fail
CREATE OR REPLACE FUNCTION joinview_upd_trig_fn() RETURNS trigger
LANGUAGE plpgsql AS
$$
BEGIN
RAISE NOTICE 'UPDATE: % -> %', old, new;
UPDATE foo SET f1 = new.f1, f3 = new.f3, f4 = new.f4 * 10
FROM joinme WHERE f2 = f2j AND f2 = old.f2
RETURNING WITH (new AS n) new.f1, n.f4 INTO new.f1, new.f4; -- now ok
RETURN NEW;
END;
$$;
EXPLAIN (verbose, costs off)
UPDATE joinview SET f3 = f3 + 1, f4 = 7 WHERE f3 = 58
RETURNING old.*, new.*, *, new.f3 - old.f3 AS delta_f3;
UPDATE joinview SET f3 = f3 + 1, f4 = 7 WHERE f3 = 58
RETURNING old.*, new.*, *, new.f3 - old.f3 AS delta_f3; -- should succeed
-- Test wholerow & dropped column handling
ALTER TABLE foo DROP COLUMN f3 CASCADE;
UPDATE foo SET f4 = f4 + 1 RETURNING old.f3; -- should fail
UPDATE foo SET f4 = f4 + 1 RETURNING old, new;
-- INSERT/DELETE on zero column table
CREATE TABLE zerocol();
INSERT INTO zerocol SELECT RETURNING old.*, new.*, *;
INSERT INTO zerocol SELECT
RETURNING old.tableoid::regclass, old.ctid,
new.tableoid::regclass, new.ctid, ctid, *;
DELETE FROM zerocol
RETURNING old.tableoid::regclass, old.ctid,
new.tableoid::regclass, new.ctid, ctid, *;
DROP TABLE zerocol;
-- Test cross-partition updates and attribute mapping
CREATE TABLE foo_parted (a int, b float8, c text) PARTITION BY LIST (a);
CREATE TABLE foo_part_s1 PARTITION OF foo_parted FOR VALUES IN (1);
CREATE TABLE foo_part_s2 PARTITION OF foo_parted FOR VALUES IN (2);
CREATE TABLE foo_part_d1 (c text, a int, b float8);
ALTER TABLE foo_parted ATTACH PARTITION foo_part_d1 FOR VALUES IN (3);
CREATE TABLE foo_part_d2 (b float8, c text, a int);
ALTER TABLE foo_parted ATTACH PARTITION foo_part_d2 FOR VALUES IN (4);
INSERT INTO foo_parted
VALUES (1, 17.1, 'P1'), (2, 17.2, 'P2'), (3, 17.3, 'P3'), (4, 17.4, 'P4')
RETURNING old.tableoid::regclass, old.ctid, old.*,
new.tableoid::regclass, new.ctid, new.*, *;
UPDATE foo_parted SET a = 2, b = b + 1, c = c || '->P2' WHERE a = 1
RETURNING old.tableoid::regclass, old.ctid, old.*,
new.tableoid::regclass, new.ctid, new.*, *;
UPDATE foo_parted SET a = 1, b = b + 1, c = c || '->P1' WHERE a = 3
RETURNING old.tableoid::regclass, old.ctid, old.*,
new.tableoid::regclass, new.ctid, new.*, *;
UPDATE foo_parted SET a = 3, b = b + 1, c = c || '->P3' WHERE a = 1
RETURNING old.tableoid::regclass, old.ctid, old.*,
new.tableoid::regclass, new.ctid, new.*, *;
UPDATE foo_parted SET a = 4, b = b + 1, c = c || '->P4' WHERE a = 3
RETURNING old.tableoid::regclass, old.ctid, old.*,
new.tableoid::regclass, new.ctid, new.*, *;
-- cross-partition update that uses ReturningExpr nodes, without returning
-- old/new table values
CREATE VIEW foo_parted_v AS SELECT *, 'xxx' AS dummy FROM foo_parted;
UPDATE foo_parted_v SET a = 1, c = c || '->P1' WHERE a = 2 AND c = 'P2'
RETURNING 'P2:'||old.dummy, 'P1:'||new.dummy;
DELETE FROM foo_parted
RETURNING old.tableoid::regclass, old.ctid, old.*,
new.tableoid::regclass, new.ctid, new.*, *;
DROP TABLE foo_parted CASCADE;
-- Test deparsing
CREATE FUNCTION foo_update()
RETURNS void
LANGUAGE sql
BEGIN ATOMIC
WITH u1 AS (
UPDATE foo SET f1 = f1 + 1 RETURNING old.*, new.*
), u2 AS (
UPDATE foo SET f1 = f1 + 1 RETURNING WITH (OLD AS "old foo") "old foo".*, new.*
), u3 AS (
UPDATE foo SET f1 = f1 + 1 RETURNING WITH (NEW AS "new foo") old.*, "new foo".*
)
UPDATE foo SET f1 = f1 + 1
RETURNING WITH (OLD AS o, NEW AS n)
o.*, n.*, o, n, o.f1 = n.f1, o = n,
(SELECT o.f2 = n.f2),
(SELECT count(*) FROM foo WHERE foo.f1 = o.f4),
(SELECT count(*) FROM foo WHERE foo.f4 = n.f4),
(SELECT count(*) FROM foo WHERE foo = o),
(SELECT count(*) FROM foo WHERE foo = n);
END;
\sf foo_update
DROP FUNCTION foo_update;

View File

@@ -1294,7 +1294,10 @@ 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 TABLE(action text, a int, b text, id int, data text, filling int[])
RETURNS TABLE(action text, a int, b text,
id int, data text, filling int[],
old_id int, old_data text, old_filling int[],
new_id int, new_data text, new_filling int[])
LANGUAGE sql
BEGIN ATOMIC
MERGE INTO sf_target t
@@ -1333,7 +1336,8 @@ WHEN NOT MATCHED
THEN INSERT (filling[1], id)
VALUES (s.a, s.a)
RETURNING
merge_action() AS action, *;
WITH (OLD AS o, NEW AS n)
merge_action() AS action, *, o.*, n.*;
END;
\sf merge_sf_test

View File

@@ -154,7 +154,8 @@ DROP SEQUENCE uv_seq CASCADE;
CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a>0;
CREATE VIEW rw_view1 AS
SELECT *, 'Const' AS c, (SELECT concat('b: ', b)) AS d FROM base_tbl WHERE a>0;
SELECT table_name, is_insertable_into
FROM information_schema.tables
@@ -175,13 +176,18 @@ UPDATE rw_view1 SET a=5 WHERE a=4;
DELETE FROM rw_view1 WHERE b='Row 2';
SELECT * FROM base_tbl;
SET jit_above_cost = 0;
MERGE INTO rw_view1 t
USING (VALUES (0, 'ROW 0'), (1, 'ROW 1'),
(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)
RETURNING merge_action(), v.*, t.*;
RETURNING merge_action(), v.*, old, new, old.*, new.*, t.*;
SET jit_above_cost TO DEFAULT;
SELECT * FROM base_tbl ORDER BY a;
MERGE INTO rw_view1 t
@@ -191,7 +197,7 @@ MERGE INTO rw_view1 t
WHEN MATCHED THEN DELETE
WHEN NOT MATCHED BY SOURCE THEN DELETE
WHEN NOT MATCHED AND a > 0 THEN INSERT (a) VALUES (v.a)
RETURNING merge_action(), v.*, t.*;
RETURNING merge_action(), v.*, old, new, old.*, new.*, t.*;
SELECT * FROM base_tbl ORDER BY a;
EXPLAIN (costs off) UPDATE rw_view1 SET a=6 WHERE a=5;
@@ -240,8 +246,10 @@ DROP TABLE base_tbl_hist;
CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
CREATE VIEW rw_view1 AS SELECT b AS bb, a AS aa FROM base_tbl WHERE a>0;
CREATE VIEW rw_view2 AS SELECT aa AS aaa, bb AS bbb FROM rw_view1 WHERE aa<10;
CREATE VIEW rw_view1 AS
SELECT b AS bb, a AS aa, 'Const1' AS c FROM base_tbl WHERE a>0;
CREATE VIEW rw_view2 AS
SELECT aa AS aaa, bb AS bbb, c AS c1, 'Const2' AS c2 FROM rw_view1 WHERE aa<10;
SELECT table_name, is_insertable_into
FROM information_schema.tables
@@ -268,7 +276,7 @@ MERGE INTO rw_view2 t
WHEN MATCHED AND aaa = 3 THEN DELETE
WHEN MATCHED THEN UPDATE SET bbb = v.b
WHEN NOT MATCHED THEN INSERT (aaa) VALUES (v.a)
RETURNING merge_action(), v.*, t.*;
RETURNING merge_action(), v.*, (SELECT old), (SELECT (SELECT new)), t.*;
SELECT * FROM rw_view2 ORDER BY aaa;
MERGE INTO rw_view2 t
@@ -277,7 +285,7 @@ MERGE INTO rw_view2 t
WHEN MATCHED THEN UPDATE SET bbb = v.b
WHEN NOT MATCHED THEN INSERT (aaa) VALUES (v.a)
WHEN NOT MATCHED BY SOURCE THEN UPDATE SET bbb = 'Not matched by source'
RETURNING merge_action(), v.*, t.*;
RETURNING merge_action(), v.*, old, (SELECT new FROM (VALUES ((SELECT new)))), t.*;
SELECT * FROM rw_view2 ORDER BY aaa;
EXPLAIN (costs off) UPDATE rw_view2 SET aaa=5 WHERE aaa=4;
@@ -362,10 +370,14 @@ SELECT table_name, column_name, is_updatable
WHERE table_name LIKE 'rw_view%'
ORDER BY table_name, ordinal_position;
INSERT INTO rw_view2 VALUES (3, 'Row 3') RETURNING *;
UPDATE rw_view2 SET b='Row three' WHERE a=3 RETURNING *;
INSERT INTO rw_view2 VALUES (3, 'Row 3') RETURNING old.*, new.*;
UPDATE rw_view2 SET b='R3' WHERE a=3 RETURNING old.*, new.*; -- rule returns NEW
DROP RULE rw_view1_upd_rule ON rw_view1;
CREATE RULE rw_view1_upd_rule AS ON UPDATE TO rw_view1
DO INSTEAD UPDATE base_tbl SET b=NEW.b WHERE a=OLD.a RETURNING *;
UPDATE rw_view2 SET b='Row three' WHERE a=3 RETURNING old.*, new.*;
SELECT * FROM rw_view2;
DELETE FROM rw_view2 WHERE a=3 RETURNING *;
DELETE FROM rw_view2 WHERE a=3 RETURNING old.*, new.*;
SELECT * FROM rw_view2;
MERGE INTO rw_view2 t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
@@ -381,8 +393,10 @@ DROP TABLE base_tbl CASCADE;
CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a>0 OFFSET 0; -- not updatable without rules/triggers
CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a<10;
CREATE VIEW rw_view1 AS
SELECT *, 'Const1' AS c1 FROM base_tbl WHERE a>0 OFFSET 0; -- not updatable without rules/triggers
CREATE VIEW rw_view2 AS
SELECT *, 'Const2' AS c2 FROM rw_view1 WHERE a<10;
SELECT table_name, is_insertable_into
FROM information_schema.tables
@@ -407,9 +421,11 @@ $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO base_tbl VALUES (NEW.a, NEW.b);
NEW.c1 = 'Trigger Const1';
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
UPDATE base_tbl SET b=NEW.b WHERE a=OLD.a;
NEW.c1 = 'Trigger Const1';
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
DELETE FROM base_tbl WHERE a=OLD.a;
@@ -479,10 +495,10 @@ SELECT table_name, column_name, is_updatable
WHERE table_name LIKE 'rw_view%'
ORDER BY table_name, ordinal_position;
INSERT INTO rw_view2 VALUES (3, 'Row 3') RETURNING *;
UPDATE rw_view2 SET b='Row three' WHERE a=3 RETURNING *;
INSERT INTO rw_view2 VALUES (3, 'Row 3') RETURNING old.*, new.*;
UPDATE rw_view2 SET b='Row three' WHERE a=3 RETURNING old.*, new.*;
SELECT * FROM rw_view2;
DELETE FROM rw_view2 WHERE a=3 RETURNING *;
DELETE FROM rw_view2 WHERE a=3 RETURNING old.*, new.*;
SELECT * FROM rw_view2;
MERGE INTO rw_view2 t
@@ -490,7 +506,7 @@ MERGE INTO rw_view2 t
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)
RETURNING merge_action(), s.*, t.*;
RETURNING merge_action(), s.*, old, new, t.*;
SELECT * FROM base_tbl ORDER BY a;
MERGE INTO rw_view2 t
@@ -498,7 +514,7 @@ MERGE INTO rw_view2 t
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 BY SOURCE THEN UPDATE SET b = 'Not matched by source'
RETURNING merge_action(), s.*, t.*;
RETURNING merge_action(), s.*, old, new, t.*;
SELECT * FROM base_tbl ORDER BY a;
EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;