summaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
authorSimon Riggs2018-04-02 20:12:47 +0000
committerSimon Riggs2018-04-02 20:12:47 +0000
commit354f13855e6381d288dfaa52bcd4f2cb0fd4a5eb (patch)
tree92710660450acee59be62dea485cc26ab147f332 /src/test
parente6597dc3533946b98acba7871bd4ca1f7a3d4c1d (diff)
Modified files for MERGE
Diffstat (limited to 'src/test')
-rw-r--r--src/test/isolation/isolation_schedule4
-rw-r--r--src/test/regress/expected/identity.out55
-rw-r--r--src/test/regress/expected/privileges.out98
-rw-r--r--src/test/regress/expected/rowsecurity.out182
-rw-r--r--src/test/regress/expected/rules.out31
-rw-r--r--src/test/regress/expected/triggers.out48
-rw-r--r--src/test/regress/parallel_schedule2
-rw-r--r--src/test/regress/serial_schedule1
-rw-r--r--src/test/regress/sql/identity.sql45
-rw-r--r--src/test/regress/sql/privileges.sql108
-rw-r--r--src/test/regress/sql/rowsecurity.sql156
-rw-r--r--src/test/regress/sql/rules.sql33
-rw-r--r--src/test/regress/sql/triggers.sql47
13 files changed, 809 insertions, 1 deletions
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index d3965fe73f7..99dd7c6bdbf 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -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
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index d7d5178f5d8..3a6016c80a6 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -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;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index ac8968d24f5..864f2c13457 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -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));
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index f1ae40df61c..bf7af3ba826 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -2139,6 +2139,188 @@ INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = '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
--
SET SESSION AUTHORIZATION regress_rls_alice;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ae0cd253d5f..a4ec5690aa2 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -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);
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 387e40d67d4..7f4a94ef7dc 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -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();
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d858a0e7db4..20d67457308 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -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
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 99f8ca37ba9..a08169f256a 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -123,6 +123,7 @@ test: tablesample
test: groupingsets
test: drop_operator
test: password
+test: merge
test: alter_generic
test: alter_operator
test: misc
diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql
index a35f331f4e0..f8f34eaf185 100644
--- a/src/test/regress/sql/identity.sql
+++ b/src/test/regress/sql/identity.sql
@@ -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;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index f7f3bbbeeb6..0a8abf20769 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -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));
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
index f3a31dbee03..6c752089980 100644
--- a/src/test/regress/sql/rowsecurity.sql
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -813,6 +813,162 @@ INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = '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
--
SET SESSION AUTHORIZATION regress_rls_alice;
diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql
index a82f52d1548..b866268892f 100644
--- a/src/test/regress/sql/rules.sql
+++ b/src/test/regress/sql/rules.sql
@@ -1192,6 +1192,39 @@ ALTER RULE rules_parted_table_insert ON rules_parted_table RENAME TO rules_parte
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
--
CREATE TABLE ruletest1 (a int);
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index c6f31dd8c8f..b51c884eee4 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -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();