Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 4adead1

Browse files
committed
Add support for passing cursor parameters in named notation in PL/pgSQL.
Yeb Havinga, reviewed by Kevin Grittner, with small changes by me.
1 parent 2dd9322 commit 4adead1

File tree

6 files changed

+429
-17
lines changed

6 files changed

+429
-17
lines changed

doc/src/sgml/plpgsql.sgml

+19-4
Original file line numberDiff line numberDiff line change
@@ -2823,11 +2823,11 @@ OPEN curs1 FOR EXECUTE 'SELECT * FROM ' || quote_ident(tabname)
28232823
</para>
28242824
</sect3>
28252825

2826-
<sect3>
2826+
<sect3 id="plpgsql-open-bound-cursor">
28272827
<title>Opening a Bound Cursor</title>
28282828

28292829
<synopsis>
2830-
OPEN <replaceable>bound_cursorvar</replaceable> <optional> ( <replaceable>argument_values</replaceable> ) </optional>;
2830+
OPEN <replaceable>bound_cursorvar</replaceable> <optional> ( <optional> <replaceable>argument_name</replaceable> := </optional> <replaceable>argument_value</replaceable> <optional>, ...</optional> ) </optional>;
28312831
</synopsis>
28322832

28332833
<para>
@@ -2846,11 +2846,22 @@ OPEN <replaceable>bound_cursorvar</replaceable> <optional> ( <replaceable>argume
28462846
behavior was already determined.
28472847
</para>
28482848

2849+
<para>
2850+
Argument values can be passed using either <firstterm>positional</firstterm>
2851+
or <firstterm>named</firstterm> notation. In positional
2852+
notation, all arguments are specified in order. In named notation,
2853+
each argument's name is specified using <literal>:=</literal> to
2854+
separate it from the argument expression. Similar to calling
2855+
functions, described in <xref linkend="sql-syntax-calling-funcs">, it
2856+
is also allowed to mix positional and named notation.
2857+
</para>
2858+
28492859
<para>
28502860
Examples (these use the cursor declaration examples above):
28512861
<programlisting>
28522862
OPEN curs2;
28532863
OPEN curs3(42);
2864+
OPEN curs3(key := 42);
28542865
</programlisting>
28552866
</para>
28562867

@@ -3169,7 +3180,7 @@ COMMIT;
31693180

31703181
<synopsis>
31713182
<optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
3172-
FOR <replaceable>recordvar</replaceable> IN <replaceable>bound_cursorvar</replaceable> <optional> ( <replaceable>argument_values</replaceable> ) </optional> LOOP
3183+
FOR <replaceable>recordvar</replaceable> IN <replaceable>bound_cursorvar</replaceable> <optional> ( <optional> <replaceable>argument_name</replaceable> := </optional> <replaceable>argument_value</replaceable> <optional>, ...</optional> ) </optional> LOOP
31733184
<replaceable>statements</replaceable>
31743185
END LOOP <optional> <replaceable>label</replaceable> </optional>;
31753186
</synopsis>
@@ -3180,7 +3191,11 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
31803191
the cursor again when the loop exits. A list of actual argument value
31813192
expressions must appear if and only if the cursor was declared to take
31823193
arguments. These values will be substituted in the query, in just
3183-
the same way as during an <command>OPEN</>.
3194+
the same way as during an <command>OPEN</> (see <xref
3195+
linkend="plpgsql-open-bound-cursor">).
3196+
</para>
3197+
3198+
<para>
31843199
The variable <replaceable>recordvar</replaceable> is automatically
31853200
defined as type <type>record</> and exists only inside the loop (any
31863201
existing definition of the variable name is ignored within the loop).

src/pl/plpgsql/src/gram.y

+141-13
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ static PLpgSQL_expr *read_sql_construct(int until,
6767
const char *sqlstart,
6868
bool isexpression,
6969
bool valid_sql,
70+
bool trim,
7071
int *startloc,
7172
int *endtoken);
7273
static PLpgSQL_expr *read_sql_expression(int until,
@@ -1313,6 +1314,7 @@ for_control : for_variable K_IN
13131314
"SELECT ",
13141315
true,
13151316
false,
1317+
true,
13161318
&expr1loc,
13171319
&tok);
13181320

@@ -1692,7 +1694,7 @@ stmt_raise : K_RAISE
16921694
expr = read_sql_construct(',', ';', K_USING,
16931695
", or ; or USING",
16941696
"SELECT ",
1695-
true, true,
1697+
true, true, true,
16961698
NULL, &tok);
16971699
new->params = lappend(new->params, expr);
16981700
}
@@ -1790,7 +1792,7 @@ stmt_dynexecute : K_EXECUTE
17901792
expr = read_sql_construct(K_INTO, K_USING, ';',
17911793
"INTO or USING or ;",
17921794
"SELECT ",
1793-
true, true,
1795+
true, true, true,
17941796
NULL, &endtoken);
17951797

