diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index e8ef966..9131991 100644
*** a/contrib/pg_stat_statements/pg_stat_statements.c
--- b/contrib/pg_stat_statements/pg_stat_statements.c
*************** fill_in_constant_lengths(pgssJumbleState
*** 3075,3082 ****
  	/* initialize the flex scanner --- should match raw_parser() */
  	yyscanner = scanner_init(query,
  							 &yyextra,
! 							 ScanKeywords,
! 							 NumScanKeywords);
  
  	/* we don't want to re-emit any escape string warnings */
  	yyextra.escape_string_warning = false;
--- 3075,3082 ----
  	/* initialize the flex scanner --- should match raw_parser() */
  	yyscanner = scanner_init(query,
  							 &yyextra,
! 							 &ScanKeywords,
! 							 ScanKeywordTokens);
  
  	/* we don't want to re-emit any escape string warnings */
  	yyextra.escape_string_warning = false;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 7e9b122..4c0c258 100644
*** a/src/backend/parser/parser.c
--- b/src/backend/parser/parser.c
*************** raw_parser(const char *str)
*** 41,47 ****
  
  	/* initialize the flex scanner */
  	yyscanner = scanner_init(str, &yyextra.core_yy_extra,
! 							 ScanKeywords, NumScanKeywords);
  
  	/* base_yylex() only needs this much initialization */
  	yyextra.have_lookahead = false;
--- 41,47 ----
  
  	/* initialize the flex scanner */
  	yyscanner = scanner_init(str, &yyextra.core_yy_extra,
! 							 &ScanKeywords, ScanKeywordTokens);
  
  	/* 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 fbeb86f..e1cae85 100644
*** a/src/backend/parser/scan.l
--- b/src/backend/parser/scan.l
*************** bool		escape_string_warning = true;
*** 67,72 ****
--- 67,87 ----
  bool		standard_conforming_strings = true;
  
  /*
+  * Constant data exported from this file.  This array maps from the
+  * zero-based keyword numbers returned by ScanKeywordLookup to the
+  * Bison token numbers needed by gram.y.  This is exported because
+  * callers need to pass it to scanner_init, if they are using the
+  * standard keyword list ScanKeywords.
+  */
+ #define PG_KEYWORD(kwname, value, category) value,
+ 
+ const uint16 ScanKeywordTokens[] = {
+ #include "parser/kwlist.h"
+ };
+ 
+ #undef PG_KEYWORD
+ 
+ /*
   * Set the type of YYSTYPE.
   */
  #define YYSTYPE core_YYSTYPE
*************** other			.
*** 504,521 ****
  					 * We will pass this along as a normal character string,
  					 * but preceded with an internally-generated "NCHAR".
  					 */
! 					const ScanKeyword *keyword;
  
  					SET_YYLLOC();
  					yyless(1);	/* eat only 'n' this time */
  
! 					keyword = ScanKeywordLookup("nchar",
! 												yyextra->keywords,
! 												yyextra->num_keywords);
! 					if (keyword != NULL)
  					{
! 						yylval->keyword = keyword->name;
! 						return keyword->value;
  					}
  					else
  					{
--- 519,536 ----
  					 * We will pass this along as a normal character string,
  					 * but preceded with an internally-generated "NCHAR".
  					 */
! 					int		kwnum;
  
  					SET_YYLLOC();
  					yyless(1);	/* eat only 'n' this time */
  
! 					kwnum = ScanKeywordLookup("nchar",
! 											  yyextra->keywordlist);
! 					if (kwnum >= 0)
  					{
! 						yylval->keyword = GetScanKeyword(kwnum,
! 														 yyextra->keywordlist);
! 						return yyextra->keyword_tokens[kwnum];
  					}
  					else
  					{
*************** other			.
*** 1021,1039 ****
  
  
  {identifier}	{
! 					const ScanKeyword *keyword;
  					char	   *ident;
  
  					SET_YYLLOC();
  
  					/* Is it a keyword? */
! 					keyword = ScanKeywordLookup(yytext,
! 												yyextra->keywords,
! 												yyextra->num_keywords);
! 					if (keyword != NULL)
  					{
! 						yylval->keyword = keyword->name;
! 						return keyword->value;
  					}
  
  					/*
--- 1036,1054 ----
  
  
  {identifier}	{
! 					int			kwnum;
  					char	   *ident;
  
  					SET_YYLLOC();
  
  					/* Is it a keyword? */
! 					kwnum = ScanKeywordLookup(yytext,
! 											  yyextra->keywordlist);
! 					if (kwnum >= 0)
  					{
! 						yylval->keyword = GetScanKeyword(kwnum,
! 														 yyextra->keywordlist);
! 						return yyextra->keyword_tokens[kwnum];
  					}
  
  					/*
*************** scanner_yyerror(const char *message, cor
*** 1142,1149 ****
  core_yyscan_t
  scanner_init(const char *str,
  			 core_yy_extra_type *yyext,
! 			 const ScanKeyword *keywords,
! 			 int num_keywords)
  {
  	Size		slen = strlen(str);
  	yyscan_t	scanner;
--- 1157,1164 ----
  core_yyscan_t
  scanner_init(const char *str,
  			 core_yy_extra_type *yyext,
! 			 const ScanKeywordList *keywordlist,
! 			 const uint16 *keyword_tokens)
  {
  	Size		slen = strlen(str);
  	yyscan_t	scanner;
*************** scanner_init(const char *str,
*** 1153,1160 ****
  
  	core_yyset_extra(yyext, scanner);
  
! 	yyext->keywords = keywords;
! 	yyext->num_keywords = num_keywords;
  
  	yyext->backslash_quote = backslash_quote;
  	yyext->escape_string_warning = escape_string_warning;
--- 1168,1175 ----
  
  	core_yyset_extra(yyext, scanner);
  
! 	yyext->keywordlist = keywordlist;
! 	yyext->keyword_tokens = keyword_tokens;
  
  	yyext->backslash_quote = backslash_quote;
  	yyext->escape_string_warning = escape_string_warning;
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 7b69b82..746b7d2 100644
*** a/src/backend/utils/adt/misc.c
--- b/src/backend/utils/adt/misc.c
*************** pg_get_keywords(PG_FUNCTION_ARGS)
*** 417,431 ****
  
  	funcctx = SRF_PERCALL_SETUP();
  
! 	if (funcctx->call_cntr < NumScanKeywords)
  	{
  		char	   *values[3];
  		HeapTuple	tuple;
  
  		/* cast-away-const is ugly but alternatives aren't much better */
! 		values[0] = unconstify(char *, ScanKeywords[funcctx->call_cntr].name);
  
! 		switch (ScanKeywords[funcctx->call_cntr].category)
  		{
  			case UNRESERVED_KEYWORD:
  				values[1] = "U";
--- 417,433 ----
  
  	funcctx = SRF_PERCALL_SETUP();
  
! 	if (funcctx->call_cntr < ScanKeywords.num_keywords)
  	{
  		char	   *values[3];
  		HeapTuple	tuple;
  
  		/* cast-away-const is ugly but alternatives aren't much better */
! 		values[0] = unconstify(char *,
! 							   GetScanKeyword(funcctx->call_cntr,
! 											  &ScanKeywords));
  
! 		switch (ScanKeywordCategories[funcctx->call_cntr])
  		{
  			case UNRESERVED_KEYWORD:
  				values[1] = "U";
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 368eacf..77811f6 100644
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** quote_identifier(const char *ident)
*** 10601,10611 ****
  		 * 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);
  
! 		if (keyword != NULL && keyword->category != UNRESERVED_KEYWORD)
  			safe = false;
  	}
  
--- 10601,10609 ----
  		 * Note: ScanKeywordLookup() does case-insensitive comparison, but
  		 * that's fine, since we already know we have all-lower-case.
  		 */
! 		int			kwnum = ScanKeywordLookup(ident, &ScanKeywords);
  
! 		if (kwnum >= 0 && ScanKeywordCategories[kwnum] != UNRESERVED_KEYWORD)
  			safe = false;
  	}
  
diff --git a/src/common/.gitignore b/src/common/.gitignore
index ...ffa3284 .
*** a/src/common/.gitignore
--- b/src/common/.gitignore
***************
*** 0 ****
--- 1 ----
+ /kwlist_d.h
diff --git a/src/common/Makefile b/src/common/Makefile
index ec8139f..317b071 100644
*** a/src/common/Makefile
--- b/src/common/Makefile
*************** override CPPFLAGS += -DVAL_LDFLAGS_EX="\
*** 41,51 ****
  override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
  override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
  
! override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
  LIBS += $(PTHREAD_LIBS)
  
  OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o file_perm.o \
! 	ip.o keywords.o link-canary.o md5.o pg_lzcompress.o \
  	pgfnames.o psprintf.o relpath.o \
  	rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
  	username.o wait_error.o
--- 41,51 ----
  override CPPFLAGS += -DVAL_LDFLAGS_SL="\"$(LDFLAGS_SL)\""
  override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
  
! override CPPFLAGS := -DFRONTEND -I. -I$(top_srcdir)/src/common $(CPPFLAGS)
  LIBS += $(PTHREAD_LIBS)
  
  OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.o file_perm.o \
! 	ip.o keywords.o kwlookup.o link-canary.o md5.o pg_lzcompress.o \
  	pgfnames.o psprintf.o relpath.o \
  	rmtree.o saslprep.o scram-common.o string.o unicode_norm.o \
  	username.o wait_error.o
*************** OBJS_SRV = $(OBJS_COMMON:%.o=%_srv.o)
*** 65,70 ****
--- 65,72 ----
  
  all: libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
  
+ distprep: kwlist_d.h
+ 
  # libpgcommon is needed by some contrib
  install: all installdirs
  	$(INSTALL_STLIB) libpgcommon.a '$(DESTDIR)$(libdir)/libpgcommon.a'
*************** libpgcommon_srv.a: $(OBJS_SRV)
*** 115,130 ****
  %_srv.o: %.c %.o
  	$(CC) $(CFLAGS) $(subst -DFRONTEND,, $(CPPFLAGS)) -c $< -o $@
  
! # Dependencies of keywords.o need to be managed explicitly to make sure
! # that you don't get broken parsing code, even in a non-enable-depend build.
! # Note that gram.h isn't required for the frontend versions of keywords.o.
! $(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
  
! clean distclean maintainer-clean:
  	rm -f libpgcommon.a libpgcommon_shlib.a libpgcommon_srv.a
  	rm -f $(OBJS_FRONTEND) $(OBJS_SHLIB) $(OBJS_SRV)
--- 117,134 ----
  %_srv.o: %.c %.o
  	$(CC) $(CFLAGS) $(subst -DFRONTEND,, $(CPPFLAGS)) -c $< -o $@
  
! # generate SQL keyword lookup table to be included into keywords*.o.
! kwlist_d.h: $(top_srcdir)/src/include/parser/kwlist.h $(top_srcdir)/src/tools/gen_keywordlist.pl
! 	$(PERL) $(top_srcdir)/src/tools/gen_keywordlist.pl --extern $<
  
! # Dependencies of keywords*.o need to be managed explicitly to make sure
! # that you don't get broken parsing code, even in a non-enable-depend build.
! keywords.o keywords_shlib.o keywords_srv.o: kwlist_d.h
  
! # 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 6f99090..103166c 100644
*** a/src/common/keywords.c
--- b/src/common/keywords.c
***************
*** 1,7 ****
  /*-------------------------------------------------------------------------
   *
   * keywords.c
!  *	  lexical token lookup for key words in PostgreSQL
   *
   *
   * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
--- 1,7 ----
  /*-------------------------------------------------------------------------
   *
   * keywords.c
!  *	  PostgreSQL's list of SQL keywords
   *
   *
   * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
***************
*** 19,114 ****
  #include "postgres_fe.h"
  #endif
  
! #ifndef FRONTEND
! 
! #include "parser/gramparse.h"
! 
! #define PG_KEYWORD(a,b,c) {a,b,c},
  
- #else
  
! #include "common/keywords.h"
  
! /*
!  * 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},
  
! #endif							/* FRONTEND */
  
  
! const ScanKeyword ScanKeywords[] = {
  #include "parser/kwlist.h"
  };
  
! 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.
!  *
!  * Returns a pointer to the ScanKeyword table entry, or NULL if no match.
!  *
!  * 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',
!  * even if we are in a locale where tolower() would produce more or different
!  * translations.  This is to conform to the SQL99 spec, which says that
!  * keywords are to be matched in this way even though non-keyword identifiers
!  * receive a different case-normalization mapping.
!  */
! const ScanKeyword *
! ScanKeywordLookup(const char *text,
! 				  const ScanKeyword *keywords,
! 				  int num_keywords)
! {
! 	int			len,
! 				i;
! 	char		word[NAMEDATALEN];
! 	const ScanKeyword *low;
! 	const ScanKeyword *high;
! 
! 	len = strlen(text);
! 	/* We assume all keywords are shorter than NAMEDATALEN. */
! 	if (len >= NAMEDATALEN)
! 		return NULL;
! 
! 	/*
! 	 * Apply an ASCII-only downcasing.  We must not use tolower() since it may
! 	 * produce the wrong translation in some locales (eg, Turkish).
! 	 */
! 	for (i = 0; i < len; i++)
! 	{
! 		char		ch = text[i];
! 
! 		if (ch >= 'A' && ch <= 'Z')
! 			ch += 'a' - 'A';
! 		word[i] = ch;
! 	}
! 	word[len] = '\0';
! 
! 	/*
! 	 * Now do a binary search using plain strcmp() comparison.
! 	 */
! 	low = keywords;
! 	high = keywords + (num_keywords - 1);
! 	while (low <= high)
! 	{
! 		const ScanKeyword *middle;
! 		int			difference;
! 
! 		middle = low + (high - low) / 2;
! 		difference = strcmp(middle->name, word);
! 		if (difference == 0)
! 			return middle;
! 		else if (difference < 0)
! 			low = middle + 1;
! 		else
! 			high = middle - 1;
! 	}
! 
! 	return NULL;
! }
--- 19,37 ----
  #include "postgres_fe.h"
  #endif
  
! #include "common/keywords.h"
  
  
! /* ScanKeywordList lookup data for SQL keywords */
  
! #include "kwlist_d.h"
  
! /* Keyword categories for SQL keywords */
  
+ #define PG_KEYWORD(kwname, value, category) category,
  
! const uint8 ScanKeywordCategories[SCANKEYWORDS_NUM_KEYWORDS] = {
  #include "parser/kwlist.h"
  };
  
! #undef PG_KEYWORD
diff --git a/src/common/kwlookup.c b/src/common/kwlookup.c
index ...db62623 .
*** a/src/common/kwlookup.c
--- b/src/common/kwlookup.c
***************
*** 0 ****
--- 1,91 ----
+ /*-------------------------------------------------------------------------
+  *
+  * kwlookup.c
+  *	  Key word lookup for PostgreSQL
+  *
+  *
+  * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  *
+  * IDENTIFICATION
+  *	  src/common/kwlookup.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ #include "c.h"
+ 
+ #include "common/kwlookup.h"
+ 
+ 
+ /*
+  * ScanKeywordLookup - see if a given word is a keyword
+  *
+  * The list of keywords to be matched against is passed as a ScanKeywordList.
+  *
+  * Returns the keyword number (0..N-1) of the keyword, or -1 if no match.
+  * Callers typically use the keyword number to index into information
+  * arrays, but that is no concern of this code.
+  *
+  * 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',
+  * even if we are in a locale where tolower() would produce more or different
+  * translations.  This is to conform to the SQL99 spec, which says that
+  * keywords are to be matched in this way even though non-keyword identifiers
+  * receive a different case-normalization mapping.
+  */
+ int
+ ScanKeywordLookup(const char *text,
+ 				  const ScanKeywordList *keywords)
+ {
+ 	int			len,
+ 				i;
+ 	char		word[NAMEDATALEN];
+ 	const char *kw_string;
+ 	const uint16 *kw_offsets;
+ 	const uint16 *low;
+ 	const uint16 *high;
+ 
+ 	len = strlen(text);
+ 	/* We assume all keywords are shorter than NAMEDATALEN. */
+ 	if (len >= NAMEDATALEN)
+ 		return -1;
+ 
+ 	/*
+ 	 * Apply an ASCII-only downcasing.  We must not use tolower() since it may
+ 	 * produce the wrong translation in some locales (eg, Turkish).
+ 	 */
+ 	for (i = 0; i < len; i++)
+ 	{
+ 		char		ch = text[i];
+ 
+ 		if (ch >= 'A' && ch <= 'Z')
+ 			ch += 'a' - 'A';
+ 		word[i] = ch;
+ 	}
+ 	word[len] = '\0';
+ 
+ 	/*
+ 	 * Now do a binary search using plain strcmp() comparison.
+ 	 */
+ 	kw_string = keywords->kw_string;
+ 	kw_offsets = keywords->kw_offsets;
+ 	low = kw_offsets;
+ 	high = kw_offsets + (keywords->num_keywords - 1);
+ 	while (low <= high)
+ 	{
+ 		const uint16 *middle;
+ 		int			difference;
+ 
+ 		middle = low + (high - low) / 2;
+ 		difference = strcmp(kw_string + *middle, word);
+ 		if (difference == 0)
+ 			return middle - kw_offsets;
+ 		else if (difference < 0)
+ 			low = middle + 1;
+ 		else
+ 			high = middle - 1;
+ 	}
+ 
+ 	return -1;
+ }
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 9b47b62..5c1732a 100644
*** a/src/fe_utils/string_utils.c
--- b/src/fe_utils/string_utils.c
*************** fmtId(const char *rawid)
*** 104,114 ****
  		 * 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);
  
! 		if (keyword != NULL && keyword->category != UNRESERVED_KEYWORD)
  			need_quotes = true;
  	}
  
--- 104,112 ----
  		 * Note: ScanKeywordLookup() does case-insensitive comparison, but
  		 * that's fine, since we already know we have all-lower-case.
  		 */
! 		int			kwnum = ScanKeywordLookup(rawid, &ScanKeywords);
  
! 		if (kwnum >= 0 && ScanKeywordCategories[kwnum] != UNRESERVED_KEYWORD)
  			need_quotes = true;
  	}
  
diff --git a/src/include/common/keywords.h b/src/include/common/keywords.h
index 8f22f32..fb18858 100644
*** a/src/include/common/keywords.h
--- b/src/include/common/keywords.h
***************
*** 1,7 ****
  /*-------------------------------------------------------------------------
   *
   * keywords.h
!  *	  lexical token lookup for key words in PostgreSQL
   *
   *
   * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
--- 1,7 ----
  /*-------------------------------------------------------------------------
   *
   * keywords.h
!  *	  PostgreSQL's list of SQL keywords
   *
   *
   * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
***************
*** 14,44 ****
  #ifndef KEYWORDS_H
  #define KEYWORDS_H
  
  /* Keyword categories --- should match lists in gram.y */
  #define UNRESERVED_KEYWORD		0
  #define COL_NAME_KEYWORD		1
  #define TYPE_FUNC_NAME_KEYWORD	2
  #define RESERVED_KEYWORD		3
  
- 
- typedef struct ScanKeyword
- {
- 	const char *name;			/* in lower case */
- 	int16		value;			/* grammar's token code */
- 	int16		category;		/* see codes above */
- } ScanKeyword;
- 
  #ifndef FRONTEND
! extern PGDLLIMPORT const ScanKeyword ScanKeywords[];
! extern PGDLLIMPORT const int NumScanKeywords;
  #else
! extern const ScanKeyword ScanKeywords[];
! extern const int NumScanKeywords;
  #endif
  
- 
- extern const ScanKeyword *ScanKeywordLookup(const char *text,
- 				  const ScanKeyword *keywords,
- 				  int num_keywords);
- 
  #endif							/* KEYWORDS_H */
--- 14,33 ----
  #ifndef KEYWORDS_H
  #define KEYWORDS_H
  
+ #include "common/kwlookup.h"
+ 
  /* Keyword categories --- should match lists in gram.y */
  #define UNRESERVED_KEYWORD		0
  #define COL_NAME_KEYWORD		1
  #define TYPE_FUNC_NAME_KEYWORD	2
  #define RESERVED_KEYWORD		3
  
  #ifndef FRONTEND
! extern PGDLLIMPORT const ScanKeywordList ScanKeywords;
! extern PGDLLIMPORT const uint8 ScanKeywordCategories[];
  #else
! extern const ScanKeywordList ScanKeywords;
! extern const uint8 ScanKeywordCategories[];
  #endif
  
  #endif							/* KEYWORDS_H */
diff --git a/src/include/common/kwlookup.h b/src/include/common/kwlookup.h
index ...3098df3 .
*** a/src/include/common/kwlookup.h
--- b/src/include/common/kwlookup.h
***************
*** 0 ****
--- 1,39 ----
+ /*-------------------------------------------------------------------------
+  *
+  * kwlookup.h
+  *	  Key word lookup for PostgreSQL
+  *
+  *
+  * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * src/include/common/kwlookup.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ #ifndef KWLOOKUP_H
+ #define KWLOOKUP_H
+ 
+ /*
+  * This struct contains the data needed by ScanKeywordLookup to perform a
+  * search within a set of keywords.  The contents are typically generated by
+  * src/tools/gen_keywordlist.pl from a header containing PG_KEYWORD macros.
+  */
+ typedef struct ScanKeywordList
+ {
+ 	const char *kw_string;		/* all keywords in order, separated by \0 */
+ 	const uint16 *kw_offsets;	/* offsets to the start of each keyword */
+ 	int			num_keywords;	/* number of keywords */
+ } ScanKeywordList;
+ 
+ 
+ extern int	ScanKeywordLookup(const char *text, const ScanKeywordList *keywords);
+ 
+ /* Code that wants to retrieve the text of the N'th keyword should use this. */
+ static inline const char *
+ GetScanKeyword(int n, const ScanKeywordList *keywords)
+ {
+ 	return keywords->kw_string + keywords->kw_offsets[n];
+ }
+ 
+ #endif							/* KWLOOKUP_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 0256d53..b8902d3 100644
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 2,8 ****
   *
   * kwlist.h
   *
!  * The keyword list is kept in its own source file for possible 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.
--- 2,8 ----
   *
   * 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.
diff --git a/src/include/parser/scanner.h b/src/include/parser/scanner.h
index 009550f..91e1c83 100644
*** a/src/include/parser/scanner.h
--- b/src/include/parser/scanner.h
*************** typedef struct core_yy_extra_type
*** 73,82 ****
  	Size		scanbuflen;
  
  	/*
! 	 * The keyword list to use.
  	 */
! 	const ScanKeyword *keywords;
! 	int			num_keywords;
  
  	/*
  	 * Scanner settings to use.  These are initialized from the corresponding
--- 73,82 ----
  	Size		scanbuflen;
  
  	/*
! 	 * The keyword list to use, and the associated grammar token codes.
  	 */
! 	const ScanKeywordList *keywordlist;
! 	const uint16 *keyword_tokens;
  
  	/*
  	 * Scanner settings to use.  These are initialized from the corresponding
*************** typedef struct core_yy_extra_type
*** 116,126 ****
  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,
! 			 int num_keywords);
  extern void scanner_finish(core_yyscan_t yyscanner);
  extern int core_yylex(core_YYSTYPE *lvalp, YYLTYPE *llocp,
  		   core_yyscan_t yyscanner);
--- 116,129 ----
  typedef void *core_yyscan_t;
  
  
+ /* Constant data exported from parser/scan.l */
+ extern PGDLLIMPORT const uint16 ScanKeywordTokens[];
+ 
  /* Entry points in parser/scan.l */
  extern core_yyscan_t scanner_init(const char *str,
  			 core_yy_extra_type *yyext,
! 			 const ScanKeywordList *keywordlist,
! 			 const uint16 *keyword_tokens);
  extern void scanner_finish(core_yyscan_t yyscanner);
  extern int core_yylex(core_YYSTYPE *lvalp, YYLTYPE *llocp,
  		   core_yyscan_t yyscanner);
diff --git a/src/interfaces/ecpg/preproc/.gitignore b/src/interfaces/ecpg/preproc/.gitignore
index 38ae2fe..958a826 100644
*** a/src/interfaces/ecpg/preproc/.gitignore
--- b/src/interfaces/ecpg/preproc/.gitignore
***************
*** 2,6 ****
--- 2,8 ----
  /preproc.c
  /preproc.h
  /pgc.c
+ /c_kwlist_d.h
+ /ecpg_kwlist_d.h
  /typename.c
  /ecpg
diff --git a/src/interfaces/ecpg/preproc/Makefile b/src/interfaces/ecpg/preproc/Makefile
index 69ddd8e..9b145a1 100644
*** a/src/interfaces/ecpg/preproc/Makefile
--- b/src/interfaces/ecpg/preproc/Makefile
*************** OBJS=	preproc.o pgc.o type.o ecpg.o outp
*** 28,33 ****
--- 28,35 ----
  	keywords.o c_keywords.o ecpg_keywords.o typename.o descriptor.o variable.o \
  	$(WIN32RES)
  
+ GEN_KEYWORDLIST = $(top_srcdir)/src/tools/gen_keywordlist.pl
+ 
  # Suppress parallel build to avoid a bug in GNU make 3.82
  # (see comments in ../Makefile)
  ifeq ($(MAKE_VERSION),3.82)
*************** preproc.y: ../../../backend/parser/gram.
*** 53,61 ****
  	$(PERL) $(srcdir)/parse.pl $(srcdir) < $< > $@
  	$(PERL) $(srcdir)/check_rules.pl $(srcdir) $<
  
  ecpg_keywords.o c_keywords.o keywords.o preproc.o pgc.o parser.o: preproc.h
  
! distprep: preproc.y preproc.c preproc.h pgc.c
  
  install: all installdirs
  	$(INSTALL_PROGRAM) ecpg$(X) '$(DESTDIR)$(bindir)'
--- 55,73 ----
  	$(PERL) $(srcdir)/parse.pl $(srcdir) < $< > $@
  	$(PERL) $(srcdir)/check_rules.pl $(srcdir) $<
  
+ # generate keyword headers
+ c_kwlist_d.h: c_kwlist.h $(GEN_KEYWORDLIST)
+ 	$(PERL) $(GEN_KEYWORDLIST) --varname ScanCKeywords $<
+ 
+ ecpg_kwlist_d.h: ecpg_kwlist.h $(GEN_KEYWORDLIST)
+ 	$(PERL) $(GEN_KEYWORDLIST) --varname ScanECPGKeywords $<
+ 
+ # 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
  
! 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)'
*************** installdirs:
*** 66,77 ****
  uninstall:
  	rm -f '$(DESTDIR)$(bindir)/ecpg$(X)'
  
  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
--- 78,88 ----
  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
  
  maintainer-clean: distclean
! 	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 c367dbf..521992f 100644
*** a/src/interfaces/ecpg/preproc/c_keywords.c
--- b/src/interfaces/ecpg/preproc/c_keywords.c
***************
*** 14,85 ****
  #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 */
  
! 	/*
! 	 * 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},
  };
  
  
  /*
   * Do a binary search using plain strcmp() comparison.  This is much like
   * ScanKeywordLookup(), except we want case-sensitive matching.
   */
! const ScanKeyword *
  ScanCKeywordLookup(const char *text)
  {
! 	const ScanKeyword *low = &ScanCKeywords[0];
! 	const ScanKeyword *high = &ScanCKeywords[lengthof(ScanCKeywords) - 1];
  
  	while (low <= high)
  	{
! 		const ScanKeyword *middle;
  		int			difference;
  
  		middle = low + (high - low) / 2;
! 		difference = strcmp(middle->name, text);
  		if (difference == 0)
! 			return middle;
  		else if (difference < 0)
  			low = middle + 1;
  		else
  			high = middle - 1;
  	}
  
! 	return NULL;
  }
--- 14,67 ----
  #include "preproc_extern.h"
  #include "preproc.h"
  
! /* ScanKeywordList lookup data for C keywords */
! #include "c_kwlist_d.h"
  
! /* Token codes for C keywords */
! #define PG_KEYWORD(kwname, value) value,
! 
! static const uint16 ScanCKeywordTokens[] = {
! #include "c_kwlist.h"
  };
  
+ #undef PG_KEYWORD
+ 
  
  /*
+  * 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.
   */
! int
  ScanCKeywordLookup(const char *text)
  {
! 	const char *kw_string;
! 	const uint16 *kw_offsets;
! 	const uint16 *low;
! 	const uint16 *high;
! 
! 	kw_string = ScanCKeywords.kw_string;
! 	kw_offsets = ScanCKeywords.kw_offsets;
! 	low = kw_offsets;
! 	high = kw_offsets + (ScanCKeywords.num_keywords - 1);
  
  	while (low <= high)
  	{
! 		const uint16 *middle;
  		int			difference;
  
  		middle = low + (high - low) / 2;
! 		difference = strcmp(kw_string + *middle, text);
  		if (difference == 0)
! 			return ScanCKeywordTokens[middle - kw_offsets];
  		else if (difference < 0)
  			low = middle + 1;
  		else
  			high = middle - 1;
  	}
  
! 	return -1;
  }
diff --git a/src/interfaces/ecpg/preproc/c_kwlist.h b/src/interfaces/ecpg/preproc/c_kwlist.h
index ...4545505 .
*** a/src/interfaces/ecpg/preproc/c_kwlist.h
--- b/src/interfaces/ecpg/preproc/c_kwlist.h
***************
*** 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 by ASCII name, 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 37c97e1..4839c37 100644
*** a/src/interfaces/ecpg/preproc/ecpg_keywords.c
--- b/src/interfaces/ecpg/preproc/ecpg_keywords.c
***************
*** 16,97 ****
  #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 */
  
! 	/*
! 	 * 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},
  };
  
  /*
   * ScanECPGKeywordLookup - see if a given word is a keyword
   *
!  * Returns a pointer to the ScanKeyword table entry, or NULL if no match.
   * Keywords are matched using the same case-folding rules as in the backend.
   */
! const ScanKeyword *
  ScanECPGKeywordLookup(const char *text)
  {
! 	const ScanKeyword *res;
  
  	/* First check SQL symbols defined by the backend. */
! 	res = ScanKeywordLookup(text, SQLScanKeywords, NumSQLScanKeywords);
! 	if (res)
! 		return res;
  
  	/* Try ECPG-specific keywords. */
! 	res = ScanKeywordLookup(text, ECPGScanKeywords, lengthof(ECPGScanKeywords));
! 	if (res)
! 		return res;
  
! 	return NULL;
  }
--- 16,55 ----
  #include "preproc_extern.h"
  #include "preproc.h"
  
! /* ScanKeywordList lookup data for ECPG keywords */
! #include "ecpg_kwlist_d.h"
  
! /* Token codes for ECPG keywords */
! #define PG_KEYWORD(kwname, value) value,
! 
! static const uint16 ECPGScanKeywordTokens[] = {
! #include "ecpg_kwlist.h"
  };
  
+ #undef PG_KEYWORD
+ 
+ 
  /*
   * ScanECPGKeywordLookup - see if a given word is a keyword
   *
!  * 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.
   */
! int
  ScanECPGKeywordLookup(const char *text)
  {
! 	int			kwnum;
  
  	/* First check SQL symbols defined by the backend. */
! 	kwnum = ScanKeywordLookup(text, &ScanKeywords);
! 	if (kwnum >= 0)
! 		return SQLScanKeywordTokens[kwnum];
  
  	/* Try ECPG-specific keywords. */
! 	kwnum = ScanKeywordLookup(text, &ScanECPGKeywords);
! 	if (kwnum >= 0)
! 		return ECPGScanKeywordTokens[kwnum];
  
! 	return -1;
  }
diff --git a/src/interfaces/ecpg/preproc/ecpg_kwlist.h b/src/interfaces/ecpg/preproc/ecpg_kwlist.h
index ...97ef254 .
*** a/src/interfaces/ecpg/preproc/ecpg_kwlist.h
--- b/src/interfaces/ecpg/preproc/ecpg_kwlist.h
***************
*** 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 by ASCII name, 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 12409e9..0380409 100644
*** a/src/interfaces/ecpg/preproc/keywords.c
--- b/src/interfaces/ecpg/preproc/keywords.c
***************
*** 17,40 ****
  
  /*
   * This is much trickier than it looks.  We are #include'ing kwlist.h
!  * but the "value" numbers that go into the table are from preproc.h
!  * not the backend's gram.h.  Therefore this table will recognize all
!  * keywords known to the backend, but will supply the token numbers used
   * by ecpg's grammar, which is what we need.  The ecpg grammar must
   * define all the same token names the backend does, else we'll get
   * undefined-symbol failures in this compile.
   */
  
- #include "common/keywords.h"
- 
  #include "preproc_extern.h"
  #include "preproc.h"
  
  
! #define PG_KEYWORD(a,b,c) {a,b,c},
! 
! const ScanKeyword SQLScanKeywords[] = {
  #include "parser/kwlist.h"
  };
  
! const int	NumSQLScanKeywords = lengthof(SQLScanKeywords);
--- 17,38 ----
  
  /*
   * This is much trickier than it looks.  We are #include'ing kwlist.h
!  * but the token numbers that go into the table are from preproc.h
!  * not the backend's gram.h.  Therefore this token table will match
!  * the ScanKeywords table supplied from common/keywords.c, including all
!  * keywords known to the backend, but it will supply the token numbers used
   * by ecpg's grammar, which is what we need.  The ecpg grammar must
   * define all the same token names the backend does, else we'll get
   * undefined-symbol failures in this compile.
   */
  
  #include "preproc_extern.h"
  #include "preproc.h"
  
+ #define PG_KEYWORD(kwname, value, category) value,
  
! const uint16 SQLScanKeywordTokens[] = {
  #include "parser/kwlist.h"
  };
  
! #undef PG_KEYWORD
diff --git a/src/interfaces/ecpg/preproc/pgc.l b/src/interfaces/ecpg/preproc/pgc.l
index a60564c..3131f5f 100644
*** a/src/interfaces/ecpg/preproc/pgc.l
--- b/src/interfaces/ecpg/preproc/pgc.l
*************** cppline			{space}*#([^i][A-Za-z]*|{if}|{
*** 920,938 ****
  				}
  
  {identifier}	{
- 					const ScanKeyword  *keyword;
- 
  					if (!isdefine())
  					{
  						/* Is it an SQL/ECPG keyword? */
! 						keyword = ScanECPGKeywordLookup(yytext);
! 						if (keyword != NULL)
! 							return keyword->value;
  
  						/* Is it a C keyword? */
! 						keyword = ScanCKeywordLookup(yytext);
! 						if (keyword != NULL)
! 							return keyword->value;
  
  						/*
  						 * None of the above.  Return it as an identifier.
--- 920,938 ----
  				}
  
  {identifier}	{
  					if (!isdefine())
  					{
+ 						int		kwvalue;
+ 
  						/* Is it an SQL/ECPG keyword? */
! 						kwvalue = ScanECPGKeywordLookup(yytext);
! 						if (kwvalue >= 0)
! 							return kwvalue;
  
  						/* Is it a C keyword? */
! 						kwvalue = ScanCKeywordLookup(yytext);
! 						if (kwvalue >= 0)
! 							return kwvalue;
  
  						/*
  						 * None of the above.  Return it as an identifier.
*************** cppline			{space}*#([^i][A-Za-z]*|{if}|{
*** 1010,1021 ****
  						return CPP_LINE;
  					}
  <C>{identifier}		{
- 						const ScanKeyword		*keyword;
- 
  						/*
  						 * Try to detect a function name:
  						 * look for identifiers at the global scope
! 						 * keep the last identifier before the first '(' and '{' */
  						if (braces_open == 0 && parenths_open == 0)
  						{
  							if (current_function)
--- 1010,1020 ----
  						return CPP_LINE;
  					}
  <C>{identifier}		{
  						/*
  						 * Try to detect a function name:
  						 * look for identifiers at the global scope
! 						 * keep the last identifier before the first '(' and '{'
! 						 */
  						if (braces_open == 0 && parenths_open == 0)
  						{
  							if (current_function)
*************** cppline			{space}*#([^i][A-Za-z]*|{if}|{
*** 1026,1034 ****
  						/* 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;
  							else
  							{
  								base_yylval.str = mm_strdup(yytext);
--- 1025,1035 ----
  						/* however, some defines have to be taken care of for compatibility */
  						if ((!INFORMIX_MODE || !isinformixdefine()) && !isdefine())
  						{
! 							int		kwvalue;
! 
! 							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 13eda67..9746780 100644
*** a/src/interfaces/ecpg/preproc/preproc_extern.h
--- b/src/interfaces/ecpg/preproc/preproc_extern.h
*************** extern struct when when_error,
*** 59,66 ****
  extern struct ECPGstruct_member *struct_member_list[STRUCT_DEPTH];
  
  /* Globals from keywords.c */
! extern const ScanKeyword SQLScanKeywords[];
! extern const int NumSQLScanKeywords;
  
  /* functions */
  
--- 59,65 ----
  extern struct ECPGstruct_member *struct_member_list[STRUCT_DEPTH];
  
  /* Globals from keywords.c */
! extern const uint16 SQLScanKeywordTokens[];
  
  /* functions */
  
*************** extern void check_indicator(struct ECPGt
*** 102,109 ****
  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 void parser_init(void);
  extern int	filtered_base_yylex(void);
  
--- 101,108 ----
  extern void remove_typedefs(int);
  extern void remove_variables(int);
  extern struct variable *new_variable(const char *, struct ECPGtype *, int);
! extern int	ScanCKeywordLookup(const char *text);
! extern int	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 ff6ac96..3ab9a22 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 25a5a9d..9dd4a74 100644
*** a/src/pl/plpgsql/src/Makefile
--- b/src/pl/plpgsql/src/Makefile
*************** REGRESS_OPTS = --dbname=$(PL_TESTDB)
*** 29,34 ****
--- 29,36 ----
  REGRESS = plpgsql_call plpgsql_control plpgsql_domain plpgsql_record \
  	plpgsql_cache plpgsql_transaction plpgsql_varprops
  
+ GEN_KEYWORDLIST = $(top_srcdir)/src/tools/gen_keywordlist.pl
+ 
  all: all-lib
  
  # Shared library stuff
*************** uninstall-headers:
*** 61,66 ****
--- 63,69 ----
  
  # 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
*************** pl_gram.c: BISONFLAGS += -d
*** 72,77 ****
--- 75,87 ----
  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 $(GEN_KEYWORDLIST)
+ 	$(PERL) $(GEN_KEYWORDLIST) --varname ReservedPLKeywords $<
+ 
+ pl_unreserved_kwlist_d.h: pl_unreserved_kwlist.h $(GEN_KEYWORDLIST)
+ 	$(PERL) $(GEN_KEYWORDLIST) --varname UnreservedPLKeywords $<
+ 
  
  check: submake
  	$(pg_regress_check) $(REGRESS_OPTS) $(REGRESS)
*************** submake:
*** 84,96 ****
  	$(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
  
  
! distprep: pl_gram.h pl_gram.c plerrcodes.h
  
! # pl_gram.c, pl_gram.h and plerrcodes.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
--- 94,107 ----
  	$(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
  
  
! 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, 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 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
index ...5c2e0c1 .
*** a/src/pl/plpgsql/src/pl_reserved_kwlist.h
--- b/src/pl/plpgsql/src/pl_reserved_kwlist.h
***************
*** 0 ****
--- 1,53 ----
+ /*-------------------------------------------------------------------------
+  *
+  * 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) pairs.
+  *
+  * 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 */
+ PG_KEYWORD("all", K_ALL)
+ PG_KEYWORD("begin", K_BEGIN)
+ PG_KEYWORD("by", K_BY)
+ PG_KEYWORD("case", K_CASE)
+ PG_KEYWORD("declare", K_DECLARE)
+ PG_KEYWORD("else", K_ELSE)
+ PG_KEYWORD("end", K_END)
+ PG_KEYWORD("execute", K_EXECUTE)
+ PG_KEYWORD("for", K_FOR)
+ PG_KEYWORD("foreach", K_FOREACH)
+ PG_KEYWORD("from", K_FROM)
+ PG_KEYWORD("if", K_IF)
+ PG_KEYWORD("in", K_IN)
+ PG_KEYWORD("into", K_INTO)
+ PG_KEYWORD("loop", K_LOOP)
+ PG_KEYWORD("not", K_NOT)
+ PG_KEYWORD("null", K_NULL)
+ PG_KEYWORD("or", K_OR)
+ PG_KEYWORD("strict", K_STRICT)
+ PG_KEYWORD("then", K_THEN)
+ PG_KEYWORD("to", K_TO)
+ PG_KEYWORD("using", K_USING)
+ PG_KEYWORD("when", K_WHEN)
+ PG_KEYWORD("while", K_WHILE)
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index 8340628..c260438 100644
*** a/src/pl/plpgsql/src/pl_scanner.c
--- b/src/pl/plpgsql/src/pl_scanner.c
***************
*** 22,37 ****
  #include "pl_gram.h"			/* must be after parser/scanner.h */
  
  
- #define PG_KEYWORD(a,b,c) {a,b,c},
- 
- 
  /* Klugy flag to tell scanner how to look up identifiers */
  IdentifierLookup plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
  
  /*
   * A word about keywords:
   *
!  * We keep reserved and unreserved keywords in separate arrays.  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
--- 22,36 ----
  #include "pl_gram.h"			/* must be after parser/scanner.h */
  
  
  /* Klugy flag to tell scanner how to look up identifiers */
  IdentifierLookup plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
  
  /*
   * A word about keywords:
   *
!  * 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
*************** IdentifierLookup plpgsql_IdentifierLooku
*** 57,186 ****
   * 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.
!  */
  
! 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 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)
  };
  
! static const int num_unreserved_keywords = lengthof(unreserved_keywords);
  
  /*
   * This macro must recognize all tokens that can immediately precede a
--- 56,77 ----
   * BEGIN BY DECLARE EXECUTE FOREACH IF LOOP STRICT WHILE
   */
  
! /* ScanKeywordList lookup data for PL/pgSQL keywords */
! #include "pl_reserved_kwlist_d.h"
! #include "pl_unreserved_kwlist_d.h"
  
! /* Token codes for PL/pgSQL keywords */
! #define PG_KEYWORD(kwname, value) value,
  
! static const uint16 ReservedPLKeywordTokens[] = {
! #include "pl_reserved_kwlist.h"
! };
  
! static const uint16 UnreservedPLKeywordTokens[] = {
! #include "pl_unreserved_kwlist.h"
  };
  
! #undef PG_KEYWORD
  
  /*
   * This macro must recognize all tokens that can immediately precede a
*************** plpgsql_yylex(void)
*** 256,262 ****
  {
  	int			tok1;
  	TokenAuxData aux1;
! 	const ScanKeyword *kw;
  
  	tok1 = internal_yylex(&aux1);
  	if (tok1 == IDENT || tok1 == PARAM)
--- 147,153 ----
  {
  	int			tok1;
  	TokenAuxData aux1;
! 	int			kwnum;
  
  	tok1 = internal_yylex(&aux1);
  	if (tok1 == IDENT || tok1 == PARAM)
*************** plpgsql_yylex(void)
*** 333,344 ****
  									   &aux1.lval.word))
  					tok1 = T_DATUM;
  				else if (!aux1.lval.word.quoted &&
! 						 (kw = ScanKeywordLookup(aux1.lval.word.ident,
! 												 unreserved_keywords,
! 												 num_unreserved_keywords)))
  				{
! 					aux1.lval.keyword = kw->name;
! 					tok1 = kw->value;
  				}
  				else
  					tok1 = T_WORD;
--- 224,235 ----
  									   &aux1.lval.word))
  					tok1 = T_DATUM;
  				else if (!aux1.lval.word.quoted &&
! 						 (kwnum = ScanKeywordLookup(aux1.lval.word.ident,
! 													&UnreservedPLKeywords)) >= 0)
  				{
! 					aux1.lval.keyword = GetScanKeyword(kwnum,
! 													   &UnreservedPLKeywords);
! 					tok1 = UnreservedPLKeywordTokens[kwnum];
  				}
  				else
  					tok1 = T_WORD;
*************** plpgsql_yylex(void)
*** 375,386 ****
  								   &aux1.lval.word))
  				tok1 = T_DATUM;
  			else if (!aux1.lval.word.quoted &&
! 					 (kw = ScanKeywordLookup(aux1.lval.word.ident,
! 											 unreserved_keywords,
! 											 num_unreserved_keywords)))
  			{
! 				aux1.lval.keyword = kw->name;
! 				tok1 = kw->value;
  			}
  			else
  				tok1 = T_WORD;
--- 266,277 ----
  								   &aux1.lval.word))
  				tok1 = T_DATUM;
  			else if (!aux1.lval.word.quoted &&
! 					 (kwnum = ScanKeywordLookup(aux1.lval.word.ident,
! 												&UnreservedPLKeywords)) >= 0)
  			{
! 				aux1.lval.keyword = GetScanKeyword(kwnum,
! 												   &UnreservedPLKeywords);
! 				tok1 = UnreservedPLKeywordTokens[kwnum];
  			}
  			else
  				tok1 = T_WORD;
*************** plpgsql_token_is_unreserved_keyword(int 
*** 497,505 ****
  {
  	int			i;
  
! 	for (i = 0; i < num_unreserved_keywords; i++)
  	{
! 		if (unreserved_keywords[i].value == token)
  			return true;
  	}
  	return false;
--- 388,396 ----
  {
  	int			i;
  
! 	for (i = 0; i < lengthof(UnreservedPLKeywordTokens); i++)
  	{
! 		if (UnreservedPLKeywordTokens[i] == token)
  			return true;
  	}
  	return false;
*************** plpgsql_scanner_init(const char *str)
*** 696,702 ****
  {
  	/* Start up the core scanner */
  	yyscanner = scanner_init(str, &core_yy,
! 							 reserved_keywords, num_reserved_keywords);
  
  	/*
  	 * scanorig points to the original string, which unlike the scanner's
--- 587,593 ----
  {
  	/* Start up the core scanner */
  	yyscanner = scanner_init(str, &core_yy,
! 							 &ReservedPLKeywords, ReservedPLKeywordTokens);
  
  	/*
  	 * 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
index ...ef2aea0 .
*** a/src/pl/plpgsql/src/pl_unreserved_kwlist.h
--- b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
***************
*** 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_keywordlist.pl b/src/tools/gen_keywordlist.pl
index ...eb5ed65 .
*** a/src/tools/gen_keywordlist.pl
--- b/src/tools/gen_keywordlist.pl
***************
*** 0 ****
--- 1,148 ----
+ #----------------------------------------------------------------------
+ #
+ # gen_keywordlist.pl
+ #	Perl script that transforms a list of keywords into a ScanKeywordList
+ #	data structure that can be passed to ScanKeywordLookup().
+ #
+ # The input is a C header file containing a series of macro calls
+ #	PG_KEYWORD("keyword", ...)
+ # Lines not starting with PG_KEYWORD are ignored.  The keywords are
+ # implicitly numbered 0..N-1 in order of appearance in the header file.
+ # Currently, the keywords are required to appear in ASCII order.
+ #
+ # The output is a C header file that defines a "const ScanKeywordList"
+ # variable named according to the -v switch ("ScanKeywords" by default).
+ # The variable is marked "static" unless the -e switch is given.
+ #
+ #
+ # Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ # Portions Copyright (c) 1994, Regents of the University of California
+ #
+ # src/tools/gen_keywordlist.pl
+ #
+ #----------------------------------------------------------------------
+ 
+ use strict;
+ use warnings;
+ use Getopt::Long;
+ 
+ my $output_path = '';
+ my $extern = 0;
+ my $varname = 'ScanKeywords';
+ 
+ GetOptions(
+ 	'output:s' => \$output_path,
+ 	'extern'   => \$extern,
+ 	'varname:s' => \$varname) || usage();
+ 
+ my $kw_input_file = shift @ARGV || die "No input file.\n";
+ 
+ # Make sure output_path ends in a slash if needed.
+ if ($output_path ne '' && substr($output_path, -1) ne '/')
+ {
+ 	$output_path .= '/';
+ }
+ 
+ $kw_input_file =~ /(\w+)\.h$/ || die "Input file must be named something.h.\n";
+ my $base_filename = $1 . '_d';
+ my $kw_def_file = $output_path . $base_filename . '.h';
+ 
+ open(my $kif, '<', $kw_input_file) || die "$kw_input_file: $!\n";
+ open(my $kwdef, '>', $kw_def_file) || die "$kw_def_file: $!\n";
+ 
+ # 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 ScanKeywordList.
+  *
+  * 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_keywordlist.pl
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #ifndef %s_H
+ #define %s_H
+ 
+ #include "common/kwlookup.h"
+ 
+ EOM
+ 
+ # Parse input file for keyword 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\n|
+ 	  if ($keywords[$i] cmp $keywords[$i + 1]) >= 0;
+ }
+ 
+ # Emit the string containing all the keywords.
+ 
+ printf $kwdef qq|static const char %s_kw_string[] =\n\t"|, $varname;
+ print $kwdef join qq|\\0"\n\t"|, @keywords;
+ print $kwdef qq|";\n\n|;
+ 
+ # Emit an array of numerical offsets which will be used to index into the
+ # keyword string.
+ 
+ printf $kwdef "static const uint16 %s_kw_offsets[] = {\n", $varname;
+ 
+ my $offset = 0;
+ foreach my $name (@keywords)
+ {
+ 	print $kwdef "\t$offset,\n";
+ 
+ 	# Calculate the cumulative offset of the next keyword,
+ 	# taking into account the null terminator.
+ 	$offset += length($name) + 1;
+ }
+ 
+ print $kwdef "};\n\n";
+ 
+ # Emit a macro defining the number of keywords.
+ 
+ printf $kwdef "#define %s_NUM_KEYWORDS %d\n\n", uc $varname, scalar @keywords;
+ 
+ # Emit the struct that wraps all this lookup info into one variable.
+ 
+ print $kwdef "static " if !$extern;
+ printf $kwdef "const ScanKeywordList %s = {\n", $varname;
+ printf $kwdef qq|\t%s_kw_string,\n|, $varname;
+ printf $kwdef qq|\t%s_kw_offsets,\n|, $varname;
+ printf $kwdef qq|\t%s_NUM_KEYWORDS\n|, uc $varname;
+ print $kwdef "};\n\n";
+ 
+ printf $kwdef "#endif\t\t\t\t\t\t\t/* %s_H */\n", uc $base_filename;
+ 
+ 
+ sub usage
+ {
+ 	die <<EOM;
+ Usage: gen_keywordlist.pl [--output/-o <path>] [--varname/-v <varname>] [--extern/-e] input_file
+     --output   Output directory (default '.')
+     --varname  Name for ScanKeywordList variable (default 'ScanKeywords')
+     --extern   Allow the ScanKeywordList variable to be globally visible
+ 
+ gen_keywordlist.pl transforms a list of keywords into a ScanKeywordList.
+ The output filename is derived from the input file by inserting _d,
+ for example kwlist_d.h is produced from kwlist.h.
+ EOM
+ }
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index eb2346b..937bf18 100644
*** a/src/tools/msvc/Solution.pm
--- b/src/tools/msvc/Solution.pm
*************** sub GenerateFiles
*** 410,415 ****
--- 410,451 ----
  	}
  
  	if (IsNewer(
+ 			'src/common/kwlist_d.h',
+ 			'src/include/parser/kwlist.h'))
+ 	{
+ 		print "Generating kwlist_d.h...\n";
+ 		system('perl src/tools/gen_keywordlist.pl --extern -o src/common src/include/parser/kwlist.h');
+ 	}
+ 
+ 	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_keywordlist.pl --varname ReservedPLKeywords pl_reserved_kwlist.h');
+ 		system('perl ../../../tools/gen_keywordlist.pl --varname UnreservedPLKeywords 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_keywordlist.pl --varname ScanCKeywords c_kwlist.h');
+ 		system('perl ../../../tools/gen_keywordlist.pl --varname ScanECPGKeywords 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 7a23a2b..069d6eb 100755
*** a/src/tools/msvc/clean.bat
--- b/src/tools/msvc/clean.bat
*************** if %DIST%==1 if exist src\pl\tcl\pltcler
*** 64,69 ****
--- 64,74 ----
  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\common\kwlist_d.h del /q src\common\kwlist_d.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\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
