v3-0004-plpgsql-yyextra.patch

text/plain

Filename: v3-0004-plpgsql-yyextra.patch
Type: text/plain
Part: 3
Message: Re: pure parsers and reentrant scanners

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v3-0004
Subject: plpgsql: yyextra
File+
src/pl/plpgsql/src/pl_comp.c 22 11
src/pl/plpgsql/src/pl_gram.y 119 115
src/pl/plpgsql/src/plpgsql.h 7 6
src/pl/plpgsql/src/pl_scanner.c 102 87
From 4c3e4f0da9a2fa1f646da9362b39974e7e44117e Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 26 Dec 2024 17:01:39 +0100
Subject: [PATCH v3 4/4] plpgsql: yyextra

---
 src/pl/plpgsql/src/pl_comp.c    |  33 +++--
 src/pl/plpgsql/src/pl_gram.y    | 234 ++++++++++++++++----------------
 src/pl/plpgsql/src/pl_scanner.c | 189 ++++++++++++++------------
 src/pl/plpgsql/src/plpgsql.h    |  13 +-
 4 files changed, 250 insertions(+), 219 deletions(-)

diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 57685c4e948..f443423b98c 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -237,6 +237,12 @@ plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator)
 	return function;
 }
 
+struct compile_error_callback_arg
+{
+	const char *proc_source;
+	yyscan_t	yyscanner;
+};
+
 /*
  * This is the slow part of plpgsql_compile().
  *
@@ -277,6 +283,7 @@ do_compile(FunctionCallInfo fcinfo,
 	PLpgSQL_variable *var;
 	PLpgSQL_rec *rec;
 	int			i;
+	struct compile_error_callback_arg cbarg;
 	ErrorContextCallback plerrcontext;
 	int			parse_rc;
 	Oid			rettypeid;
@@ -291,9 +298,7 @@ do_compile(FunctionCallInfo fcinfo,
 	MemoryContext func_cxt;
 
 	/*
-	 * Setup the scanner input and error info.  We assume that this function
-	 * cannot be invoked recursively, so there's no need to save and restore
-	 * the static variables used here.
+	 * Setup the scanner input and error info.
 	 */
 	prosrcdatum = SysCacheGetAttrNotNull(PROCOID, procTup, Anum_pg_proc_prosrc);
 	proc_source = TextDatumGetCString(prosrcdatum);