17961798
new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
@@ -1829,7 +1831,7 @@ stmt_dynexecute : K_EXECUTE
18291831
expr = read_sql_construct(',', ';', K_INTO,
18301832
", or ; or INTO",
18311833
"SELECT ",
1832-
true, true,
1834+
true, true, true,
18331835
NULL, &endtoken);
18341836
new->params = lappend(new->params, expr);
18351837
} while (endtoken == ',');
@@ -2322,7 +2324,7 @@ static PLpgSQL_expr *
23222324
read_sql_expression(int until, const char *expected)
23232325
{
23242326
return read_sql_construct(until, 0, 0, expected,
2325-
"SELECT ", true, true, NULL, NULL);
2327+
"SELECT ", true, true, true, NULL, NULL);
23262328
}
23272329

23282330
/* Convenience routine to read an expression with two possible terminators */
@@ -2331,15 +2333,15 @@ read_sql_expression2(int until, int until2, const char *expected,
23312333
int *endtoken)
23322334
{
23332335
return read_sql_construct(until, until2, 0, expected,
2334-
"SELECT ", true, true, NULL, endtoken);
2336+
"SELECT ", true, true, true, NULL, endtoken);
23352337
}
23362338

23372339
/* Convenience routine to read a SQL statement that must end with ';' */
23382340
static PLpgSQL_expr *
23392341
read_sql_stmt(const char *sqlstart)
23402342
{
23412343
return read_sql_construct(';', 0, 0, ";",
2342-
sqlstart, false, true, NULL, NULL);
2344+
sqlstart, false, true, true, NULL, NULL);
23432345
}
23442346

23452347
/*
@@ -2352,6 +2354,7 @@ read_sql_stmt(const char *sqlstart)
23522354
* sqlstart: text to prefix to the accumulated SQL text
23532355
* isexpression: whether to say we're reading an "expression" or a "statement"
23542356
* valid_sql: whether to check the syntax of the expr (prefixed with sqlstart)
2357+
* trim: trim trailing whitespace
23552358
* startloc: if not NULL, location of first token is stored at *startloc
23562359
* endtoken: if not NULL, ending token is stored at *endtoken
23572360
* (this is only interesting if until2 or until3 isn't zero)
@@ -2364,6 +2367,7 @@ read_sql_construct(int until,
23642367
const char *sqlstart,
23652368
bool isexpression,
23662369
bool valid_sql,
2370+
bool trim,
23672371
int *startloc,
23682372
int *endtoken)
23692373
{
@@ -2443,8 +2447,11 @@ read_sql_construct(int until,
24432447
plpgsql_append_source_text(&ds, startlocation, yylloc);
24442448

24452449
/* trim any trailing whitespace, for neatness */
2446-
while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
2447-
ds.data[--ds.len] = '\0';
2450+
if (trim)
2451+
{
2452+
while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
2453+
ds.data[--ds.len] = '\0';
2454+
}
24482455

