Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d8411a6

Browse files
committed
Allow functions that return sets of tuples to return simple NULLs.
ExecMakeTableFunctionResult(), which is used in SELECT FROM function(...) cases, formerly treated a simple NULL output from a function that both returnsSet and returnsTuple as a violation of the SRF protocol. What seems better is to treat a NULL output as equivalent to ROW(NULL,NULL,...). Without this, cases such as SELECT FROM unnest(...) on an array of composite are vulnerable to unexpected and not-very-helpful failures. Old code comments here suggested an alternative of just ignoring simple-NULL outputs, but that doesn't seem very principled. This change had been hung up for a long time due to uncertainty about how much we wanted to buy into the equivalence of simple NULL and ROW(NULL,NULL,...). I think that's been mostly resolved by the discussion around bug #14235, so let's go ahead and do it. Per bug #7808 from Joe Van Dyk. Although this is a pretty old report, fixing it smells a bit more like a new feature than a bug fix, and the lack of other similar complaints suggests that we shouldn't take much risk of destabilization by back-patching. (Maybe that could be revisited once this patch has withstood some field usage.) Andrew Gierth and Tom Lane Report: <[email protected]>
1 parent 976b24f commit d8411a6

File tree

3 files changed

+106
-60
lines changed

3 files changed

+106
-60
lines changed

src/backend/executor/execQual.c

Lines changed: 65 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2229,89 +2229,96 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
22292229
break;
22302230

22312231
/*
2232-
* Can't do anything very useful with NULL rowtype values. For a
2233-
* function returning set, we consider this a protocol violation
2234-
* (but another alternative would be to just ignore the result and
2235-
* "continue" to get another row). For a function not returning
2236-
* set, we fall out of the loop; we'll cons up an all-nulls result
2237-
* row below.
2238-
*/
2239-
if (returnsTuple && fcinfo.isnull)
2240-
{
2241-
if (!returnsSet)
2242-
break;
2243-
ereport(ERROR,
2244-
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
2245-
errmsg("function returning set of rows cannot return null value")));
2246-
}
2247-
2248-
/*
2249-
* If first time through, build tupdesc and tuplestore for result
2232+
* If first time through, build tuplestore for result. For a
2233+
* scalar function result type, also make a suitable tupdesc.
22502234
*/
22512235
if (first_time)
22522236
{
22532237
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
2254-
if (returnsTuple)
2255-
{
2256-
/*
2257-
* Use the type info embedded in the rowtype Datum to look
2258-
* up the needed tupdesc. Make a copy for the query.
2259-
*/
2260-
HeapTupleHeader td;
2261-
2262-
td = DatumGetHeapTupleHeader(result);
2263-
tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td),
2264-
HeapTupleHeaderGetTypMod(td));
2265-
}
2266-
else
2238+
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
2239+
rsinfo.setResult = tupstore;
2240+
if (!returnsTuple)
22672241
{
2268-
/*
2269-
* Scalar type, so make a single-column descriptor
2270-
*/
22712242
tupdesc = CreateTemplateTupleDesc(1, false);
22722243
TupleDescInitEntry(tupdesc,
22732244
(AttrNumber) 1,
22742245
"column",
22752246
funcrettype,
22762247
-1,
22772248
0);
2249+
rsinfo.setDesc = tupdesc;
22782250
}
2279-
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
22802251
MemoryContextSwitchTo(oldcontext);
2281-
rsinfo.setResult = tupstore;
2282-
rsinfo.setDesc = tupdesc;
22832252
}
22842253

22852254
/*
22862255
* Store current resultset item.
22872256
*/
22882257
if (returnsTuple)
22892258
{
2290-
HeapTupleHeader td;
2259+
if (!fcinfo.isnull)
2260+
{
2261+
HeapTupleHeader td = DatumGetHeapTupleHeader(result);
22912262

2292-
td = DatumGetHeapTupleHeader(result);
2263+
if (tupdesc == NULL)
2264+
{
2265+
/*
2266+
* This is the first non-NULL result from the
2267+
* function. Use the type info embedded in the
2268+
* rowtype Datum to look up the needed tupdesc. Make
2269+
* a copy for the query.
2270+
*/
2271+
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
2272+
tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td),
2273+
HeapTupleHeaderGetTypMod(td));
2274+
rsinfo.setDesc = tupdesc;
2275+
MemoryContextSwitchTo(oldcontext);
2276+
}
2277+
else
2278+
{
2279+
/*
2280+
* Verify all later returned rows have same subtype;
2281+
* necessary in case the type is RECORD.
2282+
*/
2283+
if (HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid ||
2284+
HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod)
2285+
ereport(ERROR,
2286+
(errcode(ERRCODE_DATATYPE_MISMATCH),
2287+
errmsg("rows returned by function are not all of the same row type")));
2288+
}
22932289

2294-
/*
2295-
* Verify all returned rows have same subtype; necessary in
2296-
* case the type is RECORD.
2297-
*/
2298-
if (HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid ||
2299-
HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod)
2300-
ereport(ERROR,
2301-
(errcode(ERRCODE_DATATYPE_MISMATCH),
2302-
errmsg("rows returned by function are not all of the same row type")));
2290+
/*
2291+
* tuplestore_puttuple needs a HeapTuple not a bare
2292+
* HeapTupleHeader, but it doesn't need all the fields.
2293+
*/
2294+
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
2295+
tmptup.t_data = td;
23032296

