@@ -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);
7273static 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 *
23222324read_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 ';' */
23382340static PLpgSQL_expr *
23392341read_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 */
33823391static PLpgSQL_expr *
33833392read_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();
0 commit comments