diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 48cb4f6c2b6338a3c9bfb7fbd0dbae83144b668e..d3529f560e3db0ff4a5dbcd2b22a2084aeb5b995 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.437 2008/05/19 18:08:15 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.438 2008/07/03 20:58:46 tgl Exp $ -->
 
  <chapter id="functions">
   <title>Functions and Operators</title>
@@ -11484,6 +11484,10 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
     <primary>format_type</primary>
    </indexterm>
 
+   <indexterm>
+    <primary>pg_get_keywords</primary>
+   </indexterm>
+
    <indexterm>
     <primary>pg_get_viewdef</primary>
    </indexterm>
@@ -11538,6 +11542,11 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
        <entry><type>text</type></entry>
        <entry>get SQL name of a data type</entry>
       </row>
+      <row>
+       <entry><literal><function>pg_get_keywords</function>()</literal></entry>
+       <entry><type>setof record</type></entry>
+       <entry>get list of SQL keywords and their categories</entry>
+      </row>
       <row>
        <entry><literal><function>pg_get_constraintdef</function>(<parameter>constraint_oid</parameter>)</literal></entry>
        <entry><type>text</type></entry>
@@ -11633,6 +11642,16 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
    for the type modifier if no specific modifier is known.
   </para>
 
+  <para>
+   <function>pg_get_keywords</function> returns a set of records describing
+   the SQL keywords recognized by the server. The <structfield>word</> column
+   contains the keyword.  The <structfield>catcode</> column contains a
+   category code: <literal>U</> for unreserved, <literal>C</> for column name,
+   <literal>T</> for type or function name, or <literal>R</> for reserved.
+   The <structfield>catdesc</> column contains a possibly-localized string
+   describing the category.
+  </para>
+
   <para>
    <function>pg_get_constraintdef</function>,
    <function>pg_get_indexdef</function>, <function>pg_get_ruledef</function>,
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index edb765966230a089130e3ed88dffb0a8d62f8ddb..43013e1e7723d0c273bd11e270780811e24e3506 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.197 2008/05/21 19:51:01 meskes Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.198 2008/07/03 20:58:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -41,7 +41,7 @@
  * !!WARNING!!: This list must be sorted by ASCII name, because binary
  *		 search is used to locate entries.
  */
-static const ScanKeyword ScanKeywords[] = {
+const ScanKeyword ScanKeywords[] = {
 	/* name, value, category */
 	{"abort", ABORT_P, UNRESERVED_KEYWORD},
 	{"absolute", ABSOLUTE_P, UNRESERVED_KEYWORD},
@@ -428,6 +428,9 @@ static const ScanKeyword ScanKeywords[] = {
 	{"zone", ZONE, UNRESERVED_KEYWORD},
 };
 
+/* End of ScanKeywords, for use elsewhere */
+const ScanKeyword *LastScanKeyword = endof(ScanKeywords);
+
 /*
  * ScanKeywordLookup - see if a given word is a keyword
  *
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index f99a07a99987767cb5154d375d371f0a79de45f1..b48c004527c0f8adede52979c474818ae8b73977 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/misc.c,v 1.62 2008/04/17 20:56:41 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/misc.c,v 1.63 2008/07/03 20:58:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,10 +20,12 @@
 #include <math.h>
 
 #include "access/xact.h"
+#include "catalog/pg_type.h"
 #include "catalog/pg_tablespace.h"
 #include "commands/dbcommands.h"
 #include "funcapi.h"
 #include "miscadmin.h"
+#include "parser/keywords.h"
 #include "postmaster/syslogger.h"
 #include "storage/fd.h"
 #include "storage/pmsignal.h"
@@ -322,3 +324,72 @@ pg_sleep(PG_FUNCTION_ARGS)
 
 	PG_RETURN_VOID();
 }
+
+/* Function to return the list of grammar keywords */
+Datum
+pg_get_keywords(PG_FUNCTION_ARGS)
+{
+	FuncCallContext *funcctx;
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		MemoryContext oldcontext;
+		TupleDesc	tupdesc;
+
+		funcctx = SRF_FIRSTCALL_INIT();
+		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+		tupdesc = CreateTemplateTupleDesc(3, false);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "word",
+						   TEXTOID, -1, 0);
+  		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "catcode",
+						   CHAROID, -1, 0);
+		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "catdesc",
+						   TEXTOID, -1, 0);
+
+		funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	funcctx = SRF_PERCALL_SETUP();
+
+	if (&ScanKeywords[funcctx->call_cntr] < LastScanKeyword)
+	{
+		char	   *values[3];
+		HeapTuple	tuple;
+
+		/* cast-away-const is ugly but alternatives aren't much better */
+		values[0] = (char *) ScanKeywords[funcctx->call_cntr].name;
+
+		switch (ScanKeywords[funcctx->call_cntr].category)
+		{
+			case UNRESERVED_KEYWORD:
+				values[1] = "U";
+				values[2] = _("Unreserved");
+				break;
+			case COL_NAME_KEYWORD:
+				values[1] = "C";
+				values[2] = _("Column name");
+				break;
+			case TYPE_FUNC_NAME_KEYWORD:
+				values[1] = "T";
+				values[2] = _("Type or function name");
+				break;
+			case RESERVED_KEYWORD:
+				values[1] = "R";
+				values[2] = _("Reserved");
+				break;
+			default:			/* shouldn't be possible */
+				values[1] = NULL;
+				values[2] = NULL;
+				break;
+		}
+
+		tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
+
+		SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
+	}
+
+	SRF_RETURN_DONE(funcctx);
+}
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index a49beccd54f1b337f985589df4fe71a688d69cb8..e7ba550c48465211245aeb59295af78e21a8d23c 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.464 2008/06/24 17:58:27 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.465 2008/07/03 20:58:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200806241
+#define CATALOG_VERSION_NO	200807031
 
 #endif
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index dbe791028222bc8197201aa9cdf8317f611d09db..09700e8f43a3fb2d8c31c876b1f5825cd4ed4fa1 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.503 2008/06/17 19:10:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.504 2008/07/03 20:58:46 tgl Exp $
  *
  * NOTES
  *	  The script catalog/genbki.sh reads this file and generates .bki
@@ -2288,6 +2288,9 @@ DESCR("deparse an encoded expression");
 DATA(insert OID = 1665 (  pg_get_serial_sequence	PGNSP PGUID 12 1 0 f f t f s 2 25 "25 25" _null_ _null_ _null_	pg_get_serial_sequence - _null_ _null_ ));
 DESCR("name of sequence for a serial column");
 
+DATA(insert OID = 1686 (  pg_get_keywords		PGNSP PGUID 12 10 400 f f t t s 0 2249 "" "{25,18,25}" "{o,o,o}" "{word,catcode,catdesc}" pg_get_keywords - _null_ _null_ ));
+DESCR("list of SQL keywords");
+
 
 /* Generic referential integrity constraint triggers */
 DATA(insert OID = 1644 (  RI_FKey_check_ins		PGNSP PGUID 12 1 0 f f t f v 0 2279 "" _null_ _null_ _null_ RI_FKey_check_ins - _null_ _null_ ));
diff --git a/src/include/parser/keywords.h b/src/include/parser/keywords.h
index 717e20e7df4518ab12483902de1372909c79735c..2da7fd3da9e30689e2fb3c0d28df9d5b5fb902b4 100644
--- a/src/include/parser/keywords.h
+++ b/src/include/parser/keywords.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/parser/keywords.h,v 1.24 2008/01/01 19:45:58 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/parser/keywords.h,v 1.25 2008/07/03 20:58:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -28,6 +28,9 @@ typedef struct ScanKeyword
 	int16		category;		/* see codes above */
 } ScanKeyword;
 
+extern const ScanKeyword ScanKeywords[];
+extern const ScanKeyword *LastScanKeyword;
+
 extern const ScanKeyword *ScanKeywordLookup(const char *text);
 
 #endif   /* KEYWORDS_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index c34ee7aa90473a1104c6557cc1a64968ca775f4a..2abbe6678914e32d9360f7f2b2b0481102b4318d 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.317 2008/06/17 19:10:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.318 2008/07/03 20:58:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -411,6 +411,7 @@ extern Datum pg_reload_conf(PG_FUNCTION_ARGS);
 extern Datum pg_tablespace_databases(PG_FUNCTION_ARGS);
 extern Datum pg_rotate_logfile(PG_FUNCTION_ARGS);
 extern Datum pg_sleep(PG_FUNCTION_ARGS);
+extern Datum pg_get_keywords(PG_FUNCTION_ARGS);
 
 /* oid.c */
 extern Datum oidin(PG_FUNCTION_ARGS);