Skip to content

Commit bebaf70

Browse files
committed
Adjust ExecMakeTableFunctionResult to produce a single all-nulls row
when a function that returns a single tuple (not a setof tuple) returns NULL. This seems to be the most consistent behavior. It would have taken a bit less code to make it return an empty table (zero rows) but ISTM a non-SETOF function ought always return exactly one row. Per bug report from Ivan-Sun1.
1 parent b84788d commit bebaf70

File tree

2 files changed

+78
-50
lines changed

2 files changed

+78
-50
lines changed

src/backend/executor/execQual.c

Lines changed: 74 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.168 2004/08/29 05:06:42 momjian Exp $
11+
* $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.169 2004/09/22 17:41:50 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -1115,7 +1115,8 @@ ExecMakeFunctionResultNoSets(FuncExprState *fcache,
11151115
* ExecMakeTableFunctionResult
11161116
*
11171117
* Evaluate a table function, producing a materialized result in a Tuplestore
1118-
* object. (If function returns an empty set, we just return NULL instead.)
1118+
* object. *returnDesc is set to the tupledesc actually returned by the
1119+
* function, or NULL if it didn't provide one.
11191120
*/
11201121
Tuplestorestate *
11211122
ExecMakeTableFunctionResult(ExprState *funcexpr,
@@ -1127,6 +1128,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
11271128
TupleDesc tupdesc = NULL;
11281129
Oid funcrettype;
11291130
bool returnsTuple;
1131+
bool returnsSet = false;
11301132
FunctionCallInfoData fcinfo;
11311133
ReturnSetInfo rsinfo;
11321134
HeapTupleData tmptup;
@@ -1135,6 +1137,31 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
11351137
bool direct_function_call;
11361138
bool first_time = true;
11371139

1140+
callerContext = CurrentMemoryContext;
1141+
1142+
funcrettype = exprType((Node *) funcexpr->expr);
1143+
1144+
returnsTuple = (funcrettype == RECORDOID ||
1145+
get_typtype(funcrettype) == 'c');
1146+
1147+
/*
1148+
* Prepare a resultinfo node for communication. We always do this
1149+
* even if not expecting a set result, so that we can pass
1150+
* expectedDesc. In the generic-expression case, the expression
1151+
* doesn't actually get to see the resultinfo, but set it up anyway
1152+
* because we use some of the fields as our own state variables.
1153+
*/
1154+
MemSet(&fcinfo, 0, sizeof(fcinfo));
1155+
fcinfo.resultinfo = (Node *) &rsinfo;
1156+
rsinfo.type = T_ReturnSetInfo;
1157+
rsinfo.econtext = econtext;
1158+
rsinfo.expectedDesc = expectedDesc;
1159+
rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
1160+
rsinfo.returnMode = SFRM_ValuePerCall;
1161+
/* isDone is filled below */
1162+
rsinfo.setResult = NULL;
1163+
rsinfo.setDesc = NULL;
1164+
11381165
/*
11391166
* Normally the passed expression tree will be a FuncExprState, since
11401167
* the grammar only allows a function call at the top level of a table
@@ -1165,6 +1192,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
11651192

11661193
init_fcache(func->funcid, fcache, econtext->ecxt_per_query_memory);
11671194
}
1195+
returnsSet = fcache->func.fn_retset;
11681196

11691197
/*
11701198
* Evaluate the function's argument list.
@@ -1174,7 +1202,6 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
11741202
* the inner loop. So do it in caller context. Perhaps we should
11751203
* make a separate context just to hold the evaluated arguments?
11761204
*/
1177-
MemSet(&fcinfo, 0, sizeof(fcinfo));
11781205
fcinfo.flinfo = &(fcache->func);
11791206
argDone = ExecEvalFuncArgs(&fcinfo, fcache->args, econtext);
11801207
/* We don't allow sets in the arguments of the table function */
@@ -1185,7 +1212,8 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
11851212

11861213
/*
11871214
* If function is strict, and there are any NULL arguments, skip
1188-
* calling the function and return NULL (actually an empty set).
1215+
* calling the function and act like it returned NULL (or an empty
1216+
* set, in the returns-set case).
11891217
*/
11901218
if (fcache->func.fn_strict)
11911219
{
@@ -1194,10 +1222,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
11941222
for (i = 0; i < fcinfo.nargs; i++)
11951223
{
11961224
if (fcinfo.argnull[i])
1197-
{
1198-
*returnDesc = NULL;
1199-
return NULL;
1200-
}
1225+
goto no_function_result;
12011226
}
12021227
}
12031228
}
@@ -1207,33 +1232,11 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
12071232
direct_function_call = false;
12081233
}
12091234

