From 556e8b9591370023418b3f2da9160a39ffbbfe09 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat Date: Tue, 26 Dec 2023 11:13:16 +0300 Subject: [PATCH 5/6] postgres_fdw: child-join with ConvertRowtypeExprs causes expression reference errors. Whole row reference of the parent is translated into ConvertRowtypeExpr with whole row of child as an argument. When partition-wise join is used, targetlist of a child-join contains ConvertRowtypeExpr/s when the parent-join's targetlist has whole-row reference/s of joining partitioned tables. The targetlist to be deparsed for a child-join is built using build_tlist_to_deparse(). The same targetlist is then saved as fdw_scan_tlist in ForeignScanPlan. build_tlist_to_deparse() pulls only Var nodes from the join's targetlist. So it pulls Var reprensenting a whole-row reference of a child from a ConvertRowtypeExpr. Thus the child-join's projected target list contains ConvertRowtypeExpr but the SELECT clause and fdw_scan_tlist contains a whole-row reference. This causes two problems: 1. When a relation (participating in the child-join being pushed down) is deparsed as a subquery, subquery's targetlist (and SELECT clause) contains expressions from foreignrel->reltarget->exprs. A Var from such a relation is deparsed as column reference of subquery using get_relation_column_alias_ids(). That function uses foreignrel's targetlist to locate given node to be deparsed. If the joining relation corresponding to ConvertRowtypeExpr is deparsed as a subquery, this function is called with whole-row reference node (Var node with varattno = 0). The relation's foreignrel->reltarget->exprs doesn't contain its whole-row reference directly but has it embedded in ConvertRowtypeExpr. So, the function doesn't find the given node and throws error. 2. When there is possibility of EvalPlanQual being called, we construct local join plan matching the pushed down foreign join. In postgresGetForeignPlan() after we have built the local join plan, the topmost plan node's targetlist is changed to fdw_scan_tlist to match the output of the ForeignScan node. As explained above, this fdw_scan_tlist contains a bare reference to the whole-row reference from a child relation if the child-join's targetlist contains a ConvertRowtypeExpr. When changing the topmost plan node's targetlist, we do not modify the targetlists of its left and right tree nodes. The left/right plan involving corresponding child relation will have ConvertRowtypeExpr expression in its targetlist, but not whole-row reference directly. When the topmost local join plan node's targetlist is processed by set_plan_ref(), it throws error "variable not found in subplan target lists" since it doesn't find bare whole-row reference of the child relation in subplan's targetlists. Solution This requires two parts a. In build_tlist_to_deparse(), instead of pulling Var node from ConvertRowtypeExpr, we pull whole ConvertRowtypeExpr and include it in the targetlist being deparsed which is also used to set fdw_scan_tlist. b. deparse ConvertRowtypeExpr. For this we need to get the conversion map between the parent and child. We then deparse ConvertRowtypeExpr as a ROW() with the attributes of child rearranged per the conversion map. A multi-level partitioned table will have nested ConvertRowtypeExpr. To deparse such expressions, we need to find the conversion map between the topmost parent and the child, by ignoring any intermediate parents. The patch is taken from /message-id/CAFjFpRc8ZoDm0%2Bzhx%2BMckwGyEqkOzWcpVqbvjaxwdGarZSNrmA%40mail.gmail.com --- contrib/postgres_fdw/deparse.c | 174 +++++++++++++++--- .../postgres_fdw/expected/postgres_fdw.out | 96 +++++++--- contrib/postgres_fdw/postgres_fdw.c | 20 +- contrib/postgres_fdw/sql/postgres_fdw.sql | 16 +- 4 files changed, 256 insertions(+), 50 deletions(-) diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index fb590c87e67..f879ff3100f 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -166,6 +166,8 @@ static void deparseBoolExpr(BoolExpr *node, deparse_expr_cxt *context); static void deparseNullTest(NullTest *node, deparse_expr_cxt *context); static void deparseCaseExpr(CaseExpr *node, deparse_expr_cxt *context); static void deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context); +static void deparseConvertRowtypeExpr(ConvertRowtypeExpr *cre, + deparse_expr_cxt *context); static void printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context); static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod, @@ -202,9 +204,9 @@ static Node *deparseSortGroupClause(Index ref, List *tlist, bool force_colno, /* * Helper functions */ -static bool is_subquery_var(Var *node, RelOptInfo *foreignrel, +static bool is_subquery_var(Node *node, Index varno, int varattno, RelOptInfo *foreignrel, int *relno, int *colno); -static void get_relation_column_alias_ids(Var *node, RelOptInfo *foreignrel, +static void get_relation_column_alias_ids(Node *node, Index varno, int varattno, RelOptInfo *foreignrel, int *relno, int *colno); @@ -1188,18 +1190,25 @@ build_tlist_to_deparse(RelOptInfo *foreignrel) /* * We require columns specified in foreignrel->reltarget->exprs and those - * required for evaluating the local conditions. + * required for evaluating the local conditions. Child relation's + * targetlist or local conditions may have ConvertRowtypeExpr when parent + * whole-row Vars were translated. We need to include those in targetlist + * to be pushed down to match the targetlists produced for the joining + * relations (in case we are using subqueries in the deparsed query) and + * also to match the targetlist of outer plan if foreign scan has one. */ tlist = add_to_flat_tlist(tlist, pull_var_clause((Node *) foreignrel->reltarget->exprs, - PVC_RECURSE_PLACEHOLDERS)); + PVC_RECURSE_PLACEHOLDERS | + PVC_INCLUDE_CONVERTROWTYPES)); foreach(lc, fpinfo->local_conds) { RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); tlist = add_to_flat_tlist(tlist, pull_var_clause((Node *) rinfo->clause, - PVC_RECURSE_PLACEHOLDERS)); + PVC_RECURSE_PLACEHOLDERS | + PVC_INCLUDE_CONVERTROWTYPES)); } return tlist; @@ -2930,6 +2939,10 @@ deparseExpr(Expr *node, deparse_expr_cxt *context) case T_Aggref: deparseAggref((Aggref *) node, context); break; + case T_ConvertRowtypeExpr: + deparseConvertRowtypeExpr(castNode(ConvertRowtypeExpr, node), + context); + break; default: elog(ERROR, "unsupported expression type for deparse: %d", (int) nodeTag(node)); @@ -2960,7 +2973,8 @@ deparseVar(Var *node, deparse_expr_cxt *context) * subquery, use the relation and column alias to the Var provided by the * subquery, instead of the remote name. */ - if (is_subquery_var(node, context->scanrel, &relno, &colno)) + if (is_subquery_var((Node *) node, node->varno, node->varattno, context->scanrel, &relno, + &colno)) { appendStringInfo(context->buf, "%s%d.%s%d", SUBQUERY_REL_ALIAS_PREFIX, relno, @@ -3742,6 +3756,115 @@ deparseAggref(Aggref *node, deparse_expr_cxt *context) appendStringInfoChar(buf, ')'); } +/* + * Deparse a ConvertRowtypeExpr node. + * + * The function handles ConvertRowtypeExpr nodes constructed to convert a child + * tuple to parent tuple. The function deparses a ConvertRowtypeExpr as a ROW + * expression child column refrences arranged according to the tuple conversion + * map for converting child tuple to that of the parent. The ROW expression is + * decorated with CASE similar to deparseColumnRef() to take care of + * ConvertRowtypeExpr nodes on nullable side of the join. + */ +static void +deparseConvertRowtypeExpr(ConvertRowtypeExpr *cre, + deparse_expr_cxt *context) +{ + ConvertRowtypeExpr *expr = cre; + Var *child_var; + TupleDesc parent_desc; + TupleDesc child_desc; + TupleConversionMap *conv_map; + AttrMap *attrMap; + int cnt; + bool qualify_col = (bms_num_members(context->scanrel->relids) > 1); + StringInfo buf = context->buf; + PlannerInfo *root = context->root; + int relno; + int colno; + bool first_col; + + /* + * Multi-level partitioned hierarchies produce nested ConvertRowtypeExprs, + * where the top ConvertRowtypeExpr gives the parent relation and the leaf + * Var node gives the child relation. Fetch the leaf Var node + * corresponding to the lowest child. + */ + while (IsA(expr->arg, ConvertRowtypeExpr)) + expr = castNode(ConvertRowtypeExpr, expr->arg); + child_var = castNode(Var, expr->arg); + Assert(child_var->varattno == 0); + + /* + * If the child Var belongs to the foreign relation that is deparsed as a + * subquery, use the relation and column alias to the child Var provided + * by the subquery, instead of the remote name. + */ + if (is_subquery_var((Node *) cre, child_var->varno, child_var->varattno, context->scanrel, + &relno, &colno)) + { + appendStringInfo(context->buf, "%s%d.%s%d", + SUBQUERY_REL_ALIAS_PREFIX, relno, + SUBQUERY_COL_ALIAS_PREFIX, colno); + return; + } + + /* Construct the conversion map. */ + parent_desc = lookup_rowtype_tupdesc(cre->resulttype, -1); + child_desc = lookup_rowtype_tupdesc(child_var->vartype, + child_var->vartypmod); + conv_map = convert_tuples_by_name(child_desc, parent_desc); + + ReleaseTupleDesc(parent_desc); + ReleaseTupleDesc(child_desc); + + /* If no conversion is needed, deparse the child Var as is. */ + if (conv_map == NULL) + { + deparseVar(child_var, context); + return; + } + + attrMap = conv_map->attrMap; + + /* + * In case the whole-row reference is under an outer join then it has to + * go NULL whenever the rest of the row goes NULL. Deparsing a join query + * would always involve multiple relations, thus qualify_col would be + * true. + */ + if (qualify_col) + { + appendStringInfoString(buf, "CASE WHEN ("); + ADD_REL_QUALIFIER(buf, child_var->varno); + appendStringInfoString(buf, "*)::text IS NOT NULL THEN "); + } + + /* Construct ROW expression according to the conversion map. */ + appendStringInfoString(buf, "ROW("); + first_col = true; + for (cnt = 0; cnt < parent_desc->natts; cnt++) + { + /* Ignore dropped columns. */ + if (attrMap->attnums[cnt] == 0) + continue; + + if (!first_col) + appendStringInfoString(buf, ", "); + deparseColumnRef(buf, child_var->varno, attrMap->attnums[cnt], + planner_rt_fetch(child_var->varno, root), + qualify_col); + first_col = false; + } + appendStringInfoChar(buf, ')'); + + /* Complete the CASE WHEN statement started above. */ + if (qualify_col) + appendStringInfoString(buf, " END"); + + free_conversion_map(conv_map); +} + /* * Append ORDER BY within aggregate function. */ @@ -4104,12 +4227,18 @@ deparseSortGroupClause(Index ref, List *tlist, bool force_colno, /* - * Returns true if given Var is deparsed as a subquery output column, in + * Returns true if given Node is deparsed as a subquery output column, in * which case, *relno and *colno are set to the IDs for the relation and - * column alias to the Var provided by the subquery. + * column alias to the Node provided by the subquery. + * + * We do not allow relations with PlaceHolderVars to be pushed down. We call + * this function only for simple or join relations, whose targetlists can + * contain only Var and ConvertRowtypeExpr (in case of child-joins) nodes + * (apart from PHVs). So support only Var and ConvertRowtypeExpr nodes. */ static bool -is_subquery_var(Var *node, RelOptInfo *foreignrel, int *relno, int *colno) +is_subquery_var(Node *node, Index varno, int varattno, RelOptInfo *foreignrel, + int *relno, int *colno) { PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; RelOptInfo *outerrel = fpinfo->outerrel; @@ -4118,6 +4247,8 @@ is_subquery_var(Var *node, RelOptInfo *foreignrel, int *relno, int *colno) /* Should only be called in these cases. */ Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); + Assert(IsA(node, ConvertRowtypeExpr) || IsA(node, Var)); + /* * If the given relation isn't a join relation, it doesn't have any lower * subqueries, so the Var isn't a subquery output column. @@ -4129,10 +4260,10 @@ is_subquery_var(Var *node, RelOptInfo *foreignrel, int *relno, int *colno) * If the Var doesn't belong to any lower subqueries, it isn't a subquery * output column. */ - if (!bms_is_member(node->varno, fpinfo->lower_subquery_rels)) + if (!bms_is_member(varno, fpinfo->lower_subquery_rels)) return false; - if (bms_is_member(node->varno, outerrel->relids)) + if (bms_is_member(varno, outerrel->relids)) { /* * If outer relation is deparsed as a subquery, the Var is an output @@ -4140,16 +4271,16 @@ is_subquery_var(Var *node, RelOptInfo *foreignrel, int *relno, int *colno) */ if (fpinfo->make_outerrel_subquery) { - get_relation_column_alias_ids(node, outerrel, relno, colno); + get_relation_column_alias_ids(node, varno, varattno, outerrel, relno, colno); return true; } /* Otherwise, recurse into the outer relation. */ - return is_subquery_var(node, outerrel, relno, colno); + return is_subquery_var(node, varno, varattno, outerrel, relno, colno); } else { - Assert(bms_is_member(node->varno, innerrel->relids)); + Assert(bms_is_member(varno, innerrel->relids)); /* * If inner relation is deparsed as a subquery, the Var is an output @@ -4157,12 +4288,12 @@ is_subquery_var(Var *node, RelOptInfo *foreignrel, int *relno, int *colno) */ if (fpinfo->make_innerrel_subquery) { - get_relation_column_alias_ids(node, innerrel, relno, colno); + get_relation_column_alias_ids(node, varno, varattno, innerrel, relno, colno); return true; } /* Otherwise, recurse into the inner relation. */ - return is_subquery_var(node, innerrel, relno, colno); + return is_subquery_var(node, varno, varattno, innerrel, relno, colno); } } @@ -4171,7 +4302,7 @@ is_subquery_var(Var *node, RelOptInfo *foreignrel, int *relno, int *colno) * given relation, which are returned into *relno and *colno. */ static void -get_relation_column_alias_ids(Var *node, RelOptInfo *foreignrel, +get_relation_column_alias_ids(Node *node, Index varno, int varattno, RelOptInfo *foreignrel, int *relno, int *colno) { PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; @@ -4191,11 +4322,12 @@ get_relation_column_alias_ids(Var *node, RelOptInfo *foreignrel, * Match reltarget entries only on varno/varattno. Ideally there * would be some cross-check on varnullingrels, but it's unclear what * to do exactly; we don't have enough context to know what that value - * should be. + * should be. Also match posible converted whole-row references. */ - if (IsA(tlvar, Var) && - tlvar->varno == node->varno && - tlvar->varattno == node->varattno) + if ((IsA(tlvar, Var) && + tlvar->varno == varno && + tlvar->varattno == varattno) + || is_equal_converted_whole_row_references((Node *) tlvar, node)) { *colno = i; return; diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index f2bcd6aa98c..46f005307e1 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -9947,6 +9947,11 @@ ALTER TABLE fprt1_p1 SET (autovacuum_enabled = 'false'); ALTER TABLE fprt1_p2 SET (autovacuum_enabled = 'false'); INSERT INTO fprt1_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 2) i; INSERT INTO fprt1_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 2) i; +-- Add and drop column from the parent partition so that the number of +-- attributes in tuple descriptors of parent and child do not match exactly. +-- Used for testing ConvertRowtypeExpr. +ALTER TABLE fprt1 DROP COLUMN c; +ALTER TABLE fprt1 ADD COLUMN c varchar; CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (0) TO (250) SERVER loopback OPTIONS (table_name 'fprt1_p1', use_remote_estimate 'true'); CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (250) TO (500) @@ -10013,23 +10018,19 @@ SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) 8 | | (5 rows) --- with whole-row reference; partitionwise join does not apply +-- with whole-row reference EXPLAIN (COSTS OFF) SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; - QUERY PLAN --------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------- Sort Sort Key: ((t1.*)::fprt1), ((t2.*)::fprt2) - -> Hash Full Join - Hash Cond: (t1.a = t2.b) - -> Append - -> Foreign Scan on ftprt1_p1 t1_1 - -> Foreign Scan on ftprt1_p2 t1_2 - -> Hash - -> Append - -> Foreign Scan on ftprt2_p1 t2_1 - -> Foreign Scan on ftprt2_p2 t2_2 -(11 rows) + -> Append + -> Foreign Scan + Relations: (ftprt1_p1 t1_1) FULL JOIN (ftprt2_p1 t2_1) + -> Foreign Scan + Relations: (ftprt1_p2 t1_2) FULL JOIN (ftprt2_p2 t2_2) +(7 rows) SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; wr | wr @@ -10112,22 +10113,26 @@ SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE | | 475 | t2_phv (14 rows) --- test FOR UPDATE; partitionwise join does not apply +-- test FOR UPDATE EXPLAIN (COSTS OFF) SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; - QUERY PLAN --------------------------------------------------------- + QUERY PLAN +----------------------------------------------------------------------------- LockRows - -> Nested Loop - Join Filter: (t1.a = t2.b) + -> Sort + Sort Key: t1.a -> Append - -> Foreign Scan on ftprt1_p1 t1_1 - -> Foreign Scan on ftprt1_p2 t1_2 - -> Materialize - -> Append - -> Foreign Scan on ftprt2_p1 t2_1 - -> Foreign Scan on ftprt2_p2 t2_2 -(10 rows) + -> Foreign Scan + Relations: (ftprt1_p1 t1_1) INNER JOIN (ftprt2_p1 t2_1) + -> Nested Loop + -> Foreign Scan on ftprt1_p1 t1_1 + -> Foreign Scan on ftprt2_p1 t2_1 + -> Foreign Scan + Relations: (ftprt1_p2 t1_2) INNER JOIN (ftprt2_p2 t2_2) + -> Nested Loop + -> Foreign Scan on ftprt1_p2 t1_2 + -> Foreign Scan on ftprt2_p2 t2_2 +(14 rows) SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; a | b @@ -10138,6 +10143,47 @@ SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a 400 | 400 (4 rows) +-- test sort paths and whole-row references +set enable_sort to off; +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY t2.a FOR UPDATE OF t1; + QUERY PLAN +----------------------------------------------------------------------- + LockRows + -> Merge Append + Sort Key: t2.a + -> Foreign Scan + Relations: (ftprt1_p1 t1_1) INNER JOIN (ftprt2_p1 t2_1) + -> Result + Disabled Nodes: 1 + -> Sort + Disabled Nodes: 1 + Sort Key: t2_1.a + -> Nested Loop + -> Foreign Scan on ftprt1_p1 t1_1 + -> Foreign Scan on ftprt2_p1 t2_1 + -> Foreign Scan + Relations: (ftprt1_p2 t1_2) INNER JOIN (ftprt2_p2 t2_2) + -> Result + Disabled Nodes: 1 + -> Sort + Disabled Nodes: 1 + Sort Key: t2_2.a + -> Nested Loop + -> Foreign Scan on ftprt1_p2 t1_2 + -> Foreign Scan on ftprt2_p2 t2_2 +(23 rows) + +SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY t2.a FOR UPDATE OF t1; + a | b +-----+----- + 0 | 0 + 150 | 150 + 250 | 250 + 400 | 400 +(4 rows) + +reset enable_sort; RESET enable_partitionwise_join; -- =================================================================== -- test partitionwise aggregates diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index adc62576d1f..26fe16cc2fa 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -1476,6 +1476,22 @@ get_tupdesc_for_join_scan_tuples(ForeignScanState *node) */ var = (Var *) list_nth_node(TargetEntry, fsplan->fdw_scan_tlist, i)->expr; + + /* + * TODO: Do we really need this? Target list can contain not only + * Vars, but ConvertRowtypeExpr will certainly contribute to + * ss.ss_ScanTupleSlot->tts_tupleDescriptor attribute type in + * ExecTypeFromTL() (called from ExecInitForeignScan()) in the same + * way. + */ + if (IsA(var, ConvertRowtypeExpr)) + { + ConvertRowtypeExpr *convexpr = castNode(ConvertRowtypeExpr, var); + + att->atttypid = convexpr->resulttype; + continue; + } + if (!IsA(var, Var) || var->varattno != 0) continue; rte = list_nth(estate->es_range_table, var->varno - 1); @@ -6119,7 +6135,7 @@ add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel, /* Include columns required for evaluating PHVs in the tlist. */ add_new_columns_to_pathtarget(target, pull_var_clause((Node *) target->exprs, - PVC_RECURSE_PLACEHOLDERS)); + PVC_RECURSE_PLACEHOLDERS | PVC_INCLUDE_CONVERTROWTYPES)); /* Include columns required for evaluating the local conditions. */ foreach(lc, fpinfo->local_conds) @@ -6128,7 +6144,7 @@ add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel, add_new_columns_to_pathtarget(target, pull_var_clause((Node *) rinfo->clause, - PVC_RECURSE_PLACEHOLDERS)); + PVC_RECURSE_PLACEHOLDERS | PVC_INCLUDE_CONVERTROWTYPES)); } /* diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 372fe6dad15..ade0ab8cae0 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -3115,6 +3115,11 @@ ALTER TABLE fprt1_p1 SET (autovacuum_enabled = 'false'); ALTER TABLE fprt1_p2 SET (autovacuum_enabled = 'false'); INSERT INTO fprt1_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 2) i; INSERT INTO fprt1_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 2) i; +-- Add and drop column from the parent partition so that the number of +-- attributes in tuple descriptors of parent and child do not match exactly. +-- Used for testing ConvertRowtypeExpr. +ALTER TABLE fprt1 DROP COLUMN c; +ALTER TABLE fprt1 ADD COLUMN c varchar; CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (0) TO (250) SERVER loopback OPTIONS (table_name 'fprt1_p1', use_remote_estimate 'true'); CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (250) TO (500) @@ -3149,7 +3154,7 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; --- with whole-row reference; partitionwise join does not apply +-- with whole-row reference EXPLAIN (COSTS OFF) SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; @@ -3164,11 +3169,18 @@ EXPLAIN (COSTS OFF) SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b; SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b; --- test FOR UPDATE; partitionwise join does not apply +-- test FOR UPDATE EXPLAIN (COSTS OFF) SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; +-- test sort paths and whole-row references +set enable_sort to off; +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY t2.a FOR UPDATE OF t1; +SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY t2.a FOR UPDATE OF t1; +reset enable_sort; + RESET enable_partitionwise_join; -- 2.34.1