@@ -304,8 +309,10 @@ do_compile(FunctionCallInfo fcinfo,
 	/*
 	 * Setup error traceback support for ereport()
 	 */
+	cbarg.proc_source = forValidator ? proc_source : NULL;
+	cbarg.yyscanner = scanner;
 	plerrcontext.callback = plpgsql_compile_error_callback;
-	plerrcontext.arg = forValidator ? proc_source : NULL;
+	plerrcontext.arg = &cbarg;
 	plerrcontext.previous = error_context_stack;
 	error_context_stack = &plerrcontext;
 
@@ -845,15 +852,14 @@ plpgsql_compile_inline(char *proc_source)
 	yyscan_t	scanner;
 	char	   *func_name = "inline_code_block";
 	PLpgSQL_function *function;
+	struct compile_error_callback_arg cbarg;
 	ErrorContextCallback plerrcontext;
 	PLpgSQL_variable *var;
 	int			parse_rc;
 	MemoryContext func_cxt;
 
 	/*
-	 * Setup the scanner input and error info.  We assume that this function
-	 * cannot be invoked recursively, so there's no need to save and restore
-	 * the static variables used here.
+	 * Setup the scanner input and error info.
 	 */
 	scanner = plpgsql_scanner_init(proc_source);
 
@@ -862,8 +868,10 @@ plpgsql_compile_inline(char *proc_source)
 	/*
 	 * Setup error traceback support for ereport()
 	 */
+	cbarg.proc_source = proc_source;
+	cbarg.yyscanner = scanner;
 	plerrcontext.callback = plpgsql_compile_error_callback;
-	plerrcontext.arg = proc_source;
+	plerrcontext.arg = &cbarg;
 	plerrcontext.previous = error_context_stack;
 	error_context_stack = &plerrcontext;
 
@@ -980,13 +988,16 @@ plpgsql_compile_inline(char *proc_source)
 static void
 plpgsql_compile_error_callback(void *arg)
 {
-	if (arg)
+	struct compile_error_callback_arg *cbarg = (struct compile_error_callback_arg *) arg;
+	yyscan_t	yyscanner = cbarg->yyscanner;
+
+	if (cbarg->proc_source)
 	{
 		/*
 		 * Try to convert syntax error position to reference text of original
 		 * CREATE FUNCTION or DO command.
 		 */
-		if (function_parse_error_transpose((const char *) arg))
+		if (function_parse_error_transpose(cbarg->proc_source))
 			return;
 
 		/*
@@ -997,7 +1008,7 @@ plpgsql_compile_error_callback(void *arg)
 
 	if (plpgsql_error_funcname)
 		errcontext("compilation of PL/pgSQL function \"%s\" near line %d",
-				   plpgsql_error_funcname, plpgsql_latest_lineno());
+				   plpgsql_error_funcname, plpgsql_latest_lineno(yyscanner));
 }
 
 
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index cb5c2dca186..5f712198e5c 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -49,16 +49,17 @@
 typedef struct
 {
 	int			location;
+	yyscan_t	yyscanner;
 } sql_error_callback_arg;
 
-#define parser_errposition(pos)  plpgsql_scanner_errposition(pos)
+#define parser_errposition(pos)  plpgsql_scanner_errposition(pos, yyscanner)
 
 union YYSTYPE;					/* need forward reference for tok_is_keyword */
 
 static	bool			tok_is_keyword(int token, union YYSTYPE *lval,
 									   int kw_token, const char *kw_str);
-static	void			word_is_not_variable(PLword *word, int location);
-static	void			cword_is_not_variable(PLcword *cword, int location);
+static	void			word_is_not_variable(PLword *word, int location, yyscan_t yyscanner);
+static	void			cword_is_not_variable(PLcword *cword, int location, yyscan_t yyscanner);
 static	void			current_token_is_not_variable(int tok, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
 static	PLpgSQL_expr	*read_sql_construct(int until,
 											int until2,
@@ -87,9 +88,9 @@ static	PLpgSQL_stmt	*make_return_stmt(int location, YYSTYPE *yylvalp, YYLTYPE *y
 static	PLpgSQL_stmt	*make_return_next_stmt(int location, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
 static	PLpgSQL_stmt	*make_return_query_stmt(int location, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
 static  PLpgSQL_stmt	*make_case(int location, PLpgSQL_expr *t_expr,
-								   List *case_when_list, List *else_stmts);
+								   List *case_when_list, List *else_stmts, yyscan_t yyscanner);
 static	char			*NameOfDatum(PLwdatum *wdatum);
-static	void			 check_assignable(PLpgSQL_datum *datum, int location);
+static	void			 check_assignable(PLpgSQL_datum *datum, int location, yyscan_t yyscanner);
 static	void			 read_into_target(PLpgSQL_variable **target, bool *strict,
 										  YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
 static	PLpgSQL_row		*read_into_scalar_list(char *initial_name,
@@ -99,14 +100,15 @@ static	PLpgSQL_row		*read_into_scalar_list(char *initial_name,
 											   yyscan_t yyscanner);
 static	PLpgSQL_row		*make_scalar_list1(char *initial_name,
 										   PLpgSQL_datum *initial_datum,
-										   int lineno, int location);
+										   int lineno, int location, yyscan_t yyscanner);
 static	void			 check_sql_expr(const char *stmt,
-										RawParseMode parseMode, int location);
+										RawParseMode parseMode, int location, yyscan_t yyscanner);
 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,
 									  const char *end_label,
-									  int end_location);
+									  int end_location,
+									  yyscan_t yyscanner);
 static	PLpgSQL_expr	*read_cursor_args(PLpgSQL_var *cursor, int until,
 										  YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
 static	List			*read_raise_options(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
@@ -421,7 +423,7 @@ pl_block		: decl_sect K_BEGIN proc_sect exception_sect K_END opt_label
 						new = palloc0(sizeof(PLpgSQL_stmt_block));
 
 						new->cmd_type	= PLPGSQL_STMT_BLOCK;
-						new->lineno		= plpgsql_location_to_lineno(@2);
+						new->lineno		= plpgsql_location_to_lineno(@2, yyscanner);
 						new->stmtid		= ++plpgsql_curr_compile->nstatements;
 						new->label		= $1.label;
 						new->n_initvars = $1.n_initvars;
@@ -429,7 +431,7 @@ pl_block		: decl_sect K_BEGIN proc_sect exception_sect K_END opt_label
 						new->body		= $3;
 						new->exceptions	= $4;
 
-						check_labels($1.label, $6, @6);
+						check_labels($1.label, $6, @6, yyscanner);
 						plpgsql_ns_pop();
 
 						$$ = (PLpgSQL_stmt *) new;
@@ -597,7 +599,7 @@ decl_cursor_args :
 						new = palloc0(sizeof(PLpgSQL_row));
 						new->dtype = PLPGSQL_DTYPE_ROW;
 						new->refname = "(unnamed row)";
-						new->lineno = plpgsql_location_to_lineno(@1);
+						new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						new->rowtupdesc = NULL;
 						new->nfields = list_length($2);
 						new->fieldnames = palloc(new->nfields * sizeof(char *));
@@ -701,7 +703,7 @@ decl_aliasitem	: T_WORD
 decl_varname	: T_WORD
 					{
 						$$.name = $1.ident;
-						$$.lineno = plpgsql_location_to_lineno(@1);
+						$$.lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						/*
 						 * Check to make sure name isn't already declared
 						 * in the current block.
@@ -729,7 +731,7 @@ decl_varname	: T_WORD
 				| unreserved_keyword
 					{
 						$$.name = pstrdup($1);
-						$$.lineno = plpgsql_location_to_lineno(@1);
+						$$.lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						/*
 						 * Check to make sure name isn't already declared
 						 * in the current block.
@@ -888,9 +890,9 @@ stmt_perform	: K_PERFORM
 
 						new = palloc0(sizeof(PLpgSQL_stmt_perform));
 						new->cmd_type = PLPGSQL_STMT_PERFORM;
-						new->lineno   = plpgsql_location_to_lineno(@1);
+						new->lineno   = plpgsql_location_to_lineno(@1, yyscanner);
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
-						plpgsql_push_back_token(K_PERFORM, &yylval, &yylloc);
+						plpgsql_push_back_token(K_PERFORM, &yylval, &yylloc, yyscanner);
 
 						/*
 						 * Since PERFORM isn't legal SQL, we have to cheat to
@@ -912,7 +914,7 @@ stmt_perform	: K_PERFORM
 								strlen(new->expr->query));
 						/* offset syntax error position to account for that */
 						check_sql_expr(new->expr->query, new->expr->parseMode,
-									   startloc + 1);
+									   startloc + 1, yyscanner);
 
 						$$ = (PLpgSQL_stmt *) new;
 					}
@@ -924,9 +926,9 @@ stmt_call		: K_CALL
 
 						new = palloc0(sizeof(PLpgSQL_stmt_call));
 						new->cmd_type = PLPGSQL_STMT_CALL;
-						new->lineno = plpgsql_location_to_lineno(@1);
+						new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
-						plpgsql_push_back_token(K_CALL, &yylval, &yylloc);
+						plpgsql_push_back_token(K_CALL, &yylval, &yylloc, yyscanner);
 						new->expr = read_sql_stmt(&yylval, &yylloc, yyscanner);
 						new->is_call = true;
 
@@ -943,9 +945,9 @@ stmt_call		: K_CALL
 
 						new = palloc0(sizeof(PLpgSQL_stmt_call));
 						new->cmd_type = PLPGSQL_STMT_CALL;
-						new->lineno = plpgsql_location_to_lineno(@1);
+						new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
-						plpgsql_push_back_token(K_DO, &yylval, &yylloc);
+						plpgsql_push_back_token(K_DO, &yylval, &yylloc, yyscanner);
 						new->expr = read_sql_stmt(&yylval, &yylloc, yyscanner);
 						new->is_call = false;
 
@@ -979,14 +981,14 @@ stmt_assign		: T_DATUM
 								pmode = 0; /* keep compiler quiet */
 						}
 
-						check_assignable($1.datum, @1);
+						check_assignable($1.datum, @1, yyscanner);
 						new = palloc0(sizeof(PLpgSQL_stmt_assign));
 						new->cmd_type = PLPGSQL_STMT_ASSIGN;
-						new->lineno = plpgsql_location_to_lineno(@1);
+						new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
 						new->varno = $1.datum->dno;
 						/* Push back the head name to include it in the stmt */
-						plpgsql_push_back_token(T_DATUM, &yylval, &yylloc);
+						plpgsql_push_back_token(T_DATUM, &yylval, &yylloc, yyscanner);
 						new->expr = read_sql_construct(';', 0, 0, ";",
 													   pmode,
 													   false, true,
@@ -1004,7 +1006,7 @@ stmt_getdiag	: K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
 
 						new = palloc0(sizeof(PLpgSQL_stmt_getdiag));
 						new->cmd_type = PLPGSQL_STMT_GETDIAG;
-						new->lineno = plpgsql_location_to_lineno(@1);
+						new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
 						new->is_stacked = $2;
 						new->diag_items = $4;
@@ -1159,18 +1161,18 @@ getdiag_target	: T_DATUM
 									 errmsg("\"%s\" is not a scalar variable",
 											NameOfDatum(&($1))),
 									 parser_errposition(@1)));
-						check_assignable($1.datum, @1);
+						check_assignable($1.datum, @1, yyscanner);
 						$$ = $1.datum;
 					}
 				| T_WORD
 					{
 						/* just to give a better message than "syntax error" */
-						word_is_not_variable(&($1), @1);
+						word_is_not_variable(&($1), @1, yyscanner);
 					}
 				| T_CWORD
 					{
 						/* just to give a better message than "syntax error" */
-						cword_is_not_variable(&($1), @1);
+						cword_is_not_variable(&($1), @1, yyscanner);
 					}
 				;
 
@@ -1180,7 +1182,7 @@ stmt_if			: K_IF expr_until_then proc_sect stmt_elsifs stmt_else K_END K_IF ';'
 
 						new = palloc0(sizeof(PLpgSQL_stmt_if));
 						new->cmd_type = PLPGSQL_STMT_IF;
-						new->lineno = plpgsql_location_to_lineno(@1);
+						new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
 						new->cond = $2;
 						new->then_body = $3;
@@ -1200,7 +1202,7 @@ stmt_elsifs		:
 						PLpgSQL_if_elsif *new;
 
 						new = palloc0(sizeof(PLpgSQL_if_elsif));
-						new->lineno = plpgsql_location_to_lineno(@2);
+						new->lineno = plpgsql_location_to_lineno(@2, yyscanner);
 						new->cond = $3;
 						new->stmts = $4;
 
@@ -1220,7 +1222,7 @@ stmt_else		:
 
 stmt_case		: K_CASE opt_expr_until_when case_when_list opt_case_else K_END K_CASE ';'
 					{
-						$$ = make_case(@1, $2, $3, $4);
+						$$ = make_case(@1, $2, $3, $4, yyscanner);
 					}
 				;
 
@@ -1231,10 +1233,10 @@ opt_expr_until_when	:
 
 						if (tok != K_WHEN)
 						{
-							plpgsql_push_back_token(tok, &yylval, &yylloc);
+							plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
 							expr = read_sql_expression(K_WHEN, "WHEN", &yylval, &yylloc, yyscanner);
 						}
-						plpgsql_push_back_token(K_WHEN, &yylval, &yylloc);
+						plpgsql_push_back_token(K_WHEN, &yylval, &yylloc, yyscanner);
 						$$ = expr;
 					}
 				;
@@ -1253,7 +1255,7 @@ case_when		: K_WHEN expr_until_then proc_sect
 					{
 						PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
 
-						new->lineno	= plpgsql_location_to_lineno(@1);
+						new->lineno	= plpgsql_location_to_lineno(@1, yyscanner);
 						new->expr = $2;
 						new->stmts = $3;
 						$$ = new;
@@ -1285,12 +1287,12 @@ stmt_loop		: opt_loop_label K_LOOP loop_body
 
 						new = palloc0(sizeof(PLpgSQL_stmt_loop));
 						new->cmd_type = PLPGSQL_STMT_LOOP;
-						new->lineno = plpgsql_location_to_lineno(@2);
+						new->lineno = plpgsql_location_to_lineno(@2, yyscanner);
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
 						new->label = $1;
 						new->body = $3.stmts;
 
-						check_labels($1, $3.end_label, $3.end_label_location);
+						check_labels($1, $3.end_label, $3.end_label_location, yyscanner);
 						plpgsql_ns_pop();
 
 						$$ = (PLpgSQL_stmt *) new;
@@ -1303,13 +1305,13 @@ stmt_while		: opt_loop_label K_WHILE expr_until_loop loop_body
 
 						new = palloc0(sizeof(PLpgSQL_stmt_while));
 						new->cmd_type = PLPGSQL_STMT_WHILE;
-						new->lineno = plpgsql_location_to_lineno(@2);
+						new->lineno = plpgsql_location_to_lineno(@2, yyscanner);
 						new->stmtid	= ++plpgsql_curr_compile->nstatements;
 						new->label = $1;
 						new->cond = $3;
 						new->body = $4.stmts;
 
-						check_labels($1, $4.end_label, $4.end_label_location);
+						check_labels($1, $4.end_label, $4.end_label_location, yyscanner);
 						plpgsql_ns_pop();
 
 						$$ = (PLpgSQL_stmt *) new;
@@ -1324,7 +1326,7 @@ stmt_for		: opt_loop_label K_FOR for_control loop_body
 							PLpgSQL_stmt_fori *new;
 
 							new = (PLpgSQL_stmt_fori *) $3;
-							new->lineno = plpgsql_location_to_lineno(@2);
+							new->lineno = plpgsql_location_to_lineno(@2, yyscanner);
 							new->label = $1;
 							new->body = $4.stmts;
 							$$ = (PLpgSQL_stmt *) new;
@@ -1338,13 +1340,13 @@ stmt_for		: opt_loop_label K_FOR for_control loop_body
 								   $3->cmd_type == PLPGSQL_STMT_DYNFORS);
 							/* forq is the common supertype of all three */
 							new = (PLpgSQL_stmt_forq *) $3;
-							new->lineno = plpgsql_location_to_lineno(@2);
+							new->lineno = plpgsql_location_to_lineno(@2, yyscanner);
 							new->label = $1;
 							new->body = $4.stmts;
 							$$ = (PLpgSQL_stmt *) new;
 						}
 
-						check_labels($1, $4.end_label, $4.end_label_location);
+						check_labels($1, $4.end_label, $4.end_label_location, yyscanner);
 						/* close namespace started in opt_loop_label */
 						plpgsql_ns_pop();
 					}
