As part of the MERGE RETURNING patch I noticed a suspicious Assert() in ExecInitPartitionInfo() that looked like it needed updating for MERGE.
After more testing, I can confirm that this is indeed a pre-existing bug, that can be triggered using MERGE into a partitioned table that has RLS enabled (and hence non-empty withCheckOptionLists to initialise). So I think we need something like the attached. Regards, Dean
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c new file mode 100644 index 651ad24..fd6ca8a --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -545,8 +545,8 @@ ExecInitPartitionInfo(ModifyTableState * * 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,13 +557,16 @@ ExecInitPartitionInfo(ModifyTableState * /* * 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))); /* @@ -619,6 +622,7 @@ ExecInitPartitionInfo(ModifyTableState * 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 new file mode 100644 index bc53b21..ae27f12 --- a/src/test/regress/expected/merge.out +++ b/src/test/regress/expected/merge.out @@ -1721,6 +1721,18 @@ SELECT * FROM pa_target ORDER BY tid; (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; -- Sub-partitioning diff --git a/src/test/regress/sql/merge.sql b/src/test/regress/sql/merge.sql new file mode 100644 index fdbcd70..3338d80 --- a/src/test/regress/sql/merge.sql +++ b/src/test/regress/sql/merge.sql @@ -1071,6 +1071,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;