Skip to content

Commit e0df808

Browse files
committed
Fix type-checking of RECORD-returning functions in FROM, redux.
Commit 2ed8f9a intended to institute a policy that if a RangeTblFunction has a coldeflist, then the function return type is certainly RECORD, and we should use the coldeflist as the source of truth about what the columns of the record type are. When the original function has been folded to a constant, inspection of the constant might give a different answer. This situation will lead to a tuple-type-mismatch error at execution, but up until that point we need to consistently believe the coldeflist, or we'll have problems from different bits of code reaching different conclusions. expandRTE didn't get that memo though, and would try to produce a tupdesc based on the constant in this situation, leading to an assertion failure. (Desultory testing suggests that non-assert builds often manage to give the expected error, although I also saw a "cache lookup failed for type 0" error, and it seems at least possible that a crash could happen.) Some other callers of get_expr_result_type and get_expr_result_tupdesc were also being incautious about this. While none of them seem to have actual bugs, they're working harder than necessary in this case, besides which it seems safest to have an explicit policy of not using those functions on an RTE with a coldeflist. Adjust the code accordingly, and add commentary to funcapi.c about this policy. Also fix an obsolete comment that claimed "get_expr_result_type() doesn't know how to extract type info from a RECORD constant". That hasn't been true since commit d575347. Per bug #18422 from Alexander Lakhin. As with the previous commit, back-patch to all supported branches. Discussion: https://postgr.es/m/18422-89ca86c8eac5246d@postgresql.org
1 parent cee8db3 commit e0df808

File tree

7 files changed

+41
-13
lines changed

7 files changed

+41
-13
lines changed

src/backend/catalog/dependency.c