@@ -1372,14 +1374,14 @@ for_control		: for_variable K_IN
 							if ($1.row)
 							{
 								new->var = (PLpgSQL_variable *) $1.row;
-								check_assignable($1.row, @1);
+								check_assignable($1.row, @1, yyscanner);
 							}
 							else if ($1.scalar)
 							{
 								/* convert single scalar to list */
 								new->var = (PLpgSQL_variable *)
 									make_scalar_list1($1.name, $1.scalar,
-													  $1.lineno, @1);
+													  $1.lineno, @1, yyscanner);
 								/* make_scalar_list1 did check_assignable */
 							}
 							else
@@ -1466,7 +1468,7 @@ for_control		: for_variable K_IN
 											   K_REVERSE, "reverse"))
 								reverse = true;
 							else
-								plpgsql_push_back_token(tok, &yylval, &yylloc);
+								plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
 
 							/*
 							 * Read tokens until we see either a ".."
@@ -1500,7 +1502,7 @@ for_control		: for_variable K_IN
 								 */
 								expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
 								check_sql_expr(expr1->query, expr1->parseMode,
-											   expr1loc);
+											   expr1loc, yyscanner);
 
 								/* Read and check the second one */
 								expr2 = read_sql_expression2(K_LOOP, K_BY,
@@ -1557,7 +1559,7 @@ for_control		: for_variable K_IN
 
 								/* Check syntax as a regular query */
 								check_sql_expr(expr1->query, expr1->parseMode,
-											   expr1loc);
+											   expr1loc, yyscanner);
 
 								new = palloc0(sizeof(PLpgSQL_stmt_fors));
 								new->cmd_type = PLPGSQL_STMT_FORS;
@@ -1565,14 +1567,14 @@ for_control		: for_variable K_IN
 								if ($1.row)
 								{
 									new->var = (PLpgSQL_variable *) $1.row;
-									check_assignable($1.row, @1);
+									check_assignable($1.row, @1, yyscanner);
 								}
 								else if ($1.scalar)
 								{
 									/* convert single scalar to list */
 									new->var = (PLpgSQL_variable *)
 										make_scalar_list1($1.name, $1.scalar,
-														  $1.lineno, @1);
+														  $1.lineno, @1, yyscanner);
 									/* make_scalar_list1 did check_assignable */
 								}
 								else
@@ -1611,7 +1613,7 @@ for_control		: for_variable K_IN
 for_variable	: T_DATUM
 					{
 						$$.name = NameOfDatum(&($1));
-						$$.lineno = plpgsql_location_to_lineno(@1);
+						$$.lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						if ($1.datum->dtype == PLPGSQL_DTYPE_ROW ||
 							$1.datum->dtype == PLPGSQL_DTYPE_REC)
 						{
@@ -1626,7 +1628,7 @@ for_variable	: T_DATUM
 							$$.row = NULL;
 							/* check for comma-separated list */
 							tok = yylex(&yylval, &yylloc, yyscanner);
-							plpgsql_push_back_token(tok, &yylval, &yylloc);
+							plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
 							if (tok == ',')
 								$$.row = (PLpgSQL_datum *)
 									read_into_scalar_list($$.name,
@@ -1641,19 +1643,19 @@ for_variable	: T_DATUM
 						int			tok;
 
 						$$.name = $1.ident;
-						$$.lineno = plpgsql_location_to_lineno(@1);
+						$$.lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						$$.scalar = NULL;
 						$$.row = NULL;
 						/* check for comma-separated list */
 						tok = yylex(&yylval, &yylloc, yyscanner);
-						plpgsql_push_back_token(tok, &yylval, &yylloc);
+						plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
 						if (tok == ',')
-							word_is_not_variable(&($1), @1);
+							word_is_not_variable(&($1), @1, yyscanner);
 					}
 				| T_CWORD
 					{
 						/* just to give a better message than "syntax error" */
-						cword_is_not_variable(&($1), @1);
+						cword_is_not_variable(&($1), @1, yyscanner);
 					}
 				;
 
@@ -1663,7 +1665,7 @@ stmt_foreach_a	: opt_loop_label K_FOREACH for_variable foreach_slice K_IN K_ARRA
 
 						new = palloc0(sizeof(PLpgSQL_stmt_foreach_a));
 						new->cmd_type = PLPGSQL_STMT_FOREACH_A;
-						new->lineno = plpgsql_location_to_lineno(@2);
+						new->lineno = plpgsql_location_to_lineno(@2, yyscanner);
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
 						new->label = $1;
 						new->slice = $4;
@@ -1673,12 +1675,12 @@ stmt_foreach_a	: opt_loop_label K_FOREACH for_variable foreach_slice K_IN K_ARRA
 						if ($3.row)
 						{
 							new->varno = $3.row->dno;
-							check_assignable($3.row, @3);
+							check_assignable($3.row, @3, yyscanner);
 						}
 						else if ($3.scalar)
 						{
 							new->varno = $3.scalar->dno;
-							check_assignable($3.scalar, @3);
+							check_assignable($3.scalar, @3, yyscanner);
 						}
 						else
 						{
@@ -1688,7 +1690,7 @@ stmt_foreach_a	: opt_loop_label K_FOREACH for_variable foreach_slice K_IN K_ARRA
 											 parser_errposition(@3)));
 						}
 
