On 12/30/18, Andres Freund <and...@anarazel.de> wrote:
> I tried to take this for a spin, an for me the build fails because various
> frontend programs don't have KeywordOffsets/Strings defined, but reference
> it
> through various functions exposed to the frontend (like fmtId()).  That I
> see
> that error but you don't is probably related to me using -fuse-ld=gold in
> CFLAGS.
>
> I can "fix" this by including kwlist_d.h in common/keywords.c
> regardless of FRONTEND. That also lead me to discover that the build
> dependencies somewhere aren't correctly set-up, because I need to
> force a clean rebuild to trigger the problem again, just changing
> keywords.c back doesn't trigger the problem.

Hmm, that was a typo, and I didn't notice even when I found I had to
include kwlist_d.h in ecpg/keywords.c. :-(  I've fixed both of those
in the attached v6.

As far as dependencies, I'm far from sure I have it up to par. That
piece could use some discussion.

On 1/4/19, Tom Lane <t...@sss.pgh.pa.us> wrote:
> Aside from the possible linkage problem, this will need a minor rebase
> over 4879a5172, which rearranged some of plpgsql's calls of
> ScanKeywordLookup.
>
> While I don't think it's going to be hard to resolve these issues,
> I'm wondering where we want to go with this.  Is anyone excited
> about pursuing the perfect-hash-function idea?  (Joerg's example
> function looked pretty neat to me.)  If we are going to do that,
> does it make sense to push this version beforehand?

If it does, for v6 I've also done the rebase, updated the copyright
year, and fixed an error in MSVC.

-John Naylor
From fb846eeabd450b6932e57f7e8d4f0aebc125d178 Mon Sep 17 00:00:00 2001
From: John Naylor <jcnay...@gmail.com>
Date: Fri, 4 Jan 2019 14:35:40 -0500
Subject: [PATCH v6] Use offset-based keyword lookup.

Replace binary search over an array of ScanKeyword structs with a binary
search over an array of offsets into a keyword string. Access auxillary
data only after a keyword hit. This has better locality of reference and
a smaller memory footprint.
---
 .../pg_stat_statements/pg_stat_statements.c   |   2 +
 src/backend/parser/parser.c                   |   4 +-
 src/backend/parser/scan.l                     |  38 ++--
 src/backend/utils/adt/misc.c                  |   2 +-
 src/backend/utils/adt/ruleutils.c             |   9 +-
 src/common/.gitignore                         |   1 +
 src/common/Makefile                           |  25 ++-
 src/common/keywords.c                         |  42 +++--
 src/fe_utils/string_utils.c                   |   9 +-
 src/include/common/keywords.h                 |  19 +-
 src/include/parser/kwlist.h                   |   2 +-
 src/include/parser/scanner.h                  |   8 +-
 src/interfaces/ecpg/preproc/.gitignore        |   2 +
 src/interfaces/ecpg/preproc/Makefile          |  20 ++-
 src/interfaces/ecpg/preproc/c_keywords.c      |  65 ++-----
 src/interfaces/ecpg/preproc/c_kwlist.h        |  53 ++++++
 src/interfaces/ecpg/preproc/ecpg_keywords.c   |  81 ++-------
 src/interfaces/ecpg/preproc/ecpg_kwlist.h     |  68 +++++++
 src/interfaces/ecpg/preproc/keywords.c        |   6 +-
 src/interfaces/ecpg/preproc/pgc.l             |  22 +--
 src/interfaces/ecpg/preproc/preproc_extern.h  |   8 +-
 src/pl/plpgsql/src/.gitignore                 |   2 +
 src/pl/plpgsql/src/Makefile                   |  16 +-
 src/pl/plpgsql/src/pl_reserved_kwlist.h       |  55 ++++++
 src/pl/plpgsql/src/pl_scanner.c               | 167 ++++--------------
 src/pl/plpgsql/src/pl_unreserved_kwlist.h     | 111 ++++++++++++
 src/tools/gen_keywords.pl                     | 136 ++++++++++++++
 src/tools/msvc/Solution.pm                    |  44 +++++
 src/tools/msvc/clean.bat                      |   6 +
 29 files changed, 696 insertions(+), 327 deletions(-)
 create mode 100644 src/common/.gitignore
 create mode 100644 src/interfaces/ecpg/preproc/c_kwlist.h
 create mode 100644 src/interfaces/ecpg/preproc/ecpg_kwlist.h
 create mode 100644 src/pl/plpgsql/src/pl_reserved_kwlist.h
 create mode 100644 src/pl/plpgsql/src/pl_unreserved_kwlist.h
 create mode 100644 src/tools/gen_keywords.pl

diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index e8ef966bb5..4776de7595 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -3076,6 +3076,8 @@ fill_in_constant_lengths(pgssJumbleState *jstate, const char *query,
 	yyscanner = scanner_init(query,
 							 &yyextra,
 							 ScanKeywords,
+							 KeywordString,
+							 KeywordOffsets,
 							 NumScanKeywords);
 
 	/* we don't want to re-emit any escape string warnings */
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 7e9b1222fd..19fa7bb8ae 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -40,8 +40,8 @@ raw_parser(const char *str)
 	int			yyresult;
 
 	/* initialize the flex scanner */
-	yyscanner = scanner_init(str, &yyextra.core_yy_extra,
-							 ScanKeywords, NumScanKeywords);
+	yyscanner = scanner_init(str, &yyextra.core_yy_extra, ScanKeywords,
+							 KeywordString, KeywordOffsets, NumScanKeywords);
 
 	/* base_yylex() only needs this much initialization */
 	yyextra.have_lookahead = false;
diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l
index fbeb86f890..fdc8ef3e9a 100644
--- a/src/backend/parser/scan.l
+++ b/src/backend/parser/scan.l
@@ -504,18 +504,20 @@ other			.
 					 * We will pass this along as a normal character string,
 					 * but preceded with an internally-generated "NCHAR".
 					 */
-					const ScanKeyword *keyword;
+					int		kwnum;
 
 					SET_YYLLOC();
 					yyless(1);	/* eat only 'n' this time */
 
-					keyword = ScanKeywordLookup("nchar",
-												yyextra->keywords,
-												yyextra->num_keywords);
-					if (keyword != NULL)
+					kwnum = ScanKeywordLookup("nchar",
+											  yyextra->kw_string,
+											  yyextra->kw_offsets,
+											  yyextra->num_keywords);
+					if (kwnum >= 0)
 					{
-						yylval->keyword = keyword->name;
-						return keyword->value;
+						yylval->keyword = yyextra->kw_string
+											+ yyextra->kw_offsets[kwnum];
+						return yyextra->keywords[kwnum].value;
 					}
 					else
 					{
@@ -1021,19 +1023,21 @@ other			.
 
 
 {identifier}	{
-					const ScanKeyword *keyword;
+					int			kwnum;
 					char	   *ident;
 
 					SET_YYLLOC();
 
 					/* Is it a keyword? */
-					keyword = ScanKeywordLookup(yytext,
-												yyextra->keywords,
-												yyextra->num_keywords);
-					if (keyword != NULL)
+					kwnum = ScanKeywordLookup(yytext,
+											  yyextra->kw_string,
+											  yyextra->kw_offsets,
+											  yyextra->num_keywords);
+					if (kwnum >= 0)
 					{
-						yylval->keyword = keyword->name;
-						return keyword->value;
+						yylval->keyword = yyextra->kw_string
+											+ yyextra->kw_offsets[kwnum];
+						return yyextra->keywords[kwnum].value;
 					}
 
 					/*
@@ -1142,7 +1146,9 @@ scanner_yyerror(const char *message, core_yyscan_t yyscanner)
 core_yyscan_t
 scanner_init(const char *str,
 			 core_yy_extra_type *yyext,
-			 const ScanKeyword *keywords,
+			 const ScanKeywordAux *keywords,
+			 const char *kw_string,
+			 const uint16 *kw_offsets,
 			 int num_keywords)
 {
 	Size		slen = strlen(str);
@@ -1154,6 +1160,8 @@ scanner_init(const char *str,
 	core_yyset_extra(yyext, scanner);
 
 	yyext->keywords = keywords;
+	yyext->kw_string = kw_string;
+	yyext->kw_offsets = kw_offsets;
 	yyext->num_keywords = num_keywords;
 
 	yyext->backslash_quote = backslash_quote;
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 7b69b824e1..773dce234e 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -423,7 +423,7 @@ pg_get_keywords(PG_FUNCTION_ARGS)
 		HeapTuple	tuple;
 
 		/* cast-away-const is ugly but alternatives aren't much better */
-		values[0] = unconstify(char *, ScanKeywords[funcctx->call_cntr].name);
+		values[0] = unconstify(char *, KeywordString + KeywordOffsets[funcctx->call_cntr]);
 
 		switch (ScanKeywords[funcctx->call_cntr].category)
 		{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 368eacf68e..e59ac4757a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10601,11 +10601,12 @@ quote_identifier(const char *ident)
 		 * Note: ScanKeywordLookup() does case-insensitive comparison, but
 		 * that's fine, since we already know we have all-lower-case.
 		 */
-		const ScanKeyword *keyword = ScanKeywordLookup(ident,
-													   ScanKeywords,
-													   NumScanKeywords);
+		int kwnum = ScanKeywordLookup(ident,
+									  KeywordString,
+									  KeywordOffsets,
+									  NumScanKeywords);
 
-		if (keyword != NULL && keyword->category != UNRESERVED_KEYWORD)
+		if (kwnum >= 0 && ScanKeywords[kwnum].category != UNRESERVED_KEYWORD)
 			safe = false;
 	}
 
diff --git a/src/common/.gitignore b/src/common/.gitignore
new file mode 100644
index 0000000000..ffa3284fbf
--- /dev/null
+++ b/src/common/.gitignore
@@ -0,0 +1 @@
+/kwlist_d.h
diff --git a/src/common/Makefile b/src/common/Makefile
index ec8139f014..fbd4933554 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -63,7 +63,18 @@ OBJS_FRONTEND = $(OBJS_COMMON) fe_memutils.o file_utils.o restricted_token.o
 OBJS_SHLIB = $(OBJS_FRONTEND:%.o=%_shlib.o)
 OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
 
-all: libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
+all: libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a kwlist_d.h
+
+distprep: kwlist_d.h
+
+# generate keyword header for the core and frontend scanners
+kwlist_d.h: $(top_srcdir)/src/include/parser/kwlist.h $(top_srcdir)/src/tools/gen_keywords.pl
+	$(PERL) $(top_srcdir)/src/tools/gen_keywords.pl --extern $<
+
+$(top_builddir)/src/include/common/kwlist_d.h: kwlist_d.h
+	prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
+	  cd '$(dir $@)' && rm -f $(notdir $@) && \
+	  $(LN_S) "$$prereqdir/$(notdir $<)" .
 
 # libpgcommon is needed by some contrib
 install: all installdirs
@@ -121,10 +132,14 @@ libpgcommon_srv.a: $(OBJS_SRV)
 $(top_builddir)/src/include/parser/gram.h: $(top_srcdir)/src/backend/parser/gram.y
 	$(MAKE) -C $(top_builddir)/src/backend $(top_builddir)/src/include/parser/gram.h
 
-keywords.o: $(top_srcdir)/src/include/parser/kwlist.h
-keywords_shlib.o: $(top_srcdir)/src/include/parser/kwlist.h
-keywords_srv.o: $(top_builddir)/src/include/parser/gram.h $(top_srcdir)/src/include/parser/kwlist.h
+keywords.o: $(top_builddir)/src/include/common/kwlist_d.h $(top_srcdir)/src/include/parser/kwlist.h
+keywords_shlib.o: $(top_builddir)/src/include/common/kwlist_d.h $(top_srcdir)/src/include/parser/kwlist.h
+keywords_srv.o: $(top_builddir)/src/include/common/kwlist_d.h $(top_builddir)/src/include/parser/gram.h $(top_srcdir)/src/include/parser/kwlist.h
 
-clean distclean maintainer-clean:
+# kwlist_d.h is in the distribution tarball, so it is not cleaned here.
+clean distclean:
 	rm -f libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
 	rm -f $(OBJS_FRONTEND) $(OBJS_SHLIB) $(OBJS_SRV)
+
+maintainer-clean: distclean
+	rm -f kwlist_d.h
diff --git a/src/common/keywords.c b/src/common/keywords.c
index 6f99090a29..76838be4c8 100644
--- a/src/common/keywords.c
+++ b/src/common/keywords.c
@@ -19,11 +19,14 @@
 #include "postgres_fe.h"
 #endif
 
+/* String lookup table for keywords */
+#include "common/kwlist_d.h"
+
 #ifndef FRONTEND
 
 #include "parser/gramparse.h"
 
-#define PG_KEYWORD(a,b,c) {a,b,c},
+#define PG_KEYWORD(kwname, value, category) {value, category},
 
 #else
 
@@ -33,12 +36,11 @@
  * We don't need the token number for frontend uses, so leave it out to avoid
  * requiring backend headers that won't compile cleanly here.
  */
-#define PG_KEYWORD(a,b,c) {a,0,c},
+#define PG_KEYWORD(kwname, value, category) {0, category},
 
 #endif							/* FRONTEND */
 
-
-const ScanKeyword ScanKeywords[] = {
+const ScanKeywordAux ScanKeywords[] = {
 #include "parser/kwlist.h"
 };
 
@@ -48,10 +50,13 @@ const int	NumScanKeywords = lengthof(ScanKeywords);
 /*
  * ScanKeywordLookup - see if a given word is a keyword
  *
- * The table to be searched is passed explicitly, so that this can be used
- * to search keyword lists other than the standard list appearing above.
+ * The keyword string along with an array of offsets into it are passed
+ * explicitly, so that callers can use different keyword lists.  These are
+ * generated from the appropriate "kwlist" header at compile time.
  *
- * Returns a pointer to the ScanKeyword table entry, or NULL if no match.
+ * Returns an array index, or -1 if no match.  The caller can use this array
+ * index to retrieve the keyword string as well as any needed auxiliary data
+ * such as token value or category.
  *
  * The match is done case-insensitively.  Note that we deliberately use a
  * dumbed-down case conversion that will only translate 'A'-'Z' into 'a'-'z',
@@ -60,21 +65,22 @@ const int	NumScanKeywords = lengthof(ScanKeywords);
  * keywords are to be matched in this way even though non-keyword identifiers
  * receive a different case-normalization mapping.
  */
-const ScanKeyword *
+int
 ScanKeywordLookup(const char *text,
-				  const ScanKeyword *keywords,
+				  const char *kw_string,
+				  const uint16 *kw_offsets,
 				  int num_keywords)
 {
 	int			len,
 				i;
 	char		word[NAMEDATALEN];
-	const ScanKeyword *low;
-	const ScanKeyword *high;
+	const uint16 *low;
+	const uint16 *high;
 
 	len = strlen(text);
 	/* We assume all keywords are shorter than NAMEDATALEN. */
 	if (len >= NAMEDATALEN)
-		return NULL;
+		return -1;
 
 	/*
 	 * Apply an ASCII-only downcasing.  We must not use tolower() since it may
@@ -93,22 +99,22 @@ ScanKeywordLookup(const char *text,
 	/*
 	 * Now do a binary search using plain strcmp() comparison.
 	 */
-	low = keywords;
-	high = keywords + (num_keywords - 1);
+	low = kw_offsets;
+	high = kw_offsets + (num_keywords - 1);
 	while (low <= high)
 	{
-		const ScanKeyword *middle;
+		const uint16 *middle;
 		int			difference;
 
 		middle = low + (high - low) / 2;
-		difference = strcmp(middle->name, word);
+		difference = strcmp(kw_string + *middle, word);
 		if (difference == 0)
-			return middle;
+			return middle - kw_offsets;
 		else if (difference < 0)
 			low = middle + 1;
 		else
 			high = middle - 1;
 	}
 
-	return NULL;
+	return -1;
 }
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 9b47b62f41..41bec23a13 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -104,11 +104,12 @@ fmtId(const char *rawid)
 		 * Note: ScanKeywordLookup() does case-insensitive comparison, but
 		 * that's fine, since we already know we have all-lower-case.
 		 */
-		const ScanKeyword *keyword = ScanKeywordLookup(rawid,
-													   ScanKeywords,
-													   NumScanKeywords);
+		int kwnum = ScanKeywordLookup(rawid,
+									  KeywordString,
+									  KeywordOffsets,
+									  NumScanKeywords);
 
-		if (keyword != NULL && keyword->category != UNRESERVED_KEYWORD)
+		if (kwnum >= 0 && ScanKeywords[kwnum].category != UNRESERVED_KEYWORD)
 			need_quotes = true;
 	}
 
diff --git a/src/include/common/keywords.h b/src/include/common/keywords.h
index 8f22f32548..309830664b 100644
--- a/src/include/common/keywords.h
+++ b/src/include/common/keywords.h
@@ -21,24 +21,29 @@
 #define RESERVED_KEYWORD		3
 
 
-typedef struct ScanKeyword
+/* Auxiliary data for keywords */
+typedef struct ScanKeywordAux
 {
-	const char *name;			/* in lower case */
 	int16		value;			/* grammar's token code */
 	int16		category;		/* see codes above */
-} ScanKeyword;
+} ScanKeywordAux;
 
 #ifndef FRONTEND
-extern PGDLLIMPORT const ScanKeyword ScanKeywords[];
+extern PGDLLIMPORT const ScanKeywordAux ScanKeywords[];
+extern PGDLLIMPORT const char *KeywordString;
+extern PGDLLIMPORT const uint16 KeywordOffsets[];
 extern PGDLLIMPORT const int NumScanKeywords;
 #else
-extern const ScanKeyword ScanKeywords[];
+extern const ScanKeywordAux ScanKeywords[];
+extern const char *KeywordString;
+extern const uint16 KeywordOffsets[];
 extern const int NumScanKeywords;
 #endif
 
 
-extern const ScanKeyword *ScanKeywordLookup(const char *text,
-				  const ScanKeyword *keywords,
+extern int ScanKeywordLookup(const char *text,
+				  const char *kw_string,
+				  const uint16 *kw_offsets,
 				  int num_keywords);
 
 #endif							/* KEYWORDS_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 0256d53998..b8902d3403 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -2,7 +2,7 @@
  *
  * kwlist.h
  *
- * The keyword list is kept in its own source file for possible use by
+ * The keyword lists are kept in their own source files for use by
  * automatic tools.  The exact representation of a keyword is determined
  * by the PG_KEYWORD macro, which is not defined in this file; it can
  * be defined by the caller for special purposes.
diff --git a/src/include/parser/scanner.h b/src/include/parser/scanner.h
index 009550f424..0678c9a697 100644
--- a/src/include/parser/scanner.h
+++ b/src/include/parser/scanner.h
@@ -75,7 +75,9 @@ typedef struct core_yy_extra_type
 	/*
 	 * The keyword list to use.
 	 */
-	const ScanKeyword *keywords;
+	const ScanKeywordAux *keywords;
+	const char *kw_string;
+	const uint16 *kw_offsets;
 	int			num_keywords;
 
 	/*
@@ -119,7 +121,9 @@ typedef void *core_yyscan_t;
 /* Entry points in parser/scan.l */
 extern core_yyscan_t scanner_init(const char *str,
 			 core_yy_extra_type *yyext,
-			 const ScanKeyword *keywords,
+			 const ScanKeywordAux *keywords,
+			 const char *kw_string,
+			 const uint16 *kw_offsets,
 			 int num_keywords);
 extern void scanner_finish(core_yyscan_t yyscanner);
 extern int core_yylex(core_YYSTYPE *lvalp, YYLTYPE *llocp,
diff --git a/src/interfaces/ecpg/preproc/.gitignore b/src/interfaces/ecpg/preproc/.gitignore
index 38ae2fe4d9..685be1c4c7 100644
--- a/src/interfaces/ecpg/preproc/.gitignore
+++ b/src/interfaces/ecpg/preproc/.gitignore
@@ -1,3 +1,5 @@
+/c_kwlist_d.h
+/ecpg_kwlist_d.h
 /preproc.y
 /preproc.c
 /preproc.h
diff --git a/src/interfaces/ecpg/preproc/Makefile b/src/interfaces/ecpg/preproc/Makefile
index 69ddd8e9f7..07a6783646 100644
--- a/src/interfaces/ecpg/preproc/Makefile
+++ b/src/interfaces/ecpg/preproc/Makefile
@@ -53,9 +53,20 @@ preproc.y: ../../../backend/parser/gram.y parse.pl ecpg.addons ecpg.header ecpg.
 	$(PERL) $(srcdir)/parse.pl $(srcdir) < $< > $@
 	$(PERL) $(srcdir)/check_rules.pl $(srcdir) $<
 
+# generate keyword headers
+c_kwlist_d.h: c_kwlist.h $(top_srcdir)/src/tools/gen_keywords.pl
+	$(PERL) $(top_srcdir)/src/tools/gen_keywords.pl --prefix c_ $<
+
+ecpg_kwlist_d.h: ecpg_kwlist.h $(top_srcdir)/src/tools/gen_keywords.pl
+	$(PERL) $(top_srcdir)/src/tools/gen_keywords.pl --prefix ecpg_ $<
+
+# Force these dependencies to be known even without dependency info built:
 ecpg_keywords.o c_keywords.o keywords.o preproc.o pgc.o parser.o: preproc.h
+ecpg_keywords.o: ecpg_kwlist_d.h
+c_keywords.o: c_kwlist_d.h
+keywords.o: $(top_builddir)/src/include/common/kwlist_d.h
 
-distprep: preproc.y preproc.c preproc.h pgc.c
+distprep: preproc.y preproc.c preproc.h pgc.c c_kwlist_d.h ecpg_kwlist_d.h
 
 install: all installdirs
 	$(INSTALL_PROGRAM) ecpg$(X) '$(DESTDIR)$(bindir)'
@@ -66,12 +77,11 @@ installdirs:
 uninstall:
 	rm -f '$(DESTDIR)$(bindir)/ecpg$(X)'
 
+# preproc.y, preproc.c, preproc.h, pgc.c, c_kwlist_d.h, and ecpg_kwlist_d.h
+# are in the distribution tarball, so they are not cleaned here.
 clean distclean:
 	rm -f *.o ecpg$(X)
 	rm -f typename.c
 
-# `make distclean' must not remove preproc.y, preproc.c, preproc.h, or pgc.c
-# since we want to ship those files in the distribution for people with
-# inadequate tools.  Instead, `make maintainer-clean' will remove them.
 maintainer-clean: distclean
-	rm -f preproc.y preproc.c preproc.h pgc.c
+	rm -f preproc.y preproc.c preproc.h pgc.c c_kwlist_d.h ecpg_kwlist_d.h
diff --git a/src/interfaces/ecpg/preproc/c_keywords.c b/src/interfaces/ecpg/preproc/c_keywords.c
index c367dbfc20..989db3a5a5 100644
--- a/src/interfaces/ecpg/preproc/c_keywords.c
+++ b/src/interfaces/ecpg/preproc/c_keywords.c
@@ -14,72 +14,45 @@
 #include "preproc_extern.h"
 #include "preproc.h"
 
-/*
- * List of (keyword-name, keyword-token-value) pairs.
- *
- * !!WARNING!!: This list must be sorted, because binary
- *		 search is used to locate entries.
- */
-static const ScanKeyword ScanCKeywords[] = {
-	/* name, value, category */
+/* String lookup table for C keywords */
+#include "c_kwlist_d.h"
 
-	/*
-	 * category is not needed in ecpg, it is only here so we can share the
-	 * data structure with the backend
-	 */
-	{"VARCHAR", VARCHAR, 0},
-	{"auto", S_AUTO, 0},
-	{"bool", SQL_BOOL, 0},
-	{"char", CHAR_P, 0},
-	{"const", S_CONST, 0},
-	{"enum", ENUM_P, 0},
-	{"extern", S_EXTERN, 0},
-	{"float", FLOAT_P, 0},
-	{"hour", HOUR_P, 0},
-	{"int", INT_P, 0},
-	{"long", SQL_LONG, 0},
-	{"minute", MINUTE_P, 0},
-	{"month", MONTH_P, 0},
-	{"register", S_REGISTER, 0},
-	{"second", SECOND_P, 0},
-	{"short", SQL_SHORT, 0},
-	{"signed", SQL_SIGNED, 0},
-	{"static", S_STATIC, 0},
-	{"struct", SQL_STRUCT, 0},
-	{"to", TO, 0},
-	{"typedef", S_TYPEDEF, 0},
-	{"union", UNION, 0},
-	{"unsigned", SQL_UNSIGNED, 0},
-	{"varchar", VARCHAR, 0},
-	{"volatile", S_VOLATILE, 0},
-	{"year", YEAR_P, 0},
-};
+#define PG_KEYWORD(kwname, value) value,
 
+static const int16 ScanCKeywords[] = {
+#include "c_kwlist.h"
+};
 
 /*
+ * ScanCKeywordLookup - see if a given word is a keyword
+ *
+ * Returns the token value of the keyword or -1 if no match.
+ *
  * Do a binary search using plain strcmp() comparison.  This is much like
  * ScanKeywordLookup(), except we want case-sensitive matching.
  */
-const ScanKeyword *
+int16
 ScanCKeywordLookup(const char *text)
 {
-	const ScanKeyword *low = &ScanCKeywords[0];
-	const ScanKeyword *high = &ScanCKeywords[lengthof(ScanCKeywords) - 1];
+	const uint16 *low = &c_kw_offsets[0];
+	const uint16 *high = &c_kw_offsets[lengthof(c_kw_offsets) - 1];
 
 	while (low <= high)
 	{
-		const ScanKeyword *middle;
+		const uint16 *middle;
 		int			difference;
 
 		middle = low + (high - low) / 2;
-		difference = strcmp(middle->name, text);
+		difference = strcmp(c_kw_string + *middle, text);
 		if (difference == 0)
-			return middle;
+		{
+			return ScanCKeywords[middle - c_kw_offsets];
+		}
 		else if (difference < 0)
 			low = middle + 1;
 		else
 			high = middle - 1;
 	}
 
-	return NULL;
+	return -1;
 }
diff --git a/src/interfaces/ecpg/preproc/c_kwlist.h b/src/interfaces/ecpg/preproc/c_kwlist.h
new file mode 100644
index 0000000000..05834328d8
--- /dev/null
+++ b/src/interfaces/ecpg/preproc/c_kwlist.h
@@ -0,0 +1,53 @@
+/*-------------------------------------------------------------------------
+ *
+ * c_kwlist.h
+ *
+ * The keyword lists are kept in their own source files for use by
+ * automatic tools.  The exact representation of a keyword is determined
+ * by the PG_KEYWORD macro, which is not defined in this file; it can
+ * be defined by the caller for special purposes.
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/ecpg/preproc/c_kwlist.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* There is deliberately not an #ifndef C_KWLIST_H here. */
+
+/*
+ * List of (keyword-name, keyword-token-value) pairs.
+ *
+ * !!WARNING!!: This list must be sorted, because binary
+ *		 search is used to locate entries.
+ */
+
+/* name, value */
+PG_KEYWORD("VARCHAR", VARCHAR)
+PG_KEYWORD("auto", S_AUTO)
+PG_KEYWORD("bool", SQL_BOOL)
+PG_KEYWORD("char", CHAR_P)
+PG_KEYWORD("const", S_CONST)
+PG_KEYWORD("enum", ENUM_P)
+PG_KEYWORD("extern", S_EXTERN)
+PG_KEYWORD("float", FLOAT_P)
+PG_KEYWORD("hour", HOUR_P)
+PG_KEYWORD("int", INT_P)
+PG_KEYWORD("long", SQL_LONG)
+PG_KEYWORD("minute", MINUTE_P)
+PG_KEYWORD("month", MONTH_P)
+PG_KEYWORD("register", S_REGISTER)
+PG_KEYWORD("second", SECOND_P)
+PG_KEYWORD("short", SQL_SHORT)
+PG_KEYWORD("signed", SQL_SIGNED)
+PG_KEYWORD("static", S_STATIC)
+PG_KEYWORD("struct", SQL_STRUCT)
+PG_KEYWORD("to", TO)
+PG_KEYWORD("typedef", S_TYPEDEF)
+PG_KEYWORD("union", UNION)
+PG_KEYWORD("unsigned", SQL_UNSIGNED)
+PG_KEYWORD("varchar", VARCHAR)
+PG_KEYWORD("volatile", S_VOLATILE)
+PG_KEYWORD("year", YEAR_P)
diff --git a/src/interfaces/ecpg/preproc/ecpg_keywords.c b/src/interfaces/ecpg/preproc/ecpg_keywords.c
index 37c97e162d..3255060976 100644
--- a/src/interfaces/ecpg/preproc/ecpg_keywords.c
+++ b/src/interfaces/ecpg/preproc/ecpg_keywords.c
@@ -16,82 +16,37 @@
 #include "preproc_extern.h"
 #include "preproc.h"
 
-/*
- * List of (keyword-name, keyword-token-value) pairs.
- *
- * !!WARNING!!: This list must be sorted, because binary
- *		 search is used to locate entries.
- */
-static const ScanKeyword ECPGScanKeywords[] = {
-	/* name, value, category */
+/* String lookup table for ECPG keywords */
+#include "ecpg_kwlist_d.h"
+
+#define PG_KEYWORD(kwname, value) value,
 
-	/*
-	 * category is not needed in ecpg, it is only here so we can share the
-	 * data structure with the backend
-	 */
-	{"allocate", SQL_ALLOCATE, 0},
-	{"autocommit", SQL_AUTOCOMMIT, 0},
-	{"bool", SQL_BOOL, 0},
-	{"break", SQL_BREAK, 0},
-	{"cardinality", SQL_CARDINALITY, 0},
-	{"connect", SQL_CONNECT, 0},
-	{"count", SQL_COUNT, 0},
-	{"datetime_interval_code", SQL_DATETIME_INTERVAL_CODE, 0},
-	{"datetime_interval_precision", SQL_DATETIME_INTERVAL_PRECISION, 0},
-	{"describe", SQL_DESCRIBE, 0},
-	{"descriptor", SQL_DESCRIPTOR, 0},
-	{"disconnect", SQL_DISCONNECT, 0},
-	{"found", SQL_FOUND, 0},
-	{"free", SQL_FREE, 0},
-	{"get", SQL_GET, 0},
-	{"go", SQL_GO, 0},
-	{"goto", SQL_GOTO, 0},
-	{"identified", SQL_IDENTIFIED, 0},
-	{"indicator", SQL_INDICATOR, 0},
-	{"key_member", SQL_KEY_MEMBER, 0},
-	{"length", SQL_LENGTH, 0},
-	{"long", SQL_LONG, 0},
-	{"nullable", SQL_NULLABLE, 0},
-	{"octet_length", SQL_OCTET_LENGTH, 0},
-	{"open", SQL_OPEN, 0},
-	{"output", SQL_OUTPUT, 0},
-	{"reference", SQL_REFERENCE, 0},
-	{"returned_length", SQL_RETURNED_LENGTH, 0},
-	{"returned_octet_length", SQL_RETURNED_OCTET_LENGTH, 0},
-	{"scale", SQL_SCALE, 0},
-	{"section", SQL_SECTION, 0},
-	{"short", SQL_SHORT, 0},
-	{"signed", SQL_SIGNED, 0},
-	{"sqlerror", SQL_SQLERROR, 0},
-	{"sqlprint", SQL_SQLPRINT, 0},
-	{"sqlwarning", SQL_SQLWARNING, 0},
-	{"stop", SQL_STOP, 0},
-	{"struct", SQL_STRUCT, 0},
-	{"unsigned", SQL_UNSIGNED, 0},
-	{"var", SQL_VAR, 0},
-	{"whenever", SQL_WHENEVER, 0},
+static const int16 ECPGScanKeywords[] = {
+#include "ecpg_kwlist.h"
 };
 
 /*
  * ScanECPGKeywordLookup - see if a given word is a keyword
  *
- * Returns a pointer to the ScanKeyword table entry, or NULL if no match.
+ * Returns the token value of the keyword or -1 if no match.
  * Keywords are matched using the same case-folding rules as in the backend.
  */
-const ScanKeyword *
+int16
 ScanECPGKeywordLookup(const char *text)
 {
-	const ScanKeyword *res;
+	int		kwnum;
 
 	/* First check SQL symbols defined by the backend. */
-	res = ScanKeywordLookup(text, SQLScanKeywords, NumSQLScanKeywords);
-	if (res)
-		return res;
+	kwnum = ScanKeywordLookup(text, KeywordString, KeywordOffsets,
+							  NumSQLScanKeywords);
+	if (kwnum >= 0)
+		return SQLScanKeywords[kwnum];
 
 	/* Try ECPG-specific keywords. */
-	res = ScanKeywordLookup(text, ECPGScanKeywords, lengthof(ECPGScanKeywords));
-	if (res)
-		return res;
+	kwnum = ScanKeywordLookup(text, ecpg_kw_string, ecpg_kw_offsets,
+							  lengthof(ECPGScanKeywords));
+	if (kwnum >= 0)
+		return ECPGScanKeywords[kwnum];
 
-	return NULL;
+	return -1;
 }
diff --git a/src/interfaces/ecpg/preproc/ecpg_kwlist.h b/src/interfaces/ecpg/preproc/ecpg_kwlist.h
new file mode 100644
index 0000000000..5a6fbd3041
--- /dev/null
+++ b/src/interfaces/ecpg/preproc/ecpg_kwlist.h
@@ -0,0 +1,68 @@
+/*-------------------------------------------------------------------------
+ *
+ * ecpg_kwlist.h
+ *
+ * The keyword lists are kept in their own source files for use by
+ * automatic tools.  The exact representation of a keyword is determined
+ * by the PG_KEYWORD macro, which is not defined in this file; it can
+ * be defined by the caller for special purposes.
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/ecpg/preproc/ecpg_kwlist.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* There is deliberately not an #ifndef ECPG_KWLIST_H here. */
+
+/*
+ * List of (keyword-name, keyword-token-value) pairs.
+ *
+ * !!WARNING!!: This list must be sorted, because binary
+ *		 search is used to locate entries.
+ */
+
+/* name, value */
+PG_KEYWORD("allocate", SQL_ALLOCATE)
+PG_KEYWORD("autocommit", SQL_AUTOCOMMIT)
+PG_KEYWORD("bool", SQL_BOOL)
+PG_KEYWORD("break", SQL_BREAK)
+PG_KEYWORD("cardinality", SQL_CARDINALITY)
+PG_KEYWORD("connect", SQL_CONNECT)
+PG_KEYWORD("count", SQL_COUNT)
+PG_KEYWORD("datetime_interval_code", SQL_DATETIME_INTERVAL_CODE)
+PG_KEYWORD("datetime_interval_precision", SQL_DATETIME_INTERVAL_PRECISION)
+PG_KEYWORD("describe", SQL_DESCRIBE)
+PG_KEYWORD("descriptor", SQL_DESCRIPTOR)
+PG_KEYWORD("disconnect", SQL_DISCONNECT)
+PG_KEYWORD("found", SQL_FOUND)
+PG_KEYWORD("free", SQL_FREE)
+PG_KEYWORD("get", SQL_GET)
+PG_KEYWORD("go", SQL_GO)
+PG_KEYWORD("goto", SQL_GOTO)
+PG_KEYWORD("identified", SQL_IDENTIFIED)
+PG_KEYWORD("indicator", SQL_INDICATOR)
+PG_KEYWORD("key_member", SQL_KEY_MEMBER)
+PG_KEYWORD("length", SQL_LENGTH)
+PG_KEYWORD("long", SQL_LONG)
+PG_KEYWORD("nullable", SQL_NULLABLE)
+PG_KEYWORD("octet_length", SQL_OCTET_LENGTH)
+PG_KEYWORD("open", SQL_OPEN)
+PG_KEYWORD("output", SQL_OUTPUT)
+PG_KEYWORD("reference", SQL_REFERENCE)
+PG_KEYWORD("returned_length", SQL_RETURNED_LENGTH)
+PG_KEYWORD("returned_octet_length", SQL_RETURNED_OCTET_LENGTH)
+PG_KEYWORD("scale", SQL_SCALE)
+PG_KEYWORD("section", SQL_SECTION)
+PG_KEYWORD("short", SQL_SHORT)
+PG_KEYWORD("signed", SQL_SIGNED)
+PG_KEYWORD("sqlerror", SQL_SQLERROR)
+PG_KEYWORD("sqlprint", SQL_SQLPRINT)
+PG_KEYWORD("sqlwarning", SQL_SQLWARNING)
+PG_KEYWORD("stop", SQL_STOP)
+PG_KEYWORD("struct", SQL_STRUCT)
+PG_KEYWORD("unsigned", SQL_UNSIGNED)
+PG_KEYWORD("var", SQL_VAR)
+PG_KEYWORD("whenever", SQL_WHENEVER)
diff --git a/src/interfaces/ecpg/preproc/keywords.c b/src/interfaces/ecpg/preproc/keywords.c
index 12409e9805..a1283793af 100644
--- a/src/interfaces/ecpg/preproc/keywords.c
+++ b/src/interfaces/ecpg/preproc/keywords.c
@@ -13,6 +13,7 @@
  *
  *-------------------------------------------------------------------------
  */
+
 #include "postgres_fe.h"
 
 /*
@@ -30,10 +31,9 @@
 #include "preproc_extern.h"
 #include "preproc.h"
 
+#define PG_KEYWORD(kwname, value, category) value,
 
-#define PG_KEYWORD(a,b,c) {a,b,c},
-
-const ScanKeyword SQLScanKeywords[] = {
+const int16 SQLScanKeywords[] = {
 #include "parser/kwlist.h"
 };
 
diff --git a/src/interfaces/ecpg/preproc/pgc.l b/src/interfaces/ecpg/preproc/pgc.l
index a60564c690..10f504596a 100644
--- a/src/interfaces/ecpg/preproc/pgc.l
+++ b/src/interfaces/ecpg/preproc/pgc.l
@@ -920,19 +920,19 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 				}
 
 {identifier}	{
-					const ScanKeyword  *keyword;
+					int16		kwvalue;
 
 					if (!isdefine())
 					{
 						/* Is it an SQL/ECPG keyword? */
-						keyword = ScanECPGKeywordLookup(yytext);
-						if (keyword != NULL)
-							return keyword->value;
+						kwvalue = ScanECPGKeywordLookup(yytext);
+						if (kwvalue >= 0)
+							return kwvalue;
 
 						/* Is it a C keyword? */
-						keyword = ScanCKeywordLookup(yytext);
-						if (keyword != NULL)
-							return keyword->value;
+						kwvalue = ScanCKeywordLookup(yytext);
+						if (kwvalue >= 0)
+							return kwvalue;
 
 						/*
 						 * None of the above.  Return it as an identifier.
@@ -1010,7 +1010,7 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 						return CPP_LINE;
 					}
 <C>{identifier}		{
-						const ScanKeyword		*keyword;
+						int16		kwvalue;
 
 						/*
 						 * Try to detect a function name:
@@ -1026,9 +1026,9 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 						/* however, some defines have to be taken care of for compatibility */
 						if ((!INFORMIX_MODE || !isinformixdefine()) && !isdefine())
 						{
-							keyword = ScanCKeywordLookup(yytext);
-							if (keyword != NULL)
-								return keyword->value;
+							kwvalue = ScanCKeywordLookup(yytext);
+							if (kwvalue >= 0)
+								return kwvalue;
 							else
 							{
 								base_yylval.str = mm_strdup(yytext);
diff --git a/src/interfaces/ecpg/preproc/preproc_extern.h b/src/interfaces/ecpg/preproc/preproc_extern.h
index 13eda670ff..0d7c63cc6c 100644
--- a/src/interfaces/ecpg/preproc/preproc_extern.h
+++ b/src/interfaces/ecpg/preproc/preproc_extern.h
@@ -59,7 +59,9 @@ extern struct when when_error,
 extern struct ECPGstruct_member *struct_member_list[STRUCT_DEPTH];
 
 /* Globals from keywords.c */
-extern const ScanKeyword SQLScanKeywords[];
+extern const int16 SQLScanKeywords[];
+extern const char *KeywordString;
+extern const uint16 KeywordOffsets[];
 extern const int NumSQLScanKeywords;
 
 /* functions */
@@ -102,8 +104,8 @@ extern void check_indicator(struct ECPGtype *);
 extern void remove_typedefs(int);
 extern void remove_variables(int);
 extern struct variable *new_variable(const char *, struct ECPGtype *, int);
-extern const ScanKeyword *ScanCKeywordLookup(const char *);
-extern const ScanKeyword *ScanECPGKeywordLookup(const char *text);
+extern int16 ScanCKeywordLookup(const char *text);
+extern int16 ScanECPGKeywordLookup(const char *text);
 extern void parser_init(void);
 extern int	filtered_base_yylex(void);
 
diff --git a/src/pl/plpgsql/src/.gitignore b/src/pl/plpgsql/src/.gitignore
index ff6ac965fd..3ab9a2243c 100644
--- a/src/pl/plpgsql/src/.gitignore
+++ b/src/pl/plpgsql/src/.gitignore
@@ -1,5 +1,7 @@
 /pl_gram.c
 /pl_gram.h
+/pl_reserved_kwlist_d.h
+/pl_unreserved_kwlist_d.h
 /plerrcodes.h
 /log/
 /results/
diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
index 25a5a9d448..bdc28c5f01 100644
--- a/src/pl/plpgsql/src/Makefile
+++ b/src/pl/plpgsql/src/Makefile
@@ -61,6 +61,7 @@ uninstall-headers:
 
 # Force these dependencies to be known even without dependency info built:
 pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o: plpgsql.h pl_gram.h plerrcodes.h
+pl_scanner.o: pl_reserved_kwlist_d.h pl_unreserved_kwlist_d.h
 
 # See notes in src/backend/parser/Makefile about the following two rules
 pl_gram.h: pl_gram.c
@@ -72,6 +73,12 @@ pl_gram.c: BISONFLAGS += -d
 plerrcodes.h: $(top_srcdir)/src/backend/utils/errcodes.txt generate-plerrcodes.pl
 	$(PERL) $(srcdir)/generate-plerrcodes.pl $< > $@
 
+# generate keyword headers for the scanner
+pl_reserved_kwlist_d.h: pl_reserved_kwlist.h $(top_srcdir)/src/tools/gen_keywords.pl
+	$(PERL) $(top_srcdir)/src/tools/gen_keywords.pl --prefix pl_reserved_ $<
+
+pl_unreserved_kwlist_d.h: pl_unreserved_kwlist.h $(top_srcdir)/src/tools/gen_keywords.pl
+	$(PERL) $(top_srcdir)/src/tools/gen_keywords.pl --prefix pl_unreserved_ $<
 
 check: submake
 	$(pg_regress_check) $(REGRESS_OPTS) $(REGRESS)
@@ -84,13 +91,14 @@ submake:
 	$(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
 
 
-distprep: pl_gram.h pl_gram.c plerrcodes.h
+distprep: pl_gram.h pl_gram.c plerrcodes.h pl_reserved_kwlist_d.h pl_unreserved_kwlist_d.h
 
-# pl_gram.c, pl_gram.h and plerrcodes.h are in the distribution tarball,
-# so they are not cleaned here.
+# pl_gram.c, pl_gram.h, plerrcodes.h, pl_reserved_kwlist_d.h, and
+# pl_unreserved_kwlist_d.h are in the distribution tarball, so they
+# are not cleaned here.
 clean distclean: clean-lib
 	rm -f $(OBJS)
 	rm -rf $(pg_regress_clean_files)
 
 maintainer-clean: distclean
-	rm -f pl_gram.c pl_gram.h plerrcodes.h
+	rm -f pl_gram.c pl_gram.h plerrcodes.h pl_reserved_kwlist_d.h pl_unreserved_kwlist_d.h
diff --git a/src/pl/plpgsql/src/pl_reserved_kwlist.h b/src/pl/plpgsql/src/pl_reserved_kwlist.h
new file mode 100644
index 0000000000..4b94b753f2
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_reserved_kwlist.h
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * pl_reserved_kwlist.h
+ *
+ * The keyword lists are kept in their own source files for use by
+ * automatic tools.  The exact representation of a keyword is determined
+ * by the PG_KEYWORD macro, which is not defined in this file; it can
+ * be defined by the caller for special purposes.
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/pl/plpgsql/src/pl_reserved_kwlist.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* There is deliberately not an #ifndef PL_RESERVED_KWLIST_H here. */
+
+/*
+ * List of (keyword-name, keyword-token-value, category) pairs.
+ * Category is only here to share the same auxiliary data structure
+ * with the backend.
+ *
+ * Be careful not to put the same word in both lists.
+ *
+ * !!WARNING!!: This list must be sorted by ASCII name, because binary
+ *		search is used to locate entries.
+ */
+
+/* name, value, category */
+PG_KEYWORD("all", K_ALL, RESERVED_KEYWORD)
+PG_KEYWORD("begin", K_BEGIN, RESERVED_KEYWORD)
+PG_KEYWORD("by", K_BY, RESERVED_KEYWORD)
+PG_KEYWORD("case", K_CASE, RESERVED_KEYWORD)
+PG_KEYWORD("declare", K_DECLARE, RESERVED_KEYWORD)
+PG_KEYWORD("else", K_ELSE, RESERVED_KEYWORD)
+PG_KEYWORD("end", K_END, RESERVED_KEYWORD)
+PG_KEYWORD("execute", K_EXECUTE, RESERVED_KEYWORD)
+PG_KEYWORD("for", K_FOR, RESERVED_KEYWORD)
+PG_KEYWORD("foreach", K_FOREACH, RESERVED_KEYWORD)
+PG_KEYWORD("from", K_FROM, RESERVED_KEYWORD)
+PG_KEYWORD("if", K_IF, RESERVED_KEYWORD)
+PG_KEYWORD("in", K_IN, RESERVED_KEYWORD)
+PG_KEYWORD("into", K_INTO, RESERVED_KEYWORD)
+PG_KEYWORD("loop", K_LOOP, RESERVED_KEYWORD)
+PG_KEYWORD("not", K_NOT, RESERVED_KEYWORD)
+PG_KEYWORD("null", K_NULL, RESERVED_KEYWORD)
+PG_KEYWORD("or", K_OR, RESERVED_KEYWORD)
+PG_KEYWORD("strict", K_STRICT, RESERVED_KEYWORD)
+PG_KEYWORD("then", K_THEN, RESERVED_KEYWORD)
+PG_KEYWORD("to", K_TO, RESERVED_KEYWORD)
+PG_KEYWORD("using", K_USING, RESERVED_KEYWORD)
+PG_KEYWORD("when", K_WHEN, RESERVED_KEYWORD)
+PG_KEYWORD("while", K_WHILE, RESERVED_KEYWORD)
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index 8340628de3..07a2a95c2d 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -21,8 +21,9 @@
 #include "plpgsql.h"
 #include "pl_gram.h"			/* must be after parser/scanner.h */
 
-
-#define PG_KEYWORD(a,b,c) {a,b,c},
+/* String lookup tables for keywords */
+#include "pl_reserved_kwlist_d.h"
+#include "pl_unreserved_kwlist_d.h"
 
 
 /* Klugy flag to tell scanner how to look up identifiers */
@@ -31,7 +32,9 @@ IdentifierLookup plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
 /*
  * A word about keywords:
  *
- * We keep reserved and unreserved keywords in separate arrays.  The
+ * We keep reserved and unreserved keywords in separate headers.  Be careful
+ * not to put the same word in both headers.  Also be sure that pl_gram.y's
+ * unreserved_keyword production agrees with the unreserved header.  The
  * reserved keywords are passed to the core scanner, so they will be
  * recognized before (and instead of) any variable name.  Unreserved words
  * are checked for separately, usually after determining that the identifier
@@ -57,127 +60,19 @@ IdentifierLookup plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
  * BEGIN BY DECLARE EXECUTE FOREACH IF LOOP STRICT WHILE
  */
 
-/*
- * Lists of keyword (name, token-value, category) entries.
- *
- * !!WARNING!!: These lists must be sorted by ASCII name, because binary
- *		 search is used to locate entries.
- *
- * Be careful not to put the same word in both lists.  Also be sure that
- * pl_gram.y's unreserved_keyword production agrees with the second list.
- */
+#define PG_KEYWORD(kwname, value, category) {value, category},
 
-static const ScanKeyword reserved_keywords[] = {
-	PG_KEYWORD("all", K_ALL, RESERVED_KEYWORD)
-	PG_KEYWORD("begin", K_BEGIN, RESERVED_KEYWORD)
-	PG_KEYWORD("by", K_BY, RESERVED_KEYWORD)
-	PG_KEYWORD("case", K_CASE, RESERVED_KEYWORD)
-	PG_KEYWORD("declare", K_DECLARE, RESERVED_KEYWORD)
-	PG_KEYWORD("else", K_ELSE, RESERVED_KEYWORD)
-	PG_KEYWORD("end", K_END, RESERVED_KEYWORD)
-	PG_KEYWORD("execute", K_EXECUTE, RESERVED_KEYWORD)
-	PG_KEYWORD("for", K_FOR, RESERVED_KEYWORD)
-	PG_KEYWORD("foreach", K_FOREACH, RESERVED_KEYWORD)
-	PG_KEYWORD("from", K_FROM, RESERVED_KEYWORD)
-	PG_KEYWORD("if", K_IF, RESERVED_KEYWORD)
-	PG_KEYWORD("in", K_IN, RESERVED_KEYWORD)
-	PG_KEYWORD("into", K_INTO, RESERVED_KEYWORD)
-	PG_KEYWORD("loop", K_LOOP, RESERVED_KEYWORD)
-	PG_KEYWORD("not", K_NOT, RESERVED_KEYWORD)
-	PG_KEYWORD("null", K_NULL, RESERVED_KEYWORD)
-	PG_KEYWORD("or", K_OR, RESERVED_KEYWORD)
-	PG_KEYWORD("strict", K_STRICT, RESERVED_KEYWORD)
-	PG_KEYWORD("then", K_THEN, RESERVED_KEYWORD)
-	PG_KEYWORD("to", K_TO, RESERVED_KEYWORD)
-	PG_KEYWORD("using", K_USING, RESERVED_KEYWORD)
-	PG_KEYWORD("when", K_WHEN, RESERVED_KEYWORD)
-	PG_KEYWORD("while", K_WHILE, RESERVED_KEYWORD)
+static const ScanKeywordAux reserved_keywords[] = {
+#include "pl_reserved_kwlist.h"
 };
 
 static const int num_reserved_keywords = lengthof(reserved_keywords);
 
-static const ScanKeyword unreserved_keywords[] = {
-	PG_KEYWORD("absolute", K_ABSOLUTE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("alias", K_ALIAS, UNRESERVED_KEYWORD)
-	PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
-	PG_KEYWORD("assert", K_ASSERT, UNRESERVED_KEYWORD)
-	PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
-	PG_KEYWORD("call", K_CALL, UNRESERVED_KEYWORD)
-	PG_KEYWORD("close", K_CLOSE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("collate", K_COLLATE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("column", K_COLUMN, UNRESERVED_KEYWORD)
-	PG_KEYWORD("column_name", K_COLUMN_NAME, UNRESERVED_KEYWORD)
-	PG_KEYWORD("commit", K_COMMIT, UNRESERVED_KEYWORD)
-	PG_KEYWORD("constant", K_CONSTANT, UNRESERVED_KEYWORD)
-	PG_KEYWORD("constraint", K_CONSTRAINT, UNRESERVED_KEYWORD)
-	PG_KEYWORD("constraint_name", K_CONSTRAINT_NAME, UNRESERVED_KEYWORD)
-	PG_KEYWORD("continue", K_CONTINUE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("current", K_CURRENT, UNRESERVED_KEYWORD)
-	PG_KEYWORD("cursor", K_CURSOR, UNRESERVED_KEYWORD)
-	PG_KEYWORD("datatype", K_DATATYPE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("debug", K_DEBUG, UNRESERVED_KEYWORD)
-	PG_KEYWORD("default", K_DEFAULT, UNRESERVED_KEYWORD)
-	PG_KEYWORD("detail", K_DETAIL, UNRESERVED_KEYWORD)
-	PG_KEYWORD("diagnostics", K_DIAGNOSTICS, UNRESERVED_KEYWORD)
-	PG_KEYWORD("do", K_DO, UNRESERVED_KEYWORD)
-	PG_KEYWORD("dump", K_DUMP, UNRESERVED_KEYWORD)
-	PG_KEYWORD("elseif", K_ELSIF, UNRESERVED_KEYWORD)
-	PG_KEYWORD("elsif", K_ELSIF, UNRESERVED_KEYWORD)
-	PG_KEYWORD("errcode", K_ERRCODE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("error", K_ERROR, UNRESERVED_KEYWORD)
-	PG_KEYWORD("exception", K_EXCEPTION, UNRESERVED_KEYWORD)
-	PG_KEYWORD("exit", K_EXIT, UNRESERVED_KEYWORD)
-	PG_KEYWORD("fetch", K_FETCH, UNRESERVED_KEYWORD)
-	PG_KEYWORD("first", K_FIRST, UNRESERVED_KEYWORD)
-	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)
-	PG_KEYWORD("last", K_LAST, UNRESERVED_KEYWORD)
-	PG_KEYWORD("log", K_LOG, UNRESERVED_KEYWORD)
-	PG_KEYWORD("message", K_MESSAGE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("message_text", K_MESSAGE_TEXT, UNRESERVED_KEYWORD)
-	PG_KEYWORD("move", K_MOVE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("next", K_NEXT, UNRESERVED_KEYWORD)
-	PG_KEYWORD("no", K_NO, UNRESERVED_KEYWORD)
-	PG_KEYWORD("notice", K_NOTICE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("open", K_OPEN, UNRESERVED_KEYWORD)
-	PG_KEYWORD("option", K_OPTION, UNRESERVED_KEYWORD)
-	PG_KEYWORD("perform", K_PERFORM, UNRESERVED_KEYWORD)
-	PG_KEYWORD("pg_context", K_PG_CONTEXT, UNRESERVED_KEYWORD)
-	PG_KEYWORD("pg_datatype_name", K_PG_DATATYPE_NAME, UNRESERVED_KEYWORD)
-	PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT, UNRESERVED_KEYWORD)
-	PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL, UNRESERVED_KEYWORD)
-	PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT, UNRESERVED_KEYWORD)
-	PG_KEYWORD("print_strict_params", K_PRINT_STRICT_PARAMS, UNRESERVED_KEYWORD)
-	PG_KEYWORD("prior", K_PRIOR, UNRESERVED_KEYWORD)
-	PG_KEYWORD("query", K_QUERY, UNRESERVED_KEYWORD)
-	PG_KEYWORD("raise", K_RAISE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("relative", K_RELATIVE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("reset", K_RESET, UNRESERVED_KEYWORD)
-	PG_KEYWORD("return", K_RETURN, UNRESERVED_KEYWORD)
-	PG_KEYWORD("returned_sqlstate", K_RETURNED_SQLSTATE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("reverse", K_REVERSE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("rollback", K_ROLLBACK, UNRESERVED_KEYWORD)
-	PG_KEYWORD("row_count", K_ROW_COUNT, UNRESERVED_KEYWORD)
-	PG_KEYWORD("rowtype", K_ROWTYPE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("schema", K_SCHEMA, UNRESERVED_KEYWORD)
-	PG_KEYWORD("schema_name", K_SCHEMA_NAME, UNRESERVED_KEYWORD)
-	PG_KEYWORD("scroll", K_SCROLL, UNRESERVED_KEYWORD)
-	PG_KEYWORD("set", K_SET, UNRESERVED_KEYWORD)
-	PG_KEYWORD("slice", K_SLICE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("sqlstate", K_SQLSTATE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("stacked", K_STACKED, UNRESERVED_KEYWORD)
-	PG_KEYWORD("table", K_TABLE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("table_name", K_TABLE_NAME, UNRESERVED_KEYWORD)
-	PG_KEYWORD("type", K_TYPE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("use_column", K_USE_COLUMN, UNRESERVED_KEYWORD)
-	PG_KEYWORD("use_variable", K_USE_VARIABLE, UNRESERVED_KEYWORD)
-	PG_KEYWORD("variable_conflict", K_VARIABLE_CONFLICT, UNRESERVED_KEYWORD)
-	PG_KEYWORD("warning", K_WARNING, UNRESERVED_KEYWORD)
+#undef PG_KEYWORD
+#define PG_KEYWORD(kwname, value) value,
+
+static const int16 unreserved_keywords[] = {
+#include "pl_unreserved_kwlist.h"
 };
 
 static const int num_unreserved_keywords = lengthof(unreserved_keywords);
@@ -256,7 +151,7 @@ plpgsql_yylex(void)
 {
 	int			tok1;
 	TokenAuxData aux1;
-	const ScanKeyword *kw;
+	int kwnum;
 
 	tok1 = internal_yylex(&aux1);
 	if (tok1 == IDENT || tok1 == PARAM)
@@ -333,12 +228,14 @@ plpgsql_yylex(void)
 									   &aux1.lval.word))
 					tok1 = T_DATUM;
 				else if (!aux1.lval.word.quoted &&
-						 (kw = ScanKeywordLookup(aux1.lval.word.ident,
-												 unreserved_keywords,
-												 num_unreserved_keywords)))
+						 (kwnum = ScanKeywordLookup(aux1.lval.word.ident,
+													pl_unreserved_kw_string,
+													pl_unreserved_kw_offsets,
+													num_unreserved_keywords)) >= 0)
 				{
-					aux1.lval.keyword = kw->name;
-					tok1 = kw->value;
+					aux1.lval.keyword = pl_unreserved_kw_string
+										+ pl_unreserved_kw_offsets[kwnum];
+					tok1 = unreserved_keywords[kwnum];
 				}
 				else
 					tok1 = T_WORD;
@@ -375,12 +272,14 @@ plpgsql_yylex(void)
 								   &aux1.lval.word))
 				tok1 = T_DATUM;
 			else if (!aux1.lval.word.quoted &&
-					 (kw = ScanKeywordLookup(aux1.lval.word.ident,
-											 unreserved_keywords,
-											 num_unreserved_keywords)))
+					 (kwnum = ScanKeywordLookup(aux1.lval.word.ident,
+												pl_unreserved_kw_string,
+												pl_unreserved_kw_offsets,
+												num_unreserved_keywords)) >= 0)
 			{
-				aux1.lval.keyword = kw->name;
-				tok1 = kw->value;
+				aux1.lval.keyword = pl_unreserved_kw_string
+									+ pl_unreserved_kw_offsets[kwnum];
+				tok1 = unreserved_keywords[kwnum];
 			}
 			else
 				tok1 = T_WORD;
@@ -499,7 +398,7 @@ plpgsql_token_is_unreserved_keyword(int token)
 
 	for (i = 0; i < num_unreserved_keywords; i++)
 	{
-		if (unreserved_keywords[i].value == token)
+		if (unreserved_keywords[i] == token)
 			return true;
 	}
 	return false;
@@ -695,8 +594,10 @@ void
 plpgsql_scanner_init(const char *str)
 {
 	/* Start up the core scanner */
-	yyscanner = scanner_init(str, &core_yy,
-							 reserved_keywords, num_reserved_keywords);
+	yyscanner = scanner_init(str, &core_yy, reserved_keywords,
+							 pl_reserved_kw_string,
+							 pl_reserved_kw_offsets,
+							 num_reserved_keywords);
 
 	/*
 	 * scanorig points to the original string, which unlike the scanner's
diff --git a/src/pl/plpgsql/src/pl_unreserved_kwlist.h b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
new file mode 100644
index 0000000000..4d30456869
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
@@ -0,0 +1,111 @@
+/*-------------------------------------------------------------------------
+ *
+ * pl_unreserved_kwlist.h
+ *
+ * The keyword lists are kept in their own source files for use by
+ * automatic tools.  The exact representation of a keyword is determined
+ * by the PG_KEYWORD macro, which is not defined in this file; it can
+ * be defined by the caller for special purposes.
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/pl/plpgsql/src/pl_unreserved_kwlist.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* There is deliberately not an #ifndef PL_UNRESERVED_KWLIST_H here. */
+
+/*
+ * List of (keyword-name, keyword-token-value) pairs.
+ *
+ * Be careful not to put the same word in both lists.  Also be sure that
+ * pl_gram.y's unreserved_keyword production agrees with this list.
+ *
+ * !!WARNING!!: This list must be sorted by ASCII name, because binary
+ *		search is used to locate entries.
+ */
+
+/* name, value */
+PG_KEYWORD("absolute", K_ABSOLUTE)
+PG_KEYWORD("alias", K_ALIAS)
+PG_KEYWORD("array", K_ARRAY)
+PG_KEYWORD("assert", K_ASSERT)
+PG_KEYWORD("backward", K_BACKWARD)
+PG_KEYWORD("call", K_CALL)
+PG_KEYWORD("close", K_CLOSE)
+PG_KEYWORD("collate", K_COLLATE)
+PG_KEYWORD("column", K_COLUMN)
+PG_KEYWORD("column_name", K_COLUMN_NAME)
+PG_KEYWORD("commit", K_COMMIT)
+PG_KEYWORD("constant", K_CONSTANT)
+PG_KEYWORD("constraint", K_CONSTRAINT)
+PG_KEYWORD("constraint_name", K_CONSTRAINT_NAME)
+PG_KEYWORD("continue", K_CONTINUE)
+PG_KEYWORD("current", K_CURRENT)
+PG_KEYWORD("cursor", K_CURSOR)
+PG_KEYWORD("datatype", K_DATATYPE)
+PG_KEYWORD("debug", K_DEBUG)
+PG_KEYWORD("default", K_DEFAULT)
+PG_KEYWORD("detail", K_DETAIL)
+PG_KEYWORD("diagnostics", K_DIAGNOSTICS)
+PG_KEYWORD("do", K_DO)
+PG_KEYWORD("dump", K_DUMP)
+PG_KEYWORD("elseif", K_ELSIF)
+PG_KEYWORD("elsif", K_ELSIF)
+PG_KEYWORD("errcode", K_ERRCODE)
+PG_KEYWORD("error", K_ERROR)
+PG_KEYWORD("exception", K_EXCEPTION)
+PG_KEYWORD("exit", K_EXIT)
+PG_KEYWORD("fetch", K_FETCH)
+PG_KEYWORD("first", K_FIRST)
+PG_KEYWORD("forward", K_FORWARD)
+PG_KEYWORD("get", K_GET)
+PG_KEYWORD("hint", K_HINT)
+PG_KEYWORD("import", K_IMPORT)
+PG_KEYWORD("info", K_INFO)
+PG_KEYWORD("insert", K_INSERT)
+PG_KEYWORD("is", K_IS)
+PG_KEYWORD("last", K_LAST)
+PG_KEYWORD("log", K_LOG)
+PG_KEYWORD("message", K_MESSAGE)
+PG_KEYWORD("message_text", K_MESSAGE_TEXT)
+PG_KEYWORD("move", K_MOVE)
+PG_KEYWORD("next", K_NEXT)
+PG_KEYWORD("no", K_NO)
+PG_KEYWORD("notice", K_NOTICE)
+PG_KEYWORD("open", K_OPEN)
+PG_KEYWORD("option", K_OPTION)
+PG_KEYWORD("perform", K_PERFORM)
+PG_KEYWORD("pg_context", K_PG_CONTEXT)
+PG_KEYWORD("pg_datatype_name", K_PG_DATATYPE_NAME)
+PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT)
+PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL)
+PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT)
+PG_KEYWORD("print_strict_params", K_PRINT_STRICT_PARAMS)
+PG_KEYWORD("prior", K_PRIOR)
+PG_KEYWORD("query", K_QUERY)
+PG_KEYWORD("raise", K_RAISE)
+PG_KEYWORD("relative", K_RELATIVE)
+PG_KEYWORD("reset", K_RESET)
+PG_KEYWORD("return", K_RETURN)
+PG_KEYWORD("returned_sqlstate", K_RETURNED_SQLSTATE)
+PG_KEYWORD("reverse", K_REVERSE)
+PG_KEYWORD("rollback", K_ROLLBACK)
+PG_KEYWORD("row_count", K_ROW_COUNT)
+PG_KEYWORD("rowtype", K_ROWTYPE)
+PG_KEYWORD("schema", K_SCHEMA)
+PG_KEYWORD("schema_name", K_SCHEMA_NAME)
+PG_KEYWORD("scroll", K_SCROLL)
+PG_KEYWORD("set", K_SET)
+PG_KEYWORD("slice", K_SLICE)
+PG_KEYWORD("sqlstate", K_SQLSTATE)
+PG_KEYWORD("stacked", K_STACKED)
+PG_KEYWORD("table", K_TABLE)
+PG_KEYWORD("table_name", K_TABLE_NAME)
+PG_KEYWORD("type", K_TYPE)
+PG_KEYWORD("use_column", K_USE_COLUMN)
+PG_KEYWORD("use_variable", K_USE_VARIABLE)
+PG_KEYWORD("variable_conflict", K_VARIABLE_CONFLICT)
+PG_KEYWORD("warning", K_WARNING)
diff --git a/src/tools/gen_keywords.pl b/src/tools/gen_keywords.pl
new file mode 100644
index 0000000000..9d66adab74
--- /dev/null
+++ b/src/tools/gen_keywords.pl
@@ -0,0 +1,136 @@
+#----------------------------------------------------------------------
+#
+# gen_keywords.pl
+#   Perl script that transforms a list of keywords into a single string
+#   and an array of offsets into it. These are emitted into a header so
+#   they can be passed to ScanKeywordLookup().
+#
+# Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/tools/gen_keywords.pl
+#
+#----------------------------------------------------------------------
+
+use strict;
+use warnings;
+use Getopt::Long;
+
+my $output_path = '';
+my $extern = 0;
+my $prefix = '';
+
+GetOptions(
+	'output:s' => \$output_path,
+	'extern'   => \$extern,
+	'prefix:s' => \$prefix) || usage();
+
+my $kw_input_file = shift @ARGV || die "No input file.\n";
+
+# Make sure output_path ends in a slash.
+if ($output_path ne '' && substr($output_path, -1) ne '/')
+{
+	$output_path .= '/';
+}
+
+$kw_input_file =~ /(\w+)\.h$/;
+my $base_filename = $1 . '_d';
+my $kw_def_file = $output_path . $base_filename . '.h';
+
+open(my $kif, '<', $kw_input_file) || die "$kw_input_file: $!";
+open(my $kwdef, '>', $kw_def_file) || die "$kw_def_file: $!";
+
+# Opening boilerplate for keyword definition header.
+printf $kwdef <<EOM, $base_filename, uc $base_filename, uc $base_filename;
+/*-------------------------------------------------------------------------
+ *
+ * %s.h
+ *    List of keywords represented as a keyword string and offsets into it.
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * NOTES
+ *  ******************************
+ *  *** DO NOT EDIT THIS FILE! ***
+ *  ******************************
+ *
+ *  It has been GENERATED by src/tools/gen_keywords.pl
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef %s_H
+#define %s_H
+
+EOM
+
+# Parse keyword header for names.
+my @keywords;
+while (<$kif>)
+{
+	if (/^PG_KEYWORD\("(\w+)",/)
+	{
+		push @keywords, $1;
+	}
+}
+
+# Error out if the keyword names are not in ASCII order.
+for my $i (0..$#keywords - 1)
+{
+	die qq|The keyword "$keywords[$i + 1]" is out of order in $kw_input_file|
+	  if ($keywords[$i] cmp $keywords[$i + 1]) >= 0;
+}
+
+# Emit the keyword string.
+
+if ($extern)
+{
+	printf $kwdef qq|const char *%sKeywordString =\n\t"|, $prefix;
+}
+else
+{
+	printf $kwdef qq|static const char *%skw_string =\n\t"|, $prefix;
+}
+
+print $kwdef join qq|\\0"\n\t"|, @keywords;
+print $kwdef qq|";\n\n|;
+printf $kwdef "#endif\t\t\t\t\t\t\t/* %s_H */\n\n", uc $base_filename;
+
+# Emit an array of numerical offsets which will be used to index into the
+# keyword string.
+
+if ($extern)
+{
+	printf $kwdef "const uint16 %sKeywordOffsets[] = {\n\t", $prefix;
+}
+else
+{
+	printf $kwdef "static const uint16 %skw_offsets[] = {\n\t", $prefix;
+}
+
+my $offset = 0;
+foreach my $name (@keywords)
+{
+	print $kwdef "$offset,\n\t";
+
+	# Calculate the cumulative offset of the next keyword,
+	# taking into account the null terminator.
+	$offset += length($name) + 1;
+}
+
+print $kwdef "};\n";
+
+
+sub usage
+{
+	die <<EOM;
+Usage: gen_keywords.pl [--output/-o <path>] [--prefix/-p <prefix>] input_file
+    --output  Output directory
+    --prefix  String prepended to var names in the output file
+
+gen_keywords.pl transforms a list of keywords into a single string and
+an array of offsets into it.
+
+EOM
+}
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index eb2346b8d3..b82c0179d5 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -268,6 +268,22 @@ sub GenerateFiles
 		"src/interfaces/ecpg/pgtypeslib/exports.txt",
 		"LIBPGTYPES");
 
+	if (IsNewer(
+			'src/common/kwlist_d.h',
+			'src/include/parser/kwlist.h'))
+	{
+		print "Generating kwlist_d.h...\n";
+		system('perl src/tools/gen_keywords.pl --extern -o src/common src/include/parser/kwlist.h');
+	}
+
+	if (IsNewer(
+			'src/include/common/kwlist_d.h',
+			'src/common/kwlist_d.h'))
+	{
+		copyFile('src/common/kwlist_d.h',
+			'src/include/common/kwlist_d.h');
+	}
+
 	chdir('src/backend/utils');
 	my $pg_proc_dat     = '../../../src/include/catalog/pg_proc.dat';
 	if (   IsNewer('fmgr-stamp', 'Gen_fmgrtab.pl')
@@ -409,6 +425,34 @@ sub GenerateFiles
 		chdir('../../..');
 	}
 
+	if (IsNewer(
+			'src/pl/plpgsql/src/pl_reserved_kwlist_d.h',
+			'src/pl/plpgsql/src/pl_reserved_kwlist.h')
+		|| IsNewer(
+			'src/pl/plpgsql/src/pl_unreserved_kwlist_d.h',
+			'src/pl/plpgsql/src/pl_unreserved_kwlist.h'))
+	{
+		print "Generating pl_reserved_kwlist_d.h and pl_unreserved_kwlist_d.h...\n";
+		chdir('src/pl/plpgsql/src');
+		system('perl ../../../tools/gen_keywords.pl --prefix pl_reserved_ pl_reserved_kwlist.h');
+		system('perl ../../../tools/gen_keywords.pl --prefix pl_unreserved_  pl_unreserved_kwlist.h');
+		chdir('../../../..');
+	}
+
+	if (IsNewer(
+			'src/interfaces/ecpg/preproc/c_kwlist_d.h',
+			'src/interfaces/ecpg/preproc/c_kwlist.h')
+		|| IsNewer(
+			'src/interfaces/ecpg/preproc/ecpg_kwlist_d.h',
+			'src/interfaces/ecpg/preproc/ecpg_kwlist.h'))
+	{
+		print "Generating c_kwlist_d.h and ecpg_kwlist_d.h...\n";
+		chdir('src/interfaces/ecpg/preproc');
+		system('perl ../../../tools/gen_keywords.pl --prefix c_ c_kwlist.h');
+		system('perl ../../../tools/gen_keywords.pl --prefix ecpg_  ecpg_kwlist.h');
+		chdir('../../../..');
+	}
+
 	if (IsNewer(
 			'src/interfaces/ecpg/preproc/preproc.y',
 			'src/backend/parser/gram.y'))
diff --git a/src/tools/msvc/clean.bat b/src/tools/msvc/clean.bat
index 7a23a2b55f..82619ce3d0 100755
--- a/src/tools/msvc/clean.bat
+++ b/src/tools/msvc/clean.bat
@@ -41,6 +41,7 @@ if exist src\include\pg_config.h del /q src\include\pg_config.h
 if exist src\include\pg_config_ext.h del /q src\include\pg_config_ext.h
 if exist src\include\pg_config_os.h del /q src\include\pg_config_os.h
 if %DIST%==1 if exist src\backend\parser\gram.h del /q src\backend\parser\gram.h
+if exist src\include\common/kwlist_d.h del /q src\include\common/kwlist_d.h
 if exist src\include\utils\errcodes.h del /q src\include\utils\errcodes.h
 if exist src\include\utils\fmgroids.h del /q src\include\utils\fmgroids.h
 if exist src\include\utils\fmgrprotos.h del /q src\include\utils\fmgrprotos.h
@@ -51,6 +52,7 @@ if exist src\include\catalog\pg_*_d.h del /q src\include\catalog\pg_*_d.h
 if exist src\include\catalog\header-stamp del /q src\include\catalog\header-stamp
 if exist doc\src\sgml\version.sgml del /q doc\src\sgml\version.sgml
 
+if %DIST%==1 if exist src\common\kwlist_d.h del /q src\common\kwlist_d.h
 if %DIST%==1 if exist src\backend\utils\fmgroids.h del /q src\backend\utils\fmgroids.h
 if %DIST%==1 if exist src\backend\utils\fmgrprotos.h del /q src\backend\utils\fmgrprotos.h
 if %DIST%==1 if exist src\backend\utils\fmgrtab.c del /q src\backend\utils\fmgrtab.c
@@ -59,11 +61,15 @@ if %DIST%==1 if exist src\backend\utils\errcodes.h del /q src\backend\utils\errc
 if %DIST%==1 if exist src\backend\storage\lmgr\lwlocknames.c del /q src\backend\storage\lmgr\lwlocknames.c
 if %DIST%==1 if exist src\backend\storage\lmgr\lwlocknames.h del /q src\backend\storage\lmgr\lwlocknames.h
 if %DIST%==1 if exist src\pl\plpython\spiexceptions.h del /q src\pl\plpython\spiexceptions.h
+if %DIST%==1 if exist src\pl\plpgsql\src\pl_reserved_kwlist_d.h del /q src\pl\plpgsql\src\pl_reserved_kwlist_d.h
+if %DIST%==1 if exist src\pl\plpgsql\src\pl_unreserved_kwlist_d.h del /q src\pl\plpgsql\src\pl_unreserved_kwlist_d.h
 if %DIST%==1 if exist src\pl\plpgsql\src\plerrcodes.h del /q src\pl\plpgsql\src\plerrcodes.h
 if %DIST%==1 if exist src\pl\tcl\pltclerrcodes.h del /q src\pl\tcl\pltclerrcodes.h
 if %DIST%==1 if exist src\backend\utils\sort\qsort_tuple.c del /q src\backend\utils\sort\qsort_tuple.c
 if %DIST%==1 if exist src\bin\psql\sql_help.c del /q src\bin\psql\sql_help.c
 if %DIST%==1 if exist src\bin\psql\sql_help.h del /q src\bin\psql\sql_help.h
+if %DIST%==1 if exist src\interfaces\ecpg\preproc\c_kwlist_d.h del /q src\interfaces\ecpg\preproc\c_kwlist_d.h
+if %DIST%==1 if exist src\interfaces\ecpg\preproc\ecpg_kwlist_d.h del /q src\interfaces\ecpg\preproc\ecpg_kwlist_d.h
 if %DIST%==1 if exist src\interfaces\ecpg\preproc\preproc.y del /q src\interfaces\ecpg\preproc\preproc.y
 if %DIST%==1 if exist src\backend\catalog\postgres.bki del /q src\backend\catalog\postgres.bki
 if %DIST%==1 if exist src\backend\catalog\postgres.description del /q src\backend\catalog\postgres.description
-- 
2.17.1

Reply via email to