From d8c3b65db58db0a074dc9f7e27846e22e9dc579f Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Wed, 22 Feb 2023 10:54:57 +0000 Subject: [PATCH] Fix Assert failure for MERGE into a partitioned table with RLS. In ExecInitPartitionInfo(), the Assert when building the WITH CHECK OPTION list for the new partition assumed that the command would be an INSERT or UPDATE, but it can also be a MERGE. This can be triggered by a MERGE into a partitioned table with RLS checks to enforce. Fix, and back-patch to v15, where MERGE was introduced. Discussion: https://postgr.es/m/CAEZATCWWFtQmW67F3XTyMU5Am10Oxa_b8oe0x%2BNu5Mo%2BCdRErg%40mail.gmail.com --- src/backend/executor/execPartition.c | 10 +++++++--- src/test/regress/expected/merge.out | 12 ++++++++++++ src/test/regress/sql/merge.sql | 12 ++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index e03ea27299..73bc5bb958 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -545,8 +545,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, * Build WITH CHECK OPTION constraints for the partition. Note that we * didn't build the withCheckOptionList for partitions within the planner, * but simple translation of varattnos will suffice. This only occurs for - * the INSERT case or in the case of UPDATE tuple routing where we didn't - * find a result rel to reuse. + * the INSERT case or in the case of UPDATE/MERGE tuple routing where we + * didn't find a result rel to reuse. */ if (node && node->withCheckOptionLists != NIL) { @@ -557,12 +557,15 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, /* * In the case of INSERT on a partitioned table, there is only one * plan. Likewise, there is only one WCO list, not one per partition. - * For UPDATE, there are as many WCO lists as there are plans. + * For UPDATE/MERGE, there are as many WCO lists as there are plans. */ Assert((node->operation == CMD_INSERT && list_length(node->withCheckOptionLists) == 1 && list_length(node->resultRelations) == 1) || (node->operation == CMD_UPDATE && + list_length(node->withCheckOptionLists) == + list_length(node->resultRelations)) || + (node->operation == CMD_MERGE && list_length(node->withCheckOptionLists) == list_length(node->resultRelations))); @@ -618,6 +621,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, List *returningList; /* See the comment above for WCO lists. */ + /* (except no RETURNING support for MERGE yet) */ Assert((node->operation == CMD_INSERT && list_length(node->returningLists) == 1 && list_length(node->resultRelations) == 1) || diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out index cc324bd313..8401401fb5 100644 --- a/src/test/regress/expected/merge.out +++ b/src/test/regress/expected/merge.out @@ -1736,6 +1736,18 @@ SELECT * FROM pa_target ORDER BY tid; 14 | 140 | inserted by merge (14 rows) +ROLLBACK; +-- test RLS enforcement +BEGIN; +ALTER TABLE pa_target ENABLE ROW LEVEL SECURITY; +ALTER TABLE pa_target FORCE ROW LEVEL SECURITY; +CREATE POLICY pa_target_pol ON pa_target USING (tid != 0); +MERGE INTO pa_target t + USING pa_source s + ON t.tid = s.sid AND t.tid IN (1,2,3,4) + WHEN MATCHED THEN + UPDATE SET tid = tid - 1; +ERROR: new row violates row-level security policy for table "pa_target" ROLLBACK; DROP TABLE pa_source; DROP TABLE pa_target CASCADE; diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql index 9ed648ad1f..f0854162a7 100644 --- a/src/test/regress/sql/merge.sql +++ b/src/test/regress/sql/merge.sql @@ -1082,6 +1082,18 @@ MERGE INTO pa_target t SELECT * FROM pa_target ORDER BY tid; ROLLBACK; +-- test RLS enforcement +BEGIN; +ALTER TABLE pa_target ENABLE ROW LEVEL SECURITY; +ALTER TABLE pa_target FORCE ROW LEVEL SECURITY; +CREATE POLICY pa_target_pol ON pa_target USING (tid != 0); +MERGE INTO pa_target t + USING pa_source s + ON t.tid = s.sid AND t.tid IN (1,2,3,4) + WHEN MATCHED THEN + UPDATE SET tid = tid - 1; +ROLLBACK; + DROP TABLE pa_source; DROP TABLE pa_target CASCADE;