-						check_labels($1, $8.end_label, $8.end_label_location);
+						check_labels($1, $8.end_label, $8.end_label_location, yyscanner);
 						plpgsql_ns_pop();
 
 						$$ = (PLpgSQL_stmt *) new;
@@ -1713,7 +1715,7 @@ stmt_exit		: exit_type opt_label opt_exitcond
 						new->cmd_type = PLPGSQL_STMT_EXIT;
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
 						new->is_exit = $1;
-						new->lineno	= plpgsql_location_to_lineno(@1);
+						new->lineno	= plpgsql_location_to_lineno(@1, yyscanner);
 						new->label = $2;
 						new->cond = $3;
 
@@ -1788,7 +1790,7 @@ stmt_return		: K_RETURN
 						}
 						else
 						{
-							plpgsql_push_back_token(tok, &yylval, &yylloc);
+							plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
 							$$ = make_return_stmt(@1, &yylval, &yylloc, yyscanner);
 						}
 					}
@@ -1802,7 +1804,7 @@ stmt_raise		: K_RAISE
 						new = palloc(sizeof(PLpgSQL_stmt_raise));
 
 						new->cmd_type = PLPGSQL_STMT_RAISE;
-						new->lineno = plpgsql_location_to_lineno(@1);
+						new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						new->stmtid	= ++plpgsql_curr_compile->nstatements;
 						new->elog_level = ERROR;	/* default */
 						new->condname = NULL;
@@ -1948,7 +1950,7 @@ stmt_assert		: K_ASSERT
 						new = palloc(sizeof(PLpgSQL_stmt_assert));
 
 						new->cmd_type = PLPGSQL_STMT_ASSERT;
-						new->lineno = plpgsql_location_to_lineno(@1);
+						new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
 
 						new->cond = read_sql_expression2(',', ';',
@@ -1999,10 +2001,10 @@ stmt_execsql	: K_IMPORT
 						int			tok;
 
 						tok = yylex(&yylval, &yylloc, yyscanner);
-						plpgsql_push_back_token(tok, &yylval, &yylloc);
+						plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
 						if (tok == '=' || tok == COLON_EQUALS ||
 							tok == '[' || tok == '.')
-							word_is_not_variable(&($1), @1);
+							word_is_not_variable(&($1), @1, yyscanner);
 						$$ = make_execsql_stmt(T_WORD, @1, &($1), &yylval, &yylloc, yyscanner);
 					}
 				| T_CWORD
@@ -2010,10 +2012,10 @@ stmt_execsql	: K_IMPORT
 						int			tok;
 
 						tok = yylex(&yylval, &yylloc, yyscanner);
-						plpgsql_push_back_token(tok, &yylval, &yylloc);
+						plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
 						if (tok == '=' || tok == COLON_EQUALS ||
 							tok == '[' || tok == '.')
-							cword_is_not_variable(&($1), @1);
+							cword_is_not_variable(&($1), @1, yyscanner);
 						$$ = make_execsql_stmt(T_CWORD, @1, NULL, &yylval, &yylloc, yyscanner);
 					}
 				;
@@ -2033,7 +2035,7 @@ stmt_dynexecute : K_EXECUTE
 
 						new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
 						new->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
-						new->lineno = plpgsql_location_to_lineno(@1);
+						new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
 						new->query = expr;
 						new->into = false;
@@ -2091,7 +2093,7 @@ stmt_open		: K_OPEN cursor_variable
 
 						new = palloc0(sizeof(PLpgSQL_stmt_open));
 						new->cmd_type = PLPGSQL_STMT_OPEN;
-						new->lineno = plpgsql_location_to_lineno(@1);
+						new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
 						new->curvar = $2->dno;
 						new->cursor_options = CURSOR_OPT_FAST_PLAN;
@@ -2148,7 +2150,7 @@ stmt_open		: K_OPEN cursor_variable
 							}
 							else
 							{
-								plpgsql_push_back_token(tok, &yylval, &yylloc);
+								plpgsql_push_back_token(tok, &yylval, &yylloc, yyscanner);
 								new->query = read_sql_stmt(&yylval, &yylloc, yyscanner);
 							}
 						}
@@ -2183,7 +2185,7 @@ stmt_fetch		: K_FETCH opt_fetch_direction cursor_variable K_INTO
 									 errmsg("FETCH statement cannot return multiple rows"),
 									 parser_errposition(@1)));
 
-						fetch->lineno = plpgsql_location_to_lineno(@1);
+						fetch->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						fetch->target	= target;
 						fetch->curvar	= $3->dno;
 						fetch->is_move	= false;
@@ -2196,7 +2198,7 @@ stmt_move		: K_MOVE opt_fetch_direction cursor_variable ';'
 					{
 						PLpgSQL_stmt_fetch *fetch = $2;
 
-						fetch->lineno = plpgsql_location_to_lineno(@1);
+						fetch->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						fetch->curvar = $3->dno;
 						fetch->is_move = true;
 
@@ -2216,7 +2218,7 @@ stmt_close		: K_CLOSE cursor_variable ';'
 
 						new = palloc(sizeof(PLpgSQL_stmt_close));
 						new->cmd_type = PLPGSQL_STMT_CLOSE;
-						new->lineno = plpgsql_location_to_lineno(@1);
+						new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
 						new->curvar = $2->dno;
 
@@ -2237,7 +2239,7 @@ stmt_commit		: K_COMMIT opt_transaction_chain ';'
 
 						new = palloc(sizeof(PLpgSQL_stmt_commit));
 						new->cmd_type = PLPGSQL_STMT_COMMIT;
-						new->lineno = plpgsql_location_to_lineno(@1);
+						new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
 						new->chain = $2;
 
@@ -2251,7 +2253,7 @@ stmt_rollback	: K_ROLLBACK opt_transaction_chain ';'
 
 						new = palloc(sizeof(PLpgSQL_stmt_rollback));
 						new->cmd_type = PLPGSQL_STMT_ROLLBACK;
-						new->lineno = plpgsql_location_to_lineno(@1);
+						new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						new->stmtid = ++plpgsql_curr_compile->nstatements;
 						new->chain = $2;
 
@@ -2291,12 +2293,12 @@ cursor_variable	: T_DATUM
 				| T_WORD
 					{
 						/* just to give a better message than "syntax error" */
-						word_is_not_variable(&($1), @1);
+						word_is_not_variable(&($1), @1, yyscanner);
 					}
 				| T_CWORD
 					{
 						/* just to give a better message than "syntax error" */
-						cword_is_not_variable(&($1), @1);
+						cword_is_not_variable(&($1), @1, yyscanner);
 					}
 				;
 
@@ -2311,7 +2313,7 @@ exception_sect	:
 						 * scope of the names extends to the end of the
 						 * current block.
 						 */
-						int			lineno = plpgsql_location_to_lineno(@1);
+						int			lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block));
 						PLpgSQL_variable *var;
 
@@ -2359,7 +2361,7 @@ proc_exception	: K_WHEN proc_conditions K_THEN proc_sect
 						PLpgSQL_exception *new;
 
 						new = palloc0(sizeof(PLpgSQL_exception));
-						new->lineno = plpgsql_location_to_lineno(@1);
+						new->lineno = plpgsql_location_to_lineno(@1, yyscanner);
 						new->conditions = $2;
 						new->action = $4;
 