1210-
funcrettype = exprType((Node *) funcexpr->expr);
1211-
1212-
returnsTuple = (funcrettype == RECORDOID ||
1213-
get_typtype(funcrettype) == 'c');
1214-
1215-
/*
1216-
* Prepare a resultinfo node for communication. We always do this
1217-
* even if not expecting a set result, so that we can pass
1218-
* expectedDesc. In the generic-expression case, the expression
1219-
* doesn't actually get to see the resultinfo, but set it up anyway
1220-
* because we use some of the fields as our own state variables.
1221-
*/
1222-
fcinfo.resultinfo = (Node *) &rsinfo;
1223-
rsinfo.type = T_ReturnSetInfo;
1224-
rsinfo.econtext = econtext;
1225-
rsinfo.expectedDesc = expectedDesc;
1226-
rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
1227-
rsinfo.returnMode = SFRM_ValuePerCall;
1228-
/* isDone is filled below */
1229-
rsinfo.setResult = NULL;
1230-
rsinfo.setDesc = NULL;
1231-
12321235
/*
12331236
* Switch to short-lived context for calling the function or
12341237
* expression.
12351238
*/
1236-
callerContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
1239+
MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
12371240

12381241
/*
12391242
* Loop to handle the ValuePerCall protocol (which is also the same
@@ -1269,23 +1272,26 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
12691272
{
12701273
/*
12711274
* Check for end of result set.
1272-
*
1273-
* Note: if function returns an empty set, we don't build a
1274-
* tupdesc or tuplestore (since we can't get a tupdesc in the
1275-
* function-returning-tuple case)
12761275
*/
12771276
if (rsinfo.isDone == ExprEndResult)
12781277
break;
12791278

12801279
/*
1281-
* Can't do anything useful with NULL rowtype values.
1282-
* Currently we raise an error, but another alternative is to
1283-
* just ignore the result and "continue" to get another row.
1280+
* Can't do anything very useful with NULL rowtype values.
1281+
* For a function returning set, we consider this a protocol
1282+
* violation (but another alternative would be to just ignore
1283+
* the result and "continue" to get another row). For a function
1284+
* not returning set, we fall out of the loop; we'll cons up
1285+
* an all-nulls result row below.
12841286
*/
12851287
if (returnsTuple && fcinfo.isnull)
1288+
{
1289+
if (!returnsSet)
1290+
break;
12861291
ereport(ERROR,
12871292
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
1288-
errmsg("function returning row cannot return null value")));
1293+
errmsg("function returning set of rows cannot return null value")));
1294+
}
12891295

12901296
/*
12911297
* If first time through, build tupdesc and tuplestore for
@@ -1381,6 +1387,35 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
13811387
first_time = false;
13821388
}
13831389

1390+
no_function_result:
1391+
1392+
/*
1393+
* If we got nothing from the function (ie, an empty-set or NULL result),
1394+
* we have to create the tuplestore to return, and if it's a
1395+
* non-set-returning function then insert a single all-nulls row.
1396+
*/
1397+
if (rsinfo.setResult == NULL)
1398+
{
1399+
MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
1400+
tupstore = tuplestore_begin_heap(true, false, work_mem);
1401+
rsinfo.setResult = tupstore;
1402+
if (!returnsSet)
1403+
{
1404+
int natts = expectedDesc->natts;
1405+
Datum *nulldatums;
1406+
char *nullflags;
1407+
HeapTuple tuple;
1408+
1409+
MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
1410+
nulldatums = (Datum *) palloc0(natts * sizeof(Datum));
1411+
nullflags = (char *) palloc(natts * sizeof(char));
1412+
memset(nullflags, 'n', natts * sizeof(char));
1413+
tuple = heap_formtuple(expectedDesc, nulldatums, nullflags);
1414+
MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
1415+
tuplestore_puttuple(tupstore, tuple);
1416+
}
1417+
}
1418+
13841419
MemoryContextSwitchTo(callerContext);
13851420

13861421
/* The returned pointers are those in rsinfo */

src/backend/executor/nodeFunctionscan.c

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.26 2004/08/29 04:12:31 momjian Exp $
11+
* $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.27 2004/09/22 17:41:51 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -96,17 +96,10 @@ FunctionNext(FunctionScanState *node)
9696
/*
9797
* Get the next tuple from tuplestore. Return NULL if no more tuples.
9898
*/
99+
heapTuple = tuplestore_getheaptuple(tuplestorestate,
100+
ScanDirectionIsForward(direction),
101+
&should_free);
99102
slot = node->ss.ss_ScanTupleSlot;
100-
if (tuplestorestate)
101-
heapTuple = tuplestore_getheaptuple(tuplestorestate,
102-
ScanDirectionIsForward(direction),
103-
&should_free);
104-
else
105-
{
106-
heapTuple = NULL;
107-
should_free = false;
108-
}
109-
110103
return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
111104
}
112105

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy