Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd8411a6

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 bothreturnsSet and returnsTuple as a violation of the SRF protocol. What seemsbetter is to treat a NULL output as equivalent to ROW(NULL,NULL,...).Without this, cases such as SELECT FROM unnest(...) on an array ofcomposite are vulnerable to unexpected and not-very-helpful failures.Old code comments here suggested an alternative of just ignoringsimple-NULL outputs, but that doesn't seem very principled.This change had been hung up for a long time due to uncertainty abouthow much we wanted to buy into the equivalence of simple NULL andROW(NULL,NULL,...). I think that's been mostly resolved by the discussionaround 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 thelack of other similar complaints suggests that we shouldn't take much riskof destabilization by back-patching. (Maybe that could be revisited oncethis patch has withstood some field usage.)Andrew Gierth and Tom LaneReport: <E1TurJE-0006Es-TK@wrigleys.postgresql.org>
1 parent976b24f commitd8411a6

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-
HeapTupleHeadertd;
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-
HeapTupleHeadertd;
2259+
if (!fcinfo.isnull)
2260+
{
2261+
HeapTupleHeadertd=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+
intnatts=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
intnatts=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 xfrom int8_tbl, extractq2_2_opt(int8_tbl) f(x);
640640

641641
select xfrom int8_tbl, extractq2_2_opt(int8_tbl) f(x);
642+
643+
-- check handling of nulls in SRF results (bug #7808)
644+
645+
createtypefoo2as (ainteger, btext);
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+
droptype foo2;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp