Re: reducing the footprint of ScanKeyword (was Re: Large writable variables) - Mailing list pgsql-hackers

From Tom Lane
Subject Re: reducing the footprint of ScanKeyword (was Re: Large writable variables)
Date
Msg-id 1661.1546751069@sss.pgh.pa.us
Whole thread Raw
In response to Re: reducing the footprint of ScanKeyword (was Re: Large writable variables)  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-hackers
I wrote:
> I spent some time hacking on this today, and I think it's committable
> now, but I'm putting it back up in case anyone wants to have another
> look (and also so the cfbot can check it on Windows).

... and indeed, the cfbot doesn't like it.  Here's v8, with the
missing addition to Mkvcbuild.pm.

            regards, tom lane

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/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 2921d19..56192f1 100644
*** a/src/tools/msvc/Mkvcbuild.pm
--- b/src/tools/msvc/Mkvcbuild.pm
*************** sub mkvcbuild
*** 118,124 ****

      our @pgcommonallfiles = qw(
        base64.c config_info.c controldata_utils.c exec.c file_perm.c ip.c
!       keywords.c link-canary.c md5.c
        pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
        saslprep.c scram-common.c string.c unicode_norm.c username.c
        wait_error.c);
--- 118,124 ----

      our @pgcommonallfiles = qw(
        base64.c config_info.c controldata_utils.c exec.c file_perm.c ip.c
!       keywords.c kwlookup.c link-canary.c md5.c
        pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
        saslprep.c scram-common.c string.c unicode_norm.c username.c
        wait_error.c);
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

pgsql-hackers by date:

Previous
From: Tom Lane
Date:
Subject: Re: reducing the footprint of ScanKeyword (was Re: Large writable variables)
Next
From: Julien Rouhaud
Date:
Subject: Re: Ordered Partitioned Table Scans