diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 99284cb6de1..d8aa35dee79 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -2964,7 +2964,8 @@ choose_hashed_distinct(PlannerInfo *root, * 'groupColIdx' receives an array of column numbers for the GROUP BY * expressions (if there are any) in the returned target list. * 'need_tlist_eval' is set true if we really need to evaluate the - * returned tlist as-is. + * returned tlist as-is. (Note: locate_grouping_columns assumes + * that if this is FALSE, all grouping columns are simple Vars.) * * The result is the targetlist to be passed to query_planner. */ @@ -3127,6 +3128,7 @@ get_grouping_column_index(Query *parse, TargetEntry *tle) * This is only needed if we don't use the sub_tlist chosen by * make_subplanTargetList. We have to forget the column indexes found * by that routine and re-locate the grouping exprs in the real sub_tlist. + * We assume the grouping exprs are just Vars (see make_subplanTargetList). */ static void locate_grouping_columns(PlannerInfo *root, @@ -3150,11 +3152,24 @@ locate_grouping_columns(PlannerInfo *root, foreach(gl, root->parse->groupClause) { SortGroupClause *grpcl = (SortGroupClause *) lfirst(gl); - Node *groupexpr = get_sortgroupclause_expr(grpcl, tlist); - TargetEntry *te = tlist_member(groupexpr, sub_tlist); + Var *groupexpr = (Var *) get_sortgroupclause_expr(grpcl, tlist); + TargetEntry *te; + /* + * The grouping column returned by create_plan might not have the same + * typmod as the original Var. (This can happen in cases where a + * set-returning function has been inlined, so that we now have more + * knowledge about what it returns than we did when the original Var + * was created.) So we can't use tlist_member() to search the tlist; + * instead use tlist_member_match_var. For safety, still check that + * the vartype matches. + */ + if (!(groupexpr && IsA(groupexpr, Var))) + elog(ERROR, "grouping column is not a Var as expected"); + te = tlist_member_match_var(groupexpr, sub_tlist); if (!te) elog(ERROR, "failed to locate grouping columns"); + Assert(((Var *) te->expr)->vartype == groupexpr->vartype); groupColIdx[keyno++] = te->resno; } } diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c index 5dc4b835fa3..5cc3cdc15a6 100644 --- a/src/backend/optimizer/util/tlist.c +++ b/src/backend/optimizer/util/tlist.c @@ -71,6 +71,35 @@ tlist_member_ignore_relabel(Node *node, List *targetlist) return NULL; } +/* + * tlist_member_match_var + * Same as above, except that we match the provided Var on the basis + * of varno/varattno/varlevelsup only, rather than using full equal(). + * + * This is needed in some cases where we can't be sure of an exact typmod + * match. It's probably a good idea to check the vartype anyway, but + * we leave it to the caller to apply any suitable sanity checks. + */ +TargetEntry * +tlist_member_match_var(Var *var, List *targetlist) +{ + ListCell *temp; + + foreach(temp, targetlist) + { + TargetEntry *tlentry = (TargetEntry *) lfirst(temp); + Var *tlvar = (Var *) tlentry->expr; + + if (!tlvar || !IsA(tlvar, Var)) + continue; + if (var->varno == tlvar->varno && + var->varattno == tlvar->varattno && + var->varlevelsup == tlvar->varlevelsup) + return tlentry; + } + return NULL; +} + /* * flatten_tlist * Create a target list that only contains unique variables. diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h index 0d6f05199ae..ad01f2e4856 100644 --- a/src/include/optimizer/tlist.h +++ b/src/include/optimizer/tlist.h @@ -19,6 +19,7 @@ extern TargetEntry *tlist_member(Node *node, List *targetlist); extern TargetEntry *tlist_member_ignore_relabel(Node *node, List *targetlist); +extern TargetEntry *tlist_member_match_var(Var *var, List *targetlist); extern List *flatten_tlist(List *tlist, PVCAggregateBehavior aggbehavior, PVCPlaceHolderBehavior phbehavior); diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index 45ffd85b1b7..37391dcfce7 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -1247,6 +1247,17 @@ SELECT * FROM foo(3); (9 rows) DROP FUNCTION foo(int); +-- case that causes change of typmod knowledge during inlining +CREATE OR REPLACE FUNCTION foo() +RETURNS TABLE(a varchar(5)) +AS $$ SELECT 'hello'::varchar(5) $$ LANGUAGE sql STABLE; +SELECT * FROM foo() GROUP BY 1; + a +------- + hello +(1 row) + +DROP FUNCTION foo(); -- -- some tests on SQL functions with RETURNING -- diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql index d522cdb10e4..e82a1d55712 100644 --- a/src/test/regress/sql/rangefuncs.sql +++ b/src/test/regress/sql/rangefuncs.sql @@ -330,6 +330,13 @@ AS $$ SELECT a, b SELECT * FROM foo(3); DROP FUNCTION foo(int); +-- case that causes change of typmod knowledge during inlining +CREATE OR REPLACE FUNCTION foo() +RETURNS TABLE(a varchar(5)) +AS $$ SELECT 'hello'::varchar(5) $$ LANGUAGE sql STABLE; +SELECT * FROM foo() GROUP BY 1; +DROP FUNCTION foo(); + -- -- some tests on SQL functions with RETURNING --