24492456
expr = palloc0(sizeof(PLpgSQL_expr));
24502457
expr->dtype = PLPGSQL_DTYPE_EXPR;
@@ -3375,15 +3382,23 @@ check_labels(const char *start_label, const char *end_label, int end_location)
33753382
* Read the arguments (if any) for a cursor, followed by the until token
33763383
*
33773384
* If cursor has no args, just swallow the until token and return NULL.
3378-
* If it does have args, we expect to see "( expr [, expr ...] )" followed
3379-
* by the until token. Consume all that and return a SELECT query that
3380-
* evaluates the expression(s) (without the outer parens).
3385+
* If it does have args, we expect to see "( arg [, arg ...] )" followed
3386+
* by the until token, where arg may be a plain expression, or a named
3387+
* parameter assignment of the form argname := expr. Consume all that and
3388+
* return a SELECT query that evaluates the expression(s) (without the outer
3389+
* parens).
33813390
*/
33823391
static PLpgSQL_expr *
33833392
read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
33843393
{
33853394
PLpgSQL_expr *expr;
3395+
PLpgSQL_row *row;
33863396
int tok;
3397+
int argc = 0;
3398+
char **argv;
3399+
StringInfoData ds;
3400+
char *sqlstart = "SELECT ";
3401+
bool named = false;
33873402

33883403
tok = yylex();
33893404
if (cursor->cursor_explicit_argrow < 0)
@@ -3402,6 +3417,9 @@ read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
34023417
return NULL;
34033418
}
34043419

3420+
row = (PLpgSQL_row *) plpgsql_Datums[cursor->cursor_explicit_argrow];
3421+
argv = (char **) palloc0(row->nfields * sizeof(char *));
3422+
34053423
/* Else better provide arguments */
34063424
if (tok != '(')
34073425
ereport(ERROR,
@@ -3411,9 +3429,119 @@ read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
34113429
parser_errposition(yylloc)));
34123430

34133431
/*
3414-
* Read expressions until the matching ')'.
3432+
* Read the arguments, one by one.
34153433
*/
3416-
expr = read_sql_expression(')', ")");
3434+
for (argc = 0; argc < row->nfields; argc++)
3435+
{
3436+
PLpgSQL_expr *item;
3437+
int endtoken;
3438+
int argpos;
3439+
int tok1,
3440+
tok2;
3441+
int arglocation;
3442+
3443+
/* Check if it's a named parameter: "param := value" */
3444+
plpgsql_peek2(&tok1, &tok2, &arglocation, NULL);
3445+
if (tok1 == IDENT && tok2 == COLON_EQUALS)
3446+
{
3447+
char *argname;
3448+
3449+
/* Read the argument name, and find its position */
3450+
yylex();
3451+
argname = yylval.str;
3452+
3453+
for (argpos = 0; argpos < row->nfields; argpos++)
3454+
{
3455+
if (strcmp(row->fieldnames[argpos], argname) == 0)
3456+
break;
3457+
}
3458+
if (argpos == row->nfields)
3459+
ereport(ERROR,
3460+
(errcode(ERRCODE_SYNTAX_ERROR),
3461+
errmsg("cursor \"%s\" has no argument named \"%s\"",
3462+
cursor->refname, argname),
3463+
parser_errposition(yylloc)));
3464+
3465+
/*
3466+
* Eat the ":=". We already peeked, so the error should never
3467+
* happen.
3468+
*/
3469+
tok2 = yylex();
3470+
if (tok2 != COLON_EQUALS)
3471+
yyerror("syntax error");
3472+
3473+
named = true;
3474+
}
3475+
else
3476+
argpos = argc;
3477+
3478+
/*
3479+
* Read the value expression. To provide the user with meaningful
3480+
* parse error positions, we check the syntax immediately, instead of
3481+
* checking the final expression that may have the arguments
3482+
* reordered. Trailing whitespace must not be trimmed, because
3483+
* otherwise input of the form (param -- comment\n, param) would be
3484+
* translated into a form where the second parameter is commented
3485+
* out.
3486+
*/
3487+
item = read_sql_construct(',', ')', 0,
3488+
",\" or \")",
3489+
sqlstart,
3490+
true, true,
3491+
false, /* do not trim */
3492+
NULL, &endtoken);
3493+
3494+
if (endtoken == ')' && !(argc == row->nfields - 1))
3495+
ereport(ERROR,
3496+
(errcode(ERRCODE_SYNTAX_ERROR),
3497+
errmsg("not enough arguments for cursor \"%s\"",
3498+
cursor->refname),
3499+
parser_errposition(yylloc)));
3500+
3501+
if (endtoken == ',' && (argc == row->nfields - 1))
3502+
ereport(ERROR,
3503+
(errcode(ERRCODE_SYNTAX_ERROR),
3504+
errmsg("too many arguments for cursor \"%s\"",
3505+
cursor->refname),
3506+
parser_errposition(yylloc)));
3507+
3508+
if (argv[argpos] != NULL)
3509+
ereport(ERROR,
3510+
(errcode(ERRCODE_SYNTAX_ERROR),
3511+
errmsg("duplicate value for cursor \"%s\" parameter \"%s\"",
3512+
cursor->refname, row->fieldnames[argpos]),
3513+
parser_errposition(arglocation)));
3514+
3515+
argv[argpos] = item->query + strlen(sqlstart);
3516+
}
3517+
3518+
/* Make positional argument list */
3519+
initStringInfo(&ds);
3520+
appendStringInfoString(&ds, sqlstart);
3521+
for (argc = 0; argc < row->nfields; argc++)
3522+
{
3523+
Assert(argv[argc] != NULL);
3524+
3525+
/*
3526+
* Because named notation allows permutated argument lists, include
3527+
* the parameter name for meaningful runtime errors.
3528+
*/
3529+
appendStringInfoString(&ds, argv[argc]);
3530+
if (named)
3531+
appendStringInfo(&ds, " AS %s",
3532+
quote_identifier(row->fieldnames[argc]));
3533+
if (argc < row->nfields - 1)
3534+
appendStringInfoString(&ds, ", ");
3535+
}
3536+
appendStringInfoChar(&ds, ';');
3537+
3538+
expr = palloc0(sizeof(PLpgSQL_expr));
3539+
expr->dtype = PLPGSQL_DTYPE_EXPR;
3540+
expr->query = pstrdup(ds.data);
3541+
expr->plan = NULL;
3542+
expr->paramnos = NULL;
3543+
expr->ns = plpgsql_ns_top();
3544+
pfree(ds.data);
34173545

