Skip to content

Commit e240a65

Browse files
committed
Provide an error cursor for "can't call an SRF here" errors.
Since it appears that v10 is going to move the goalposts by some amount in terms of where you can and can't invoke set-returning functions, arrange for the executor's "set-valued function called in context that cannot accept a set" errors to include a syntax position if possible, pointing to the specific SRF that can't be called where it's located. The main bit of infrastructure needed for this is to make the query source text accessible in the executor; but it turns out that commit 4c728f3 already did that. We just need a new function executor_errposition() modeled on parser_errposition(), and we're ready to rock. While experimenting with this, I noted that the error position wasn't properly reported if it occurred in a plpgsql FOR-over-query loop, which turned out to be because SPI_cursor_open_internal wasn't providing an error context callback during PortalStart. Fix that. There's a whole lot more that could be done with this infrastructure now that it's there, but this is not the right time in the development cycle for that sort of work. Hence, resist the temptation to plaster executor_errposition() calls everywhere ... for the moment. Discussion: https://postgr.es/m/5263.1492471571@sss.pgh.pa.us
1 parent 280c53e commit e240a65

File tree

6 files changed

+57
-10
lines changed

6 files changed

+57
-10
lines changed

src/backend/executor/execExpr.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2103,7 +2103,9 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
21032103
if (flinfo->fn_retset)
21042104
ereport(ERROR,
21052105
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2106-
errmsg("set-valued function called in context that cannot accept a set")));
2106+
errmsg("set-valued function called in context that cannot accept a set"),
2107+
parent ? executor_errposition(parent->state,
2108+
exprLocation((Node *) node)) : 0));
21072109

21082110
/* Build code to evaluate arguments directly into the fcinfo struct */
21092111
argno = 0;

src/backend/executor/execSRF.c

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434

3535