2304-
/*
2305-
* tuplestore_puttuple needs a HeapTuple not a bare
2306-
* HeapTupleHeader, but it doesn't need all the fields.
2307-
*/
2308-
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
2309-
tmptup.t_data = td;
2297+
tuplestore_puttuple(tupstore, &tmptup);
2298+
}
2299+
else
2300+
{
2301+
/*
2302+
* NULL result from a tuple-returning function; expand it
2303+
* to a row of all nulls. We rely on the expectedDesc to
2304+
* form such rows. (Note: this would be problematic if
2305+
* tuplestore_putvalues saved the tdtypeid/tdtypmod from
2306+
* the provided descriptor, since that might not match
2307+
* what we get from the function itself. But it doesn't.)
2308+
*/
2309+
int natts = expectedDesc->natts;
2310+
bool *nullflags;
23102311

2311-
tuplestore_puttuple(tupstore, &tmptup);
2312+
nullflags = (bool *) palloc(natts * sizeof(bool));
2313+
memset(nullflags, true, natts * sizeof(bool));
2314+
tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
2315+
}
23122316
}
23132317
else
2318+
{
2319+
/* Scalar-type case: just store the function result */
23142320
tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo.isnull);
2321+
}
23152322

23162323
/*
23172324
* Are we done?
@@ -2343,7 +2350,8 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
23432350
/*
23442351
* If we got nothing from the function (ie, an empty-set or NULL result),
23452352
* we have to create the tuplestore to return, and if it's a
2346-
* non-set-returning function then insert a single all-nulls row.
2353+
* non-set-returning function then insert a single all-nulls row. As
2354+
* above, we depend on the expectedDesc to manufacture the dummy row.
23472355
*/
23482356
if (rsinfo.setResult == NULL)
23492357
{
@@ -2353,15 +2361,12 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
23532361
if (!returnsSet)
23542362
{
23552363
int natts = expectedDesc->natts;
2356-
Datum *nulldatums;
23572364
bool *nullflags;
23582365

23592366
MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
2360-
nulldatums = (Datum *) palloc0(natts * sizeof(Datum));
23612367
nullflags = (bool *) palloc(natts * sizeof(bool));
23622368
memset(nullflags, true, natts * sizeof(bool));
2363-
MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
2364-
tuplestore_putvalues(tupstore, expectedDesc, nulldatums, nullflags);
2369+
tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
23652370
}
23662371
}
23672372

src/test/regress/expected/rangefuncs.out

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2076,3 +2076,33 @@ select x from int8_tbl, extractq2_2_opt(int8_tbl) f(x);
20762076
-4567890123456789
20772077
(5 rows)
20782078

2079+
-- check handling of nulls in SRF results (bug #7808)
2080+
create type foo2 as (a integer, b text);
2081+
select *, row_to_json(u) from unnest(array[(1,'foo')::foo2, null::foo2]) u;
2082+
a | b | row_to_json
2083+
---+-----+---------------------
2084+
1 | foo | {"a":1,"b":"foo"}
2085+
| | {"a":null,"b":null}
2086+
(2 rows)
2087+
2088+
select *, row_to_json(u) from unnest(array[null::foo2, null::foo2]) u;
2089+
a | b | row_to_json
2090+
---+---+---------------------
2091+
| | {"a":null,"b":null}
2092+
| | {"a":null,"b":null}
2093+
(2 rows)
2094+
2095+
select *, row_to_json(u) from unnest(array[null::foo2, (1,'foo')::foo2, null::foo2]) u;
2096+
a | b | row_to_json
2097+
---+-----+---------------------
2098+
| | {"a":null,"b":null}
2099+
1 | foo | {"a":1,"b":"foo"}
2100+
| | {"a":null,"b":null}
2101+
(3 rows)
2102+
2103+
select *, row_to_json(u) from unnest(array[]::foo2[]) u;
2104+
a | b | row_to_json
2105+
---+---+-------------
2106+
(0 rows)
2107+
2108+
drop type foo2;

src/test/regress/sql/rangefuncs.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,3 +639,14 @@ explain (verbose, costs off)
639639
select x from int8_tbl, extractq2_2_opt(int8_tbl) f(x);
640640

641641
select x from int8_tbl, extractq2_2_opt(int8_tbl) f(x);
642+
643+
-- check handling of nulls in SRF results (bug #7808)
644+
645+
create type foo2 as (a integer, b text);
646+
647+
select *, row_to_json(u) from unnest(array[(1,'foo')::foo2, null::foo2]) u;
648+
select *, row_to_json(u) from unnest(array[null::foo2, null::foo2]) u;
649+
select *, row_to_json(u) from unnest(array[null::foo2, (1,'foo')::foo2, null::foo2]) u;
650+
select *, row_to_json(u) from unnest(array[]::foo2[]) u;
651+
652+
drop type foo2;

0 commit comments

Comments
 (0)