34183546
/* Next we'd better find the until token */
34193547
tok = yylex();

src/pl/plpgsql/src/pl_scanner.c

+30
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,36 @@ plpgsql_append_source_text(StringInfo buf,
423423
endlocation - startlocation);
424424
}
425425

426+
/*
427+
* Peek two tokens ahead in the input stream. The first token and its
428+
* location the query are returned in *tok1_p and *tok1_loc, second token
429+
* and its location in *tok2_p and *tok2_loc.
430+
*
431+
* NB: no variable or unreserved keyword lookup is performed here, they will
432+
* be returned as IDENT. Reserved keywords are resolved as usual.
433+
*/
434+
void
435+
plpgsql_peek2(int *tok1_p, int *tok2_p, int *tok1_loc, int *tok2_loc)
436+
{
437+
int tok1,
438+
tok2;
439+
TokenAuxData aux1,
440+
aux2;
441+
442+
tok1 = internal_yylex(&aux1);
443+
tok2 = internal_yylex(&aux2);
444+
445+
*tok1_p = tok1;
446+
if (tok1_loc)
447+
*tok1_loc = aux1.lloc;
448+
*tok2_p = tok2;
449+
if (tok2_loc)
450+
*tok2_loc = aux2.lloc;
451+
452+
push_back_token(tok2, &aux2);
453+
push_back_token(tok1, &aux1);
454+
}
455+
426456
/*
427457
* plpgsql_scanner_errposition
428458
* Report an error cursor position, if possible.

src/pl/plpgsql/src/plpgsql.h

+2
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,8 @@ extern int plpgsql_yylex(void);
962962
extern void plpgsql_push_back_token(int token);
963963
extern void plpgsql_append_source_text(StringInfo buf,
964964
int startlocation, int endlocation);
965+
extern void plpgsql_peek2(int *tok1_p, int *tok2_p, int *tok1_loc,
966+
int *tok2_loc);
965967
extern int plpgsql_scanner_errposition(int location);
966968
extern void plpgsql_yyerror(const char *message);
967969
extern int plpgsql_location_to_lineno(int location);

0 commit comments

Comments
 (0)