3636
/* static function decls */
37-
static void init_sexpr(Oid foid, Oid input_collation, SetExprState *sexpr,
37+
static void init_sexpr(Oid foid, Oid input_collation, Expr *node,
38+
SetExprState *sexpr, PlanState *parent,
3839
MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF);
3940
static void ShutdownSetExpr(Datum arg);
4041
static void ExecEvalFuncArgs(FunctionCallInfo fcinfo,
@@ -77,7 +78,7 @@ ExecInitTableFunctionResult(Expr *expr,
7778
state->funcReturnsSet = func->funcretset;
7879
state->args = ExecInitExprList(func->args, parent);
7980

80-
init_sexpr(func->funcid, func->inputcollid, state,
81+
init_sexpr(func->funcid, func->inputcollid, expr, state, parent,
8182
econtext->ecxt_per_query_memory, func->funcretset, false);
8283
}
8384
else
@@ -438,15 +439,15 @@ ExecInitFunctionResultSet(Expr *expr,
438439
FuncExpr *func = (FuncExpr *) expr;
439440

440441
state->args = ExecInitExprList(func->args, parent);
441-
init_sexpr(func->funcid, func->inputcollid, state,
442+
init_sexpr(func->funcid, func->inputcollid, expr, state, parent,
442443
econtext->ecxt_per_query_memory, true, true);
443444
}
444445
else if (IsA(expr, OpExpr))
445446
{
446447
OpExpr *op = (OpExpr *) expr;
447448

448449
state->args = ExecInitExprList(op->args, parent);
449-
init_sexpr(op->opfuncid, op->inputcollid, state,
450+
init_sexpr(op->opfuncid, op->inputcollid, expr, state, parent,
450451
econtext->ecxt_per_query_memory, true, true);
451452
}
452453
else
@@ -645,7 +646,8 @@ ExecMakeFunctionResultSet(SetExprState *fcache,
645646
* init_sexpr - initialize a SetExprState node during first use
646647
*/
647648
static void
648-
init_sexpr(Oid foid, Oid input_collation, SetExprState *sexpr,
649+
init_sexpr(Oid foid, Oid input_collation, Expr *node,
650+
SetExprState *sexpr, PlanState *parent,
649651
MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF)
650652
{
651653
AclResult aclresult;
@@ -683,7 +685,9 @@ init_sexpr(Oid foid, Oid input_collation, SetExprState *sexpr,
683685
if (sexpr->func.fn_retset && !allowSRF)
684686
ereport(ERROR,
685687
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
686-
errmsg("set-valued function called in context that cannot accept a set")));
688+
errmsg("set-valued function called in context that cannot accept a set"),
689+
parent ? executor_errposition(parent->state,
690+
exprLocation((Node *) node)) : 0));
687691

688692
/* Otherwise, caller should have marked the sexpr correctly */
689693
Assert(sexpr->func.fn_retset == sexpr->funcReturnsSet);

src/backend/executor/execUtils.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
* ExecOpenScanRelation Common code for scan node init routines.
2929
* ExecCloseScanRelation
3030
*
31+
* executor_errposition Report syntactic position of an error.
32+
*
3133
* RegisterExprContextCallback Register function shutdown callback
3234
* UnregisterExprContextCallback Deregister function shutdown callback
3335
*
@@ -44,6 +46,7 @@
4446
#include "access/relscan.h"
4547
#include "access/transam.h"
4648
#include "executor/executor.h"
49+
#include "mb/pg_wchar.h"
4750
#include "nodes/nodeFuncs.h"
4851
#include "parser/parsetree.h"
4952
#include "storage/lmgr.h"
@@ -685,6 +688,36 @@ UpdateChangedParamSet(PlanState *node, Bitmapset *newchg)
685688
bms_free(parmset);
686689
}
687690

691+
/*
692+
* executor_errposition
693+
* Report an execution-time cursor position, if possible.
694+
*
695+
* This is expected to be used within an ereport() call. The return value
696+
* is a dummy (always 0, in fact).
697+
*
698+
* The locations stored in parsetrees are byte offsets into the source string.
699+
* We have to convert them to 1-based character indexes for reporting to
700+
* clients. (We do things this way to avoid unnecessary overhead in the
701+
* normal non-error case: computing character indexes would be much more
702+
* expensive than storing token offsets.)
703+
*/
704+
int
705+
executor_errposition(EState *estate, int location)
706+
{
707+
int pos;
708+
709+
/* No-op if location was not provided */
710+
if (location < 0)
711+
return 0;
712+
/* Can't do anything if source text is not available */
713+
if (estate == NULL || estate->es_sourceText == NULL)
714+
return 0;
715+
/* Convert offset to character number */
716+
pos = pg_mbstrlen_with_len(estate->es_sourceText, location) + 1;
717+
/* And pass it to the ereport mechanism */
718+
return errposition(pos);
719+
}
720+
688721
/*
689722
* Register a shutdown callback in an ExprContext.
690723
*

src/backend/executor/spi.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,9 +1197,6 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
11971197
cplan = GetCachedPlan(plansource, paramLI, false, _SPI_current->queryEnv);
11981198
stmt_list = cplan->stmt_list;
11991199

1200-
/* Pop the error context stack */
1201-
error_context_stack = spierrcontext.previous;
1202-
12031200
if (!plan->saved)
12041201
{
12051202
/*
@@ -1318,6 +1315,9 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
13181315

13191316
Assert(portal->strategy != PORTAL_MULTI_QUERY);
13201317

1318+
/* Pop the error context stack */
1319+
error_context_stack = spierrcontext.previous;
1320+
13211321
/* Pop the SPI stack */
13221322
_SPI_end_call(true);
13231323

src/include/executor/executor.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,8 @@ extern bool ExecRelationIsTargetRelation(EState *estate, Index scanrelid);
482482
extern Relation ExecOpenScanRelation(EState *estate, Index scanrelid, int eflags);
483483
extern void ExecCloseScanRelation(Relation scanrel);
484484

485+
extern int executor_errposition(EState *estate, int location);
486+
485487
extern void RegisterExprContextCallback(ExprContext *econtext,
486488
ExprContextCallbackFunction function,
487489
Datum arg);

src/test/regress/expected/tsrf.out

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,13 @@ SELECT few.dataa, count(*) FROM few WHERE dataa = 'a' GROUP BY few.dataa, unnest
193193
-- SRFs are not allowed in aggregate arguments
194194
SELECT min(generate_series(1, 3)) FROM few;
195195
ERROR: set-valued function called in context that cannot accept a set
196+
LINE 1: SELECT min(generate_series(1, 3)) FROM few;
197+
^
196198
-- SRFs are not allowed in window function arguments, either
197199
SELECT min(generate_series(1, 3)) OVER() FROM few;
198200
ERROR: set-valued function called in context that cannot accept a set
201+
LINE 1: SELECT min(generate_series(1, 3)) OVER() FROM few;
202+
^
199203
-- SRFs are normally computed after window functions
200204
SELECT id,lag(id) OVER(), count(*) OVER(), generate_series(1,3) FROM few;
201205
id | lag | count | generate_series
@@ -424,6 +428,8 @@ SELECT int4mul(generate_series(1,2), 10);
424428
-- but SRFs in function RTEs must be at top level (annoying restriction)
425429
SELECT * FROM int4mul(generate_series(1,2), 10);
426430
ERROR: set-valued function called in context that cannot accept a set
431+
LINE 1: SELECT * FROM int4mul(generate_series(1,2), 10);
432+
^
427433
-- DISTINCT ON is evaluated before tSRF evaluation if SRF is not
428434
-- referenced either in ORDER BY or in the DISTINCT ON list. The ORDER
429435
-- BY reference can be implicitly generated, if there's no other ORDER BY.

0 commit comments

Comments
 (0)