v1-0003-fix-plpgsql-expressions.patch
text/x-diff
Filename: v1-0003-fix-plpgsql-expressions.patch
Type: text/x-diff
Part: 2
Patch
Format: unified
Series: patch v1-0003
| File | + | − |
|---|---|---|
| src/backend/executor/spi.c | 41 | 8 |
| src/backend/parser/gram.y | 48 | 0 |
| src/backend/parser/parser.c | 2 | 1 |
| src/include/parser/parser.h | 4 | 2 |
| src/interfaces/ecpg/preproc/parse.pl | 2 | 1 |
| src/pl/plpgsql/src/expected/plpgsql_record.out | 4 | 4 |
| src/pl/plpgsql/src/expected/plpgsql_varprops.out | 1 | 1 |
| src/pl/plpgsql/src/pl_exec.c | 1 | 1 |
| src/pl/plpgsql/src/pl_gram.y | 90 | 68 |
| src/pl/plpgsql/src/plpgsql.h | 3 | 2 |
| src/test/regress/expected/plpgsql.out | 18 | 18 |
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 3acf88e67c..f529707458 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -51,6 +51,12 @@ static _SPI_connection *_SPI_current = NULL;
static int _SPI_stack_depth = 0; /* allocated size of _SPI_stack */
static int _SPI_connected = -1; /* current stack index */
+typedef struct SPICallbackArg
+{
+ const char *query;
+ RawParseMode mode;
+} SPICallbackArg;
+
static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
ParamListInfo paramLI, bool read_only);
@@ -1479,6 +1485,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
Snapshot snapshot;
MemoryContext oldcontext;
Portal portal;
+ SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext;
/*
@@ -1533,8 +1540,10 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
* Setup error traceback support for ereport(), in case GetCachedPlan
* throws an error.
*/
+ spicallbackarg.query = plansource->query_string;
+ spicallbackarg.mode = plan->parse_mode;
spierrcontext.callback = _SPI_error_callback;
- spierrcontext.arg = unconstify(char *, plansource->query_string);
+ spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
@@ -1952,6 +1961,7 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
{
CachedPlanSource *plansource;
CachedPlan *cplan;
+ SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext;
Assert(plan->magic == _SPI_PLAN_MAGIC);
@@ -1966,8 +1976,10 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
plansource = (CachedPlanSource *) linitial(plan->plancache_list);
/* Setup error traceback support for ereport() */
+ spicallbackarg.query = plansource->query_string;
+ spicallbackarg.mode = plan->parse_mode;
spierrcontext.callback = _SPI_error_callback;
- spierrcontext.arg = unconstify(char *, plansource->query_string);
+ spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
@@ -2094,13 +2106,16 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan)
List *raw_parsetree_list;
List *plancache_list;
ListCell *list_item;
+ SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext;
/*
* Setup error traceback support for ereport()
*/
+ spicallbackarg.query = src;
+ spicallbackarg.mode = plan->parse_mode;
spierrcontext.callback = _SPI_error_callback;
- spierrcontext.arg = unconstify(char *, src);
+ spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
@@ -2199,13 +2214,16 @@ _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan)
List *raw_parsetree_list;
List *plancache_list;
ListCell *list_item;
+ SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext;
/*
* Setup error traceback support for ereport()
*/
+ spicallbackarg.query = src;
+ spicallbackarg.mode = plan->parse_mode;
spierrcontext.callback = _SPI_error_callback;
- spierrcontext.arg = unconstify(char *, src);
+ spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
@@ -2263,6 +2281,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
SPITupleTable *my_tuptable = NULL;
int res = 0;
bool pushed_active_snap = false;
+ SPICallbackArg spicallbackarg;
ErrorContextCallback spierrcontext;
CachedPlan *cplan = NULL;
ListCell *lc1;
@@ -2270,8 +2289,10 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
/*
* Setup error traceback support for ereport()
*/
+ spicallbackarg.query = NULL; /* we'll fill this below */
+ spicallbackarg.mode = plan->parse_mode;
spierrcontext.callback = _SPI_error_callback;
- spierrcontext.arg = NULL; /* we'll fill this below */
+ spierrcontext.arg = &spicallbackarg;
spierrcontext.previous = error_context_stack;
error_context_stack = &spierrcontext;
@@ -2318,7 +2339,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
List *stmt_list;
ListCell *lc2;
- spierrcontext.arg = unconstify(char *, plansource->query_string);
+ spicallbackarg.query = plansource->query_string;
/*
* If this is a one-shot plan, we still need to do parse analysis.
@@ -2722,7 +2743,8 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount)
static void
_SPI_error_callback(void *arg)
{
- const char *query = (const char *) arg;
+ SPICallbackArg *carg = (SPICallbackArg *) arg;
+ const char *query = carg->query;
int syntaxerrposition;
if (query == NULL) /* in case arg wasn't set yet */
@@ -2740,7 +2762,18 @@ _SPI_error_callback(void *arg)
internalerrquery(query);
}
else
- errcontext("SQL statement \"%s\"", query);
+ {
+ /* Use the parse mode to decide how to describe the query */
+ switch (carg->mode)
+ {
+ case RAW_PARSE_PLPGSQL_EXPR:
+ errcontext("SQL expression \"%s\"", query);
+ break;
+ default:
+ errcontext("SQL statement \"%s\"", query);
+ break;
+ }
+ }
}
/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 88c76dd985..4c58b46651 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -294,6 +294,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
+ PLpgSQL_Expr
%type <node> alter_column_default opclass_item opclass_drop alter_using
%type <ival> add_drop opt_asc_desc opt_nulls_order
@@ -731,6 +732,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* something other than the usual list of SQL commands.
*/
%token MODE_TYPE_NAME
+%token MODE_PLPGSQL_EXPR
/* Precedence: lowest to highest */
@@ -808,6 +810,11 @@ stmtblock: stmtmulti
{
pg_yyget_extra(yyscanner)->parsetree = list_make1($2);
}
+ | MODE_PLPGSQL_EXPR PLpgSQL_Expr
+ {
+ pg_yyget_extra(yyscanner)->parsetree =
+ list_make1(makeRawStmt($2, 0));
+ }
;
/*
@@ -15022,6 +15029,47 @@ role_list: RoleSpec
{ $$ = lappend($1, $3); }
;
+
+/*****************************************************************************
+ *
+ * PL/pgSQL extensions
+ *
+ * You'd think a PL/pgSQL "expression" should be just an a_expr, but
+ * historically it can include just about anything that can follow SELECT.
+ * Therefore the returned struct is a SelectStmt.
+ *****************************************************************************/
+
+PLpgSQL_Expr: opt_target_list
+ from_clause where_clause
+ group_clause having_clause window_clause
+ opt_sort_clause opt_select_limit opt_for_locking_clause
+ {
+ SelectStmt *n = makeNode(SelectStmt);
+
+ n->targetList = $1;
+ n->fromClause = $2;
+ n->whereClause = $3;
+ n->groupClause = $4;
+ n->havingClause = $5;
+ n->windowClause = $6;
+ n->sortClause = $7;
+ if ($8)
+ {
+ n->limitOffset = $8->limitOffset;
+ n->limitCount = $8->limitCount;
+ if (!n->sortClause &&
+ $8->limitOption == LIMIT_OPTION_WITH_TIES)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("WITH TIES cannot be specified without ORDER BY clause")));
+ n->limitOption = $8->limitOption;
+ }
+ n->lockingClause = $9;
+ $$ = (Node *) n;
+ }
+ ;
+
+
/*
* Name classification hierarchy.
*
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index b04be3bca5..71df8ef022 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -57,7 +57,8 @@ raw_parser(const char *str, RawParseMode mode)
/* this array is indexed by RawParseMode enum */
static const int mode_token[] = {
0, /* RAW_PARSE_DEFAULT */
- MODE_TYPE_NAME /* RAW_PARSE_TYPE_NAME */
+ MODE_TYPE_NAME, /* RAW_PARSE_TYPE_NAME */
+ MODE_PLPGSQL_EXPR /* RAW_PARSE_PLPGSQL_EXPR */
};
yyextra.have_lookahead = true;
diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h
index ebbcb788cc..3b7dab17ef 100644
--- a/src/include/parser/parser.h
+++ b/src/include/parser/parser.h
@@ -27,12 +27,14 @@
* RAW_PARSE_TYPE_NAME: parse a type name, and return a one-element List
* containing a TypeName node.
*
- * ... more to come ...
+ * RAW_PARSE_PLPGSQL_EXPR: parse a PL/pgSQL expression, and return
+ * a one-element List containing a RawStmt node.
*/
typedef enum
{
RAW_PARSE_DEFAULT = 0,
- RAW_PARSE_TYPE_NAME
+ RAW_PARSE_TYPE_NAME,
+ RAW_PARSE_PLPGSQL_EXPR
} RawParseMode;
/* Values for the backslash_quote GUC */
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index 52ba7dfa0c..2b254c2b77 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -70,7 +70,8 @@ my %replace_types = (
'ColId' => 'ignore',
'type_function_name' => 'ignore',
'ColLabel' => 'ignore',
- 'Sconst' => 'ignore',);
+ 'Sconst' => 'ignore',
+ 'PLpgSQL_Expr' => 'ignore',);
# these replace_line commands excise certain keywords from the core keyword
# lists. Be sure to account for these in ColLabel and related productions.
diff --git a/src/pl/plpgsql/src/expected/plpgsql_record.out b/src/pl/plpgsql/src/expected/plpgsql_record.out
index cf6089cbb2..52207e9b10 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_record.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_record.out
@@ -188,7 +188,7 @@ NOTICE: r1.q1 = <NULL>
NOTICE: r1.q2 = <NULL>
NOTICE: r1 = <NULL>
ERROR: record "r1" has no field "nosuchfield"
-CONTEXT: SQL statement "SELECT r1.nosuchfield"
+CONTEXT: SQL expression "r1.nosuchfield"
PL/pgSQL function inline_code_block line 7 at RAISE
-- records, not so much
do $$
@@ -202,7 +202,7 @@ end$$;
NOTICE: r1 = <NULL>
ERROR: record "r1" is not assigned yet
DETAIL: The tuple structure of a not-yet-assigned record is indeterminate.
-CONTEXT: SQL statement "SELECT r1.f1"
+CONTEXT: SQL expression "r1.f1"
PL/pgSQL function inline_code_block line 5 at RAISE
-- but OK if you assign first
do $$
@@ -220,7 +220,7 @@ NOTICE: r1.f1 = 1
NOTICE: r1.f2 = 2
NOTICE: r1 = (1,2)
ERROR: record "r1" has no field "nosuchfield"
-CONTEXT: SQL statement "SELECT r1.nosuchfield"
+CONTEXT: SQL expression "r1.nosuchfield"
PL/pgSQL function inline_code_block line 9 at RAISE
-- check repeated assignments to composite fields
create table some_table (id int, data text);
@@ -431,7 +431,7 @@ create function getf3(x mutable) returns int language plpgsql as
$$ begin return x.f3; end $$;
select getf3(null::mutable); -- doesn't work yet
ERROR: record "x" has no field "f3"
-CONTEXT: SQL statement "SELECT x.f3"
+CONTEXT: SQL expression "x.f3"
PL/pgSQL function getf3(mutable) line 1 at RETURN
alter table mutable add column f3 int;
select getf3(null::mutable); -- now it works
diff --git a/src/pl/plpgsql/src/expected/plpgsql_varprops.out b/src/pl/plpgsql/src/expected/plpgsql_varprops.out
index 18f03d75b4..3801dccc95 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_varprops.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_varprops.out
@@ -76,7 +76,7 @@ begin
raise notice 'x = %', x;
end$$;
ERROR: division by zero
-CONTEXT: SQL statement "SELECT 1/0"
+CONTEXT: SQL expression "1/0"
PL/pgSQL function inline_code_block line 3 during statement block local variable initialization
do $$
declare x bigint[] := array[1,3,5];
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 86f5e9fd24..f56dcd0e79 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -4184,7 +4184,7 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
memset(&options, 0, sizeof(options));
options.parserSetup = (ParserSetupHook) plpgsql_parser_setup;
options.parserSetupArg = (void *) expr;
- options.parseMode = RAW_PARSE_DEFAULT;
+ options.parseMode = expr->parseMode;
options.cursorOptions = cursorOptions;
plan = SPI_prepare_extended(expr->query, &options);
if (plan == NULL)
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 26732d875e..3b36220d73 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -51,7 +51,6 @@
typedef struct
{
int location;
- int leaderlen;
} sql_error_callback_arg;
#define parser_errposition(pos) plpgsql_scanner_errposition(pos)
@@ -67,7 +66,7 @@ static PLpgSQL_expr *read_sql_construct(int until,
int until2,
int until3,
const char *expected,
- const char *sqlstart,
+ RawParseMode parsemode,
bool isexpression,
bool valid_sql,
bool trim,
@@ -78,7 +77,7 @@ static PLpgSQL_expr *read_sql_expression(int until,
static PLpgSQL_expr *read_sql_expression2(int until, int until2,
const char *expected,
int *endtoken);
-static PLpgSQL_expr *read_sql_stmt(const char *sqlstart);
+static PLpgSQL_expr *read_sql_stmt(void);
static PLpgSQL_type *read_datatype(int tok);
static PLpgSQL_stmt *make_execsql_stmt(int firsttoken, int location);
static PLpgSQL_stmt_fetch *read_fetch_direction(void);
@@ -99,8 +98,8 @@ static PLpgSQL_row *read_into_scalar_list(char *initial_name,
static PLpgSQL_row *make_scalar_list1(char *initial_name,
PLpgSQL_datum *initial_datum,
int lineno, int location);
-static void check_sql_expr(const char *stmt, int location,
- int leaderlen);
+static void check_sql_expr(const char *stmt,
+ RawParseMode parseMode, int location);
static void plpgsql_sql_error_callback(void *arg);
static PLpgSQL_type *parse_datatype(const char *string, int location);
static void check_labels(const char *start_label,
@@ -540,7 +539,7 @@ decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull
{
PLpgSQL_var *new;
PLpgSQL_expr *curname_def;
- char buf[1024];
+ char buf[NAMEDATALEN * 2 + 64];
char *cp1;
char *cp2;
@@ -557,9 +556,9 @@ decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull
curname_def = palloc0(sizeof(PLpgSQL_expr));
- strcpy(buf, "SELECT ");
+ /* Note: refname has been truncated to NAMEDATALEN */
cp1 = new->refname;
- cp2 = buf + strlen(buf);
+ cp2 = buf;
/*
* Don't trust standard_conforming_strings here;
* it might change before we use the string.
@@ -575,6 +574,7 @@ decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull
}
strcpy(cp2, "'::pg_catalog.refcursor");
curname_def->query = pstrdup(buf);
+ curname_def->parseMode = RAW_PARSE_PLPGSQL_EXPR;
new->default_val = curname_def;
new->cursor_explicit_expr = $7;
@@ -602,7 +602,7 @@ opt_scrollable :
decl_cursor_query :
{
- $$ = read_sql_stmt("");
+ $$ = read_sql_stmt();
}
;
@@ -904,15 +904,37 @@ proc_stmt : pl_block ';'
{ $$ = $1; }
;
-stmt_perform : K_PERFORM expr_until_semi
+stmt_perform : K_PERFORM
{
PLpgSQL_stmt_perform *new;
+ int startloc;
new = palloc0(sizeof(PLpgSQL_stmt_perform));
new->cmd_type = PLPGSQL_STMT_PERFORM;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
- new->expr = $2;
+ plpgsql_push_back_token(K_PERFORM);
+
+ /*
+ * Since PERFORM isn't legal SQL, we have to cheat to
+ * the extent of substituting "SELECT" for "PERFORM"
+ * in the parsed text. It does not seem worth
+ * inventing a separate parse mode for this one case.
+ * We can't do syntax-checking until after we make the
+ * substitution.
+ */
+ new->expr = read_sql_construct(';', 0, 0, ";",
+ RAW_PARSE_DEFAULT,
+ false, false, true,
+ &startloc, NULL);
+ /* overwrite "perform" ... */
+ memcpy(new->expr->query, " SELECT", 7);
+ /* left-justify to get rid of the leading space */
+ memmove(new->expr->query, new->expr->query + 1,
+ strlen(new->expr->query));
+ /* offset syntax error position to account for that */
+ check_sql_expr(new->expr->query, new->expr->parseMode,
+ startloc + 1);
$$ = (PLpgSQL_stmt *)new;
}
@@ -926,7 +948,8 @@ stmt_call : K_CALL
new->cmd_type = PLPGSQL_STMT_CALL;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
- new->expr = read_sql_stmt("CALL ");
+ plpgsql_push_back_token(K_CALL);
+ new->expr = read_sql_stmt();
new->is_call = true;
$$ = (PLpgSQL_stmt *)new;
@@ -941,7 +964,8 @@ stmt_call : K_CALL
new->cmd_type = PLPGSQL_STMT_CALL;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
- new->expr = read_sql_stmt("DO ");
+ plpgsql_push_back_token(K_DO);
+ new->expr = read_sql_stmt();
new->is_call = false;
$$ = (PLpgSQL_stmt *)new;
@@ -1452,16 +1476,16 @@ for_control : for_variable K_IN
/*
* Read tokens until we see either a ".."
- * or a LOOP. The text we read may not
- * necessarily be a well-formed SQL
- * statement, so we need to invoke
- * read_sql_construct directly.
+ * or a LOOP. The text we read may be either
+ * an expression or a whole SQL statement, so
+ * we need to invoke read_sql_construct directly,
+ * and tell it not to check syntax yet.
*/
expr1 = read_sql_construct(DOT_DOT,
K_LOOP,
0,
"LOOP",
- "SELECT ",
+ RAW_PARSE_DEFAULT,
true,
false,
true,
@@ -1476,8 +1500,13 @@ for_control : for_variable K_IN
PLpgSQL_var *fvar;
PLpgSQL_stmt_fori *new;
- /* Check first expression is well-formed */
- check_sql_expr(expr1->query, expr1loc, 7);
+ /*
+ * Relabel first expression as an expression;
+ * then we can check its syntax.
+ */
+ expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ check_sql_expr(expr1->query, expr1->parseMode,
+ expr1loc);
/* Read and check the second one */
expr2 = read_sql_expression2(K_LOOP, K_BY,
@@ -1522,12 +1551,8 @@ for_control : for_variable K_IN
else
{
/*
- * No "..", so it must be a query loop. We've
- * prefixed an extra SELECT to the query text,
- * so we need to remove that before performing
- * syntax checking.
+ * No "..", so it must be a query loop.
*/
- char *tmp_query;
PLpgSQL_stmt_fors *new;
if (reverse)
@@ -1536,12 +1561,9 @@ for_control : for_variable K_IN
errmsg("cannot specify REVERSE in query FOR loop"),
parser_errposition(tokloc)));
- Assert(strncmp(expr1->query, "SELECT ", 7) == 0);
- tmp_query = pstrdup(expr1->query + 7);
- pfree(expr1->query);
- expr1->query = tmp_query;
-
- check_sql_expr(expr1->query, expr1loc, 0);
+ /* Check syntax as a regular query */
+ check_sql_expr(expr1->query, expr1->parseMode,
+ expr1loc);
new = palloc0(sizeof(PLpgSQL_stmt_fors));
new->cmd_type = PLPGSQL_STMT_FORS;
@@ -1870,7 +1892,7 @@ stmt_raise : K_RAISE
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
- "SELECT ",
+ RAW_PARSE_PLPGSQL_EXPR,
true, true, true,
NULL, &tok);
new->params = lappend(new->params, expr);
@@ -2001,7 +2023,7 @@ stmt_dynexecute : K_EXECUTE
expr = read_sql_construct(K_INTO, K_USING, ';',
"INTO or USING or ;",
- "SELECT ",
+ RAW_PARSE_PLPGSQL_EXPR,
true, true, true,
NULL, &endtoken);
@@ -2040,7 +2062,7 @@ stmt_dynexecute : K_EXECUTE
{
expr = read_sql_construct(',', ';', K_INTO,
", or ; or INTO",
- "SELECT ",
+ RAW_PARSE_PLPGSQL_EXPR,
true, true, true,
NULL, &endtoken);
new->params = lappend(new->params, expr);
@@ -2122,7 +2144,7 @@ stmt_open : K_OPEN cursor_variable
else
{
plpgsql_push_back_token(tok);
- new->query = read_sql_stmt("");
+ new->query = read_sql_stmt();
}
}
else
@@ -2246,8 +2268,8 @@ stmt_set : K_SET
new->cmd_type = PLPGSQL_STMT_SET;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
-
- new->expr = read_sql_stmt("SET ");
+ plpgsql_push_back_token(K_SET);
+ new->expr = read_sql_stmt();
$$ = (PLpgSQL_stmt *)new;
}
@@ -2259,7 +2281,8 @@ stmt_set : K_SET
new->cmd_type = PLPGSQL_STMT_SET;
new->lineno = plpgsql_location_to_lineno(@1);
new->stmtid = ++plpgsql_curr_compile->nstatements;
- new->expr = read_sql_stmt("RESET ");
+ plpgsql_push_back_token(K_RESET);
+ new->expr = read_sql_stmt();
$$ = (PLpgSQL_stmt *)new;
}
@@ -2656,7 +2679,8 @@ static PLpgSQL_expr *
read_sql_expression(int until, const char *expected)
{
return read_sql_construct(until, 0, 0, expected,
- "SELECT ", true, true, true, NULL, NULL);
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true, true, NULL, NULL);
}
/* Convenience routine to read an expression with two possible terminators */
@@ -2665,15 +2689,17 @@ read_sql_expression2(int until, int until2, const char *expected,
int *endtoken)
{
return read_sql_construct(until, until2, 0, expected,
- "SELECT ", true, true, true, NULL, endtoken);
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true, true, NULL, endtoken);
}
/* Convenience routine to read a SQL statement that must end with ';' */
static PLpgSQL_expr *
-read_sql_stmt(const char *sqlstart)
+read_sql_stmt(void)
{
return read_sql_construct(';', 0, 0, ";",
- sqlstart, false, true, true, NULL, NULL);
+ RAW_PARSE_DEFAULT,
+ false, true, true, NULL, NULL);
}
/*
@@ -2683,9 +2709,9 @@ read_sql_stmt(const char *sqlstart)
* until2: token code for alternate terminator (pass 0 if none)
* until3: token code for another alternate terminator (pass 0 if none)
* expected: text to use in complaining that terminator was not found
- * sqlstart: text to prefix to the accumulated SQL text
+ * parsemode: raw_parser() mode to use
* isexpression: whether to say we're reading an "expression" or a "statement"
- * valid_sql: whether to check the syntax of the expr (prefixed with sqlstart)
+ * valid_sql: whether to check the syntax of the expr
* trim: trim trailing whitespace
* startloc: if not NULL, location of first token is stored at *startloc
* endtoken: if not NULL, ending token is stored at *endtoken
@@ -2696,7 +2722,7 @@ read_sql_construct(int until,
int until2,
int until3,
const char *expected,
- const char *sqlstart,
+ RawParseMode parsemode,
bool isexpression,
bool valid_sql,
bool trim,
@@ -2711,7 +2737,6 @@ read_sql_construct(int until,
PLpgSQL_expr *expr;
initStringInfo(&ds);
- appendStringInfoString(&ds, sqlstart);
/* special lookup mode for identifiers within the SQL text */
save_IdentifierLookup = plpgsql_IdentifierLookup;
@@ -2787,6 +2812,7 @@ read_sql_construct(int until,
expr = palloc0(sizeof(PLpgSQL_expr));
expr->query = pstrdup(ds.data);
+ expr->parseMode = parsemode;
expr->plan = NULL;
expr->paramnos = NULL;
expr->rwparam = -1;
@@ -2794,7 +2820,7 @@ read_sql_construct(int until,
pfree(ds.data);
if (valid_sql)
- check_sql_expr(expr->query, startlocation, strlen(sqlstart));
+ check_sql_expr(expr->query, expr->parseMode, startlocation);
return expr;
}
@@ -3033,13 +3059,14 @@ make_execsql_stmt(int firsttoken, int location)
expr = palloc0(sizeof(PLpgSQL_expr));
expr->query = pstrdup(ds.data);
+ expr->parseMode = RAW_PARSE_DEFAULT;
expr->plan = NULL;
expr->paramnos = NULL;
expr->rwparam = -1;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
- check_sql_expr(expr->query, location, 0);
+ check_sql_expr(expr->query, expr->parseMode, location);
execsql = palloc(sizeof(PLpgSQL_stmt_execsql));
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
@@ -3382,7 +3409,7 @@ make_return_query_stmt(int location)
{
/* ordinary static query */
plpgsql_push_back_token(tok);
- new->query = read_sql_stmt("");
+ new->query = read_sql_stmt();
}
else
{
@@ -3637,13 +3664,12 @@ make_scalar_list1(char *initial_name,
* borders. So it is best to bail out as early as we can.
*
* It is assumed that "stmt" represents a copy of the function source text
- * beginning at offset "location", with leader text of length "leaderlen"
- * (typically "SELECT ") prefixed to the source text. We use this assumption
- * to transpose any error cursor position back to the function source text.
+ * beginning at offset "location". We use this assumption to transpose
+ * any error cursor position back to the function source text.
* If no error cursor is provided, we'll just point at "location".
*/
static void
-check_sql_expr(const char *stmt, int location, int leaderlen)
+check_sql_expr(const char *stmt, RawParseMode parseMode, int location)
{
sql_error_callback_arg cbarg;
ErrorContextCallback syntax_errcontext;
@@ -3653,7 +3679,6 @@ check_sql_expr(const char *stmt, int location, int leaderlen)
return;
cbarg.location = location;
- cbarg.leaderlen = leaderlen;
syntax_errcontext.callback = plpgsql_sql_error_callback;
syntax_errcontext.arg = &cbarg;
@@ -3661,7 +3686,7 @@ check_sql_expr(const char *stmt, int location, int leaderlen)
error_context_stack = &syntax_errcontext;
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
- (void) raw_parser(stmt, RAW_PARSE_DEFAULT);
+ (void) raw_parser(stmt, parseMode);
MemoryContextSwitchTo(oldCxt);
/* Restore former ereport callback */
@@ -3686,12 +3711,12 @@ plpgsql_sql_error_callback(void *arg)
* Note we are dealing with 1-based character numbers at this point.
*/
errpos = geterrposition();
- if (errpos > cbarg->leaderlen)
+ if (errpos > 0)
{
int myerrpos = getinternalerrposition();
if (myerrpos > 0) /* safety check */
- internalerrposition(myerrpos + errpos - cbarg->leaderlen - 1);
+ internalerrposition(myerrpos + errpos - 1);
}
/* In any case, flush errposition --- we want internalerrposition only */
@@ -3717,7 +3742,6 @@ parse_datatype(const char *string, int location)
ErrorContextCallback syntax_errcontext;
cbarg.location = location;
- cbarg.leaderlen = 0;
syntax_errcontext.callback = plpgsql_sql_error_callback;
syntax_errcontext.arg = &cbarg;
@@ -3780,7 +3804,6 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
int argc;
char **argv;
StringInfoData ds;
- char *sqlstart = "SELECT ";
bool any_named = false;
tok = yylex();
@@ -3881,12 +3904,12 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
*/
item = read_sql_construct(',', ')', 0,
",\" or \")",
- sqlstart,
+ RAW_PARSE_PLPGSQL_EXPR,
true, true,
false, /* do not trim */
NULL, &endtoken);
- argv[argpos] = item->query + strlen(sqlstart);
+ argv[argpos] = item->query;
if (endtoken == ')' && !(argc == row->nfields - 1))
ereport(ERROR,
@@ -3905,7 +3928,6 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
/* Make positional argument list */
initStringInfo(&ds);
- appendStringInfoString(&ds, sqlstart);
for (argc = 0; argc < row->nfields; argc++)
{
Assert(argv[argc] != NULL);
@@ -3921,10 +3943,10 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
if (argc < row->nfields - 1)
appendStringInfoString(&ds, ", ");
}
- appendStringInfoChar(&ds, ';');
expr = palloc0(sizeof(PLpgSQL_expr));
expr->query = pstrdup(ds.data);
+ expr->parseMode = RAW_PARSE_PLPGSQL_EXPR;
expr->plan = NULL;
expr->paramnos = NULL;
expr->rwparam = -1;
@@ -4097,14 +4119,14 @@ make_case(int location, PLpgSQL_expr *t_expr,
PLpgSQL_expr *expr = cwt->expr;
StringInfoData ds;
- /* copy expression query without SELECT keyword (expr->query + 7) */
- Assert(strncmp(expr->query, "SELECT ", 7) == 0);
+ /* We expect to have expressions not statements */
+ Assert(expr->parseMode == RAW_PARSE_PLPGSQL_EXPR);
- /* And do the string hacking */
+ /* Do the string hacking */
initStringInfo(&ds);
- appendStringInfo(&ds, "SELECT \"%s\" IN (%s)",
- varname, expr->query + 7);
+ appendStringInfo(&ds, "\"%s\" IN (%s)",
+ varname, expr->query);
pfree(expr->query);
expr->query = pstrdup(ds.data);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 0c3d30fb13..d152a4354b 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -218,8 +218,9 @@ typedef struct PLpgSQL_type
*/
typedef struct PLpgSQL_expr
{
- char *query;
- SPIPlanPtr plan;
+ char *query; /* query string, verbatim from function body */
+ RawParseMode parseMode; /* raw_parser() mode to use */
+ SPIPlanPtr plan; /* plan, or NULL if not made yet */
Bitmapset *paramnos; /* all dnos referenced by this query */
int rwparam; /* dno of read/write param, or -1 if none */
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8..6ae1b6e3d4 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1761,10 +1761,10 @@ select f1(42) as int, f1(4.5) as num;
select f1(point(3,4)); -- fail for lack of + operator
ERROR: operator does not exist: point + integer
-LINE 1: SELECT x + 1
- ^
+LINE 1: x + 1
+ ^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
-QUERY: SELECT x + 1
+QUERY: x + 1
CONTEXT: PL/pgSQL function f1(anyelement) line 3 at RETURN
drop function f1(x anyelement);
create function f1(x anyelement) returns anyarray as $$
@@ -2361,7 +2361,7 @@ begin
end $$ language plpgsql;
select namedparmcursor_test7();
ERROR: division by zero
-CONTEXT: SQL statement "SELECT 42/0 AS p1, 77 AS p2;"
+CONTEXT: SQL expression "42/0 AS p1, 77 AS p2"
PL/pgSQL function namedparmcursor_test7() line 6 at OPEN
-- check that line comments work correctly within the argument list (there
-- is some special handling of this case in the code: the newline after the
@@ -2574,9 +2574,9 @@ end; $$ language plpgsql;
-- blocks
select excpt_test1();
ERROR: column "sqlstate" does not exist
-LINE 1: SELECT sqlstate
- ^
-QUERY: SELECT sqlstate
+LINE 1: sqlstate
+ ^
+QUERY: sqlstate
CONTEXT: PL/pgSQL function excpt_test1() line 3 at RAISE
create function excpt_test2() returns void as $$
begin
@@ -2589,9 +2589,9 @@ end; $$ language plpgsql;
-- should fail
select excpt_test2();
ERROR: column "sqlstate" does not exist
-LINE 1: SELECT sqlstate
- ^
-QUERY: SELECT sqlstate
+LINE 1: sqlstate
+ ^
+QUERY: sqlstate
CONTEXT: PL/pgSQL function excpt_test2() line 5 at RAISE
create function excpt_test3() returns void as $$
begin
@@ -4467,11 +4467,11 @@ end
$$;
select fail();
ERROR: division by zero
-CONTEXT: SQL statement "SELECT 1/0"
+CONTEXT: SQL expression "1/0"
PL/pgSQL function fail() line 3 at RETURN
select fail();
ERROR: division by zero
-CONTEXT: SQL statement "SELECT 1/0"
+CONTEXT: SQL expression "1/0"
PL/pgSQL function fail() line 3 at RETURN
drop function fail();
-- Test handling of string literals.
@@ -4497,10 +4497,10 @@ HINT: Use the escape string syntax for backslashes, e.g., E'\\'.
select strtest();
NOTICE: foo\bar!baz
WARNING: nonstandard use of \\ in a string literal
-LINE 1: SELECT 'foo\\bar\041baz'
- ^
+LINE 1: 'foo\\bar\041baz'
+ ^
HINT: Use the escape string syntax for backslashes, e.g., E'\\'.
-QUERY: SELECT 'foo\\bar\041baz'
+QUERY: 'foo\\bar\041baz'
strtest
-------------
foo\bar!baz
@@ -5621,9 +5621,9 @@ ALTER TABLE alter_table_under_transition_tables
UPDATE alter_table_under_transition_tables
SET id = id;
ERROR: column "name" does not exist
-LINE 1: SELECT (SELECT string_agg(id || '=' || name, ',') FROM d)
- ^
-QUERY: SELECT (SELECT string_agg(id || '=' || name, ',') FROM d)
+LINE 1: (SELECT string_agg(id || '=' || name, ',') FROM d)
+ ^
+QUERY: (SELECT string_agg(id || '=' || name, ',') FROM d)
CONTEXT: PL/pgSQL function alter_table_under_transition_tables_upd_func() line 3 at RAISE
--
-- Test multiple reference to a transition table