@@ -2611,7 +2613,7 @@ tok_is_keyword(int token, union YYSTYPE *lval,
  * ie, unrecognized variable.
  */
 static void
-word_is_not_variable(PLword *word, int location)
+word_is_not_variable(PLword *word, int location, yyscan_t yyscanner)
 {
 	ereport(ERROR,
 			(errcode(ERRCODE_SYNTAX_ERROR),
@@ -2622,7 +2624,7 @@ word_is_not_variable(PLword *word, int location)
 
 /* Same, for a CWORD */
 static void
-cword_is_not_variable(PLcword *cword, int location)
+cword_is_not_variable(PLcword *cword, int location, yyscan_t yyscanner)
 {
 	ereport(ERROR,
 			(errcode(ERRCODE_SYNTAX_ERROR),
@@ -2640,9 +2642,9 @@ static void
 current_token_is_not_variable(int tok, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 {
 	if (tok == T_WORD)
-		word_is_not_variable(&(yylvalp->word), *yyllocp);
+		word_is_not_variable(&(yylvalp->word), *yyllocp, yyscanner);
 	else if (tok == T_CWORD)
-		cword_is_not_variable(&(yylvalp->cword), *yyllocp);
+		cword_is_not_variable(&(yylvalp->cword), *yyllocp, yyscanner);
 	else
 		yyerror(yyllocp, yyscanner, "syntax error");
 }
@@ -2762,7 +2764,7 @@ read_sql_construct(int until,
 						 parser_errposition(*yyllocp)));
 		}
 		/* Remember end+1 location of last accepted token */
-		endlocation = *yyllocp + plpgsql_token_length();
+		endlocation = *yyllocp + plpgsql_token_length(yyscanner);
 	}
 
 	plpgsql_IdentifierLookup = save_IdentifierLookup;
@@ -2788,7 +2790,7 @@ read_sql_construct(int until,
 	 * whitespace by hand, but that causes problems if there's a "-- comment"
 	 * in front of said whitespace.)
 	 */
-	plpgsql_append_source_text(&ds, startlocation, endlocation);
+	plpgsql_append_source_text(&ds, startlocation, endlocation, yyscanner);
 
 	expr = palloc0(sizeof(PLpgSQL_expr));
 	expr->query = pstrdup(ds.data);
@@ -2800,7 +2802,7 @@ read_sql_construct(int until,
 	pfree(ds.data);
 
 	if (valid_sql)
-		check_sql_expr(expr->query, expr->parseMode, startlocation);
+		check_sql_expr(expr->query, expr->parseMode, startlocation, yyscanner);
 
 	return expr;
 }
@@ -2910,7 +2912,7 @@ read_datatype(int tok, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 				yyerror(yyllocp, yyscanner, "syntax error, expected \"]\"");
 			tok = yylex(yylvalp, yyllocp, yyscanner);
 		}
-		plpgsql_push_back_token(tok, yylvalp, yyllocp);
+		plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);
 
 		if (is_array)
 			result = plpgsql_build_datatype_arrayof(result);
@@ -2950,7 +2952,7 @@ read_datatype(int tok, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 
 	/* set up ds to contain complete typename text */
 	initStringInfo(&ds);
-	plpgsql_append_source_text(&ds, startlocation, *yyllocp);
+	plpgsql_append_source_text(&ds, startlocation, *yyllocp, yyscanner);
 	type_name = ds.data;
 
 	if (type_name[0] == '\0')
@@ -2960,7 +2962,7 @@ read_datatype(int tok, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 
 	pfree(ds.data);
 
-	plpgsql_push_back_token(tok, yylvalp, yyllocp);
+	plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);
 
 	return result;
 }
@@ -3107,12 +3109,12 @@ make_execsql_stmt(int firsttoken, int location, PLword *word, YYSTYPE *yylvalp,
 		 * text, so that locations within the redacted SQL statement still
 		 * line up with those in the original source text.
 		 */
-		plpgsql_append_source_text(&ds, location, into_start_loc);
+		plpgsql_append_source_text(&ds, location, into_start_loc, yyscanner);
 		appendStringInfoSpaces(&ds, into_end_loc - into_start_loc);
-		plpgsql_append_source_text(&ds, into_end_loc, *yyllocp);
+		plpgsql_append_source_text(&ds, into_end_loc, *yyllocp, yyscanner);
 	}
 	else
-		plpgsql_append_source_text(&ds, location, *yyllocp);
+		plpgsql_append_source_text(&ds, location, *yyllocp, yyscanner);
 
 	/* trim any trailing whitespace, for neatness */
 	while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
@@ -3127,11 +3129,11 @@ make_execsql_stmt(int firsttoken, int location, PLword *word, YYSTYPE *yylvalp,
 	expr->ns = plpgsql_ns_top();
 	pfree(ds.data);
 
-	check_sql_expr(expr->query, expr->parseMode, location);
+	check_sql_expr(expr->query, expr->parseMode, location, yyscanner);
 
 	execsql = palloc0(sizeof(PLpgSQL_stmt_execsql));
 	execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
-	execsql->lineno = plpgsql_location_to_lineno(location);
+	execsql->lineno = plpgsql_location_to_lineno(location, yyscanner);
 	execsql->stmtid = ++plpgsql_curr_compile->nstatements;
 	execsql->sqlstmt = expr;
 	execsql->into = have_into;
@@ -3233,7 +3235,7 @@ read_fetch_direction(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 	else if (tok == T_DATUM)
 	{
 		/* Assume there's no direction clause and tok is a cursor name */
-		plpgsql_push_back_token(tok, yylvalp, yyllocp);
+		plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);
 		check_FROM = false;
 	}
 	else
@@ -3246,7 +3248,7 @@ read_fetch_direction(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 		 * will trigger.  Perhaps this can be improved someday, but it hardly
 		 * seems worth a lot of work.
 		 */
-		plpgsql_push_back_token(tok, yylvalp, yyllocp);
+		plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);
 		fetch->expr = read_sql_expression2(K_FROM, K_IN,
 										   "FROM or IN",
 										   NULL, yylvalp, yyllocp, yyscanner);
@@ -3294,7 +3296,7 @@ complete_direction(PLpgSQL_stmt_fetch *fetch, bool *check_FROM, YYSTYPE *yylvalp
 		return;
 	}
 
-	plpgsql_push_back_token(tok, yylvalp, yyllocp);
+	plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);
 	fetch->expr = read_sql_expression2(K_FROM, K_IN,
 									   "FROM or IN",
 									   NULL, yylvalp, yyllocp, yyscanner);
@@ -3310,7 +3312,7 @@ make_return_stmt(int location, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yysc
 
 	new = palloc0(sizeof(PLpgSQL_stmt_return));
 	new->cmd_type = PLPGSQL_STMT_RETURN;
-	new->lineno = plpgsql_location_to_lineno(location);
+	new->lineno = plpgsql_location_to_lineno(location, yyscanner);
 	new->stmtid = ++plpgsql_curr_compile->nstatements;
 	new->expr = NULL;
 	new->retvarno = -1;
@@ -3376,7 +3378,7 @@ make_return_stmt(int location, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yysc
 			 * Note that a well-formed expression is _required_ here; anything
 			 * else is a compile-time error.
 			 */
-			plpgsql_push_back_token(tok, yylvalp, yyllocp);
+			plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);
 			new->expr = read_sql_expression(';', ";", yylvalp, yyllocp, yyscanner);
 		}
 	}
@@ -3398,7 +3400,7 @@ make_return_next_stmt(int location, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t
 
 	new = palloc0(sizeof(PLpgSQL_stmt_return_next));
 	new->cmd_type = PLPGSQL_STMT_RETURN_NEXT;
-	new->lineno = plpgsql_location_to_lineno(location);
+	new->lineno = plpgsql_location_to_lineno(location, yyscanner);
 	new->stmtid = ++plpgsql_curr_compile->nstatements;
 	new->expr = NULL;
 	new->retvarno = -1;
@@ -3439,7 +3441,7 @@ make_return_next_stmt(int location, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t
 			 * Note that a well-formed expression is _required_ here; anything
 			 * else is a compile-time error.
 			 */
-			plpgsql_push_back_token(tok, yylvalp, yyllocp);
+			plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);
 			new->expr = read_sql_expression(';', ";", yylvalp, yyllocp, yyscanner);
 		}
 	}
@@ -3462,14 +3464,14 @@ make_return_query_stmt(int location, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_
 
 	new = palloc0(sizeof(PLpgSQL_stmt_return_query));
 	new->cmd_type = PLPGSQL_STMT_RETURN_QUERY;
-	new->lineno = plpgsql_location_to_lineno(location);
+	new->lineno = plpgsql_location_to_lineno(location, yyscanner);
 	new->stmtid = ++plpgsql_curr_compile->nstatements;
 
 	/* check for RETURN QUERY EXECUTE */
 	if ((tok = yylex(yylvalp, yyllocp, yyscanner)) != K_EXECUTE)
 	{
 		/* ordinary static query */
-		plpgsql_push_back_token(tok, yylvalp, yyllocp);
+		plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);
 		new->query = read_sql_stmt(yylvalp, yyllocp, yyscanner);
 	}
 	else
@@ -3506,7 +3508,7 @@ NameOfDatum(PLwdatum *wdatum)
 }
 
 static void
-check_assignable(PLpgSQL_datum *datum, int location)
+check_assignable(PLpgSQL_datum *datum, int location, yyscan_t yyscanner)
 {
 	switch (datum->dtype)
 	{
@@ -3526,7 +3528,7 @@ check_assignable(PLpgSQL_datum *datum, int location)
 		case PLPGSQL_DTYPE_RECFIELD:
 			/* assignable if parent record is */
 			check_assignable(plpgsql_Datums[((PLpgSQL_recfield *) datum)->recparentno],
-							 location);
+							 location, yyscanner);
 			break;
 		default:
 			elog(ERROR, "unrecognized dtype: %d", datum->dtype);
@@ -3568,7 +3570,7 @@ read_into_target(PLpgSQL_variable **target, bool *strict, YYSTYPE *yylvalp, YYLT
 			if (yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
 				yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
 			{
-				check_assignable(yylvalp->wdatum.datum, *yyllocp);
+				check_assignable(yylvalp->wdatum.datum, *yyllocp, yyscanner);
 				*target = (PLpgSQL_variable *) yylvalp->wdatum.datum;
 
 				if ((tok = yylex(yylvalp, yyllocp, yyscanner)) == ',')
@@ -3576,7 +3578,7 @@ read_into_target(PLpgSQL_variable **target, bool *strict, YYSTYPE *yylvalp, YYLT
 							(errcode(ERRCODE_SYNTAX_ERROR),
 							 errmsg("record variable cannot be part of multiple-item INTO list"),
 							 parser_errposition(*yyllocp)));
-				plpgsql_push_back_token(tok, yylvalp, yyllocp);
+				plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);
 			}
 			else
 			{
@@ -3611,7 +3613,7 @@ read_into_scalar_list(char *initial_name,
 	PLpgSQL_row *row;
 	int			tok;
 
-	check_assignable(initial_datum, initial_location);
+	check_assignable(initial_datum, initial_location, yyscanner);
 	fieldnames[0] = initial_name;
 	varnos[0] = initial_datum->dno;
 	nfields = 1;
@@ -3629,7 +3631,7 @@ read_into_scalar_list(char *initial_name,
 		switch (tok)
 		{
 			case T_DATUM:
-				check_assignable(yylvalp->wdatum.datum, *yyllocp);
+				check_assignable(yylvalp->wdatum.datum, *yyllocp, yyscanner);
 				if (yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
 					yylvalp->wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
 					ereport(ERROR,
@@ -3651,12 +3653,12 @@ read_into_scalar_list(char *initial_name,
 	 * We read an extra, non-comma token from yylex(), so push it back onto
 	 * the input stream
 	 */
-	plpgsql_push_back_token(tok, yylvalp, yyllocp);
+	plpgsql_push_back_token(tok, yylvalp, yyllocp, yyscanner);
 
 	row = palloc0(sizeof(PLpgSQL_row));
 	row->dtype = PLPGSQL_DTYPE_ROW;
 	row->refname = "(unnamed row)";
-	row->lineno = plpgsql_location_to_lineno(initial_location);
+	row->lineno = plpgsql_location_to_lineno(initial_location, yyscanner);
 	row->rowtupdesc = NULL;
 	row->nfields = nfields;
 	row->fieldnames = palloc(sizeof(char *) * nfields);
@@ -3682,11 +3684,11 @@ read_into_scalar_list(char *initial_name,
 static PLpgSQL_row *
 make_scalar_list1(char *initial_name,
 				  PLpgSQL_datum *initial_datum,
-				  int lineno, int location)
+				  int lineno, int location, yyscan_t yyscanner)
 {
 	PLpgSQL_row *row;
 
-	check_assignable(initial_datum, location);
+	check_assignable(initial_datum, location, yyscanner);
 
 	row = palloc0(sizeof(PLpgSQL_row));
 	row->dtype = PLPGSQL_DTYPE_ROW;
@@ -3727,7 +3729,7 @@ make_scalar_list1(char *initial_name,
  * If no error cursor is provided, we'll just point at "location".
  */
 static void
-check_sql_expr(const char *stmt, RawParseMode parseMode, int location)
+check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t yyscanner)
 {
 	sql_error_callback_arg cbarg;
 	ErrorContextCallback syntax_errcontext;
@@ -3737,6 +3739,7 @@ check_sql_expr(const char *stmt, RawParseMode parseMode, int location)
 		return;
 
 	cbarg.location = location;
+	cbarg.yyscanner = yyscanner;
 
 	syntax_errcontext.callback = plpgsql_sql_error_callback;
 	syntax_errcontext.arg = &cbarg;
@@ -3755,6 +3758,7 @@ static void
 plpgsql_sql_error_callback(void *arg)
 {
 	sql_error_callback_arg *cbarg = (sql_error_callback_arg *) arg;
+	yyscan_t	yyscanner = cbarg->yyscanner;
 	int			errpos;
 
 	/*
@@ -3823,7 +3827,7 @@ parse_datatype(const char *string, int location)
  * Check block starting and ending labels match.
  */
 static void
-check_labels(const char *start_label, const char *end_label, int end_location)
+check_labels(const char *start_label, const char *end_label, int end_location, yyscan_t yyscanner)
 {
 	if (end_label)
 	{
@@ -4119,13 +4123,13 @@ check_raise_parameters(PLpgSQL_stmt_raise *stmt)
  */
 static PLpgSQL_stmt *
 make_case(int location, PLpgSQL_expr *t_expr,
-		  List *case_when_list, List *else_stmts)
+		  List *case_when_list, List *else_stmts, yyscan_t yyscanner)
 {
 	PLpgSQL_stmt_case *new;
 
 	new = palloc(sizeof(PLpgSQL_stmt_case));
 	new->cmd_type = PLPGSQL_STMT_CASE;
-	new->lineno = plpgsql_location_to_lineno(location);
+	new->lineno = plpgsql_location_to_lineno(location, yyscanner);
 	new->stmtid = ++plpgsql_curr_compile->nstatements;
 	new->t_expr = t_expr;
 	new->t_varno = 0;
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index abf2d40c07a..e3cff48b322 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -95,41 +95,56 @@ typedef struct
 	int			leng;			/* length in bytes */
 } TokenAuxData;
 
+#define MAX_PUSHBACKS 4
+
 /*
- * Scanner working state.  At some point we might wish to fold all this
- * into a YY_EXTRA struct.  For the moment, there is no need for plpgsql's
- * lexer to be re-entrant, and the notational burden of passing a yyscanner
- * pointer around is great enough to not want to do it without need.
+ * Scanner working state.
  */
+struct plpgsql_yy_extra_type
+{
+	/* The stuff the core lexer needs */
+	core_yy_extra_type core_yy_extra;
 
-/* The stuff the core lexer needs */
-static core_yy_extra_type core_yy_extra;
-
-/* The original input string */
-static const char *scanorig;
-
-/* Current token's length (corresponds to plpgsql_yylval and plpgsql_yylloc) */
-static int	plpgsql_yyleng;
+	/* The original input string */
+	const char *scanorig;
 
-/* Current token's code (corresponds to plpgsql_yylval and plpgsql_yylloc) */
-static int	plpgsql_yytoken;
+	/*
+	 * Current token's length (corresponds to plpgsql_yylval and
+	 * plpgsql_yylloc)
+	 */
+	int			plpgsql_yyleng;
 
-/* Token pushback stack */
-#define MAX_PUSHBACKS 4
+	/* Current token's code (corresponds to plpgsql_yylval and plpgsql_yylloc) */
+	int			plpgsql_yytoken;
 
-static int	num_pushbacks;
-static int	pushback_token[MAX_PUSHBACKS];
-static TokenAuxData pushback_auxdata[MAX_PUSHBACKS];
+	/* Token pushback stack */
+	int			num_pushbacks;
+	int			pushback_token[MAX_PUSHBACKS];
+	TokenAuxData pushback_auxdata[MAX_PUSHBACKS];
 
-/* State for plpgsql_location_to_lineno() */
-static const char *cur_line_start;
-static const char *cur_line_end;
-static int	cur_line_num;
+	/* State for plpgsql_location_to_lineno() */
+	const char *cur_line_start;
+	const char *cur_line_end;
+	int			cur_line_num;
+};
 
 /* Internal functions */
 static int	internal_yylex(TokenAuxData *auxdata, yyscan_t yyscanner);
-static void push_back_token(int token, TokenAuxData *auxdata);
-static void location_lineno_init(void);
+static void push_back_token(int token, TokenAuxData *auxdata, yyscan_t yyscanner);
+static void location_lineno_init(yyscan_t yyscanner);
+
+/*
+ * This is normally provided by the generated flex code, but we don't have
+ * that here, so we make a minimal version ourselves.
+ */
+struct yyguts_t
+{
+	struct plpgsql_yy_extra_type *yyextra_r;
+};
+
+/* see scan.l */
+#undef yyextra
+#define yyextra (((struct yyguts_t *) yyscanner)->yyextra_r)
 
 
 /*
@@ -189,8 +204,8 @@ plpgsql_yylex(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 					else
 					{
 						/* not A.B.C, so just process A.B */
-						push_back_token(tok5, &aux5);
-						push_back_token(tok4, &aux4);
+						push_back_token(tok5, &aux5, yyscanner);
+						push_back_token(tok4, &aux4, yyscanner);
 						if (plpgsql_parse_dblword(aux1.lval.str,
 												  aux3.lval.str,
 												  &aux1.lval.wdatum,
@@ -205,7 +220,7 @@ plpgsql_yylex(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 				else
 				{
 					/* not A.B.C, so just process A.B */
-					push_back_token(tok4, &aux4);
+					push_back_token(tok4, &aux4, yyscanner);
 					if (plpgsql_parse_dblword(aux1.lval.str,
 											  aux3.lval.str,
 											  &aux1.lval.wdatum,
@@ -220,10 +235,10 @@ plpgsql_yylex(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 			else
 			{
 				/* not A.B, so just process A */
-				push_back_token(tok3, &aux3);
-				push_back_token(tok2, &aux2);
+				push_back_token(tok3, &aux3, yyscanner);
+				push_back_token(tok2, &aux2, yyscanner);
 				if (plpgsql_parse_word(aux1.lval.str,
-									   core_yy_extra.scanbuf + aux1.lloc,
+									   yyextra->core_yy_extra.scanbuf + aux1.lloc,
 									   true,
 									   &aux1.lval.wdatum,
 									   &aux1.lval.word))
@@ -243,7 +258,7 @@ plpgsql_yylex(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 		else
 		{
 			/* not A.B, so just process A */
-			push_back_token(tok2, &aux2);
+			push_back_token(tok2, &aux2, yyscanner);
 
 			/*
 			 * See if it matches a variable name, except in the context where
@@ -263,8 +278,8 @@ plpgsql_yylex(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 			 * non-variable cases.
 			 */
 			if (plpgsql_parse_word(aux1.lval.str,
-								   core_yy_extra.scanbuf + aux1.lloc,
-								   (!AT_STMT_START(plpgsql_yytoken) ||
+								   yyextra->core_yy_extra.scanbuf + aux1.lloc,
+								   (!AT_STMT_START(yyextra->plpgsql_yytoken) ||
 									(tok2 == '=' || tok2 == COLON_EQUALS ||
 									 tok2 == '[')),
 								   &aux1.lval.wdatum,
@@ -298,8 +313,8 @@ plpgsql_yylex(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 
 	*yylvalp = aux1.lval;
 	*yyllocp = aux1.lloc;
-	plpgsql_yyleng = aux1.leng;
-	plpgsql_yytoken = tok1;
+	yyextra->plpgsql_yyleng = aux1.leng;
+	yyextra->plpgsql_yytoken = tok1;
 	return tok1;
 }
 
@@ -309,9 +324,9 @@ plpgsql_yylex(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
  * In the case of compound tokens, the length includes all the parts.
  */
 int
-plpgsql_token_length(void)
+plpgsql_token_length(yyscan_t yyscanner)
 {
-	return plpgsql_yyleng;
+	return yyextra->plpgsql_yyleng;
 }
 
 /*
@@ -326,11 +341,11 @@ internal_yylex(TokenAuxData *auxdata, yyscan_t yyscanner)
 	int			token;
 	const char *yytext;
 
-	if (num_pushbacks > 0)
+	if (yyextra->num_pushbacks > 0)
 	{
-		num_pushbacks--;
-		token = pushback_token[num_pushbacks];
-		*auxdata = pushback_auxdata[num_pushbacks];
+		yyextra->num_pushbacks--;
+		token = yyextra->pushback_token[yyextra->num_pushbacks];
+		*auxdata = yyextra->pushback_auxdata[yyextra->num_pushbacks];
 	}
 	else
 	{
@@ -339,7 +354,7 @@ internal_yylex(TokenAuxData *auxdata, yyscan_t yyscanner)
 						   yyscanner);
 
 		/* remember the length of yytext before it gets changed */
-		yytext = core_yy_extra.scanbuf + auxdata->lloc;
+		yytext = yyextra->core_yy_extra.scanbuf + auxdata->lloc;
 		auxdata->leng = strlen(yytext);
 
 		/* Check for << >> and #, which the core considers operators */
@@ -367,13 +382,13 @@ internal_yylex(TokenAuxData *auxdata, yyscan_t yyscanner)
  * Push back a token to be re-read by next internal_yylex() call.
  */
 static void
-push_back_token(int token, TokenAuxData *auxdata)
+push_back_token(int token, TokenAuxData *auxdata, yyscan_t yyscanner)
 {
-	if (num_pushbacks >= MAX_PUSHBACKS)
+	if (yyextra->num_pushbacks >= MAX_PUSHBACKS)
 		elog(ERROR, "too many tokens pushed back");
-	pushback_token[num_pushbacks] = token;
-	pushback_auxdata[num_pushbacks] = *auxdata;
-	num_pushbacks++;
+	yyextra->pushback_token[yyextra->num_pushbacks] = token;
+	yyextra->pushback_auxdata[yyextra->num_pushbacks] = *auxdata;
+	yyextra->num_pushbacks++;
 }
 
 /*
@@ -383,14 +398,14 @@ push_back_token(int token, TokenAuxData *auxdata)
  * is not a good idea to push back a token code other than what you read.
  */
 void
-plpgsql_push_back_token(int token, YYSTYPE *yylvalp, YYLTYPE *yyllocp)
+plpgsql_push_back_token(int token, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 {
 	TokenAuxData auxdata;
 
 	auxdata.lval = *yylvalp;
 	auxdata.lloc = *yyllocp;
-	auxdata.leng = plpgsql_yyleng;
-	push_back_token(token, &auxdata);
+	auxdata.leng = yyextra->plpgsql_yyleng;
+	push_back_token(token, &auxdata, yyscanner);
 }
 
 /*
@@ -418,10 +433,11 @@ plpgsql_token_is_unreserved_keyword(int token)
  */
 void
 plpgsql_append_source_text(StringInfo buf,
-						   int startlocation, int endlocation)
+						   int startlocation, int endlocation,
+						   yyscan_t yyscanner)
 {
 	Assert(startlocation <= endlocation);
-	appendBinaryStringInfo(buf, scanorig + startlocation,
+	appendBinaryStringInfo(buf, yyextra->scanorig + startlocation,
 						   endlocation - startlocation);
 }
 
@@ -439,7 +455,7 @@ plpgsql_peek(yyscan_t yyscanner)
 	TokenAuxData aux1;
 
 	tok1 = internal_yylex(&aux1, yyscanner);
-	push_back_token(tok1, &aux1);
+	push_back_token(tok1, &aux1, yyscanner);
 	return tok1;
 }
 
@@ -469,8 +485,8 @@ plpgsql_peek2(int *tok1_p, int *tok2_p, int *tok1_loc, int *tok2_loc, yyscan_t y
 	if (tok2_loc)
 		*tok2_loc = aux2.lloc;
 
-	push_back_token(tok2, &aux2);
-	push_back_token(tok1, &aux1);
+	push_back_token(tok2, &aux2, yyscanner);
+	push_back_token(tok1, &aux1, yyscanner);
 }
 
 /*
@@ -485,19 +501,19 @@ plpgsql_peek2(int *tok1_p, int *tok2_p, int *tok1_loc, int *tok2_loc, yyscan_t y
  * to still be available.
  */
 int
-plpgsql_scanner_errposition(int location)
+plpgsql_scanner_errposition(int location, yyscan_t yyscanner)
 {
 	int			pos;
 
-	if (location < 0 || scanorig == NULL)
+	if (location < 0 || yyextra->scanorig == NULL)
 		return 0;				/* no-op if location is unknown */
 
 	/* Convert byte offset to character number */
-	pos = pg_mbstrlen_with_len(scanorig, location) + 1;
+	pos = pg_mbstrlen_with_len(yyextra->scanorig, location) + 1;
 	/* And pass it to the ereport mechanism */
 	(void) internalerrposition(pos);
 	/* Also pass the function body string */
-	return internalerrquery(scanorig);
+	return internalerrquery(yyextra->scanorig);
 }
 
 /*
@@ -514,7 +530,7 @@ plpgsql_scanner_errposition(int location)
 void
 plpgsql_yyerror(YYLTYPE *yyllocp, yyscan_t yyscanner, const char *message)
 {
-	char	   *yytext = core_yy_extra.scanbuf + *yyllocp;
+	char	   *yytext = yyextra->core_yy_extra.scanbuf + *yyllocp;
 
 	if (*yytext == '\0')
 	{
@@ -522,7 +538,7 @@ plpgsql_yyerror(YYLTYPE *yyllocp, yyscan_t yyscanner, const char *message)
 				(errcode(ERRCODE_SYNTAX_ERROR),
 		/* translator: %s is typically the translation of "syntax error" */
 				 errmsg("%s at end of input", _(message)),
-				 plpgsql_scanner_errposition(*yyllocp)));
+				 plpgsql_scanner_errposition(*yyllocp, yyscanner)));
 	}
 	else
 	{
@@ -532,13 +548,13 @@ plpgsql_yyerror(YYLTYPE *yyllocp, yyscan_t yyscanner, const char *message)
 		 * only the single token here.  This modifies scanbuf but we no longer
 		 * care about that.
 		 */
-		yytext[plpgsql_yyleng] = '\0';
+		yytext[yyextra->plpgsql_yyleng] = '\0';
 
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 		/* translator: first %s is typically the translation of "syntax error" */
 				 errmsg("%s at or near \"%s\"", _(message), yytext),
-				 plpgsql_scanner_errposition(*yyllocp)));
+				 plpgsql_scanner_errposition(*yyllocp, yyscanner)));
 	}
 }
 
@@ -551,43 +567,43 @@ plpgsql_yyerror(YYLTYPE *yyllocp, yyscan_t yyscanner, const char *message)
  * of the "current" line.
  */
 int
-plpgsql_location_to_lineno(int location)
+plpgsql_location_to_lineno(int location, yyscan_t yyscanner)
 {
 	const char *loc;
 
-	if (location < 0 || scanorig == NULL)
+	if (location < 0 || yyextra->scanorig == NULL)
 		return 0;				/* garbage in, garbage out */
-	loc = scanorig + location;
+	loc = yyextra->scanorig + location;
 
 	/* be correct, but not fast, if input location goes backwards */
-	if (loc < cur_line_start)
-		location_lineno_init();
+	if (loc < yyextra->cur_line_start)
+		location_lineno_init(yyscanner);
 
-	while (cur_line_end != NULL && loc > cur_line_end)
+	while (yyextra->cur_line_end != NULL && loc > yyextra->cur_line_end)
 	{
-		cur_line_start = cur_line_end + 1;
-		cur_line_num++;
-		cur_line_end = strchr(cur_line_start, '\n');
+		yyextra->cur_line_start = yyextra->cur_line_end + 1;
+		yyextra->cur_line_num++;
+		yyextra->cur_line_end = strchr(yyextra->cur_line_start, '\n');
 	}
 
-	return cur_line_num;
+	return yyextra->cur_line_num;
 }
 
 /* initialize or reset the state for plpgsql_location_to_lineno */
 static void
-location_lineno_init(void)
+location_lineno_init(yyscan_t yyscanner)
 {
-	cur_line_start = scanorig;
-	cur_line_num = 1;
+	yyextra->cur_line_start = yyextra->scanorig;
+	yyextra->cur_line_num = 1;
 
-	cur_line_end = strchr(cur_line_start, '\n');
+	yyextra->cur_line_end = strchr(yyextra->cur_line_start, '\n');
 }
 
 /* return the most recently computed lineno */
 int
-plpgsql_latest_lineno(void)
+plpgsql_latest_lineno(yyscan_t yyscanner)
 {
-	return cur_line_num;
+	return yyextra->cur_line_num;
 }
 
 
@@ -602,9 +618,10 @@ yyscan_t
 plpgsql_scanner_init(const char *str)
 {
 	yyscan_t	yyscanner;
+	struct plpgsql_yy_extra_type *yyext = palloc0_object(struct plpgsql_yy_extra_type);
 
 	/* Start up the core scanner */
-	yyscanner = scanner_init(str, &core_yy_extra,
+	yyscanner = scanner_init(str, (core_yy_extra_type *) yyext,
 							 &ReservedPLKeywords, ReservedPLKeywordTokens);
 
 	/*
@@ -613,15 +630,15 @@ plpgsql_scanner_init(const char *str)
 	 * yytext points into scanbuf, we rely on being able to apply locations
 	 * (offsets from string start) to scanorig as well.
 	 */
-	scanorig = str;
+	yyext->scanorig = str;
 
 	/* Other setup */
 	plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
-	plpgsql_yytoken = 0;
+	yyext->plpgsql_yytoken = 0;
 
-	num_pushbacks = 0;
+	yyext->num_pushbacks = 0;
 
-	location_lineno_init();
+	location_lineno_init(yyscanner);
 
 	return yyscanner;
 }
@@ -634,6 +651,4 @@ plpgsql_scanner_finish(yyscan_t yyscanner)
 {
 	/* release storage */
 	scanner_finish(yyscanner);
-	/* avoid leaving any dangling pointers */
-	scanorig = NULL;
 }
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 151ae7943df..2562c816a1b 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -1322,18 +1322,19 @@ union YYSTYPE;
 typedef void *yyscan_t;
 #endif
 extern int	plpgsql_yylex(union YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
-extern int	plpgsql_token_length(void);
-extern void plpgsql_push_back_token(int token, union YYSTYPE *yylvalp, YYLTYPE *yyllocp);
+extern int	plpgsql_token_length(yyscan_t yyscanner);
+extern void plpgsql_push_back_token(int token, union YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
 extern bool plpgsql_token_is_unreserved_keyword(int token);
 extern void plpgsql_append_source_text(StringInfo buf,
-									   int startlocation, int endlocation);
+									   int startlocation, int endlocation,
+									   yyscan_t yyscanner);
 extern int	plpgsql_peek(yyscan_t yyscanner);
 extern void plpgsql_peek2(int *tok1_p, int *tok2_p, int *tok1_loc,
 						  int *tok2_loc, yyscan_t yyscanner);
-extern int	plpgsql_scanner_errposition(int location);
+extern int	plpgsql_scanner_errposition(int location, yyscan_t yyscanner);
 extern void plpgsql_yyerror(YYLTYPE *yyllocp, yyscan_t yyscanner, const char *message) pg_attribute_noreturn();
-extern int	plpgsql_location_to_lineno(int location);
-extern int	plpgsql_latest_lineno(void);
+extern int	plpgsql_location_to_lineno(int location, yyscan_t yyscanner);
+extern int	plpgsql_latest_lineno(yyscan_t yyscanner);
 extern yyscan_t plpgsql_scanner_init(const char *str);
 extern void plpgsql_scanner_finish(yyscan_t yyscanner);
 
-- 
2.47.1