summaryrefslogtreecommitdiff
path: root/src/backend/optimizer/plan/createplan.c
diff options
context:
space:
mode:
authorEtsuro Fujita <efujita@postgresql.org>2025-08-08 10:50:00 +0900
committerEtsuro Fujita <efujita@postgresql.org>2025-08-08 10:50:00 +0900
commit62a1211d3389095b685a4f80f7b864794cf114f6 (patch)
tree5a505845e06f48d12f3657aeed59a135db46aa7d /src/backend/optimizer/plan/createplan.c
parent84b32fd2283090b2ab87dc38445d487e98e9e231 (diff)
Disallow collecting transition tuples from child foreign tables.
Commit 9e6104c66 disallowed transition tables on foreign tables, but failed to account for cases where a foreign table is a child table of a partitioned/inherited table on which transition tables exist, leading to incorrect transition tuples collected from such foreign tables for queries on the parent table triggering transition capture. This occurred not only for inherited UPDATE/DELETE but for partitioned INSERT later supported by commit 3d956d956, which should have handled it at least for the INSERT case, but didn't. To fix, modify ExecAR*Triggers to throw an error if the given relation is a foreign table requesting transition capture. Also, this commit fixes make_modifytable so that in case of an inherited UPDATE/DELETE triggering transition capture, FDWs choose normal operations to modify child foreign tables, not DirectModify; which is needed because they would otherwise skip the calls to ExecAR*Triggers at execution, causing unexpected behavior. Author: Etsuro Fujita <etsuro.fujita@gmail.com> Reviewed-by: Amit Langote <amitlangote09@gmail.com> Discussion: https://postgr.es/m/CAPmGK14QJYikKzBDCe3jMbpGENnQ7popFmbEgm-XTNuk55oyHg%40mail.gmail.com Backpatch-through: 13
Diffstat (limited to 'src/backend/optimizer/plan/createplan.c')
-rw-r--r--src/backend/optimizer/plan/createplan.c24
1 files changed, 20 insertions, 4 deletions
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index bfefc7dbea1..9fd5c31edf2 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -7233,6 +7233,8 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
ModifyTable *node = makeNode(ModifyTable);
bool returning_old_or_new = false;
bool returning_old_or_new_valid = false;
+ bool transition_tables = false;
+ bool transition_tables_valid = false;
List *fdw_private_list;
Bitmapset *direct_modify_plans;
ListCell *lc;
@@ -7379,8 +7381,8 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
* callback functions needed for that and (2) there are no local
* structures that need to be run for each modified row: row-level
* triggers on the foreign table, stored generated columns, WITH CHECK
- * OPTIONs from parent views, or Vars returning OLD/NEW in the
- * RETURNING list.
+ * OPTIONs from parent views, Vars returning OLD/NEW in the RETURNING
+ * list, or transition tables on the named relation.
*/
direct_modify = false;
if (fdwroutine != NULL &&
@@ -7392,7 +7394,10 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
!has_row_triggers(root, rti, operation) &&
!has_stored_generated_columns(root, rti))
{
- /* returning_old_or_new is the same for all result relations */
+ /*
+ * returning_old_or_new and transition_tables are the same for all
+ * result relations, respectively
+ */
if (!returning_old_or_new_valid)
{
returning_old_or_new =
@@ -7401,7 +7406,18 @@ make_modifytable(PlannerInfo *root, Plan *subplan,
returning_old_or_new_valid = true;
}
if (!returning_old_or_new)
- direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i);
+ {
+ if (!transition_tables_valid)
+ {
+ transition_tables = has_transition_tables(root,
+ nominalRelation,
+ operation);
+ transition_tables_valid = true;
+ }
+ if (!transition_tables)
+ direct_modify = fdwroutine->PlanDirectModify(root, node,
+ rti, i);
+ }
}
if (direct_modify)
direct_modify_plans = bms_add_member(direct_modify_plans, i);