From baebab3ace480477f210dadc4633d8d119dfa978 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 12 Jul 2016 18:06:50 -0400
Subject: [PATCH] Allow IMPORT FOREIGN SCHEMA within pl/pgsql.

Since IMPORT FOREIGN SCHEMA has an INTO clause, pl/pgsql needs to be
aware of that and avoid capturing the INTO as an INTO-variables clause.
This isn't hard, though it's annoying to have to make IMPORT a plpgsql
keyword just for this.  (Fortunately, we have the infrastructure now
to make it an unreserved keyword, so at least this shouldn't break any
existing pl/pgsql code.)

Per report from Merlin Moncure.  Back-patch to 9.5 where IMPORT FOREIGN
SCHEMA was introduced.

Report: <CAHyXU0wpHf2bbtKGL1gtUEFATCY86r=VKxfcACVcTMQ70mCyig@mail.gmail.com>
---
 src/pl/plpgsql/src/pl_gram.y    | 47 ++++++++++++++++++++++++++-------
 src/pl/plpgsql/src/pl_scanner.c |  1 +
 2 files changed, 39 insertions(+), 9 deletions(-)

diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index df09575e123..0b41e3acb6c 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -287,6 +287,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_GET
 %token <keyword>	K_HINT
 %token <keyword>	K_IF
+%token <keyword>	K_IMPORT
 %token <keyword>	K_IN
 %token <keyword>	K_INFO
 %token <keyword>	K_INSERT
@@ -1929,7 +1930,11 @@ loop_body		: proc_sect K_END K_LOOP opt_label ';'
  * assignment.  Give an appropriate complaint for that, instead of letting
  * the core parser throw an unhelpful "syntax error".
  */
-stmt_execsql	: K_INSERT
+stmt_execsql	: K_IMPORT
+					{
+						$$ = make_execsql_stmt(K_IMPORT, @1);
+					}
+				| K_INSERT
 					{
 						$$ = make_execsql_stmt(K_INSERT, @1);
 					}
@@ -2418,6 +2423,7 @@ unreserved_keyword	:
 				| K_FORWARD
 				| K_GET
 				| K_HINT
+				| K_IMPORT
 				| K_INFO
 				| K_INSERT
 				| K_IS
@@ -2843,12 +2849,32 @@ make_execsql_stmt(int firsttoken, int location)
 	plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
 
 	/*
-	 * We have to special-case the sequence INSERT INTO, because we don't want
-	 * that to be taken as an INTO-variables clause.  Fortunately, this is the
-	 * only valid use of INTO in a pl/pgsql SQL command, and INTO is already a
-	 * fully reserved word in the main grammar.  We have to treat it that way
-	 * anywhere in the string, not only at the start; consider CREATE RULE
-	 * containing an INSERT statement.
+	 * Scan to the end of the SQL command.  Identify any INTO-variables
+	 * clause lurking within it, and parse that via read_into_target().
+	 *
+	 * Because INTO is sometimes used in the main SQL grammar, we have to be
+	 * careful not to take any such usage of INTO as a pl/pgsql INTO clause.
+	 * There are currently three such cases:
+	 *
+	 * 1. SELECT ... INTO.  We don't care, we just override that with the
+	 * pl/pgsql definition.
+	 *
+	 * 2. INSERT INTO.  This is relatively easy to recognize since the words
+	 * must appear adjacently; but we can't assume INSERT starts the command,
+	 * because it can appear in CREATE RULE or WITH.  Unfortunately, INSERT is
+	 * *not* fully reserved, so that means there is a chance of a false match;
+	 * but it's not very likely.
+	 *
+	 * 3. IMPORT FOREIGN SCHEMA ... INTO.  This is not allowed in CREATE RULE
+	 * or WITH, so we just check for IMPORT as the command's first token.
+	 * (If IMPORT FOREIGN SCHEMA returned data someone might wish to capture
+	 * with an INTO-variables clause, we'd have to work much harder here.)
+	 *
+	 * Fortunately, INTO is a fully reserved word in the main grammar, so
+	 * at least we need not worry about it appearing as an identifier.
+	 *
+	 * Any future additional uses of INTO in the main grammar will doubtless
+	 * break this logic again ... beware!
 	 */
 	tok = firsttoken;
 	for (;;)
@@ -2861,9 +2887,12 @@ make_execsql_stmt(int firsttoken, int location)
 			break;
 		if (tok == 0)
 			yyerror("unexpected end of function definition");
-
-		if (tok == K_INTO && prev_tok != K_INSERT)
+		if (tok == K_INTO)
 		{
+			if (prev_tok == K_INSERT)
+				continue;		/* INSERT INTO is not an INTO-target */
+			if (firsttoken == K_IMPORT)
+				continue;		/* IMPORT ... INTO is not an INTO-target */
 			if (have_into)
 				yyerror("INTO specified more than once");
 			have_into = true;
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index bb0f25b18ce..2737fff3fbf 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -127,6 +127,7 @@ static const ScanKeyword unreserved_keywords[] = {
 	PG_KEYWORD("forward", K_FORWARD, UNRESERVED_KEYWORD)
 	PG_KEYWORD("get", K_GET, UNRESERVED_KEYWORD)
 	PG_KEYWORD("hint", K_HINT, UNRESERVED_KEYWORD)
+	PG_KEYWORD("import", K_IMPORT, UNRESERVED_KEYWORD)
 	PG_KEYWORD("info", K_INFO, UNRESERVED_KEYWORD)
 	PG_KEYWORD("insert", K_INSERT, UNRESERVED_KEYWORD)
 	PG_KEYWORD("is", K_IS, UNRESERVED_KEYWORD)
-- 
GitLab