diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index 0370f2e2b7..6d3bec507c 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -344,8 +344,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) Node *funcexpr = rtfunc->funcexpr; int colcount = rtfunc->funccolcount; FunctionScanPerFuncState *fs = &scanstate->funcstates[i]; - TypeFuncClass functypclass; - Oid funcrettype; TupleDesc tupdesc; fs->setexpr = @@ -362,39 +360,18 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) fs->rowcount = -1; /* - * Now determine if the function returns a simple or composite type, - * and build an appropriate tupdesc. Note that in the composite case, - * the function may now return more columns than it did when the plan - * was made; we have to ignore any columns beyond "colcount". + * Now build a tupdesc showing the result type we expect from the + * function. If we have a coldeflist then that takes priority (note + * the parser enforces that there is one if the function's nominal + * output type is RECORD). Otherwise use get_expr_result_type. + * + * Note that if the function returns a named composite type, that may + * now contain more or different columns than it did when the plan was + * made. For both that and the RECORD case, we need to check tuple + * compatibility. ExecMakeTableFunctionResult handles some of this, + * and CheckVarSlotCompatibility provides a backstop. */ - functypclass = get_expr_result_type(funcexpr, - &funcrettype, - &tupdesc); - - if (functypclass == TYPEFUNC_COMPOSITE || - functypclass == TYPEFUNC_COMPOSITE_DOMAIN) - { - /* Composite data type, e.g. a table's row type */ - Assert(tupdesc); - Assert(tupdesc->natts >= colcount); - /* Must copy it out of typcache for safety */ - tupdesc = CreateTupleDescCopy(tupdesc); - } - else if (functypclass == TYPEFUNC_SCALAR) - { - /* Base data type, i.e. scalar */ - tupdesc = CreateTemplateTupleDesc(1); - TupleDescInitEntry(tupdesc, - (AttrNumber) 1, - NULL, /* don't care about the name here */ - funcrettype, - -1, - 0); - TupleDescInitEntryCollation(tupdesc, - (AttrNumber) 1, - exprCollation(funcexpr)); - } - else if (functypclass == TYPEFUNC_RECORD) + if (rtfunc->funccolnames != NIL) { tupdesc = BuildDescFromLists(rtfunc->funccolnames, rtfunc->funccoltypes, @@ -410,8 +387,40 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) } else { - /* crummy error message, but parser should have caught this */ - elog(ERROR, "function in FROM has unsupported return type"); + TypeFuncClass functypclass; + Oid funcrettype; + + functypclass = get_expr_result_type(funcexpr, + &funcrettype, + &tupdesc); + + if (functypclass == TYPEFUNC_COMPOSITE || + functypclass == TYPEFUNC_COMPOSITE_DOMAIN) + { + /* Composite data type, e.g. a table's row type */ + Assert(tupdesc); + /* Must copy it out of typcache for safety */ + tupdesc = CreateTupleDescCopy(tupdesc); + } + else if (functypclass == TYPEFUNC_SCALAR) + { + /* Base data type, i.e. scalar */ + tupdesc = CreateTemplateTupleDesc(1); + TupleDescInitEntry(tupdesc, + (AttrNumber) 1, + NULL, /* don't care about the name here */ + funcrettype, + -1, + 0); + TupleDescInitEntryCollation(tupdesc, + (AttrNumber) 1, + exprCollation(funcexpr)); + } + else + { + /* crummy error message, but parser should have caught this */ + elog(ERROR, "function in FROM has unsupported return type"); + } } fs->tupdesc = tupdesc; diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index 36a5929113..e7b569ed31 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -2098,3 +2098,16 @@ select *, row_to_json(u) from unnest(array[]::rngfunc2[]) u; (0 rows) drop type rngfunc2; +-- check detection of mismatching record types with a const-folded expression +with a(b) as (values (row(1,2,3))) +select * from a, coalesce(b) as c(d int, e int); -- fail +ERROR: function return row and query-specified return row do not match +DETAIL: Returned row contains 3 attributes, but query expects 2. +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 +ERROR: function return row and query-specified return row do not match +DETAIL: Returned row contains 3 attributes, but query expects 4. +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. diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql index 5d29d2e401..6a56aedac1 100644 --- a/src/test/regress/sql/rangefuncs.sql +++ b/src/test/regress/sql/rangefuncs.sql @@ -656,3 +656,12 @@ select *, row_to_json(u) from unnest(array[null::rngfunc2, (1,'foo')::rngfunc2, select *, row_to_json(u) from unnest(array[]::rngfunc2[]) u; drop type rngfunc2; + +-- check detection of mismatching record types with a const-folded expression + +with a(b) as (values (row(1,2,3))) +select * from a, coalesce(b) as c(d int, e int); -- fail +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