+5-1
Original file line numberDiff line numberDiff line change
@@ -2337,7 +2337,11 @@ process_function_rte_ref(RangeTblEntry *rte, AttrNumber attnum,
23372337
{
23382338
TupleDesc tupdesc;
23392339

2340-
tupdesc = get_expr_result_tupdesc(rtfunc->funcexpr, true);
2340+
/* If it has a coldeflist, it certainly returns RECORD */
2341+
if (rtfunc->funccolnames != NIL)
2342+
tupdesc = NULL; /* no need to work hard */
2343+
else
2344+
tupdesc = get_expr_result_tupdesc(rtfunc->funcexpr, true);
23412345
if (tupdesc && tupdesc->tdtypeid != RECORDOID)
23422346
{
23432347
/*

src/backend/optimizer/prep/prepjointree.c

+4
Original file line numberDiff line numberDiff line change
@@ -1854,6 +1854,10 @@ pull_up_constant_function(PlannerInfo *root, Node *jtnode,
18541854
if (rtf->funccolcount != 1)
18551855
return jtnode; /* definitely composite */
18561856

1857+
/* If it has a coldeflist, it certainly returns RECORD */
1858+
if (rtf->funccolnames != NIL)
1859+
return jtnode; /* must be a one-column RECORD type */
1860+
18571861
functypclass = get_expr_result_type(rtf->funcexpr,
18581862
&funcrettype,
18591863
&tupdesc);

src/backend/optimizer/util/clauses.c

+5-6
Original file line numberDiff line numberDiff line change
@@ -4431,12 +4431,11 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
44314431
* Can't simplify if it returns RECORD. The immediate problem is that it
44324432
* will be needing an expected tupdesc which we can't supply here.
44334433
*
4434-
* In the case where it has OUT parameters, it could get by without an
4435-
* expected tupdesc, but we still have issues: get_expr_result_type()
4436-
* doesn't know how to extract type info from a RECORD constant, and in
4437-
* the case of a NULL function result there doesn't seem to be any clean
4438-
* way to fix that. In view of the likelihood of there being still other
4439-
* gotchas, seems best to leave the function call unreduced.
4434+
* In the case where it has OUT parameters, we could build an expected
4435+
* tupdesc from those, but there may be other gotchas lurking. In
4436+
* particular, if the function were to return NULL, we would produce a
4437+
* null constant with no remaining indication of which concrete record
4438+
* type it is. For now, seems best to leave the function call unreduced.
44404439
*/
44414440
if (funcform->prorettype == RECORDOID)
44424441
return NULL;

src/backend/parser/parse_relation.c

+14-5
Original file line numberDiff line numberDiff line change
@@ -2738,12 +2738,17 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
27382738
{
27392739
RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
27402740
TypeFuncClass functypclass;
2741-
Oid funcrettype;
2742-
TupleDesc tupdesc;
2741+
Oid funcrettype = InvalidOid;
2742+
TupleDesc tupdesc = NULL;
2743+
2744+
/* If it has a coldeflist, it returns RECORD */
2745+
if (rtfunc->funccolnames != NIL)
2746+
functypclass = TYPEFUNC_RECORD;
2747+
else
2748+
functypclass = get_expr_result_type(rtfunc->funcexpr,
2749+
&funcrettype,
2750+
&tupdesc);
27432751

2744-
functypclass = get_expr_result_type(rtfunc->funcexpr,
2745-
&funcrettype,
2746-
&tupdesc);
27472752
if (functypclass == TYPEFUNC_COMPOSITE ||
27482753
functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
27492754
{
@@ -3369,6 +3374,10 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
33693374
{
33703375
TupleDesc tupdesc;
33713376

3377+
/* If it has a coldeflist, it returns RECORD */
3378+
if (rtfunc->funccolnames != NIL)
3379+
return false; /* can't have any dropped columns */
3380+
33723381
tupdesc = get_expr_result_tupdesc(rtfunc->funcexpr,
33733382
true);
33743383
if (tupdesc)

src/backend/utils/fmgr/funcapi.c

+9-1
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,13 @@ get_call_result_type(FunctionCallInfo fcinfo,
287287
/*
288288
* get_expr_result_type
289289
* As above, but work from a calling expression node tree
290+
*
291+
* Beware of using this on the funcexpr of a RTE that has a coldeflist.
292+
* The correct conclusion in such cases is always that the function returns
293+
* RECORD with the columns defined by the coldeflist fields (funccolnames etc).
294+
* If it does not, it's the executor's responsibility to catch the discrepancy
295+
* at runtime; but code processing the query in advance of that point might
296+
* come to inconsistent conclusions if it checks the actual expression.
290297
*/
291298
TypeFuncClass
292299
get_expr_result_type(Node *expr,
@@ -537,7 +544,8 @@ internal_get_result_type(Oid funcid,
537544
* if noError is true, else throws error.
538545
*
539546
* This is a simpler version of get_expr_result_type() for use when the caller
540-
* is only interested in determinate rowtype results.
547+
* is only interested in determinate rowtype results. As with that function,
548+
* beware of using this on the funcexpr of a RTE that has a coldeflist.
541549
*/
542550
TupleDesc
543551
get_expr_result_tupdesc(Node *expr, bool noError)

src/test/regress/expected/rangefuncs.out

+3
Original file line numberDiff line numberDiff line change
@@ -2498,3 +2498,6 @@ with a(b) as (values (row(1,2,3)))
24982498
select * from a, coalesce(b) as c(d int, e int, f float); -- fail
24992499
ERROR: function return row and query-specified return row do not match
25002500
DETAIL: Returned type integer at ordinal position 3, but query expects double precision.
2501+
select * from int8_tbl, coalesce(row(1)) as (a int, b int); -- fail
2502+
ERROR: function return row and query-specified return row do not match
2503+
DETAIL: Returned row contains 1 attribute, but query expects 2.

src/test/regress/sql/rangefuncs.sql

+1
Original file line numberDiff line numberDiff line change
@@ -824,3 +824,4 @@ with a(b) as (values (row(1,2,3)))
824824
select * from a, coalesce(b) as c(d int, e int, f int, g int); -- fail
825825
with a(b) as (values (row(1,2,3)))
826826
select * from a, coalesce(b) as c(d int, e int, f float); -- fail
827+
select * from int8_tbl, coalesce(row(1)) as (a int, b int); -- fail

0 commit comments

Comments
 (0)