mirror of
https://github.com/postgres/postgres.git
synced 2025-07-27 12:41:57 +03:00
MERGE SQL Command following SQL:2016
MERGE performs actions that modify rows in the target table using a source table or query. MERGE provides a single SQL statement that can conditionally INSERT/UPDATE/DELETE rows a task that would other require multiple PL statements. e.g. MERGE INTO target AS t USING source AS s ON t.tid = s.sid WHEN MATCHED AND t.balance > s.delta THEN UPDATE SET balance = t.balance - s.delta WHEN MATCHED THEN DELETE WHEN NOT MATCHED AND s.delta > 0 THEN INSERT VALUES (s.sid, s.delta) WHEN NOT MATCHED THEN DO NOTHING; MERGE works with regular and partitioned tables, including column and row security enforcement, as well as support for row, statement and transition triggers. MERGE is optimized for OLTP and is parameterizable, though also useful for large scale ETL/ELT. MERGE is not intended to be used in preference to existing single SQL commands for INSERT, UPDATE or DELETE since there is some overhead. MERGE can be used statically from PL/pgSQL. MERGE does not yet support inheritance, write rules, RETURNING clauses, updatable views or foreign tables. MERGE follows SQL Standard per the most recent SQL:2016. Includes full tests and documentation, including full isolation tests to demonstrate the concurrent behavior. This version written from scratch in 2017 by Simon Riggs, using docs and tests originally written in 2009. Later work from Pavan Deolasee has been both complex and deep, leaving the lead author credit now in his hands. Extensive discussion of concurrency from Peter Geoghegan, with thanks for the time and effort contributed. Various issues reported via sqlsmith by Andreas Seltenreich Authors: Pavan Deolasee, Simon Riggs Reviewer: Peter Geoghegan, Amit Langote, Tomas Vondra, Simon Riggs Discussion: https://postgr.es/m/CANP8+jKitBSrB7oTgT9CY2i1ObfOt36z0XMraQc+Xrz8QB0nXA@mail.gmail.com https://postgr.es/m/CAH2-WzkJdBuxj9PO=2QaO9-3h3xGbQPZ34kJH=HukRekwM-GZg@mail.gmail.com
This commit is contained in:
@ -33,6 +33,10 @@ test: insert-conflict-do-update
|
||||
test: insert-conflict-do-update-2
|
||||
test: insert-conflict-do-update-3
|
||||
test: insert-conflict-toast
|
||||
test: merge-insert-update
|
||||
test: merge-delete
|
||||
test: merge-update
|
||||
test: merge-match-recheck
|
||||
test: delete-abort-savept
|
||||
test: delete-abort-savept-2
|
||||
test: aborted-keyrevoke
|
||||
|
@ -386,3 +386,58 @@ CREATE TABLE itest_child PARTITION OF itest_parent (
|
||||
) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error
|
||||
ERROR: identity columns are not supported on partitions
|
||||
DROP TABLE itest_parent;
|
||||
-- MERGE tests
|
||||
CREATE TABLE itest14 (a int GENERATED ALWAYS AS IDENTITY, b text);
|
||||
CREATE TABLE itest15 (a int GENERATED BY DEFAULT AS IDENTITY, b text);
|
||||
MERGE INTO itest14 t
|
||||
USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
|
||||
ON t.a = s.s_a
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (a, b) VALUES (s.s_a, s.s_b);
|
||||
ERROR: cannot insert into column "a"
|
||||
DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
|
||||
HINT: Use OVERRIDING SYSTEM VALUE to override.
|
||||
MERGE INTO itest14 t
|
||||
USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
|
||||
ON t.a = s.s_a
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
|
||||
ERROR: cannot insert into column "a"
|
||||
DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS.
|
||||
HINT: Use OVERRIDING SYSTEM VALUE to override.
|
||||
MERGE INTO itest14 t
|
||||
USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
|
||||
ON t.a = s.s_a
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
|
||||
MERGE INTO itest15 t
|
||||
USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
|
||||
ON t.a = s.s_a
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (a, b) VALUES (s.s_a, s.s_b);
|
||||
MERGE INTO itest15 t
|
||||
USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
|
||||
ON t.a = s.s_a
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
|
||||
MERGE INTO itest15 t
|
||||
USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
|
||||
ON t.a = s.s_a
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
|
||||
SELECT * FROM itest14;
|
||||
a | b
|
||||
----+-------------------
|
||||
30 | inserted by merge
|
||||
(1 row)
|
||||
|
||||
SELECT * FROM itest15;
|
||||
a | b
|
||||
----+-------------------
|
||||
10 | inserted by merge
|
||||
1 | inserted by merge
|
||||
30 | inserted by merge
|
||||
(3 rows)
|
||||
|
||||
DROP TABLE itest14;
|
||||
DROP TABLE itest15;
|
||||
|
@ -517,6 +517,104 @@ SELECT atest6 FROM atest6; -- ok
|
||||
(0 rows)
|
||||
|
||||
COPY atest6 TO stdout; -- ok
|
||||
-- test column privileges with MERGE
|
||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
||||
CREATE TABLE mtarget (a int, b text);
|
||||
CREATE TABLE msource (a int, b text);
|
||||
INSERT INTO mtarget VALUES (1, 'init1'), (2, 'init2');
|
||||
INSERT INTO msource VALUES (1, 'source1'), (2, 'source2'), (3, 'source3');
|
||||
GRANT SELECT (a) ON msource TO regress_priv_user4;
|
||||
GRANT SELECT (a) ON mtarget TO regress_priv_user4;
|
||||
GRANT INSERT (a,b) ON mtarget TO regress_priv_user4;
|
||||
GRANT UPDATE (b) ON mtarget TO regress_priv_user4;
|
||||
SET SESSION AUTHORIZATION regress_priv_user4;
|
||||
--
|
||||
-- test source privileges
|
||||
--
|
||||
-- fail (no SELECT priv on s.b)
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET b = s.b
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, NULL);
|
||||
ERROR: permission denied for table msource
|
||||
-- fail (s.b used in the INSERTed values)
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET b = 'x'
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, b);
|
||||
ERROR: permission denied for table msource
|
||||
-- fail (s.b used in the WHEN quals)
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED AND s.b = 'x' THEN
|
||||
UPDATE SET b = 'x'
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, NULL);
|
||||
ERROR: permission denied for table msource
|
||||
-- this should be ok since only s.a is accessed
|
||||
BEGIN;
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET b = 'ok'
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, NULL);
|
||||
ROLLBACK;
|
||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
||||
GRANT SELECT (b) ON msource TO regress_priv_user4;
|
||||
SET SESSION AUTHORIZATION regress_priv_user4;
|
||||
-- should now be ok
|
||||
BEGIN;
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET b = s.b
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, b);
|
||||
ROLLBACK;
|
||||
--
|
||||
-- test target privileges
|
||||
--
|
||||
-- fail (no SELECT priv on t.b)
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET b = t.b
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, NULL);
|
||||
ERROR: permission denied for table mtarget
|
||||
-- fail (no UPDATE on t.a)
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET b = s.b, a = t.a + 1
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, b);
|
||||
ERROR: permission denied for table mtarget
|
||||
-- fail (no SELECT on t.b)
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED AND t.b IS NOT NULL THEN
|
||||
UPDATE SET b = s.b
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, b);
|
||||
ERROR: permission denied for table mtarget
|
||||
-- ok
|
||||
BEGIN;
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET b = s.b;
|
||||
ROLLBACK;
|
||||
-- fail (no DELETE)
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED AND t.b IS NOT NULL THEN
|
||||
DELETE;
|
||||
ERROR: permission denied for table mtarget
|
||||
-- grant delete privileges
|
||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
||||
GRANT DELETE ON mtarget TO regress_priv_user4;
|
||||
-- should be ok now
|
||||
BEGIN;
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED AND t.b IS NOT NULL THEN
|
||||
DELETE;
|
||||
ROLLBACK;
|
||||
-- check error reporting with column privs
|
||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
||||
CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2));
|
||||
|
@ -2138,6 +2138,188 @@ ERROR: new row violates row-level security policy (USING expression) for table
|
||||
INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')
|
||||
ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol';
|
||||
ERROR: new row violates row-level security policy for table "document"
|
||||
--
|
||||
-- MERGE
|
||||
--
|
||||
RESET SESSION AUTHORIZATION;
|
||||
DROP POLICY p3_with_all ON document;
|
||||
ALTER TABLE document ADD COLUMN dnotes text DEFAULT '';
|
||||
-- all documents are readable
|
||||
CREATE POLICY p1 ON document FOR SELECT USING (true);
|
||||
-- one may insert documents only authored by them
|
||||
CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);
|
||||
-- one may only update documents in 'novel' category
|
||||
CREATE POLICY p3 ON document FOR UPDATE
|
||||
USING (cid = (SELECT cid from category WHERE cname = 'novel'))
|
||||
WITH CHECK (dauthor = current_user);
|
||||
-- one may only delete documents in 'manga' category
|
||||
CREATE POLICY p4 ON document FOR DELETE
|
||||
USING (cid = (SELECT cid from category WHERE cname = 'manga'));
|
||||
SELECT * FROM document;
|
||||
did | cid | dlevel | dauthor | dtitle | dnotes
|
||||
-----+-----+--------+-------------------+----------------------------------+--------
|
||||
1 | 11 | 1 | regress_rls_bob | my first novel |
|
||||
3 | 22 | 2 | regress_rls_bob | my science fiction |
|
||||
4 | 44 | 1 | regress_rls_bob | my first manga |
|
||||
5 | 44 | 2 | regress_rls_bob | my second manga |
|
||||
6 | 22 | 1 | regress_rls_carol | great science fiction |
|
||||
7 | 33 | 2 | regress_rls_carol | great technology book |
|
||||
8 | 44 | 1 | regress_rls_carol | great manga |
|
||||
9 | 22 | 1 | regress_rls_dave | awesome science fiction |
|
||||
10 | 33 | 2 | regress_rls_dave | awesome technology book |
|
||||
11 | 33 | 1 | regress_rls_carol | hoge |
|
||||
33 | 22 | 1 | regress_rls_bob | okay science fiction |
|
||||
2 | 11 | 2 | regress_rls_bob | my first novel |
|
||||
78 | 33 | 1 | regress_rls_bob | some technology novel |
|
||||
79 | 33 | 1 | regress_rls_bob | technology book, can only insert |
|
||||
(14 rows)
|
||||
|
||||
SET SESSION AUTHORIZATION regress_rls_bob;
|
||||
-- Fails, since update violates WITH CHECK qual on dauthor
|
||||
MERGE INTO document d
|
||||
USING (SELECT 1 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge1 ', dauthor = 'regress_rls_alice';
|
||||
ERROR: new row violates row-level security policy for table "document"
|
||||
-- Should be OK since USING and WITH CHECK quals pass
|
||||
MERGE INTO document d
|
||||
USING (SELECT 1 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge2 ';
|
||||
-- Even when dauthor is updated explicitly, but to the existing value
|
||||
MERGE INTO document d
|
||||
USING (SELECT 1 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge3 ', dauthor = 'regress_rls_bob';
|
||||
-- There is a MATCH for did = 3, but UPDATE's USING qual does not allow
|
||||
-- updating an item in category 'science fiction'
|
||||
MERGE INTO document d
|
||||
USING (SELECT 3 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge ';
|
||||
ERROR: target row violates row-level security policy (USING expression) for table "document"
|
||||
-- The same thing with DELETE action, but fails again because no permissions
|
||||
-- to delete items in 'science fiction' category that did 3 belongs to.
|
||||
MERGE INTO document d
|
||||
USING (SELECT 3 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
DELETE;
|
||||
ERROR: target row violates row-level security policy (USING expression) for table "document"
|
||||
-- Document with did 4 belongs to 'manga' category which is allowed for
|
||||
-- deletion. But this fails because the UPDATE action is matched first and
|
||||
-- UPDATE policy does not allow updation in the category.
|
||||
MERGE INTO document d
|
||||
USING (SELECT 4 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED AND dnotes = '' THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
||||
WHEN MATCHED THEN
|
||||
DELETE;
|
||||
ERROR: target row violates row-level security policy (USING expression) for table "document"
|
||||
-- UPDATE action is not matched this time because of the WHEN AND qual.
|
||||
-- DELETE still fails because role regress_rls_bob does not have SELECT
|
||||
-- privileges on 'manga' category row in the category table.
|
||||
MERGE INTO document d
|
||||
USING (SELECT 4 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED AND dnotes <> '' THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
||||
WHEN MATCHED THEN
|
||||
DELETE;
|
||||
ERROR: target row violates row-level security policy (USING expression) for table "document"
|
||||
SELECT * FROM document WHERE did = 4;
|
||||
did | cid | dlevel | dauthor | dtitle | dnotes
|
||||
-----+-----+--------+-----------------+----------------+--------
|
||||
4 | 44 | 1 | regress_rls_bob | my first manga |
|
||||
(1 row)
|
||||
|
||||
-- Switch to regress_rls_carol role and try the DELETE again. It should succeed
|
||||
-- this time
|
||||
RESET SESSION AUTHORIZATION;
|
||||
SET SESSION AUTHORIZATION regress_rls_carol;
|
||||
MERGE INTO document d
|
||||
USING (SELECT 4 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED AND dnotes <> '' THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
||||
WHEN MATCHED THEN
|
||||
DELETE;
|
||||
-- Switch back to regress_rls_bob role
|
||||
RESET SESSION AUTHORIZATION;
|
||||
SET SESSION AUTHORIZATION regress_rls_bob;
|
||||
-- Try INSERT action. This fails because we are trying to insert
|
||||
-- dauthor = regress_rls_dave and INSERT's WITH CHECK does not allow
|
||||
-- that
|
||||
MERGE INTO document d
|
||||
USING (SELECT 12 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
DELETE
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (12, 11, 1, 'regress_rls_dave', 'another novel');
|
||||
ERROR: new row violates row-level security policy for table "document"
|
||||
-- This should be fine
|
||||
MERGE INTO document d
|
||||
USING (SELECT 12 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
DELETE
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
||||
-- ok
|
||||
MERGE INTO document d
|
||||
USING (SELECT 1 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge4 '
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
||||
-- drop and create a new SELECT policy which prevents us from reading
|
||||
-- any document except with category 'magna'
|
||||
RESET SESSION AUTHORIZATION;
|
||||
DROP POLICY p1 ON document;
|
||||
CREATE POLICY p1 ON document FOR SELECT
|
||||
USING (cid = (SELECT cid from category WHERE cname = 'manga'));
|
||||
SET SESSION AUTHORIZATION regress_rls_bob;
|
||||
-- MERGE can no longer see the matching row and hence attempts the
|
||||
-- NOT MATCHED action, which results in unique key violation
|
||||
MERGE INTO document d
|
||||
USING (SELECT 1 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge5 '
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
||||
ERROR: duplicate key value violates unique constraint "document_pkey"
|
||||
RESET SESSION AUTHORIZATION;
|
||||
-- drop the restrictive SELECT policy so that we can look at the
|
||||
-- final state of the table
|
||||
DROP POLICY p1 ON document;
|
||||
-- Just check everything went per plan
|
||||
SELECT * FROM document;
|
||||
did | cid | dlevel | dauthor | dtitle | dnotes
|
||||
-----+-----+--------+-------------------+----------------------------------+-----------------------------------------------------------------------
|
||||
3 | 22 | 2 | regress_rls_bob | my science fiction |
|
||||
5 | 44 | 2 | regress_rls_bob | my second manga |
|
||||
6 | 22 | 1 | regress_rls_carol | great science fiction |
|
||||
7 | 33 | 2 | regress_rls_carol | great technology book |
|
||||
8 | 44 | 1 | regress_rls_carol | great manga |
|
||||
9 | 22 | 1 | regress_rls_dave | awesome science fiction |
|
||||
10 | 33 | 2 | regress_rls_dave | awesome technology book |
|
||||
11 | 33 | 1 | regress_rls_carol | hoge |
|
||||
33 | 22 | 1 | regress_rls_bob | okay science fiction |
|
||||
2 | 11 | 2 | regress_rls_bob | my first novel |
|
||||
78 | 33 | 1 | regress_rls_bob | some technology novel |
|
||||
79 | 33 | 1 | regress_rls_bob | technology book, can only insert |
|
||||
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
|
||||
(14 rows)
|
||||
|
||||
--
|
||||
-- ROLE/GROUP
|
||||
--
|
||||
|
@ -3265,6 +3265,37 @@ CREATE RULE rules_parted_table_insert AS ON INSERT to rules_parted_table
|
||||
ALTER RULE rules_parted_table_insert ON rules_parted_table RENAME TO rules_parted_table_insert_redirect;
|
||||
DROP TABLE rules_parted_table;
|
||||
--
|
||||
-- test MERGE
|
||||
--
|
||||
CREATE TABLE rule_merge1 (a int, b text);
|
||||
CREATE TABLE rule_merge2 (a int, b text);
|
||||
CREATE RULE rule1 AS ON INSERT TO rule_merge1
|
||||
DO INSTEAD INSERT INTO rule_merge2 VALUES (NEW.*);
|
||||
CREATE RULE rule2 AS ON UPDATE TO rule_merge1
|
||||
DO INSTEAD UPDATE rule_merge2 SET a = NEW.a, b = NEW.b
|
||||
WHERE a = OLD.a;
|
||||
CREATE RULE rule3 AS ON DELETE TO rule_merge1
|
||||
DO INSTEAD DELETE FROM rule_merge2 WHERE a = OLD.a;
|
||||
-- MERGE not supported for table with rules
|
||||
MERGE INTO rule_merge1 t USING (SELECT 1 AS a) s
|
||||
ON t.a = s.a
|
||||
WHEN MATCHED AND t.a < 2 THEN
|
||||
UPDATE SET b = b || ' updated by merge'
|
||||
WHEN MATCHED AND t.a > 2 THEN
|
||||
DELETE
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (s.a, '');
|
||||
ERROR: MERGE is not supported for relations with rules
|
||||
-- should be ok with the other table though
|
||||
MERGE INTO rule_merge2 t USING (SELECT 1 AS a) s
|
||||
ON t.a = s.a
|
||||
WHEN MATCHED AND t.a < 2 THEN
|
||||
UPDATE SET b = b || ' updated by merge'
|
||||
WHEN MATCHED AND t.a > 2 THEN
|
||||
DELETE
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (s.a, '');
|
||||
--
|
||||
-- Test enabling/disabling
|
||||
--
|
||||
CREATE TABLE ruletest1 (a int);
|
||||
|
@ -2761,6 +2761,54 @@ delete from self_ref where a = 1;
|
||||
NOTICE: trigger_func(self_ref) called: action = DELETE, when = BEFORE, level = STATEMENT
|
||||
NOTICE: trigger = self_ref_s_trig, old table = (1,), (2,1), (3,2), (4,3)
|
||||
drop table self_ref;
|
||||
--
|
||||
-- test transition tables with MERGE
|
||||
--
|
||||
create table merge_target_table (a int primary key, b text);
|
||||
create trigger merge_target_table_insert_trig
|
||||
after insert on merge_target_table referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger merge_target_table_update_trig
|
||||
after update on merge_target_table referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger merge_target_table_delete_trig
|
||||
after delete on merge_target_table referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
create table merge_source_table (a int, b text);
|
||||
insert into merge_source_table
|
||||
values (1, 'initial1'), (2, 'initial2'),
|
||||
(3, 'initial3'), (4, 'initial4');
|
||||
merge into merge_target_table t
|
||||
using merge_source_table s
|
||||
on t.a = s.a
|
||||
when not matched then
|
||||
insert values (a, b);
|
||||
NOTICE: trigger = merge_target_table_insert_trig, new table = (1,initial1), (2,initial2), (3,initial3), (4,initial4)
|
||||
merge into merge_target_table t
|
||||
using merge_source_table s
|
||||
on t.a = s.a
|
||||
when matched and s.a <= 2 then
|
||||
update set b = t.b || ' updated by merge'
|
||||
when matched and s.a > 2 then
|
||||
delete
|
||||
when not matched then
|
||||
insert values (a, b);
|
||||
NOTICE: trigger = merge_target_table_delete_trig, old table = (3,initial3), (4,initial4)
|
||||
NOTICE: trigger = merge_target_table_update_trig, old table = (1,initial1), (2,initial2), new table = (1,"initial1 updated by merge"), (2,"initial2 updated by merge")
|
||||
NOTICE: trigger = merge_target_table_insert_trig, new table = <NULL>
|
||||
merge into merge_target_table t
|
||||
using merge_source_table s
|
||||
on t.a = s.a
|
||||
when matched and s.a <= 2 then
|
||||
update set b = t.b || ' updated again by merge'
|
||||
when matched and s.a > 2 then
|
||||
delete
|
||||
when not matched then
|
||||
insert values (a, b);
|
||||
NOTICE: trigger = merge_target_table_delete_trig, old table = <NULL>
|
||||
NOTICE: trigger = merge_target_table_update_trig, old table = (1,"initial1 updated by merge"), (2,"initial2 updated by merge"), new table = (1,"initial1 updated by merge updated again by merge"), (2,"initial2 updated by merge updated again by merge")
|
||||
NOTICE: trigger = merge_target_table_insert_trig, new table = (3,initial3), (4,initial4)
|
||||
drop table merge_source_table, merge_target_table;
|
||||
-- cleanup
|
||||
drop function dump_insert();
|
||||
drop function dump_update();
|
||||
|
@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
|
||||
# ----------
|
||||
# Another group of parallel tests
|
||||
# ----------
|
||||
test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password func_index
|
||||
test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password func_index merge
|
||||
|
||||
# ----------
|
||||
# Another group of parallel tests
|
||||
|
@ -123,6 +123,7 @@ test: tablesample
|
||||
test: groupingsets
|
||||
test: drop_operator
|
||||
test: password
|
||||
test: merge
|
||||
test: alter_generic
|
||||
test: alter_operator
|
||||
test: misc
|
||||
|
@ -246,3 +246,48 @@ CREATE TABLE itest_child PARTITION OF itest_parent (
|
||||
f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY
|
||||
) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error
|
||||
DROP TABLE itest_parent;
|
||||
|
||||
-- MERGE tests
|
||||
CREATE TABLE itest14 (a int GENERATED ALWAYS AS IDENTITY, b text);
|
||||
CREATE TABLE itest15 (a int GENERATED BY DEFAULT AS IDENTITY, b text);
|
||||
|
||||
MERGE INTO itest14 t
|
||||
USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
|
||||
ON t.a = s.s_a
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (a, b) VALUES (s.s_a, s.s_b);
|
||||
|
||||
MERGE INTO itest14 t
|
||||
USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
|
||||
ON t.a = s.s_a
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
|
||||
|
||||
MERGE INTO itest14 t
|
||||
USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
|
||||
ON t.a = s.s_a
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
|
||||
|
||||
MERGE INTO itest15 t
|
||||
USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s
|
||||
ON t.a = s.s_a
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (a, b) VALUES (s.s_a, s.s_b);
|
||||
|
||||
MERGE INTO itest15 t
|
||||
USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s
|
||||
ON t.a = s.s_a
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b);
|
||||
|
||||
MERGE INTO itest15 t
|
||||
USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s
|
||||
ON t.a = s.s_a
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b);
|
||||
|
||||
SELECT * FROM itest14;
|
||||
SELECT * FROM itest15;
|
||||
DROP TABLE itest14;
|
||||
DROP TABLE itest15;
|
||||
|
@ -349,6 +349,114 @@ UPDATE atest5 SET one = 1; -- fail
|
||||
SELECT atest6 FROM atest6; -- ok
|
||||
COPY atest6 TO stdout; -- ok
|
||||
|
||||
-- test column privileges with MERGE
|
||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
||||
CREATE TABLE mtarget (a int, b text);
|
||||
CREATE TABLE msource (a int, b text);
|
||||
INSERT INTO mtarget VALUES (1, 'init1'), (2, 'init2');
|
||||
INSERT INTO msource VALUES (1, 'source1'), (2, 'source2'), (3, 'source3');
|
||||
|
||||
GRANT SELECT (a) ON msource TO regress_priv_user4;
|
||||
GRANT SELECT (a) ON mtarget TO regress_priv_user4;
|
||||
GRANT INSERT (a,b) ON mtarget TO regress_priv_user4;
|
||||
GRANT UPDATE (b) ON mtarget TO regress_priv_user4;
|
||||
|
||||
SET SESSION AUTHORIZATION regress_priv_user4;
|
||||
|
||||
--
|
||||
-- test source privileges
|
||||
--
|
||||
|
||||
-- fail (no SELECT priv on s.b)
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET b = s.b
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, NULL);
|
||||
|
||||
-- fail (s.b used in the INSERTed values)
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET b = 'x'
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, b);
|
||||
|
||||
-- fail (s.b used in the WHEN quals)
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED AND s.b = 'x' THEN
|
||||
UPDATE SET b = 'x'
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, NULL);
|
||||
|
||||
-- this should be ok since only s.a is accessed
|
||||
BEGIN;
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET b = 'ok'
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, NULL);
|
||||
ROLLBACK;
|
||||
|
||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
||||
GRANT SELECT (b) ON msource TO regress_priv_user4;
|
||||
SET SESSION AUTHORIZATION regress_priv_user4;
|
||||
|
||||
-- should now be ok
|
||||
BEGIN;
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET b = s.b
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, b);
|
||||
ROLLBACK;
|
||||
|
||||
--
|
||||
-- test target privileges
|
||||
--
|
||||
|
||||
-- fail (no SELECT priv on t.b)
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET b = t.b
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, NULL);
|
||||
|
||||
-- fail (no UPDATE on t.a)
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET b = s.b, a = t.a + 1
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, b);
|
||||
|
||||
-- fail (no SELECT on t.b)
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED AND t.b IS NOT NULL THEN
|
||||
UPDATE SET b = s.b
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (a, b);
|
||||
|
||||
-- ok
|
||||
BEGIN;
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET b = s.b;
|
||||
ROLLBACK;
|
||||
|
||||
-- fail (no DELETE)
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED AND t.b IS NOT NULL THEN
|
||||
DELETE;
|
||||
|
||||
-- grant delete privileges
|
||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
||||
GRANT DELETE ON mtarget TO regress_priv_user4;
|
||||
-- should be ok now
|
||||
BEGIN;
|
||||
MERGE INTO mtarget t USING msource s ON t.a = s.a
|
||||
WHEN MATCHED AND t.b IS NOT NULL THEN
|
||||
DELETE;
|
||||
ROLLBACK;
|
||||
|
||||
-- check error reporting with column privs
|
||||
SET SESSION AUTHORIZATION regress_priv_user1;
|
||||
CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2));
|
||||
|
@ -812,6 +812,162 @@ INSERT INTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel')
|
||||
INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')
|
||||
ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol';
|
||||
|
||||
--
|
||||
-- MERGE
|
||||
--
|
||||
RESET SESSION AUTHORIZATION;
|
||||
DROP POLICY p3_with_all ON document;
|
||||
|
||||
ALTER TABLE document ADD COLUMN dnotes text DEFAULT '';
|
||||
-- all documents are readable
|
||||
CREATE POLICY p1 ON document FOR SELECT USING (true);
|
||||
-- one may insert documents only authored by them
|
||||
CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);
|
||||
-- one may only update documents in 'novel' category
|
||||
CREATE POLICY p3 ON document FOR UPDATE
|
||||
USING (cid = (SELECT cid from category WHERE cname = 'novel'))
|
||||
WITH CHECK (dauthor = current_user);
|
||||
-- one may only delete documents in 'manga' category
|
||||
CREATE POLICY p4 ON document FOR DELETE
|
||||
USING (cid = (SELECT cid from category WHERE cname = 'manga'));
|
||||
|
||||
SELECT * FROM document;
|
||||
|
||||
SET SESSION AUTHORIZATION regress_rls_bob;
|
||||
|
||||
-- Fails, since update violates WITH CHECK qual on dauthor
|
||||
MERGE INTO document d
|
||||
USING (SELECT 1 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge1 ', dauthor = 'regress_rls_alice';
|
||||
|
||||
-- Should be OK since USING and WITH CHECK quals pass
|
||||
MERGE INTO document d
|
||||
USING (SELECT 1 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge2 ';
|
||||
|
||||
-- Even when dauthor is updated explicitly, but to the existing value
|
||||
MERGE INTO document d
|
||||
USING (SELECT 1 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge3 ', dauthor = 'regress_rls_bob';
|
||||
|
||||
-- There is a MATCH for did = 3, but UPDATE's USING qual does not allow
|
||||
-- updating an item in category 'science fiction'
|
||||
MERGE INTO document d
|
||||
USING (SELECT 3 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge ';
|
||||
|
||||
-- The same thing with DELETE action, but fails again because no permissions
|
||||
-- to delete items in 'science fiction' category that did 3 belongs to.
|
||||
MERGE INTO document d
|
||||
USING (SELECT 3 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
DELETE;
|
||||
|
||||
-- Document with did 4 belongs to 'manga' category which is allowed for
|
||||
-- deletion. But this fails because the UPDATE action is matched first and
|
||||
-- UPDATE policy does not allow updation in the category.
|
||||
MERGE INTO document d
|
||||
USING (SELECT 4 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED AND dnotes = '' THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
||||
WHEN MATCHED THEN
|
||||
DELETE;
|
||||
|
||||
-- UPDATE action is not matched this time because of the WHEN AND qual.
|
||||
-- DELETE still fails because role regress_rls_bob does not have SELECT
|
||||
-- privileges on 'manga' category row in the category table.
|
||||
MERGE INTO document d
|
||||
USING (SELECT 4 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED AND dnotes <> '' THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
||||
WHEN MATCHED THEN
|
||||
DELETE;
|
||||
|
||||
SELECT * FROM document WHERE did = 4;
|
||||
|
||||
-- Switch to regress_rls_carol role and try the DELETE again. It should succeed
|
||||
-- this time
|
||||
RESET SESSION AUTHORIZATION;
|
||||
SET SESSION AUTHORIZATION regress_rls_carol;
|
||||
|
||||
MERGE INTO document d
|
||||
USING (SELECT 4 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED AND dnotes <> '' THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
||||
WHEN MATCHED THEN
|
||||
DELETE;
|
||||
|
||||
-- Switch back to regress_rls_bob role
|
||||
RESET SESSION AUTHORIZATION;
|
||||
SET SESSION AUTHORIZATION regress_rls_bob;
|
||||
|
||||
-- Try INSERT action. This fails because we are trying to insert
|
||||
-- dauthor = regress_rls_dave and INSERT's WITH CHECK does not allow
|
||||
-- that
|
||||
MERGE INTO document d
|
||||
USING (SELECT 12 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
DELETE
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (12, 11, 1, 'regress_rls_dave', 'another novel');
|
||||
|
||||
-- This should be fine
|
||||
MERGE INTO document d
|
||||
USING (SELECT 12 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
DELETE
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
||||
|
||||
-- ok
|
||||
MERGE INTO document d
|
||||
USING (SELECT 1 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge4 '
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
||||
|
||||
-- drop and create a new SELECT policy which prevents us from reading
|
||||
-- any document except with category 'magna'
|
||||
RESET SESSION AUTHORIZATION;
|
||||
DROP POLICY p1 ON document;
|
||||
CREATE POLICY p1 ON document FOR SELECT
|
||||
USING (cid = (SELECT cid from category WHERE cname = 'manga'));
|
||||
|
||||
SET SESSION AUTHORIZATION regress_rls_bob;
|
||||
|
||||
-- MERGE can no longer see the matching row and hence attempts the
|
||||
-- NOT MATCHED action, which results in unique key violation
|
||||
MERGE INTO document d
|
||||
USING (SELECT 1 as sdid) s
|
||||
ON did = s.sdid
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET dnotes = dnotes || ' notes added by merge5 '
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
||||
|
||||
RESET SESSION AUTHORIZATION;
|
||||
-- drop the restrictive SELECT policy so that we can look at the
|
||||
-- final state of the table
|
||||
DROP POLICY p1 ON document;
|
||||
-- Just check everything went per plan
|
||||
SELECT * FROM document;
|
||||
|
||||
--
|
||||
-- ROLE/GROUP
|
||||
--
|
||||
|
@ -1191,6 +1191,39 @@ CREATE RULE rules_parted_table_insert AS ON INSERT to rules_parted_table
|
||||
ALTER RULE rules_parted_table_insert ON rules_parted_table RENAME TO rules_parted_table_insert_redirect;
|
||||
DROP TABLE rules_parted_table;
|
||||
|
||||
--
|
||||
-- test MERGE
|
||||
--
|
||||
CREATE TABLE rule_merge1 (a int, b text);
|
||||
CREATE TABLE rule_merge2 (a int, b text);
|
||||
CREATE RULE rule1 AS ON INSERT TO rule_merge1
|
||||
DO INSTEAD INSERT INTO rule_merge2 VALUES (NEW.*);
|
||||
CREATE RULE rule2 AS ON UPDATE TO rule_merge1
|
||||
DO INSTEAD UPDATE rule_merge2 SET a = NEW.a, b = NEW.b
|
||||
WHERE a = OLD.a;
|
||||
CREATE RULE rule3 AS ON DELETE TO rule_merge1
|
||||
DO INSTEAD DELETE FROM rule_merge2 WHERE a = OLD.a;
|
||||
|
||||
-- MERGE not supported for table with rules
|
||||
MERGE INTO rule_merge1 t USING (SELECT 1 AS a) s
|
||||
ON t.a = s.a
|
||||
WHEN MATCHED AND t.a < 2 THEN
|
||||
UPDATE SET b = b || ' updated by merge'
|
||||
WHEN MATCHED AND t.a > 2 THEN
|
||||
DELETE
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (s.a, '');
|
||||
|
||||
-- should be ok with the other table though
|
||||
MERGE INTO rule_merge2 t USING (SELECT 1 AS a) s
|
||||
ON t.a = s.a
|
||||
WHEN MATCHED AND t.a < 2 THEN
|
||||
UPDATE SET b = b || ' updated by merge'
|
||||
WHEN MATCHED AND t.a > 2 THEN
|
||||
DELETE
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (s.a, '');
|
||||
|
||||
--
|
||||
-- Test enabling/disabling
|
||||
--
|
||||
|
@ -2110,6 +2110,53 @@ delete from self_ref where a = 1;
|
||||
|
||||
drop table self_ref;
|
||||
|
||||
--
|
||||
-- test transition tables with MERGE
|
||||
--
|
||||
create table merge_target_table (a int primary key, b text);
|
||||
create trigger merge_target_table_insert_trig
|
||||
after insert on merge_target_table referencing new table as new_table
|
||||
for each statement execute procedure dump_insert();
|
||||
create trigger merge_target_table_update_trig
|
||||
after update on merge_target_table referencing old table as old_table new table as new_table
|
||||
for each statement execute procedure dump_update();
|
||||
create trigger merge_target_table_delete_trig
|
||||
after delete on merge_target_table referencing old table as old_table
|
||||
for each statement execute procedure dump_delete();
|
||||
|
||||
create table merge_source_table (a int, b text);
|
||||
insert into merge_source_table
|
||||
values (1, 'initial1'), (2, 'initial2'),
|
||||
(3, 'initial3'), (4, 'initial4');
|
||||
|
||||
merge into merge_target_table t
|
||||
using merge_source_table s
|
||||
on t.a = s.a
|
||||
when not matched then
|
||||
insert values (a, b);
|
||||
|
||||
merge into merge_target_table t
|
||||
using merge_source_table s
|
||||
on t.a = s.a
|
||||
when matched and s.a <= 2 then
|
||||
update set b = t.b || ' updated by merge'
|
||||
when matched and s.a > 2 then
|
||||
delete
|
||||
when not matched then
|
||||
insert values (a, b);
|
||||
|
||||
merge into merge_target_table t
|
||||
using merge_source_table s
|
||||
on t.a = s.a
|
||||
when matched and s.a <= 2 then
|
||||
update set b = t.b || ' updated again by merge'
|
||||
when matched and s.a > 2 then
|
||||
delete
|
||||
when not matched then
|
||||
insert values (a, b);
|
||||
|
||||
drop table merge_source_table, merge_target_table;
|
||||
|
||||
-- cleanup
|
||||
drop function dump_insert();
|
||||
drop function dump_update();
|
||||
|
Reference in New Issue
Block a user