@@ -67,6 +67,7 @@ static PLpgSQL_expr *read_sql_construct(int until,
67
67
const char *sqlstart,
68
68
bool isexpression,
69
69
bool valid_sql,
70
+ bool trim,
70
71
int *startloc,
71
72
int *endtoken);
72
73
static PLpgSQL_expr *read_sql_expression (int until,
@@ -1313,6 +1314,7 @@ for_control : for_variable K_IN
1313
1314
" SELECT " ,
1314
1315
true ,
1315
1316
false ,
1317
+ true ,
1316
1318
&expr1loc,
1317
1319
&tok);
1318
1320
@@ -1692,7 +1694,7 @@ stmt_raise : K_RAISE
1692
1694
expr = read_sql_construct (' ,' , ' ;' , K_USING,
1693
1695
" , or ; or USING" ,
1694
1696
" SELECT " ,
1695
- true , true ,
1697
+ true , true , true ,
1696
1698
NULL , &tok);
1697
1699
new ->params = lappend (new ->params , expr);
1698
1700
}
@@ -1790,7 +1792,7 @@ stmt_dynexecute : K_EXECUTE
1790
1792
expr = read_sql_construct (K_INTO, K_USING, ' ;' ,
1791
1793
" INTO or USING or ;" ,
1792
1794
" SELECT " ,
1793
- true , true ,
1795
+ true , true , true ,
1794
1796
NULL , &endtoken);
1795
1797
1796
1798
new = palloc (sizeof (PLpgSQL_stmt_dynexecute));
@@ -1829,7 +1831,7 @@ stmt_dynexecute : K_EXECUTE
1829
1831
expr = read_sql_construct (' ,' , ' ;' , K_INTO,
1830
1832
" , or ; or INTO" ,
1831
1833
" SELECT " ,
1832
- true , true ,
1834
+ true , true , true ,
1833
1835
NULL , &endtoken);
1834
1836
new ->params = lappend (new ->params , expr);
1835
1837
} while (endtoken == ' ,' );
@@ -2322,7 +2324,7 @@ static PLpgSQL_expr *
2322
2324
read_sql_expression(int until, const char *expected)
2323
2325
{
2324
2326
return read_sql_construct(until, 0, 0, expected,
2325
- "SELECT ", true, true, NULL, NULL);
2327
+ "SELECT ", true, true, true, NULL, NULL);
2326
2328
}
2327
2329
2328
2330
/* Convenience routine to read an expression with two possible terminators */
@@ -2331,15 +2333,15 @@ read_sql_expression2(int until, int until2, const char *expected,
2331
2333
int *endtoken)
2332
2334
{
2333
2335
return read_sql_construct(until, until2, 0, expected,
2334
- "SELECT ", true, true, NULL, endtoken);
2336
+ "SELECT ", true, true, true, NULL, endtoken);
2335
2337
}
2336
2338
2337
2339
/* Convenience routine to read a SQL statement that must end with ';' */
2338
2340
static PLpgSQL_expr *
2339
2341
read_sql_stmt(const char *sqlstart)
2340
2342
{
2341
2343
return read_sql_construct(';', 0, 0, ";",
2342
- sqlstart, false, true, NULL, NULL);
2344
+ sqlstart, false, true, true, NULL, NULL);
2343
2345
}
2344
2346
2345
2347
/*
@@ -2352,6 +2354,7 @@ read_sql_stmt(const char *sqlstart)
2352
2354
* sqlstart: text to prefix to the accumulated SQL text
2353
2355
* isexpression: whether to say we're reading an "expression" or a "statement"
2354
2356
* valid_sql: whether to check the syntax of the expr (prefixed with sqlstart)
2357
+ * trim: trim trailing whitespace
2355
2358
* startloc: if not NULL, location of first token is stored at *startloc
2356
2359
* endtoken: if not NULL, ending token is stored at *endtoken
2357
2360
* (this is only interesting if until2 or until3 isn't zero)
@@ -2364,6 +2367,7 @@ read_sql_construct(int until,
2364
2367
const char *sqlstart,
2365
2368
bool isexpression,
2366
2369
bool valid_sql,
2370
+ bool trim,
2367
2371
int *startloc,
2368
2372
int *endtoken)
2369
2373
{
@@ -2443,8 +2447,11 @@ read_sql_construct(int until,
2443
2447
plpgsql_append_source_text(&ds, startlocation, yylloc);
2444
2448
2445
2449
/* 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
+ }
2448
2455
2449
2456
expr = palloc0(sizeof(PLpgSQL_expr));
2450
2457
expr->dtype = PLPGSQL_DTYPE_EXPR;
@@ -3375,15 +3382,23 @@ check_labels(const char *start_label, const char *end_label, int end_location)
3375
3382
* Read the arguments (if any) for a cursor, followed by the until token
3376
3383
*
3377
3384
* 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).
3381
3390
*/
3382
3391
static PLpgSQL_expr *
3383
3392
read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
3384
3393
{
3385
3394
PLpgSQL_expr *expr;
3395
+ PLpgSQL_row *row;
3386
3396
int tok;
3397
+ int argc = 0;
3398
+ char **argv;
3399
+ StringInfoData ds;
3400
+ char *sqlstart = "SELECT ";
3401
+ bool named = false;
3387
3402
3388
3403
tok = yylex();
3389
3404
if (cursor->cursor_explicit_argrow < 0)
@@ -3402,6 +3417,9 @@ read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
3402
3417
return NULL;
3403
3418
}
3404
3419
3420
+ row = (PLpgSQL_row *) plpgsql_Datums[cursor->cursor_explicit_argrow];
3421
+ argv = (char **) palloc0(row->nfields * sizeof(char *));
3422
+
3405
3423
/* Else better provide arguments */
3406
3424
if (tok != '(')
3407
3425
ereport(ERROR,
@@ -3411,9 +3429,119 @@ read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
3411
3429
parser_errposition(yylloc)));
3412
3430
3413
3431
/*
3414
- * Read expressions until the matching ')' .
3432
+ * Read the arguments, one by one .
3415
3433
*/
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);
3417
3545
3418
3546
/* Next we'd better find the until token */
3419
3547
tok = yylex();
0 commit comments