diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 264d5af65e..235fdf550b 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -1735,6 +1735,10 @@ pull_up_constant_function(PlannerInfo *root, Node *jtnode, if (rtf->funccolcount != 1) return jtnode; /* definitely composite */ + /* If it has a coldeflist, it certainly returns RECORD */ + if (rtf->funccolnames != NIL) + return jtnode; /* must be a one-column RECORD type */ + functypclass = get_expr_result_type(rtf->funcexpr, &funcrettype, &tupdesc); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index a076fa9646..eed6fea624 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -4275,12 +4275,11 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, * Can't simplify if it returns RECORD. The immediate problem is that it * will be needing an expected tupdesc which we can't supply here. * - * In the case where it has OUT parameters, it could get by without an - * expected tupdesc, but we still have issues: get_expr_result_type() - * doesn't know how to extract type info from a RECORD constant, and in - * the case of a NULL function result there doesn't seem to be any clean - * way to fix that. In view of the likelihood of there being still other - * gotchas, seems best to leave the function call unreduced. + * In the case where it has OUT parameters, we could build an expected + * tupdesc from those, but there may be other gotchas lurking. In + * particular, if the function were to return NULL, we would produce a + * null constant with no remaining indication of which concrete record + * type it is. For now, seems best to leave the function call unreduced. */ if (funcform->prorettype == RECORDOID) return NULL; diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 4193a03ebb..3ea98fcf45 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -2708,12 +2708,17 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, { RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); TypeFuncClass functypclass; - Oid funcrettype; - TupleDesc tupdesc; + Oid funcrettype = InvalidOid; + TupleDesc tupdesc = NULL; + + /* If it has a coldeflist, it returns RECORD */ + if (rtfunc->funccolnames != NIL) + functypclass = TYPEFUNC_RECORD; + else + functypclass = get_expr_result_type(rtfunc->funcexpr, + &funcrettype, + &tupdesc); - functypclass = get_expr_result_type(rtfunc->funcexpr, - &funcrettype, - &tupdesc); if (functypclass == TYPEFUNC_COMPOSITE || functypclass == TYPEFUNC_COMPOSITE_DOMAIN) { @@ -3327,6 +3332,10 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum) { TupleDesc tupdesc; + /* If it has a coldeflist, it returns RECORD */ + if (rtfunc->funccolnames != NIL) + return false; /* can't have any dropped columns */ + tupdesc = get_expr_result_tupdesc(rtfunc->funcexpr, true); if (tupdesc) diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index cff8232567..d7e0efda32 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -218,6 +218,13 @@ get_call_result_type(FunctionCallInfo fcinfo, /* * get_expr_result_type * As above, but work from a calling expression node tree + * + * Beware of using this on the funcexpr of a RTE that has a coldeflist. + * The correct conclusion in such cases is always that the function returns + * RECORD with the columns defined by the coldeflist fields (funccolnames etc). + * If it does not, it's the executor's responsibility to catch the discrepancy + * at runtime; but code processing the query in advance of that point might + * come to inconsistent conclusions if it checks the actual expression. */ TypeFuncClass get_expr_result_type(Node *expr, @@ -468,7 +475,8 @@ internal_get_result_type(Oid funcid, * if noError is true, else throws error. * * This is a simpler version of get_expr_result_type() for use when the caller - * is only interested in determinate rowtype results. + * is only interested in determinate rowtype results. As with that function, + * beware of using this on the funcexpr of a RTE that has a coldeflist. */ TupleDesc get_expr_result_tupdesc(Node *expr, bool noError) diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index caebbad29d..0f8e686a92 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -2458,3 +2458,6 @@ with a(b) as (values (row(1,2,3))) select * from a, coalesce(b) as c(d int, e int, f float); -- fail ERROR: function return row and query-specified return row do not match DETAIL: Returned type integer at ordinal position 3, but query expects double precision. +select * from int8_tbl, coalesce(row(1)) as (a int, b int); -- fail +ERROR: function return row and query-specified return row do not match +DETAIL: Returned row contains 1 attribute, but query expects 2. diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql index 30448faa01..e7ce0d3287 100644 --- a/src/test/regress/sql/rangefuncs.sql +++ b/src/test/regress/sql/rangefuncs.sql @@ -791,3 +791,4 @@ with a(b) as (values (row(1,2,3))) select * from a, coalesce(b) as c(d int, e int, f int, g int); -- fail with a(b) as (values (row(1,2,3))) select * from a, coalesce(b) as c(d int, e int, f float); -- fail +select * from int8_tbl, coalesce(row(1)) as (a int, b int); -- fail